From 8b89a525d64ffc2b9d9aa0af497cd3f49e276c09 Mon Sep 17 00:00:00 2001 From: IvoPaunov Date: Wed, 28 Aug 2024 06:59:32 +0300 Subject: [PATCH 01/84] fix acc reset on not auth --- src/contexts/AccountContext/AccountContext.tsx | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/contexts/AccountContext/AccountContext.tsx b/src/contexts/AccountContext/AccountContext.tsx index af7ad4db..6f74deff 100644 --- a/src/contexts/AccountContext/AccountContext.tsx +++ b/src/contexts/AccountContext/AccountContext.tsx @@ -29,7 +29,7 @@ const ambireLoginSDK = new AmbireLoginSDK({ export const BACKEND_BASE_URL = process.env.REACT_APP_BACKEND_BASE_URL export const VALIDATOR_BASE_URL = process.env.REACT_APP_VALIDATOR_BASE_URL -const UNAUTHORIZED_ERR_STR = 'Unauthorized!' +const UNAUTHORIZED_ERR_STR = 'Unauthorized' console.log({ BACKEND_BASE_URL }) const processResponse = (res: Response): Promise => { @@ -286,8 +286,7 @@ const AccountProvider: FC = ({ children }) => { const res = await fetchService(req) return await processResponse(res) } catch (err: any) { - console.log(err) - if (err && err.message && err.message.includes(UNAUTHORIZED_ERR_STR)) { + if (service === 'backend' && err && (err?.message || err).includes(UNAUTHORIZED_ERR_STR)) { resetAdexAccount() } showNotification('error', err.message, reqOptions.onErrMsg || 'Data error') @@ -394,9 +393,11 @@ const AccountProvider: FC = ({ children }) => { useEffect(() => { ambireSDK.onLoginSuccess(handleRegistrationOrLoginSuccess) }, [ambireSDK, handleRegistrationOrLoginSuccess]) + useEffect(() => { ambireSDK.onAlreadyLoggedIn(handleRegistrationOrLoginSuccess) }, [ambireSDK, handleRegistrationOrLoginSuccess]) + useEffect(() => { ambireSDK.onMsgSigned(({ signature }: any) => setSdkMsgSignature(() => { From b144502016f1be19100558ef740d595b75a4d655 Mon Sep 17 00:00:00 2001 From: IvoPaunov Date: Wed, 28 Aug 2024 06:59:56 +0300 Subject: [PATCH 02/84] fix cmp details layout --- .../CampaignDetails/CampaignDetails.tsx | 60 +++++++++---------- 1 file changed, 29 insertions(+), 31 deletions(-) diff --git a/src/components/CampaignDetails/CampaignDetails.tsx b/src/components/CampaignDetails/CampaignDetails.tsx index 6afe89a1..6155e667 100644 --- a/src/components/CampaignDetails/CampaignDetails.tsx +++ b/src/components/CampaignDetails/CampaignDetails.tsx @@ -265,9 +265,9 @@ const CampaignDetails = ({ isAdminPanel }: { isAdminPanel?: boolean }) => { ) : ( - + - + Overview @@ -397,8 +397,8 @@ const CampaignDetails = ({ isAdminPanel }: { isAdminPanel?: boolean }) => { - - + + Targeting @@ -419,33 +419,31 @@ const CampaignDetails = ({ isAdminPanel }: { isAdminPanel?: boolean }) => { {!!campaign.adUnits.length && ( - - - - Creatives - - - {campaign.adUnits.map((item: AdUnit, index: number) => { - const isLast = index === campaign.adUnits.length - 1 - return ( - - } - align="center" - noBorder={isLast} - /> - ) - })} - - + + + Creatives + + + {campaign.adUnits.map((item: AdUnit, index: number) => { + const isLast = index === campaign.adUnits.length - 1 + return ( + + } + align="center" + noBorder={isLast} + /> + ) + })} + )} From 89dce209470ce10a3550f0c9a745eb097e149261 Mon Sep 17 00:00:00 2001 From: IvoPaunov Date: Wed, 28 Aug 2024 11:58:50 +0300 Subject: [PATCH 03/84] fix access and refresh tokens update logic --- .../AccountContext/AccountContext.tsx | 85 +++++++++---------- src/lib/backend/login.ts | 16 +--- 2 files changed, 39 insertions(+), 62 deletions(-) diff --git a/src/contexts/AccountContext/AccountContext.tsx b/src/contexts/AccountContext/AccountContext.tsx index 6f74deff..2577e475 100644 --- a/src/contexts/AccountContext/AccountContext.tsx +++ b/src/contexts/AccountContext/AccountContext.tsx @@ -9,13 +9,7 @@ import { useState } from 'react' import { Account, BillingDetails, IAdExAccount } from 'types' -import { - getMessageToSign, - isAdminToken, - isTokenExpired, - refreshAccessToken, - verifyLogin -} from 'lib/backend' +import { getMessageToSign, isAdminToken, isTokenExpired, verifyLogin } from 'lib/backend' import { AmbireLoginSDK } from '@ambire/login-sdk-core' import { DAPP_ICON_PATH, DAPP_NAME, DEFAULT_CHAIN_ID } from 'constants/login' import useCustomNotifications from 'hooks/useCustomNotifications' @@ -64,7 +58,6 @@ interface IAccountContext { isAdmin: boolean connectWallet: () => void disconnectWallet: () => void - updateAccessToken: () => Promise resetAdexAccount: () => void adexServicesRequest: ( service: AdExService, @@ -196,44 +189,38 @@ const AccountProvider: FC = ({ children }) => { [ambireSDK] ) - const updateAccessToken = useCallback(async () => { - if (!adexAccount.accessToken || !adexAccount.refreshToken) return - - if (isTokenExpired(adexAccount.refreshToken)) { - resetAdexAccount() - showNotification('error', 'Refresh token has been expired', 'Refresh token') - return + const checkAndGetNewAccessTokens = useCallback(async (): Promise<{ + accessToken: string + refreshToken: string + } | null> => { + if (!adexAccount.accessToken || !adexAccount.refreshToken) { + throw new Error('Missing access tokens') } if (isTokenExpired(adexAccount.accessToken)) { try { - const response = await refreshAccessToken(adexAccount.refreshToken) - if (response) { - setAdexAccount((prev) => { - const next = { - ...prev, - accessToken: response.accessToken, - refreshToken: response.refreshToken - } - - return next + const req: RequestOptions = { + url: `${BACKEND_BASE_URL}/dsp/refresh-token`, + method: 'POST', + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify({ + refreshToken: adexAccount.refreshToken }) - return response } - return null + + const res = await fetchService(req) + return await processResponse<{ accessToken: string; refreshToken: string }>(res) } catch (error: any) { console.error('Updating access token failed:', error) showNotification('error', error?.message, 'Updating access token failed') - throw error + throw new Error(error) } } - }, [ - adexAccount.accessToken, - adexAccount.refreshToken, - resetAdexAccount, - showNotification, - setAdexAccount - ]) + + return null + }, [adexAccount.accessToken, adexAccount.refreshToken, showNotification]) const adexServicesRequest = useCallback( // Note @@ -264,14 +251,16 @@ const AccountProvider: FC = ({ children }) => { [authHeaderProp]: `Bearer ${adexAccount.accessToken}` } - // TODO: log-out if no access token - // TODO: fix updateAccessToken logic - it returns if there are no access token, - // it should throw ot log-out - // TODO: if using updateAccessToken triggers some circular updates - account context should be fixed - const response = await updateAccessToken() + const newAccessTokens = await checkAndGetNewAccessTokens() - if (response) { - const updatedAccessToken = response.accessToken + if (newAccessTokens) { + setAdexAccount((prev) => { + return { + ...prev, + ...newAccessTokens + } + }) + const updatedAccessToken = newAccessTokens.accessToken authHeader[authHeaderProp] = `Bearer ${updatedAccessToken}` } @@ -280,8 +269,6 @@ const AccountProvider: FC = ({ children }) => { ...req.headers } - console.log('req', req) - try { const res = await fetchService(req) return await processResponse(res) @@ -293,7 +280,13 @@ const AccountProvider: FC = ({ children }) => { return Promise.reject() } }, - [adexAccount.accessToken, resetAdexAccount, showNotification, updateAccessToken] + [ + adexAccount.accessToken, + checkAndGetNewAccessTokens, + setAdexAccount, + showNotification, + resetAdexAccount + ] ) const handleRegistrationOrLoginSuccess = useCallback( @@ -510,7 +503,6 @@ const AccountProvider: FC = ({ children }) => { disconnectWallet, signMessage, ambireSDK, - updateAccessToken, resetAdexAccount, adexServicesRequest, updateBalance, @@ -525,7 +517,6 @@ const AccountProvider: FC = ({ children }) => { disconnectWallet, signMessage, ambireSDK, - updateAccessToken, resetAdexAccount, adexServicesRequest, updateBalance, diff --git a/src/lib/backend/login.ts b/src/lib/backend/login.ts index 490c19ef..7ae99c4b 100644 --- a/src/lib/backend/login.ts +++ b/src/lib/backend/login.ts @@ -51,6 +51,7 @@ export const isTokenExpired = (token: string) => { const timeNowInSeconds = Math.floor(new Date().getTime() / 1000) const decodeToken = parseJwt(token) + console.log({ decodeToken }) return decodeToken.exp < timeNowInSeconds } @@ -93,21 +94,6 @@ export const verifyLogin = async (body: VerifyLoginProps) => { return fetchService(req).then(processResponse) } -export const refreshAccessToken = async (refreshToken: string) => { - const req: RequestOptions = { - url: `${BACKEND_BASE_URL}/dsp/refresh-token`, - method: 'POST', - headers: { - 'Content-Type': 'application/json' - }, - body: JSON.stringify({ - refreshToken - }) - } - - return fetchService(req).then(processResponse) -} - export async function registerUser( adexUser: IAdExAccount ): Promise<{ adexUser: IAdExAccount | null; error?: AppError }> { From 81232f829b59a7225f584f5eeaaf10801dab0510 Mon Sep 17 00:00:00 2001 From: IvoPaunov Date: Wed, 28 Aug 2024 15:02:39 +0300 Subject: [PATCH 04/84] log out on refresh token expire --- .../AccountContext/AccountContext.tsx | 86 ++++++++++--------- 1 file changed, 44 insertions(+), 42 deletions(-) diff --git a/src/contexts/AccountContext/AccountContext.tsx b/src/contexts/AccountContext/AccountContext.tsx index 2577e475..6316d84a 100644 --- a/src/contexts/AccountContext/AccountContext.tsx +++ b/src/contexts/AccountContext/AccountContext.tsx @@ -194,7 +194,7 @@ const AccountProvider: FC = ({ children }) => { refreshToken: string } | null> => { if (!adexAccount.accessToken || !adexAccount.refreshToken) { - throw new Error('Missing access tokens') + throw new Error(`${UNAUTHORIZED_ERR_STR}: missing access tokens`) } if (isTokenExpired(adexAccount.accessToken)) { @@ -215,61 +215,63 @@ const AccountProvider: FC = ({ children }) => { } catch (error: any) { console.error('Updating access token failed:', error) showNotification('error', error?.message, 'Updating access token failed') - throw new Error(error) + throw new Error(`${UNAUTHORIZED_ERR_STR}: ${error}`) } + } else if (isTokenExpired(adexAccount.refreshToken)) { + resetAdexAccount() + showNotification('info', 'Please log in!', 'Session expired') } return null - }, [adexAccount.accessToken, adexAccount.refreshToken, showNotification]) + }, [adexAccount.accessToken, adexAccount.refreshToken, resetAdexAccount, showNotification]) const adexServicesRequest = useCallback( // Note async (service: AdExService, reqOptions: ApiRequestOptions): Promise => { - // temp hax for using the same token fot validator auth - const authHeaderProp = service === 'backend' ? 'X-DSP-AUTH' : 'authorization' - - // url check - // TODO: route instead url in props - const baseUrl = (service === 'backend' ? BACKEND_BASE_URL : VALIDATOR_BASE_URL) || '' - const urlCheck = reqOptions.route.replace(baseUrl, '').replace(/^\//, '') - - const req: RequestOptions = { - url: `${baseUrl}/${urlCheck}`, - method: reqOptions.method, - body: - reqOptions.body instanceof FormData - ? reqOptions.body - : reqOptions.body && JSON.stringify(SuperJSON.serialize(reqOptions.body).json), - queryParams: reqOptions.queryParams, - headers: reqOptions.headers - } + try { + // temp hax for using the same token fot validator auth + const authHeaderProp = service === 'backend' ? 'X-DSP-AUTH' : 'authorization' - // console.log('adexAccount', adexAccount) - if (!adexAccount.accessToken) throw new Error('Access token is missing') + // url check + // TODO: route instead url in props + const baseUrl = (service === 'backend' ? BACKEND_BASE_URL : VALIDATOR_BASE_URL) || '' + const urlCheck = reqOptions.route.replace(baseUrl, '').replace(/^\//, '') - const authHeader = { - [authHeaderProp]: `Bearer ${adexAccount.accessToken}` - } + const req: RequestOptions = { + url: `${baseUrl}/${urlCheck}`, + method: reqOptions.method, + body: + reqOptions.body instanceof FormData + ? reqOptions.body + : reqOptions.body && JSON.stringify(SuperJSON.serialize(reqOptions.body).json), + queryParams: reqOptions.queryParams, + headers: reqOptions.headers + } - const newAccessTokens = await checkAndGetNewAccessTokens() + // console.log('adexAccount', adexAccount) + if (!adexAccount.accessToken) throw new Error('Access token is missing') - if (newAccessTokens) { - setAdexAccount((prev) => { - return { - ...prev, - ...newAccessTokens - } - }) - const updatedAccessToken = newAccessTokens.accessToken - authHeader[authHeaderProp] = `Bearer ${updatedAccessToken}` - } + const authHeader = { + [authHeaderProp]: `Bearer ${adexAccount.accessToken}` + } - req.headers = { - ...authHeader, - ...req.headers - } + const newAccessTokens = await checkAndGetNewAccessTokens() + + if (newAccessTokens) { + setAdexAccount((prev) => { + return { + ...prev, + ...newAccessTokens + } + }) + authHeader[authHeaderProp] = `Bearer ${newAccessTokens.accessToken}` + } + + req.headers = { + ...authHeader, + ...req.headers + } - try { const res = await fetchService(req) return await processResponse(res) } catch (err: any) { From 4f6f9e2136bf616d1a87418edec4b6d9b0678388 Mon Sep 17 00:00:00 2001 From: IvoPaunov Date: Wed, 28 Aug 2024 15:16:59 +0300 Subject: [PATCH 05/84] add logOut to account context --- src/components/TopBar/TopBar.tsx | 53 ++----------------- .../AccountContext/AccountContext.tsx | 37 +++++++++++-- 2 files changed, 36 insertions(+), 54 deletions(-) diff --git a/src/components/TopBar/TopBar.tsx b/src/components/TopBar/TopBar.tsx index 58ee0115..032ab9f6 100644 --- a/src/components/TopBar/TopBar.tsx +++ b/src/components/TopBar/TopBar.tsx @@ -15,17 +15,15 @@ import { import { createStyles } from '@mantine/emotion' import { formatDate, maskAddress } from 'helpers/formatters' import useAccount from 'hooks/useAccount' -import { useCallback, useMemo, useState } from 'react' +import { useMemo, useState } from 'react' import DownArrowIcon from 'resources/icons/DownArrow' import LogoutIcon from 'resources/icons/Logout' // import BellIcon from 'resources/icons/Bell' import Blockies from 'components/common/Blockies' // import ValidatorsIcon from 'resources/icons/Validators' // import WithdrawIcon from 'resources/icons/Withdraw' -import { useLocation, useNavigate, useParams } from 'react-router-dom' +import { useLocation, useParams } from 'react-router-dom' import StakingIcon from 'resources/icons/Staking' -import { useAdExApi } from 'hooks/useAdexServices' -import useCustomNotifications from 'hooks/useCustomNotifications' import CopyIcon from 'resources/icons/Copy' import { useColorScheme } from '@mantine/hooks' @@ -64,8 +62,7 @@ const formatTitle = (str: string) => { function TopBar() { const { classes, cx } = useStyles() - const { adexAccount, disconnectWallet, resetAdexAccount } = useAccount() - const { showNotification } = useCustomNotifications() + const { adexAccount, logOut } = useAccount() const location = useLocation() const { id } = useParams() const splitPath = useMemo(() => location.pathname.split('/'), [location.pathname]) @@ -81,45 +78,6 @@ function TopBar() { const [opened, setOpened] = useState(false) - const { adexServicesRequest } = useAdExApi() - const navigate = useNavigate() - - const handleLogutBtnClicked = useCallback(() => { - if (!adexAccount.accessToken && !adexAccount.refreshToken) return - - adexServicesRequest('backend', { - route: '/dsp/logout', - method: 'POST', - headers: { - 'Content-Type': 'application/json' - }, - body: { - refreshToken: adexAccount.refreshToken - } - }) - .then((res) => { - if (!res) throw new Error('Logout failed') - if (Object.keys(res).length === 0 && res.constructor === Object) { - disconnectWallet() - resetAdexAccount() - showNotification('info', 'Successfully logged out', 'Logging out') - navigate('/login', { replace: true }) - } - }) - .catch((e) => { - console.error('Logging out failed', e) - showNotification('error', e.message, 'Logging out failed') - }) - }, [ - disconnectWallet, - adexAccount.accessToken, - adexAccount.refreshToken, - adexServicesRequest, - resetAdexAccount, - showNotification, - navigate - ]) - return ( }> Validators */} - } - > + }> Log out diff --git a/src/contexts/AccountContext/AccountContext.tsx b/src/contexts/AccountContext/AccountContext.tsx index 6316d84a..6b040d3c 100644 --- a/src/contexts/AccountContext/AccountContext.tsx +++ b/src/contexts/AccountContext/AccountContext.tsx @@ -58,7 +58,7 @@ interface IAccountContext { isAdmin: boolean connectWallet: () => void disconnectWallet: () => void - resetAdexAccount: () => void + logOut: () => void adexServicesRequest: ( service: AdExService, reqOptions: ApiRequestOptions @@ -291,6 +291,33 @@ const AccountProvider: FC = ({ children }) => { ] ) + const logOut = useCallback(async () => { + try { + const resp = await adexServicesRequest<{}>('backend', { + route: '/dsp/logout', + method: 'POST', + body: { + refreshToken: adexAccount.refreshToken + } + }) + console.log(resp) + if (resp) { + disconnectWallet() + resetAdexAccount() + showNotification('info', 'Successfully logged out', 'Logging out') + } + } catch (err: any) { + console.error('logOut: ', err) + showNotification('error', err?.message || err, 'Logging out failed') + } + }, [ + adexAccount.refreshToken, + adexServicesRequest, + disconnectWallet, + resetAdexAccount, + showNotification + ]) + const handleRegistrationOrLoginSuccess = useCallback( async ({ address, chainId }: any) => { if (!address || !chainId) { @@ -505,11 +532,11 @@ const AccountProvider: FC = ({ children }) => { disconnectWallet, signMessage, ambireSDK, - resetAdexAccount, adexServicesRequest, updateBalance, updateBillingDetails, - isLoading + isLoading, + logOut }), [ adexAccount, @@ -519,11 +546,11 @@ const AccountProvider: FC = ({ children }) => { disconnectWallet, signMessage, ambireSDK, - resetAdexAccount, adexServicesRequest, updateBalance, updateBillingDetails, - isLoading + isLoading, + logOut ] ) From f19a988b7e87c9b53e3afd79b01819be12422da5 Mon Sep 17 00:00:00 2001 From: IvoPaunov Date: Wed, 28 Aug 2024 17:51:11 +0300 Subject: [PATCH 06/84] fix create campaign --- .../CreateCampaign/CampaignSummary.tsx | 7 ++- .../CreateCampaign/CreateCampaign.tsx | 32 ++++--------- src/constants/createCampaign.ts | 1 - .../CreateCampaignContext.tsx | 48 +++++++++++++------ src/types/createCampaignCommon.ts | 2 +- 5 files changed, 46 insertions(+), 44 deletions(-) diff --git a/src/components/CreateCampaign/CampaignSummary.tsx b/src/components/CreateCampaign/CampaignSummary.tsx index 0fb5ec24..7a80a031 100644 --- a/src/components/CreateCampaign/CampaignSummary.tsx +++ b/src/components/CreateCampaign/CampaignSummary.tsx @@ -1,12 +1,11 @@ import { Button, Group, Stack, Text } from '@mantine/core' -import { CREATE_CAMPAIGN_STEPS } from 'constants/createCampaign' import useCreateCampaignContext from 'hooks/useCreateCampaignContext' import LeftArrowIcon from 'resources/icons/LeftArrow' import useCreateCampaignData from 'hooks/useCreateCampaignData/useCreateCampaignData' import CampaignDetailsRow from 'components/common/CampainDetailsRow' import { UtmInfo } from './CreateCampaignCommon' -const CampaignSummary = () => { +const CampaignSummary = ({ onLaunchClick }: { onLaunchClick: () => void }) => { const { campaign: { autoUTMChecked, @@ -92,8 +91,8 @@ const CampaignSummary = () => { 0 */} - {step === CREATE_CAMPAIGN_STEPS ? ( - ) : ( diff --git a/src/components/CreateCampaign/CreateCampaign.tsx b/src/components/CreateCampaign/CreateCampaign.tsx index 3393432c..616d4bf2 100644 --- a/src/components/CreateCampaign/CreateCampaign.tsx +++ b/src/components/CreateCampaign/CreateCampaign.tsx @@ -2,14 +2,12 @@ import { Flex, Stack, Paper, Text, Box } from '@mantine/core' import useCreateCampaignContext from 'hooks/useCreateCampaignContext' import { useCallback, useEffect, useMemo, useState } from 'react' import { modals } from '@mantine/modals' -import useCustomNotifications from 'hooks/useCustomNotifications' import type { unstable_Blocker as Blocker, unstable_BlockerFunction as BlockerFunction } from 'react-router-dom' import { unstable_useBlocker as useBlocker, useNavigate } from 'react-router-dom' import { defaultConfirmModalProps } from 'components/common/Modals/CustomConfirmModal' -import useAccount from 'hooks/useAccount' import throttle from 'lodash.throttle' import { SuccessModal } from 'components/common/Modals' import CustomStepper from './CampaignStepper' @@ -35,11 +33,10 @@ const Wizard = ({ step }: { step: number }) => { } const CreateCampaign = () => { + // TODO: use modals Providers for this const [isSuccessModalOpened, SetIsSuccessModalOpened] = useState(false) - const { updateBalance } = useAccount() const navigate = useNavigate() - const { publishCampaign, resetCampaign, form, step } = useCreateCampaignContext() - const { showNotification } = useCustomNotifications() + const { publishCampaign, resetCampaign, step } = useCreateCampaignContext() const shouldBlock = useCallback( ({ currentLocation, nextLocation }) => currentLocation.pathname !== nextLocation.pathname, @@ -55,21 +52,10 @@ const CreateCampaign = () => { }, [blocker, resetCampaign]) const launchCampaign = useCallback(async () => { - try { - const res = await publishCampaign() - - if (res && res.success) { - await updateBalance() - SetIsSuccessModalOpened(true) - resetCampaign() - } else { - showNotification('warning', 'invalid campaign data response', 'Data error') - } - } catch (err) { - console.error(err) - showNotification('error', 'Creating campaign failed', 'Data error') - } - }, [publishCampaign, resetCampaign, SetIsSuccessModalOpened, showNotification, updateBalance]) + await publishCampaign(() => { + SetIsSuccessModalOpened(true) + }) + }, [publishCampaign]) const throttledLaunchCampaign = useMemo( () => throttle(launchCampaign, 1069, { leading: true }), @@ -95,7 +81,7 @@ const CreateCampaign = () => { }, [navigate, SetIsSuccessModalOpened]) return ( -
+ <> @@ -107,7 +93,7 @@ const CreateCampaign = () => { - + { opened={isSuccessModalOpened} close={handleOnModalClose} /> - + ) } diff --git a/src/constants/createCampaign.ts b/src/constants/createCampaign.ts index a97af034..8aae455b 100644 --- a/src/constants/createCampaign.ts +++ b/src/constants/createCampaign.ts @@ -6,7 +6,6 @@ import { AllCountries } from 'adex-common' -export const CREATE_CAMPAIGN_STEPS = 4 export const CAMPAIGN_PLACEMENTS_INPUT = 'targetingInput.inputs.placements.in' export const CAMPAIGN_INCLUDE_INCENTIVIZED_INPUT = 'targetingInput.inputs.advanced.includeIncentivized' diff --git a/src/contexts/CreateCampaignContext/CreateCampaignContext.tsx b/src/contexts/CreateCampaignContext/CreateCampaignContext.tsx index 8324f451..468d8908 100644 --- a/src/contexts/CreateCampaignContext/CreateCampaignContext.tsx +++ b/src/contexts/CreateCampaignContext/CreateCampaignContext.tsx @@ -87,7 +87,8 @@ const CreateCampaignContextProvider: FC = ({ children }) => { balanceToken, balanceToken: { decimals } }, - isAdmin + isAdmin, + updateBalance } = useAccount() const defaultValue = useMemo( @@ -371,19 +372,6 @@ const CreateCampaignContextProvider: FC = ({ children }) => { } }, [campaign, form]) - const publishCampaign = useCallback(() => { - const preparedCampaign = form.getTransformedValues() - - return adexServicesRequest('backend', { - route: '/dsp/campaigns', - method: 'POST', - body: preparedCampaign, - headers: { - 'Content-Type': 'application/json' - } - }) - }, [form, adexServicesRequest]) - const saveToDraftCampaign = useCallback(async () => { const preparedCampaign = form.getTransformedValues() @@ -463,7 +451,7 @@ const CreateCampaignContextProvider: FC = ({ children }) => { if (current === 2 && form.getValues().autoUTMChecked) { addUTMToTargetURLS() } - return current < 4 ? current + 1 : current + return current < 3 ? current + 1 : current }), [addUTMToTargetURLS, form] ) @@ -486,6 +474,7 @@ const CreateCampaignContextProvider: FC = ({ children }) => { localStorage.removeItem(LS_KEY_CREATE_CAMPAIGN_STEP) onReset && onReset() } + if (form.isDirty()) { modals.openConfirmModal( defaultConfirmModalProps({ @@ -512,6 +501,35 @@ const CreateCampaignContextProvider: FC = ({ children }) => { [form, saveToDraftCampaign] ) + const publishCampaign = useCallback( + async (onSuccess?: () => void) => { + const preparedCampaign = form.getTransformedValues() + + try { + const res = await adexServicesRequest<{ success: boolean }>('backend', { + route: '/dsp/campaigns', + method: 'POST', + body: preparedCampaign, + headers: { + 'Content-Type': 'application/json' + } + }) + + if (res && res.success) { + form.resetDirty() + updateBalance() + onSuccess && typeof onSuccess === 'function' && onSuccess() + } else { + showNotification('warning', 'invalid campaign data response', 'Data error') + } + } catch (err) { + console.error(err) + showNotification('error', 'Creating campaign failed', 'Data error') + } + }, + [form, adexServicesRequest, updateBalance, showNotification] + ) + const contextValue = useMemo( () => ({ campaign, diff --git a/src/types/createCampaignCommon.ts b/src/types/createCampaignCommon.ts index 7398b753..a90f50f0 100644 --- a/src/types/createCampaignCommon.ts +++ b/src/types/createCampaignCommon.ts @@ -53,7 +53,7 @@ export type CreateCampaignType = { prevStep: () => void // stepsCount: number campaign: CampaignUI - publishCampaign: () => Promise + publishCampaign: (onSuccess?: () => void) => Promise resetCampaign: (reasonMsg?: string, onReset?: () => void) => void allowedBannerSizes: string[] saveToDraftCampaign: () => Promise From 9cc4cfd16fe46e3e1cbccf158fde60441b88609a Mon Sep 17 00:00:00 2001 From: IvoPaunov Date: Thu, 29 Aug 2024 06:45:35 +0300 Subject: [PATCH 07/84] fix log out --- .../AccountContext/AccountContext.tsx | 25 ++++++++----------- src/services/fetch.ts | 4 +-- 2 files changed, 12 insertions(+), 17 deletions(-) diff --git a/src/contexts/AccountContext/AccountContext.tsx b/src/contexts/AccountContext/AccountContext.tsx index 6b040d3c..dede4180 100644 --- a/src/contexts/AccountContext/AccountContext.tsx +++ b/src/contexts/AccountContext/AccountContext.tsx @@ -14,7 +14,7 @@ import { AmbireLoginSDK } from '@ambire/login-sdk-core' import { DAPP_ICON_PATH, DAPP_NAME, DEFAULT_CHAIN_ID } from 'constants/login' import useCustomNotifications from 'hooks/useCustomNotifications' import { fetchService, getReqErr, RequestOptions } from 'services' -import SuperJSON from 'superjson' +// import SuperJSON from 'superjson' const ambireLoginSDK = new AmbireLoginSDK({ dappName: DAPP_NAME, @@ -46,7 +46,7 @@ type AdExService = 'backend' | 'validator' type ApiRequestOptions = Omit & { route: string - body?: BodyInit | object | string | null + body?: BodyInit | object | string | FormData noAuth?: boolean onErrMsg?: string } @@ -241,9 +241,9 @@ const AccountProvider: FC = ({ children }) => { url: `${baseUrl}/${urlCheck}`, method: reqOptions.method, body: - reqOptions.body instanceof FormData - ? reqOptions.body - : reqOptions.body && JSON.stringify(SuperJSON.serialize(reqOptions.body).json), + !(reqOptions.body instanceof FormData) && typeof reqOptions.body === 'object' + ? serializeJSON(reqOptions.body) + : reqOptions.body, queryParams: reqOptions.queryParams, headers: reqOptions.headers } @@ -278,17 +278,10 @@ const AccountProvider: FC = ({ children }) => { if (service === 'backend' && err && (err?.message || err).includes(UNAUTHORIZED_ERR_STR)) { resetAdexAccount() } - showNotification('error', err.message, reqOptions.onErrMsg || 'Data error') - return Promise.reject() + return Promise.reject(err) } }, - [ - adexAccount.accessToken, - checkAndGetNewAccessTokens, - setAdexAccount, - showNotification, - resetAdexAccount - ] + [adexAccount.accessToken, checkAndGetNewAccessTokens, setAdexAccount, resetAdexAccount] ) const logOut = useCallback(async () => { @@ -296,11 +289,13 @@ const AccountProvider: FC = ({ children }) => { const resp = await adexServicesRequest<{}>('backend', { route: '/dsp/logout', method: 'POST', + headers: { + 'content-type': 'application/json' + }, body: { refreshToken: adexAccount.refreshToken } }) - console.log(resp) if (resp) { disconnectWallet() resetAdexAccount() diff --git a/src/services/fetch.ts b/src/services/fetch.ts index 1f0fa6eb..fe64c28e 100644 --- a/src/services/fetch.ts +++ b/src/services/fetch.ts @@ -1,9 +1,9 @@ -export interface RequestOptions { +export type RequestOptions = { url: string method?: 'GET' | 'POST' | 'OPTIONS' | 'PUT' | 'DELETE' headers?: Record queryParams?: Record - body?: BodyInit | null + body?: BodyInit } export async function fetchService(options: RequestOptions): Promise { From 1ab4dea48494056fe23d43d2b0c8c99a955fa229 Mon Sep 17 00:00:00 2001 From: IvoPaunov Date: Thu, 29 Aug 2024 09:23:30 +0300 Subject: [PATCH 08/84] remove redundant callback for login btn --- src/components/LogIn/LogIn.tsx | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/components/LogIn/LogIn.tsx b/src/components/LogIn/LogIn.tsx index 9397a2d1..a985814c 100644 --- a/src/components/LogIn/LogIn.tsx +++ b/src/components/LogIn/LogIn.tsx @@ -15,7 +15,7 @@ import LogInBackground from 'resources/backgrounds/pattern.svg' import LowerShape from 'resources/backgrounds/lowerShape.svg' import UpperShape from 'resources/backgrounds/upperShape.svg' import AdExLogo from 'resources/logos/AdExLogo' -import { useCallback, useEffect, useMemo } from 'react' +import { useEffect, useMemo } from 'react' import HelpIcon from 'resources/icons/Help' import CustomAnchor from 'components/common/customAnchor/CustomAnchor' import { useNavigate } from 'react-router-dom' @@ -49,7 +49,6 @@ function LogIn() { if (authenticated) navigate('/dashboard', { replace: true }) }, [authenticated, navigate]) - const handleGetStartedBtnClicked = useCallback(() => connectWallet(), [connectWallet]) return ( - From dc0bc1f1cdbde23042b5d51f1b9ef67b7aafcc73 Mon Sep 17 00:00:00 2001 From: IvoPaunov Date: Thu, 29 Aug 2024 14:55:50 +0300 Subject: [PATCH 09/84] wip: fixing login flow and access tokens update --- .../AccountContext/AccountContext.tsx | 233 +++++++++++------- src/helpers/dateTime.ts | 3 +- src/lib/backend/login.ts | 129 +++------- src/types/account.ts | 2 - 4 files changed, 180 insertions(+), 187 deletions(-) diff --git a/src/contexts/AccountContext/AccountContext.tsx b/src/contexts/AccountContext/AccountContext.tsx index dede4180..d555405d 100644 --- a/src/contexts/AccountContext/AccountContext.tsx +++ b/src/contexts/AccountContext/AccountContext.tsx @@ -9,7 +9,7 @@ import { useState } from 'react' import { Account, BillingDetails, IAdExAccount } from 'types' -import { getMessageToSign, isAdminToken, isTokenExpired, verifyLogin } from 'lib/backend' +import { isAdminToken, isTokenExpired } from 'lib/backend' import { AmbireLoginSDK } from '@ambire/login-sdk-core' import { DAPP_ICON_PATH, DAPP_NAME, DEFAULT_CHAIN_ID } from 'constants/login' import useCustomNotifications from 'hooks/useCustomNotifications' @@ -42,6 +42,32 @@ const processResponse = (res: Response): Promise => { }) } +type AuthMsgResp = { + authMsg: { + domain: { + name: string + chainId: any + } + types: { + LoginInfo: { + name: string + type: string + }[] + } + primaryType: string + message: { + wallet: string + purpose: string + requestedAt: Date + } + } +} + +type AccessTokensResp = { + accessToken: string + refreshToken: string +} + type AdExService = 'backend' | 'validator' type ApiRequestOptions = Omit & { @@ -75,8 +101,6 @@ const defaultValue: IAccountContext['adexAccount'] = { chainId: 0, accessToken: null, refreshToken: null, - authenticated: false, - authMsgResp: null, loaded: false, // This ensures there is some obj in the ls initialLoad: false, @@ -140,7 +164,8 @@ const AccountProvider: FC = ({ children }) => { const { showNotification } = useCustomNotifications() const ambireSDK = useMemo(() => ambireLoginSDK, []) const [isLoading, setIsLoading] = useState(false) - const [sdkMsgSignature, setSdkMsgSignature] = useState('') + const [sdkMsgSignature, setSdkMsgSignature] = useState(null) + const [authMsg, setAuthMsg] = useState(null) const [adexAccount, setAdexAccount] = useLocalStorage({ key: 'adexAccount', defaultValue: { ...defaultValue }, @@ -161,11 +186,17 @@ const AccountProvider: FC = ({ children }) => { return seri } }) + const authenticated = useMemo( + () => !!adexAccount.accessToken && !!adexAccount.refreshToken, + [adexAccount.accessToken, adexAccount.refreshToken] + ) // NOTE: hax to ensure there is storage value as there is no way to differentiate the default value from storage value using useLocalStorage useEffect(() => { - const lsAcc = deserializeJSON(localStorage.getItem('adexAccount') || '') - // console.log({ lsAcc }) + const lsAcc: IAccountContext['adexAccount'] = deserializeJSON( + localStorage.getItem('adexAccount') || '' + ) + console.log({ lsAcc }) if (!lsAcc) { setAdexAccount({ ...defaultValue, initialLoad: true }) @@ -173,14 +204,18 @@ const AccountProvider: FC = ({ children }) => { }, [setAdexAccount]) const resetAdexAccount = useCallback( - () => setAdexAccount({ ...defaultValue, initialLoad: true }), + (reason?: string) => { + console.log('reset account: ', reason) + setAdexAccount({ ...defaultValue, initialLoad: true }) + }, [setAdexAccount] ) const connectWallet = useCallback(() => { console.log({ ambireSDK }) + resetAdexAccount('connecting new wallet') ambireSDK.openLogin({ chainId: DEFAULT_CHAIN_ID }) - }, [ambireSDK]) + }, [ambireSDK, resetAdexAccount]) const disconnectWallet = useCallback(() => ambireSDK.openLogout(), [ambireSDK]) @@ -197,7 +232,8 @@ const AccountProvider: FC = ({ children }) => { throw new Error(`${UNAUTHORIZED_ERR_STR}: missing access tokens`) } - if (isTokenExpired(adexAccount.accessToken)) { + if (isTokenExpired(adexAccount.accessToken, 100)) { + console.log('updating access tokens') try { const req: RequestOptions = { url: `${BACKEND_BASE_URL}/dsp/refresh-token`, @@ -218,7 +254,7 @@ const AccountProvider: FC = ({ children }) => { throw new Error(`${UNAUTHORIZED_ERR_STR}: ${error}`) } } else if (isTokenExpired(adexAccount.refreshToken)) { - resetAdexAccount() + resetAdexAccount('refresh token expired') showNotification('info', 'Please log in!', 'Session expired') } @@ -276,7 +312,7 @@ const AccountProvider: FC = ({ children }) => { return await processResponse(res) } catch (err: any) { if (service === 'backend' && err && (err?.message || err).includes(UNAUTHORIZED_ERR_STR)) { - resetAdexAccount() + resetAdexAccount(UNAUTHORIZED_ERR_STR) } return Promise.reject(err) } @@ -298,7 +334,7 @@ const AccountProvider: FC = ({ children }) => { }) if (resp) { disconnectWallet() - resetAdexAccount() + resetAdexAccount('Log out btn (backend)') showNotification('info', 'Successfully logged out', 'Logging out') } } catch (err: any) { @@ -313,115 +349,145 @@ const AccountProvider: FC = ({ children }) => { showNotification ]) - const handleRegistrationOrLoginSuccess = useCallback( + const getAuthMsg = useCallback( + async ({ wallet, chainId }: { wallet: string; chainId: string }): Promise => { + try { + const req: RequestOptions = { + url: `${BACKEND_BASE_URL}/dsp/login-msg`, + method: 'POST', + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify({ + wallet, + chainId + }) + } + + const res = await fetchService(req) + return await processResponse(res) + } catch (err: any) { + console.log(err) + throw new Error(err) + } + }, + [] + ) + + const verifyLoginMsg = useCallback( + async (veryData: AuthMsgResp & { signature: string }): Promise => { + try { + const req: RequestOptions = { + url: `${BACKEND_BASE_URL}/dsp/login-verify`, + method: 'POST', + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify(veryData) + } + + const res = await fetchService(req) + return await processResponse(res) + } catch (err: any) { + console.log(err) + throw new Error(err) + } + }, + [] + ) + + const handleSDKAuthSuccess = useCallback( async ({ address, chainId }: any) => { if (!address || !chainId) { showNotification('warning', 'Ambire sdk no address or chain') return } - // TODO: this need to be fixed because it can be called more than once - // because it's triggered on more than oe event - // This check works atm but it's not ok - // if (prev.address !== '' && prev.chainId !== 0 && prev.authMsgResp !== null) { - // return prev - // } + console.log('handleSDKAuthSuccess') try { - console.log('getMessageToSign', { address, chainId }) - const authMsgRsp = await getMessageToSign({ address, chainId }) - - setAdexAccount((prev) => { - if (prev.address !== '' && prev.chainId !== 0 && prev.authMsgResp !== null) { - return prev - } - - const next = { ...prev, address, chainId, authMsgResp: authMsgRsp.authMsg } - return next - }) + const authMsgResp = await getAuthMsg({ wallet: address, chainId }) + console.log({ authMsgResp }) + setAuthMsg(authMsgResp) + signMessage('eth_signTypedData', JSON.stringify(authMsgResp.authMsg)) + setAdexAccount({ ...defaultValue, address, chainId }) } catch (error) { console.error('Get message to sign failed', error) showNotification('error', 'Get message to sign failed') } }, - [setAdexAccount, showNotification] + [getAuthMsg, setAdexAccount, showNotification, signMessage] ) useEffect(() => { - if (!sdkMsgSignature || !adexAccount.authMsgResp || adexAccount.authenticated) return - async function verify() { + if (!sdkMsgSignature || !authMsg) return try { - // console.log('verifyLogin') - const authResp = await verifyLogin({ - authMsg: { ...adexAccount.authMsgResp }, + const authResp = await verifyLoginMsg({ + ...authMsg, signature: sdkMsgSignature }) - if (!authResp) { - setIsLoading(false) - throw new Error('Verify login failed') + if (!authResp.accessToken || !authResp.refreshToken) { + throw new Error('Verify login failed, invalid tokens response') } - setAdexAccount((prev) => { - const { accessToken, refreshToken } = authResp - const next = { + setAdexAccount((prev: IAccountContext['adexAccount']) => { + const next: IAccountContext['adexAccount'] = { ...prev, - accessToken, - refreshToken, - authenticated: !!authResp.accessToken && !!authResp.refreshToken + ...authResp } - return next }) - setIsLoading(false) - } catch (error) { + } catch (error: any) { console.error('Error verify login:', error) - showNotification('error', 'Verify login failed') + showNotification('error', 'Verify login failed', error?.message || error) + setAdexAccount({ + ...defaultValue + }) + } finally { setIsLoading(false) + setSdkMsgSignature(null) + setAuthMsg(null) } } verify() - }, [ - sdkMsgSignature, - adexAccount.authMsgResp, - adexAccount.authenticated, - showNotification, - setAdexAccount - ]) - - const handleMsgRejected = useCallback(() => { - disconnectWallet() - }, [disconnectWallet]) + }, [sdkMsgSignature, showNotification, setAdexAccount, verifyLoginMsg, authMsg]) const handleLogoutSuccess = useCallback(() => { - resetAdexAccount() + resetAdexAccount('Log out SDK') }, [resetAdexAccount]) - const handleActionRejected = useCallback(() => { - disconnectWallet() - }, [disconnectWallet]) + const handleMsgRejected = useCallback((data: any) => { + console.log('message rejected', data) + }, []) + + const handleActionRejected = useCallback((data: any) => { + console.log('action rejected', data) + }, []) + + const handleTxnRejected = useCallback((data: any) => { + console.log('action rejected', data) + }, []) useEffect(() => { - ambireSDK.onRegistrationSuccess(handleRegistrationOrLoginSuccess) - }, [ambireSDK, handleRegistrationOrLoginSuccess]) + ambireSDK.onRegistrationSuccess(handleSDKAuthSuccess) + }, [ambireSDK, handleSDKAuthSuccess]) useEffect(() => { - ambireSDK.onLoginSuccess(handleRegistrationOrLoginSuccess) - }, [ambireSDK, handleRegistrationOrLoginSuccess]) + ambireSDK.onLoginSuccess(handleSDKAuthSuccess) + }, [ambireSDK, handleSDKAuthSuccess]) useEffect(() => { - ambireSDK.onAlreadyLoggedIn(handleRegistrationOrLoginSuccess) - }, [ambireSDK, handleRegistrationOrLoginSuccess]) + ambireSDK.onAlreadyLoggedIn(handleSDKAuthSuccess) + }, [ambireSDK, handleSDKAuthSuccess]) useEffect(() => { - ambireSDK.onMsgSigned(({ signature }: any) => - setSdkMsgSignature(() => { - setIsLoading(true) - return signature - }) - ) + ambireSDK.onMsgSigned((data: { signature: string; type: string }) => { + setIsLoading(true) + setSdkMsgSignature(data.signature) + }) }, [ambireSDK]) useEffect(() => { @@ -437,15 +503,8 @@ const AccountProvider: FC = ({ children }) => { }, [ambireSDK, handleActionRejected]) useEffect(() => { - if (adexAccount.authMsgResp && !adexAccount.authenticated) { - signMessage('eth_signTypedData', JSON.stringify(adexAccount.authMsgResp)) - } - }, [adexAccount.authMsgResp, adexAccount.authenticated, signMessage]) - - const authenticated = useMemo( - () => Boolean(adexAccount.authenticated), - [adexAccount.authenticated] - ) + ambireSDK.onTxnRejected(handleTxnRejected) + }, [ambireSDK, handleTxnRejected]) const isAdmin = useMemo( () => Boolean(isAdminToken(adexAccount.accessToken)), @@ -511,12 +570,12 @@ const AccountProvider: FC = ({ children }) => { ) useEffect(() => { - if (adexAccount.authenticated) { + if (authenticated) { console.log('adexAccount.authenticated') updateBalance() } // eslint-disable-next-line react-hooks/exhaustive-deps - }, [adexAccount.authenticated]) + }, [authenticated]) const contextValue = useMemo( () => ({ diff --git a/src/helpers/dateTime.ts b/src/helpers/dateTime.ts index ae920c54..93a10af3 100644 --- a/src/helpers/dateTime.ts +++ b/src/helpers/dateTime.ts @@ -1,6 +1,7 @@ import { Timeframe } from 'types' -export const MINUTE = 60 * 1000 +export const SECOND = 1000 +export const MINUTE = 60 * SECOND export const HOUR = 60 * MINUTE export const DAY = 24 * HOUR export const WEEK = 7 * DAY diff --git a/src/lib/backend/login.ts b/src/lib/backend/login.ts index 7ae99c4b..025ea4ea 100644 --- a/src/lib/backend/login.ts +++ b/src/lib/backend/login.ts @@ -1,24 +1,32 @@ -import { BACKEND_BASE_URL } from 'constants/login' -import { fetchService, RequestOptions, getReqErr } from 'services' -import { IAdExAccount, AppError, ErrorLevel } from 'types' - -// TODO: fix this to use useAdExApi (adexServicesRequest) +export interface LibJwtPayload { + [key: string]: any + iss?: string | undefined + sub?: string | undefined + aud?: string | string[] | undefined + exp?: number | undefined + nbf?: number | undefined + iat?: number | undefined + jti?: string | undefined +} -const processResponse = (res: any) => { - if (res.status >= 200 && res.status < 400) { - return res.json() - } - // TODO: fix that - return res.text().then((text: any) => { - if (res.status === 401 || res.status === 403) { - console.error('unauthorized', text) - } +export enum JwtType { + ACCESS = 'access', + REFRESH = 'refresh' +} +interface PayloadData { + user_id: string + active_account_id: string + is_admin: boolean + is_dsp?: boolean +} - getReqErr(res, text) - }) +interface JwtPayload extends LibJwtPayload { + data: PayloadData + type: JwtType + exp: number } -const parseJwt = (token: string) => { +const parseJwt = (token: string): JwtPayload => { const base64Url = token.split('.')[1] const base64 = base64Url.replace(/-/g, '+').replace(/_/g, '/') @@ -26,8 +34,7 @@ const parseJwt = (token: string) => { window .atob(base64) .split('') - // eslint-disable-next-line prefer-template - .map((c) => `%${('00' + c.charCodeAt(0).toString(16)).slice(-2)}`) + .map((c) => `%${`00${c.charCodeAt(0).toString(16)}`.slice(-2)}`) .join('') ) @@ -44,86 +51,14 @@ export const isAdminToken = (accessToken: string | null) => { return decodeAccessToken.data.is_admin } -export const isTokenExpired = (token: string) => { +export const isTokenExpired = (token: string, extraSecondsBeforeExpiry: number = 0) => { if (!token) { return true } - const timeNowInSeconds = Math.floor(new Date().getTime() / 1000) - const decodeToken = parseJwt(token) - console.log({ decodeToken }) - return decodeToken.exp < timeNowInSeconds -} - -export const getMessageToSign = async (user: any) => { - const url = `${BACKEND_BASE_URL}/dsp/login-msg` - const body = { - wallet: user.address, - chainId: user.chainId - } - - const req: RequestOptions = { - url, - method: 'POST', - headers: { - 'Content-Type': 'application/json' - }, - body: JSON.stringify(body) - } - - return fetchService(req).then(processResponse) -} - -type VerifyLoginProps = { - authMsg: any - signature: string -} - -export const verifyLogin = async (body: VerifyLoginProps) => { - const url = `${BACKEND_BASE_URL}/dsp/login-verify` - - const req: RequestOptions = { - url, - method: 'POST', - headers: { - 'Content-Type': 'application/json' - }, - body: JSON.stringify(body) - } - - return fetchService(req).then(processResponse) -} - -export async function registerUser( - adexUser: IAdExAccount -): Promise<{ adexUser: IAdExAccount | null; error?: AppError }> { - // TODO: post to backend and return the result as IAdExAccount - - const registered = adexUser?.address && adexUser.chainId ? { ...adexUser } : null - const error: AppError | undefined = !registered - ? { - severity: ErrorLevel.ERROR, - error: new Error('User data not provided'), - message: 'User data not provided' - } - : undefined - return { adexUser: registered, error } -} - -export async function getAdexAccountByAddress( - address: string -): Promise<{ identityAccount: IAdExAccount | null; error?: AppError }> { - // TODO: - - const current: Array = [ - { - chainId: 1, - address: '69x70fC54B13FA83571006c289B9A6bbAE69dfD4eA4', - accessToken: '', - refreshToken: '', - authenticated: false - } - ] - - return { identityAccount: [...current].find((x) => x.address === address) || null } + const timeNowInSeconds = new Date().getTime() / 1000 + const { exp, type } = parseJwt(token) + const isAboutToExpire = exp - extraSecondsBeforeExpiry < timeNowInSeconds + console.log({ exp, timeNowInSeconds, isAboutToExpire, type }) + return isAboutToExpire } diff --git a/src/types/account.ts b/src/types/account.ts index 643359ac..bf3987e9 100644 --- a/src/types/account.ts +++ b/src/types/account.ts @@ -78,6 +78,4 @@ export interface IAdExAccount { address: string accessToken: string | null refreshToken: string | null - authenticated: boolean - authMsgResp?: any } From 55d5c906351407060a9b561dd58d7e14fec560e5 Mon Sep 17 00:00:00 2001 From: IvoPaunov Date: Thu, 29 Aug 2024 15:47:02 +0300 Subject: [PATCH 10/84] fix account context loaded state --- src/contexts/AccountContext/AccountContext.tsx | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/src/contexts/AccountContext/AccountContext.tsx b/src/contexts/AccountContext/AccountContext.tsx index d555405d..728d214c 100644 --- a/src/contexts/AccountContext/AccountContext.tsx +++ b/src/contexts/AccountContext/AccountContext.tsx @@ -78,7 +78,7 @@ type ApiRequestOptions = Omit & { } interface IAccountContext { - adexAccount: IAdExAccount & Account & { loaded: boolean; initialLoad: boolean } + adexAccount: IAdExAccount & Account & { loaded: boolean } authenticated: boolean ambireSDK: AmbireLoginSDK isAdmin: boolean @@ -102,8 +102,6 @@ const defaultValue: IAccountContext['adexAccount'] = { accessToken: null, refreshToken: null, loaded: false, - // This ensures there is some obj in the ls - initialLoad: false, id: '', name: '', active: false, @@ -199,14 +197,14 @@ const AccountProvider: FC = ({ children }) => { console.log({ lsAcc }) if (!lsAcc) { - setAdexAccount({ ...defaultValue, initialLoad: true }) + setAdexAccount({ ...defaultValue, loaded: true }) } }, [setAdexAccount]) const resetAdexAccount = useCallback( (reason?: string) => { console.log('reset account: ', reason) - setAdexAccount({ ...defaultValue, initialLoad: true }) + setAdexAccount({ ...defaultValue, loaded: true }) }, [setAdexAccount] ) @@ -410,7 +408,7 @@ const AccountProvider: FC = ({ children }) => { console.log({ authMsgResp }) setAuthMsg(authMsgResp) signMessage('eth_signTypedData', JSON.stringify(authMsgResp.authMsg)) - setAdexAccount({ ...defaultValue, address, chainId }) + setAdexAccount({ ...defaultValue, address, chainId, loaded: true }) } catch (error) { console.error('Get message to sign failed', error) showNotification('error', 'Get message to sign failed') @@ -443,7 +441,8 @@ const AccountProvider: FC = ({ children }) => { console.error('Error verify login:', error) showNotification('error', 'Verify login failed', error?.message || error) setAdexAccount({ - ...defaultValue + ...defaultValue, + loaded: true }) } finally { setIsLoading(false) From f1f72e267bffad4ef4bd39e1d5b3fc0d75cd3471 Mon Sep 17 00:00:00 2001 From: IvoPaunov Date: Thu, 29 Aug 2024 15:47:17 +0300 Subject: [PATCH 11/84] fix auth router --- src/Router.tsx | 21 +++++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/src/Router.tsx b/src/Router.tsx index 2fefde81..fa7aea58 100644 --- a/src/Router.tsx +++ b/src/Router.tsx @@ -6,7 +6,7 @@ import Billing from 'components/Billing' import GetStarted from 'components/GetStarted' import CampaignAnalytics from 'components/CampaignAnalytics' import { createBrowserRouter, Navigate, useLocation, useRouteError, Link } from 'react-router-dom' -import { Button } from '@mantine/core' +import { Button, Loader, Center } from '@mantine/core' import useAccount from 'hooks/useAccount' import Deposit from 'components/Deposit' @@ -33,14 +33,27 @@ function ErrorBoundary() { } function RequireAuth({ children, admin }: { children: JSX.Element; admin?: boolean }) { - const { authenticated, isAdmin } = useAccount() + const { + authenticated, + isAdmin, + adexAccount: { loaded } + } = useAccount() + const location = useLocation() + if (!loaded) { + return ( +
+ +
+ ) + } + if (!authenticated) { return } - if (admin && isAdmin) { + if (admin && !isAdmin) { return } @@ -107,7 +120,7 @@ export const router = createBrowserRouter( { path: 'admin/:tabValue', element: ( - + From ac117bde0684dc98179c9279b5b50780739fe779 Mon Sep 17 00:00:00 2001 From: IvoPaunov Date: Thu, 29 Aug 2024 15:47:37 +0300 Subject: [PATCH 12/84] fix dashboard table loading state --- src/components/Dashboard/Dashboard.tsx | 21 ++++++++------------- 1 file changed, 8 insertions(+), 13 deletions(-) diff --git a/src/components/Dashboard/Dashboard.tsx b/src/components/Dashboard/Dashboard.tsx index 2393d3d6..6a625786 100644 --- a/src/components/Dashboard/Dashboard.tsx +++ b/src/components/Dashboard/Dashboard.tsx @@ -4,7 +4,7 @@ import { // CampaignType, EventType } from 'adex-common' -import { Container, Flex, Text, Loader, UnstyledButton, Anchor, Stack, Group } from '@mantine/core' +import { Container, Flex, Text, UnstyledButton, Anchor, Stack, Group } from '@mantine/core' import { useCallback, useEffect, useMemo, useState } from 'react' import CustomTable, { TableElement, TableRowAction } from 'components/common/CustomTable' import { Link, useNavigate } from 'react-router-dom' @@ -403,18 +403,13 @@ const Dashboard = ({ isAdminPanel, accountId }: { isAdminPanel?: boolean; accoun )}
- {!initialDataLoading ? ( - - ) : ( - - - - )} +
) From a53d9319a4d68c09ebcdb19dc33810ebcd80701c Mon Sep 17 00:00:00 2001 From: IvoPaunov Date: Fri, 30 Aug 2024 12:27:26 +0300 Subject: [PATCH 13/84] update access tokens with timeout fn before expire no on request --- .../CampaignDetails/CampaignDetails.tsx | 3 +- src/components/Dashboard/Dashboard.tsx | 3 +- .../AccountContext/AccountContext.tsx | 91 +++++++++++-------- .../CampaignsContext/CampaignsDataContext.tsx | 17 +--- src/lib/backend/login.ts | 7 ++ 5 files changed, 63 insertions(+), 58 deletions(-) diff --git a/src/components/CampaignDetails/CampaignDetails.tsx b/src/components/CampaignDetails/CampaignDetails.tsx index 6155e667..6935745c 100644 --- a/src/components/CampaignDetails/CampaignDetails.tsx +++ b/src/components/CampaignDetails/CampaignDetails.tsx @@ -119,7 +119,8 @@ const CampaignDetails = ({ isAdminPanel }: { isAdminPanel?: boolean }) => { if (id) { updateCampaignDataById(id) } - }, [id, updateCampaignDataById]) + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [id]) const canArchive = useMemo(() => { return ( diff --git a/src/components/Dashboard/Dashboard.tsx b/src/components/Dashboard/Dashboard.tsx index 6a625786..1891e1ae 100644 --- a/src/components/Dashboard/Dashboard.tsx +++ b/src/components/Dashboard/Dashboard.tsx @@ -310,7 +310,8 @@ const Dashboard = ({ isAdminPanel, accountId }: { isAdminPanel?: boolean; accoun useEffect(() => { updateAllCampaignsData(true) - }, [updateAllCampaignsData]) + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []) const actions = useMemo(() => { const dashboardActions: TableRowAction[] = [ diff --git a/src/contexts/AccountContext/AccountContext.tsx b/src/contexts/AccountContext/AccountContext.tsx index 728d214c..ba6bbf8e 100644 --- a/src/contexts/AccountContext/AccountContext.tsx +++ b/src/contexts/AccountContext/AccountContext.tsx @@ -9,7 +9,7 @@ import { useState } from 'react' import { Account, BillingDetails, IAdExAccount } from 'types' -import { isAdminToken, isTokenExpired } from 'lib/backend' +import { isAdminToken, isTokenExpired, getJWTExpireTime } from 'lib/backend' import { AmbireLoginSDK } from '@ambire/login-sdk-core' import { DAPP_ICON_PATH, DAPP_NAME, DEFAULT_CHAIN_ID } from 'constants/login' import useCustomNotifications from 'hooks/useCustomNotifications' @@ -172,14 +172,11 @@ const AccountProvider: FC = ({ children }) => { const res = !str ? { ...defaultValue, updated: true } : { ...defaultValue, ...deserializeJSON(str), loaded: true } - - // console.log({ res }) - return res }, serialize: (acc) => { const seri = serializeJSON({ ...acc, loaded: true }) - // console.log({ ser }) + // console.log({ seri }) return seri } @@ -222,15 +219,12 @@ const AccountProvider: FC = ({ children }) => { [ambireSDK] ) - const checkAndGetNewAccessTokens = useCallback(async (): Promise<{ - accessToken: string - refreshToken: string - } | null> => { + const checkAndUpdateNewAccessTokens = useCallback(async (): Promise => { if (!adexAccount.accessToken || !adexAccount.refreshToken) { throw new Error(`${UNAUTHORIZED_ERR_STR}: missing access tokens`) } - if (isTokenExpired(adexAccount.accessToken, 100)) { + if (!isTokenExpired(adexAccount.refreshToken)) { console.log('updating access tokens') try { const req: RequestOptions = { @@ -245,19 +239,57 @@ const AccountProvider: FC = ({ children }) => { } const res = await fetchService(req) - return await processResponse<{ accessToken: string; refreshToken: string }>(res) + const newAccessTokens = await processResponse<{ + accessToken: string + refreshToken: string + }>(res) + setAdexAccount((prev) => { + return { + ...prev, + ...newAccessTokens + } + }) } catch (error: any) { console.error('Updating access token failed:', error) showNotification('error', error?.message, 'Updating access token failed') throw new Error(`${UNAUTHORIZED_ERR_STR}: ${error}`) } - } else if (isTokenExpired(adexAccount.refreshToken)) { + } else { resetAdexAccount('refresh token expired') showNotification('info', 'Please log in!', 'Session expired') } + }, [ + adexAccount.accessToken, + adexAccount.refreshToken, + resetAdexAccount, + setAdexAccount, + showNotification + ]) - return null - }, [adexAccount.accessToken, adexAccount.refreshToken, resetAdexAccount, showNotification]) + // NOTE: updating access tokens some second before the access token expire instead of checking on each request where can have "racing" condition with multiple request at the same time + // TODO: add retry functionality + useEffect(() => { + let updateTokensTimeout: ReturnType + + if (adexAccount.accessToken) { + const now = Date.now() + const accessTokenExpireTime = getJWTExpireTime(adexAccount.accessToken, 10) + if (now >= accessTokenExpireTime) { + checkAndUpdateNewAccessTokens() + } else { + updateTokensTimeout = setTimeout( + () => checkAndUpdateNewAccessTokens(), + accessTokenExpireTime - now + ) + } + } + + return () => { + if (updateTokensTimeout) { + clearTimeout(updateTokensTimeout) + } + } + }, [adexAccount.accessToken, checkAndUpdateNewAccessTokens]) const adexServicesRequest = useCallback( // Note @@ -279,31 +311,10 @@ const AccountProvider: FC = ({ children }) => { ? serializeJSON(reqOptions.body) : reqOptions.body, queryParams: reqOptions.queryParams, - headers: reqOptions.headers - } - - // console.log('adexAccount', adexAccount) - if (!adexAccount.accessToken) throw new Error('Access token is missing') - - const authHeader = { - [authHeaderProp]: `Bearer ${adexAccount.accessToken}` - } - - const newAccessTokens = await checkAndGetNewAccessTokens() - - if (newAccessTokens) { - setAdexAccount((prev) => { - return { - ...prev, - ...newAccessTokens - } - }) - authHeader[authHeaderProp] = `Bearer ${newAccessTokens.accessToken}` - } - - req.headers = { - ...authHeader, - ...req.headers + headers: { + ...reqOptions.headers, + [authHeaderProp]: `Bearer ${adexAccount.accessToken}` + } } const res = await fetchService(req) @@ -315,7 +326,7 @@ const AccountProvider: FC = ({ children }) => { return Promise.reject(err) } }, - [adexAccount.accessToken, checkAndGetNewAccessTokens, setAdexAccount, resetAdexAccount] + [adexAccount.accessToken, resetAdexAccount] ) const logOut = useCallback(async () => { diff --git a/src/contexts/CampaignsContext/CampaignsDataContext.tsx b/src/contexts/CampaignsContext/CampaignsDataContext.tsx index 2b4000eb..b85ce7e9 100644 --- a/src/contexts/CampaignsContext/CampaignsDataContext.tsx +++ b/src/contexts/CampaignsContext/CampaignsDataContext.tsx @@ -9,7 +9,6 @@ import { useEffect } from 'react' import { useAdExApi } from 'hooks/useAdexServices' -import useAccount from 'hooks/useAccount' import useCustomNotifications from 'hooks/useCustomNotifications' import { BaseData, CampaignData, EvAggrData, SupplyStats } from 'types' import { CREATE_CAMPAIGN_DEFAULT_VALUE } from 'constants/createCampaign' @@ -146,7 +145,7 @@ const CampaignsDataProvider: FC const { showNotification } = useCustomNotifications() const { adexServicesRequest } = useAdExApi() - const { authenticated } = useAccount() + // const { authenticated } = useAccount() const [initialDataLoading, setInitialDataLoading] = useState(true) const [supplyStats, setSupplyStats] = useState(defaultSupplyStats) @@ -342,20 +341,6 @@ const CampaignsDataProvider: FC // eslint-disable-next-line react-hooks/exhaustive-deps }, []) - useEffect(() => { - if (authenticated) { - const updateCampaigns = async () => { - await updateAllCampaignsData(true) - // setInitialDataLoading(false) - } - - updateCampaigns() - } else { - setCampaignData(new Map()) - // setInitialDataLoading(false) - } - }, [updateAllCampaignsData, authenticated]) - // TODO: move to separate context delete and archive const deleteDraftCampaign = useCallback( async (id: string) => { diff --git a/src/lib/backend/login.ts b/src/lib/backend/login.ts index 025ea4ea..547a5e4b 100644 --- a/src/lib/backend/login.ts +++ b/src/lib/backend/login.ts @@ -62,3 +62,10 @@ export const isTokenExpired = (token: string, extraSecondsBeforeExpiry: number = console.log({ exp, timeNowInSeconds, isAboutToExpire, type }) return isAboutToExpire } + +export const getJWTExpireTime = (token: string, extraSecondsBeforeExpiry: number = 0): number => { + const { exp, type } = parseJwt(token) + const expireTime = (exp - extraSecondsBeforeExpiry) * 1000 + console.log({ exp, expireTime, type }) + return expireTime +} From 855a633672c3a89afd8790b8e5a214c3cc3af30a Mon Sep 17 00:00:00 2001 From: IvoPaunov Date: Fri, 30 Aug 2024 12:43:04 +0300 Subject: [PATCH 14/84] cleanup and fix one extra update at campaigns data context --- .../CampaignsContext/CampaignsDataContext.tsx | 54 ++++++------------- 1 file changed, 17 insertions(+), 37 deletions(-) diff --git a/src/contexts/CampaignsContext/CampaignsDataContext.tsx b/src/contexts/CampaignsContext/CampaignsDataContext.tsx index b85ce7e9..48cfcce0 100644 --- a/src/contexts/CampaignsContext/CampaignsDataContext.tsx +++ b/src/contexts/CampaignsContext/CampaignsDataContext.tsx @@ -1,13 +1,5 @@ import { Campaign, CampaignStatus } from 'adex-common' -import { - createContext, - FC, - PropsWithChildren, - useMemo, - useState, - useCallback, - useEffect -} from 'react' +import { createContext, FC, PropsWithChildren, useMemo, useState, useCallback } from 'react' import { useAdExApi } from 'hooks/useAdexServices' import useCustomNotifications from 'hooks/useCustomNotifications' import { BaseData, CampaignData, EvAggrData, SupplyStats } from 'types' @@ -183,8 +175,7 @@ const CampaignsDataProvider: FC }) if (!campaignStatusRes.success) { - showNotification('error', `changing campaign status with id ${campaignId}`, 'Data error') - return + throw new Error(`changing campaign status with id ${campaignId}`) } setCampaignData((prev) => { @@ -202,9 +193,9 @@ const CampaignsDataProvider: FC return next }) - } catch (err) { + } catch (err: any) { console.log(err) - showNotification('error', `changing campaign status with id ${campaignId}`, 'Data error') + showNotification('error', err?.message || err, 'Campaign status update error') } }, [adexServicesRequest, showNotification] @@ -220,9 +211,7 @@ const CampaignsDataProvider: FC }) if (campaignId !== campaignDetailsRes?.id) { - // NOTE: skip state update - showNotification('error', `getting campaign with id ${campaignId}`, 'Data error') - return + throw new Error(`Getting campaign with id ${campaignId}`) } // const advData = await getCampaignAdvancedData(campaignId) @@ -238,9 +227,9 @@ const CampaignsDataProvider: FC next.set(campaignId, updatedCmp) return next }) - } catch (err) { + } catch (err: any) { console.log(err) - showNotification('error', `getting campaign with id ${campaignId}`, 'Data error') + showNotification('error', err?.message || err, 'Data error') } }, [adexServicesRequest, showNotification] @@ -284,24 +273,21 @@ const CampaignsDataProvider: FC next.set(cmp.id, currentCMP) }) - prev.forEach((value, key) => { + prev.forEach((_value, key) => { if (!dataResIds.has(key)) { next.delete(key) } }) - // TODO: check it again when dev has been merged - setInitialDataLoading(false) return next }) } else { - showNotification('warning', 'invalid campaigns data response', 'Data error') - console.log({ dataRes }) - setInitialDataLoading(false) + throw new Error('Invalid campaigns data response') } - } catch (err) { + } catch (err: any) { console.log(err) - showNotification('error', 'getting campaigns data', 'Data error') - // setInitialDataLoading(false) + showNotification('error', err?.message || err, 'Campaigns data error') + } finally { + setInitialDataLoading(false) } }, [adexServicesRequest, showNotification, type] @@ -317,7 +303,7 @@ const CampaignsDataProvider: FC }) if (!result) { - throw new Error('Getting banner sizes failed.') + throw new Error('No supply stats') } const hasEmptyValueResponse = Object.values(result).every( @@ -325,22 +311,16 @@ const CampaignsDataProvider: FC ) if (hasEmptyValueResponse) { - throw new Error('Supply stats not available') + throw new Error('Invalid supply stats response') } setSupplyStats(result as SupplyStats) - } catch (e) { + } catch (e: any) { console.error(e) - showNotification('error', 'Getting banner sizes failed', 'Getting banner sizes failed') + showNotification('error', e?.message || e, 'Supply stats error') } }, [adexServicesRequest, showNotification]) - useEffect(() => { - console.log('updateSupplyStats') - updateSupplyStats() - // eslint-disable-next-line react-hooks/exhaustive-deps - }, []) - // TODO: move to separate context delete and archive const deleteDraftCampaign = useCallback( async (id: string) => { From 2f4b9d152848f5377e3382b8f502165f06d16fcf Mon Sep 17 00:00:00 2001 From: Ivo Paunov Date: Fri, 30 Aug 2024 12:48:10 +0300 Subject: [PATCH 15/84] v0.69.50 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index fd93c34e..9c8422ce 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "adex-interface", - "version": "0.69.49", + "version": "0.69.50", "private": true, "dependencies": { "@ambire/login-sdk-core": "^0.0.21", From 6800bc25ebf3c7b26b8c22893cee7845ed66e495 Mon Sep 17 00:00:00 2001 From: IvoPaunov Date: Fri, 30 Aug 2024 13:53:36 +0300 Subject: [PATCH 16/84] cleanup logs --- src/contexts/CampaignsContext/CampaignsDataContext.tsx | 4 ---- src/lib/backend/login.ts | 6 ++---- 2 files changed, 2 insertions(+), 8 deletions(-) diff --git a/src/contexts/CampaignsContext/CampaignsDataContext.tsx b/src/contexts/CampaignsContext/CampaignsDataContext.tsx index 48cfcce0..3d1c0997 100644 --- a/src/contexts/CampaignsContext/CampaignsDataContext.tsx +++ b/src/contexts/CampaignsContext/CampaignsDataContext.tsx @@ -245,8 +245,6 @@ const CampaignsDataProvider: FC queryParams: { all: 'true' } }) - console.log({ dataRes }) - let advData: EvAggrData[] // if (updateAdvanced) { @@ -258,10 +256,8 @@ const CampaignsDataProvider: FC if (updateAdvanced) { advData = [...dataRes].map((cmpDataRes) => campaignDataResToAdvData(cmpDataRes)) - console.log({ advData }) } - console.log({ dataRes }) if (Array.isArray(dataRes)) { setCampaignData((prev) => { const next = new Map() diff --git a/src/lib/backend/login.ts b/src/lib/backend/login.ts index 547a5e4b..66cd3f16 100644 --- a/src/lib/backend/login.ts +++ b/src/lib/backend/login.ts @@ -57,15 +57,13 @@ export const isTokenExpired = (token: string, extraSecondsBeforeExpiry: number = } const timeNowInSeconds = new Date().getTime() / 1000 - const { exp, type } = parseJwt(token) + const { exp } = parseJwt(token) const isAboutToExpire = exp - extraSecondsBeforeExpiry < timeNowInSeconds - console.log({ exp, timeNowInSeconds, isAboutToExpire, type }) return isAboutToExpire } export const getJWTExpireTime = (token: string, extraSecondsBeforeExpiry: number = 0): number => { - const { exp, type } = parseJwt(token) + const { exp } = parseJwt(token) const expireTime = (exp - extraSecondsBeforeExpiry) * 1000 - console.log({ exp, expireTime, type }) return expireTime } From 8b4591f858cfbe7bb20c37beddffdd295b6c36e7 Mon Sep 17 00:00:00 2001 From: IvoPaunov Date: Fri, 30 Aug 2024 21:51:01 +0300 Subject: [PATCH 17/84] fix table keys --- src/components/common/CustomTable/CustomTable.tsx | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/components/common/CustomTable/CustomTable.tsx b/src/components/common/CustomTable/CustomTable.tsx index a93820ed..0215ad1c 100644 --- a/src/components/common/CustomTable/CustomTable.tsx +++ b/src/components/common/CustomTable/CustomTable.tsx @@ -263,8 +263,8 @@ export const CustomTable = ({ ) return isMobile ? ( - - + + {colsToMap[cidx]}: @@ -281,8 +281,8 @@ export const CustomTable = ({ if (isMobile) { return ( - - + + {/* */} {cols} From 80871c33c552540680ba6653ca7621296f69fe59 Mon Sep 17 00:00:00 2001 From: IvoPaunov Date: Fri, 30 Aug 2024 21:51:53 +0300 Subject: [PATCH 18/84] wip: fixing access token if tab not open --- .../AccountContext/AccountContext.tsx | 62 ++++++++++++------- .../CampaignsContext/CampaignsDataContext.tsx | 8 +-- 2 files changed, 44 insertions(+), 26 deletions(-) diff --git a/src/contexts/AccountContext/AccountContext.tsx b/src/contexts/AccountContext/AccountContext.tsx index ba6bbf8e..2457243d 100644 --- a/src/contexts/AccountContext/AccountContext.tsx +++ b/src/contexts/AccountContext/AccountContext.tsx @@ -219,12 +219,18 @@ const AccountProvider: FC = ({ children }) => { [ambireSDK] ) - const checkAndUpdateNewAccessTokens = useCallback(async (): Promise => { + const checkAndUpdateNewAccessTokens = useCallback(async (): Promise<{ accessToken: string }> => { if (!adexAccount.accessToken || !adexAccount.refreshToken) { throw new Error(`${UNAUTHORIZED_ERR_STR}: missing access tokens`) } - if (!isTokenExpired(adexAccount.refreshToken)) { + const isAccessTokenExpired = isTokenExpired(adexAccount.accessToken, 10) + + if (!isAccessTokenExpired) { + return { accessToken: adexAccount.accessToken } + } + + if (isAccessTokenExpired && !isTokenExpired(adexAccount.refreshToken)) { console.log('updating access tokens') try { const req: RequestOptions = { @@ -239,24 +245,29 @@ const AccountProvider: FC = ({ children }) => { } const res = await fetchService(req) - const newAccessTokens = await processResponse<{ - accessToken: string - refreshToken: string - }>(res) + const { accessToken, refreshToken } = await processResponse(res) setAdexAccount((prev) => { return { ...prev, - ...newAccessTokens + accessToken, + refreshToken } }) + + return { accessToken } } catch (error: any) { console.error('Updating access token failed:', error) - showNotification('error', error?.message, 'Updating access token failed') + showNotification( + 'error', + error?.message || error.toString(), + 'Updating access token failed' + ) throw new Error(`${UNAUTHORIZED_ERR_STR}: ${error}`) } } else { resetAdexAccount('refresh token expired') showNotification('info', 'Please log in!', 'Session expired') + throw new Error('Session expired!') } }, [ adexAccount.accessToken, @@ -270,18 +281,18 @@ const AccountProvider: FC = ({ children }) => { // TODO: add retry functionality useEffect(() => { let updateTokensTimeout: ReturnType + try { + if (adexAccount.accessToken) { + const now = Date.now() + const accessTokenExpireTime = getJWTExpireTime(adexAccount.accessToken, 15) - if (adexAccount.accessToken) { - const now = Date.now() - const accessTokenExpireTime = getJWTExpireTime(adexAccount.accessToken, 10) - if (now >= accessTokenExpireTime) { - checkAndUpdateNewAccessTokens() - } else { updateTokensTimeout = setTimeout( () => checkAndUpdateNewAccessTokens(), - accessTokenExpireTime - now + now >= accessTokenExpireTime ? 0 : accessTokenExpireTime - now ) } + } catch (err: any) { + showNotification('info', err?.message || err.toString(), 'Updating session error') } return () => { @@ -289,7 +300,7 @@ const AccountProvider: FC = ({ children }) => { clearTimeout(updateTokensTimeout) } } - }, [adexAccount.accessToken, checkAndUpdateNewAccessTokens]) + }, [adexAccount.accessToken, checkAndUpdateNewAccessTokens, showNotification]) const adexServicesRequest = useCallback( // Note @@ -303,6 +314,9 @@ const AccountProvider: FC = ({ children }) => { const baseUrl = (service === 'backend' ? BACKEND_BASE_URL : VALIDATOR_BASE_URL) || '' const urlCheck = reqOptions.route.replace(baseUrl, '').replace(/^\//, '') + // NOTE: needed because the page can "sleep" and the refresh timeout might not work + const { accessToken } = await checkAndUpdateNewAccessTokens() + const req: RequestOptions = { url: `${baseUrl}/${urlCheck}`, method: reqOptions.method, @@ -313,20 +327,24 @@ const AccountProvider: FC = ({ children }) => { queryParams: reqOptions.queryParams, headers: { ...reqOptions.headers, - [authHeaderProp]: `Bearer ${adexAccount.accessToken}` + [authHeaderProp]: `Bearer ${accessToken}` } } const res = await fetchService(req) return await processResponse(res) } catch (err: any) { - if (service === 'backend' && err && (err?.message || err).includes(UNAUTHORIZED_ERR_STR)) { - resetAdexAccount(UNAUTHORIZED_ERR_STR) + if ( + service === 'backend' && + err && + (err?.message || err.toString()).includes(UNAUTHORIZED_ERR_STR) + ) { + await checkAndUpdateNewAccessTokens() } return Promise.reject(err) } }, - [adexAccount.accessToken, resetAdexAccount] + [checkAndUpdateNewAccessTokens] ) const logOut = useCallback(async () => { @@ -348,7 +366,7 @@ const AccountProvider: FC = ({ children }) => { } } catch (err: any) { console.error('logOut: ', err) - showNotification('error', err?.message || err, 'Logging out failed') + showNotification('error', err?.message || err.toString(), 'Logging out failed') } }, [ adexAccount.refreshToken, @@ -450,7 +468,7 @@ const AccountProvider: FC = ({ children }) => { }) } catch (error: any) { console.error('Error verify login:', error) - showNotification('error', 'Verify login failed', error?.message || error) + showNotification('error', error?.message || error.toString(), 'Verify login failed') setAdexAccount({ ...defaultValue, loaded: true diff --git a/src/contexts/CampaignsContext/CampaignsDataContext.tsx b/src/contexts/CampaignsContext/CampaignsDataContext.tsx index 3d1c0997..65503a1f 100644 --- a/src/contexts/CampaignsContext/CampaignsDataContext.tsx +++ b/src/contexts/CampaignsContext/CampaignsDataContext.tsx @@ -195,7 +195,7 @@ const CampaignsDataProvider: FC }) } catch (err: any) { console.log(err) - showNotification('error', err?.message || err, 'Campaign status update error') + showNotification('error', err?.message || err.toString(), 'Campaign status update error') } }, [adexServicesRequest, showNotification] @@ -229,7 +229,7 @@ const CampaignsDataProvider: FC }) } catch (err: any) { console.log(err) - showNotification('error', err?.message || err, 'Data error') + showNotification('error', err?.message || err.toString(), 'Data error') } }, [adexServicesRequest, showNotification] @@ -281,7 +281,7 @@ const CampaignsDataProvider: FC } } catch (err: any) { console.log(err) - showNotification('error', err?.message || err, 'Campaigns data error') + showNotification('error', err?.message || err.toString(), 'Campaigns data error') } finally { setInitialDataLoading(false) } @@ -313,7 +313,7 @@ const CampaignsDataProvider: FC setSupplyStats(result as SupplyStats) } catch (e: any) { console.error(e) - showNotification('error', e?.message || e, 'Supply stats error') + showNotification('error', e?.message || e.toString(), 'Supply stats error') } }, [adexServicesRequest, showNotification]) From daa85dad4383e6643d8a82bc08be3c3386585f08 Mon Sep 17 00:00:00 2001 From: IvoPaunov Date: Sun, 1 Sep 2024 09:28:55 +0300 Subject: [PATCH 19/84] fix access tokens update --- .../AccountContext/AccountContext.tsx | 34 ++++++++++++------- 1 file changed, 22 insertions(+), 12 deletions(-) diff --git a/src/contexts/AccountContext/AccountContext.tsx b/src/contexts/AccountContext/AccountContext.tsx index 2457243d..6337f58a 100644 --- a/src/contexts/AccountContext/AccountContext.tsx +++ b/src/contexts/AccountContext/AccountContext.tsx @@ -24,6 +24,7 @@ const ambireLoginSDK = new AmbireLoginSDK({ export const BACKEND_BASE_URL = process.env.REACT_APP_BACKEND_BASE_URL export const VALIDATOR_BASE_URL = process.env.REACT_APP_VALIDATOR_BASE_URL const UNAUTHORIZED_ERR_STR = 'Unauthorized' +const TOKEN_CHECK_SECONDS_BEFORE_EXPIRE = process.env.NODE_ENV === 'production' ? 69 : 10 console.log({ BACKEND_BASE_URL }) const processResponse = (res: Response): Promise => { @@ -224,7 +225,10 @@ const AccountProvider: FC = ({ children }) => { throw new Error(`${UNAUTHORIZED_ERR_STR}: missing access tokens`) } - const isAccessTokenExpired = isTokenExpired(adexAccount.accessToken, 10) + const isAccessTokenExpired = isTokenExpired( + adexAccount.accessToken, + TOKEN_CHECK_SECONDS_BEFORE_EXPIRE * 1.5 + ) if (!isAccessTokenExpired) { return { accessToken: adexAccount.accessToken } @@ -284,10 +288,15 @@ const AccountProvider: FC = ({ children }) => { try { if (adexAccount.accessToken) { const now = Date.now() - const accessTokenExpireTime = getJWTExpireTime(adexAccount.accessToken, 15) + const accessTokenExpireTime = getJWTExpireTime( + adexAccount.accessToken, + TOKEN_CHECK_SECONDS_BEFORE_EXPIRE * 1 + ) updateTokensTimeout = setTimeout( - () => checkAndUpdateNewAccessTokens(), + async () => { + await checkAndUpdateNewAccessTokens() + }, now >= accessTokenExpireTime ? 0 : accessTokenExpireTime - now ) } @@ -296,7 +305,7 @@ const AccountProvider: FC = ({ children }) => { } return () => { - if (updateTokensTimeout) { + if (updateTokensTimeout !== undefined) { clearTimeout(updateTokensTimeout) } } @@ -424,17 +433,15 @@ const AccountProvider: FC = ({ children }) => { ) const handleSDKAuthSuccess = useCallback( - async ({ address, chainId }: any) => { + async ({ address, chainId }: any, type: string) => { + console.log('handleSDKAuthSuccess: ', type) if (!address || !chainId) { showNotification('warning', 'Ambire sdk no address or chain') return } - console.log('handleSDKAuthSuccess') - try { const authMsgResp = await getAuthMsg({ wallet: address, chainId }) - console.log({ authMsgResp }) setAuthMsg(authMsgResp) signMessage('eth_signTypedData', JSON.stringify(authMsgResp.authMsg)) setAdexAccount({ ...defaultValue, address, chainId, loaded: true }) @@ -499,16 +506,19 @@ const AccountProvider: FC = ({ children }) => { console.log('action rejected', data) }, []) + // TODO: types for success data useEffect(() => { - ambireSDK.onRegistrationSuccess(handleSDKAuthSuccess) + ambireSDK.onRegistrationSuccess((data: any) => + handleSDKAuthSuccess(data, 'handleSDKAuthSuccess') + ) }, [ambireSDK, handleSDKAuthSuccess]) useEffect(() => { - ambireSDK.onLoginSuccess(handleSDKAuthSuccess) + ambireSDK.onLoginSuccess((data: any) => handleSDKAuthSuccess(data, 'onLoginSuccess')) }, [ambireSDK, handleSDKAuthSuccess]) useEffect(() => { - ambireSDK.onAlreadyLoggedIn(handleSDKAuthSuccess) + ambireSDK.onAlreadyLoggedIn((data: any) => handleSDKAuthSuccess(data, 'onAlreadyLoggedIn')) }, [ambireSDK, handleSDKAuthSuccess]) useEffect(() => { @@ -562,7 +572,7 @@ const AccountProvider: FC = ({ children }) => { } } catch (err: any) { console.error('Updating account balance failed:', err) - showNotification('error', err, 'Updating account balance failed') + showNotification('error', err?.message || err.toString(), 'Updating account balance failed') } }, [adexServicesRequest, setAdexAccount, showNotification]) From 8961fa4ea1e62967f30a6beec74912a4a9aea9ca Mon Sep 17 00:00:00 2001 From: IvoPaunov Date: Sun, 1 Sep 2024 09:29:22 +0300 Subject: [PATCH 20/84] fix crash on notifications when error provided --- src/components/AdminPanel/AccoutInfo.tsx | 2 +- src/hooks/useCustomNotifications/useCustomNotifications.ts | 7 +++---- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/src/components/AdminPanel/AccoutInfo.tsx b/src/components/AdminPanel/AccoutInfo.tsx index 9c0bea9b..405b0c93 100644 --- a/src/components/AdminPanel/AccoutInfo.tsx +++ b/src/components/AdminPanel/AccoutInfo.tsx @@ -43,7 +43,7 @@ function AccountInfo({ accountData }: { accountData: Account }) { showNotification('info', 'Account data updated!') }, (err) => { - showNotification('error', err, 'Error on account Account data updated!') + showNotification('error', err.toString(), 'Error on account Account data updated!') } ) diff --git a/src/hooks/useCustomNotifications/useCustomNotifications.ts b/src/hooks/useCustomNotifications/useCustomNotifications.ts index 949e7e9b..37c7879e 100644 --- a/src/hooks/useCustomNotifications/useCustomNotifications.ts +++ b/src/hooks/useCustomNotifications/useCustomNotifications.ts @@ -6,8 +6,6 @@ type NotificationSeverity = 'error' | 'warning' | 'info' const useCustomNotifications = () => { const showNotification = useCallback( (severity: NotificationSeverity, message: string, title?: string) => { - console.log({ message }) - let color = '' let notificationTitle = '' // eslint-disable-next-line default-case @@ -27,8 +25,9 @@ const useCustomNotifications = () => { } console.log({ notificationTitle }) notifications.show({ - title: notificationTitle, - message, + // NOTE: just in case if somewhere is passed error objet + title: notificationTitle.toString(), + message: message.toString(), color }) }, From 2a5b9847254f694c6e4ce781e548e0b42a52001a Mon Sep 17 00:00:00 2001 From: Ivo Paunov Date: Sun, 1 Sep 2024 09:31:49 +0300 Subject: [PATCH 21/84] v0.69.51 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 9c8422ce..52835821 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "adex-interface", - "version": "0.69.50", + "version": "0.69.51", "private": true, "dependencies": { "@ambire/login-sdk-core": "^0.0.21", From e4e8e8f78dbaa4c279da2ef2e5dff288752b0c82 Mon Sep 17 00:00:00 2001 From: IvoPaunov Date: Mon, 2 Sep 2024 11:14:08 +0300 Subject: [PATCH 22/84] fix auth errors and notifications --- .../AccountContext/AccountContext.tsx | 19 +++---------------- 1 file changed, 3 insertions(+), 16 deletions(-) diff --git a/src/contexts/AccountContext/AccountContext.tsx b/src/contexts/AccountContext/AccountContext.tsx index 6337f58a..c23de0b0 100644 --- a/src/contexts/AccountContext/AccountContext.tsx +++ b/src/contexts/AccountContext/AccountContext.tsx @@ -235,7 +235,6 @@ const AccountProvider: FC = ({ children }) => { } if (isAccessTokenExpired && !isTokenExpired(adexAccount.refreshToken)) { - console.log('updating access tokens') try { const req: RequestOptions = { url: `${BACKEND_BASE_URL}/dsp/refresh-token`, @@ -261,25 +260,13 @@ const AccountProvider: FC = ({ children }) => { return { accessToken } } catch (error: any) { console.error('Updating access token failed:', error) - showNotification( - 'error', - error?.message || error.toString(), - 'Updating access token failed' - ) - throw new Error(`${UNAUTHORIZED_ERR_STR}: ${error}`) + throw new Error(`Updating access token failed: ${error?.message || error.toString()}`) } } else { resetAdexAccount('refresh token expired') - showNotification('info', 'Please log in!', 'Session expired') - throw new Error('Session expired!') + throw new Error(`${UNAUTHORIZED_ERR_STR}: Session expired!`) } - }, [ - adexAccount.accessToken, - adexAccount.refreshToken, - resetAdexAccount, - setAdexAccount, - showNotification - ]) + }, [adexAccount.accessToken, adexAccount.refreshToken, resetAdexAccount, setAdexAccount]) // NOTE: updating access tokens some second before the access token expire instead of checking on each request where can have "racing" condition with multiple request at the same time // TODO: add retry functionality From 71182ca1c19e2e5e5bc5a95cf23365396793be17 Mon Sep 17 00:00:00 2001 From: IvoPaunov Date: Mon, 2 Sep 2024 11:19:41 +0300 Subject: [PATCH 23/84] fix side nav links --- src/components/SideNav/NavLink.tsx | 4 +++- src/components/SideNav/SideNav.tsx | 7 ++++--- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/src/components/SideNav/NavLink.tsx b/src/components/SideNav/NavLink.tsx index 623c00f6..bdf6c038 100644 --- a/src/components/SideNav/NavLink.tsx +++ b/src/components/SideNav/NavLink.tsx @@ -7,9 +7,10 @@ type NavLinkProps = LinkProps & { label: string action?: () => void active?: boolean + external?: boolean } -function NavLink({ to = '', icon, label, action, active }: NavLinkProps) { +function NavLink({ to = '', icon, label, action, active, external }: NavLinkProps) { return ( @@ -17,6 +18,7 @@ function NavLink({ to = '', icon, label, action, active }: NavLinkProps) { radius="sm" to={to} component={Link} + target={external ? '_blank' : undefined} title={label} onClick={action} variant="subtle" diff --git a/src/components/SideNav/SideNav.tsx b/src/components/SideNav/SideNav.tsx index 24baea1f..5060f920 100644 --- a/src/components/SideNav/SideNav.tsx +++ b/src/components/SideNav/SideNav.tsx @@ -18,6 +18,7 @@ import AdExLogo from 'resources/logos/AdExLogo' import { useMemo } from 'react' import { appVersion } from 'helpers' import { IS_MANUAL_DEPOSITING } from 'constants/balances' +import CustomAnchor from 'components/common/customAnchor' import NavLink from './NavLink' import Balance from './Balance' import CreateCampaignBtn from './CreateCampaignBtn' @@ -78,7 +79,7 @@ function SideNav() { } label="Help Center" /> @@ -93,9 +94,9 @@ function SideNav() { - + ©{year} AdEx. - + All Rights Reserved. From 87a7d6a3976e444b2b907eea1e0da25f82cee867 Mon Sep 17 00:00:00 2001 From: IvoPaunov Date: Mon, 2 Sep 2024 11:55:36 +0300 Subject: [PATCH 24/84] update analytics err notification --- .../CampaignsContext/CampaignsAnalyticsContext.tsx | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/contexts/CampaignsContext/CampaignsAnalyticsContext.tsx b/src/contexts/CampaignsContext/CampaignsAnalyticsContext.tsx index 42059cef..4108a30f 100644 --- a/src/contexts/CampaignsContext/CampaignsAnalyticsContext.tsx +++ b/src/contexts/CampaignsContext/CampaignsAnalyticsContext.tsx @@ -208,10 +208,14 @@ const CampaignsAnalyticsProvider: FC = ({ children }) => { next.set(dataKey, nextAggr) return next }) - } catch (err) { + } catch (err: any) { console.log(err) // TODO: see how to use campaignId out of segment - showNotification('error', `getting analytics ${params.timeframe}`, 'Data error') + showNotification( + 'error', + err?.message || err.toString(), + `Fetching analytics${forAdmin ? ' (admin)' : ''}` + ) } return dataKey From f5cce5549a77694dd1e7087c6e8d4f325f23ff3a Mon Sep 17 00:00:00 2001 From: IvoPaunov Date: Mon, 2 Sep 2024 17:02:53 +0300 Subject: [PATCH 25/84] cleanup and update account context --- .../AccountContext/AccountContext.tsx | 62 +++++++++---------- 1 file changed, 31 insertions(+), 31 deletions(-) diff --git a/src/contexts/AccountContext/AccountContext.tsx b/src/contexts/AccountContext/AccountContext.tsx index c23de0b0..a5e45690 100644 --- a/src/contexts/AccountContext/AccountContext.tsx +++ b/src/contexts/AccountContext/AccountContext.tsx @@ -192,7 +192,6 @@ const AccountProvider: FC = ({ children }) => { const lsAcc: IAccountContext['adexAccount'] = deserializeJSON( localStorage.getItem('adexAccount') || '' ) - console.log({ lsAcc }) if (!lsAcc) { setAdexAccount({ ...defaultValue, loaded: true }) @@ -208,7 +207,6 @@ const AccountProvider: FC = ({ children }) => { ) const connectWallet = useCallback(() => { - console.log({ ambireSDK }) resetAdexAccount('connecting new wallet') ambireSDK.openLogin({ chainId: DEFAULT_CHAIN_ID }) }, [ambireSDK, resetAdexAccount]) @@ -358,7 +356,7 @@ const AccountProvider: FC = ({ children }) => { if (resp) { disconnectWallet() resetAdexAccount('Log out btn (backend)') - showNotification('info', 'Successfully logged out', 'Logging out') + showNotification('info', 'Logged out', 'Success') } } catch (err: any) { console.error('logOut: ', err) @@ -421,9 +419,9 @@ const AccountProvider: FC = ({ children }) => { const handleSDKAuthSuccess = useCallback( async ({ address, chainId }: any, type: string) => { - console.log('handleSDKAuthSuccess: ', type) + console.log('sdk auth success: ', type) if (!address || !chainId) { - showNotification('warning', 'Ambire sdk no address or chain') + showNotification('warning', 'Ambire sdk no address or chain selected') return } @@ -432,9 +430,9 @@ const AccountProvider: FC = ({ children }) => { setAuthMsg(authMsgResp) signMessage('eth_signTypedData', JSON.stringify(authMsgResp.authMsg)) setAdexAccount({ ...defaultValue, address, chainId, loaded: true }) - } catch (error) { + } catch (error: any) { console.error('Get message to sign failed', error) - showNotification('error', 'Get message to sign failed') + showNotification('error', error?.message || error.toString(), 'Message sign failed') } }, [getAuthMsg, setAdexAccount, showNotification, signMessage] @@ -481,17 +479,29 @@ const AccountProvider: FC = ({ children }) => { resetAdexAccount('Log out SDK') }, [resetAdexAccount]) - const handleMsgRejected = useCallback((data: any) => { - console.log('message rejected', data) - }, []) + const handleMsgRejected = useCallback( + (data: any) => { + console.log('message rejected', data) + showNotification('info', 'Message rejected') + }, + [showNotification] + ) - const handleActionRejected = useCallback((data: any) => { - console.log('action rejected', data) - }, []) + const handleActionRejected = useCallback( + (data: any) => { + console.log('action rejected', data) + showNotification('info', 'Action rejected') + }, + [showNotification] + ) - const handleTxnRejected = useCallback((data: any) => { - console.log('action rejected', data) - }, []) + const handleTxnRejected = useCallback( + (data: any) => { + console.log('action rejected', data) + showNotification('info', 'Transaction rejected') + }, + [showNotification] + ) // TODO: types for success data useEffect(() => { @@ -543,23 +553,17 @@ const AccountProvider: FC = ({ children }) => { method: 'GET' }) - console.log({ accountData }) - if (accountData) { setAdexAccount((prev) => { const next = { ...prev, ...accountData } return next }) } else { - showNotification( - 'error', - 'Updating account balance failed', - 'Updating account balance failed' - ) + throw new Error('Invalid response') } } catch (err: any) { console.error('Updating account balance failed:', err) - showNotification('error', err?.message || err.toString(), 'Updating account balance failed') + showNotification('error', err?.message || err.toString(), 'Updating account balance') } }, [adexServicesRequest, setAdexAccount, showNotification]) @@ -578,17 +582,13 @@ const AccountProvider: FC = ({ children }) => { const next = { ...prev, billingDetails } return next }) - showNotification('info', 'Billing details updated', 'Successfully') + showNotification('info', 'Updating billing details', 'Success') } else { - showNotification( - 'error', - 'Updating billing details failed', - 'Updating billing details failed' - ) + throw new Error('Invalid response') } } catch (err: any) { console.error('Updating billing details failed:', err) - showNotification('error', 'Updating billing details failed') + showNotification('error', err?.err || err?.toString(), 'Updating billing details') } }, [adexServicesRequest, setAdexAccount, showNotification] From cb79da1eae8df232ed6b13d0d0dfd7f153d6eeeb Mon Sep 17 00:00:00 2001 From: Ivo Paunov Date: Mon, 2 Sep 2024 17:05:37 +0300 Subject: [PATCH 26/84] v0.69.52 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 52835821..564aca20 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "adex-interface", - "version": "0.69.51", + "version": "0.69.52", "private": true, "dependencies": { "@ambire/login-sdk-core": "^0.0.21", From f70631822177d6f4bd7c7d0e0ae2082a640d0309 Mon Sep 17 00:00:00 2001 From: IvoPaunov Date: Tue, 3 Sep 2024 15:24:16 +0300 Subject: [PATCH 27/84] fix - analytics extra update #285 --- .../CampaignsContext/CampaignsAnalyticsContext.tsx | 8 ++++---- .../useCampaignAnalytics/useCampaignAnalyticsData.ts | 12 +++++++++--- 2 files changed, 13 insertions(+), 7 deletions(-) diff --git a/src/contexts/CampaignsContext/CampaignsAnalyticsContext.tsx b/src/contexts/CampaignsContext/CampaignsAnalyticsContext.tsx index 4108a30f..5ad657d7 100644 --- a/src/contexts/CampaignsContext/CampaignsAnalyticsContext.tsx +++ b/src/contexts/CampaignsContext/CampaignsAnalyticsContext.tsx @@ -224,8 +224,8 @@ const CampaignsAnalyticsProvider: FC = ({ children }) => { ) const updateCampaignAnalyticsByQuery = useCallback( - (query: AnalyticsDataQuery, dataKey: string, forAdmin?: boolean): void => { - updateAnalytics(query, dataKey, forAdmin) + async (query: AnalyticsDataQuery, dataKey: string, forAdmin?: boolean): Promise => { + await updateAnalytics(query, dataKey, forAdmin) }, [updateAnalytics] ) @@ -305,10 +305,10 @@ const CampaignsAnalyticsProvider: FC = ({ children }) => { // NOTE: use in case to call the queries in some intervals // eslint-disable-next-line no-restricted-syntax for (const [i, q] of queries.entries()) { - updateCampaignAnalyticsByQuery(q, keys[i], forAdmin) + const update = updateCampaignAnalyticsByQuery(q, keys[i], forAdmin) if (i < queries.length) { // eslint-disable-next-line no-await-in-loop - await timeout(69) + await Promise.race([update, timeout(69)]) } } diff --git a/src/hooks/useCampaignAnalytics/useCampaignAnalyticsData.ts b/src/hooks/useCampaignAnalytics/useCampaignAnalyticsData.ts index fbff83cf..6b441b06 100644 --- a/src/hooks/useCampaignAnalytics/useCampaignAnalyticsData.ts +++ b/src/hooks/useCampaignAnalytics/useCampaignAnalyticsData.ts @@ -54,16 +54,22 @@ export function useCampaignsAnalyticsData({ ) useEffect(() => { - if (campaignId) { + console.log('useCampaignsAnalyticsData', { campaignId, analyticsType, forAdmin }) + }, [analyticsType, campaignId, forAdmin]) + + useEffect(() => { + if (campaignId && !campaign?.id) { console.log({ campaignId }) updateCampaignDataById(campaignId) } - }, [campaignId, updateCampaignDataById]) + }, [campaign?.id, campaignId, updateCampaignDataById]) useEffect(() => { - if (!campaign) return + if (!campaign?.id) return setAnalyticsKey(undefined) + console.log('KURAMIIIII', { analyticsType, campaign, forAdmin }) + const checkAnalytics = async () => { const key = await getAnalyticsKeyAndUpdate(analyticsType, campaign, !!forAdmin) setAnalyticsKey(key) From a0a852332e536fe2dbe960cb2bfafdba170f4b3e Mon Sep 17 00:00:00 2001 From: IvoPaunov Date: Wed, 4 Sep 2024 12:32:04 +0300 Subject: [PATCH 28/84] wip: adding error status to analytics data --- src/components/AdminPanel/AdminAnalytics.tsx | 50 ++++-- .../CampaignsAnalyticsContext.tsx | 151 ++++++++---------- .../useCampaignAnalyticsData.ts | 11 +- 3 files changed, 109 insertions(+), 103 deletions(-) diff --git a/src/components/AdminPanel/AdminAnalytics.tsx b/src/components/AdminPanel/AdminAnalytics.tsx index 9028d4e7..f26093b4 100644 --- a/src/components/AdminPanel/AdminAnalytics.tsx +++ b/src/components/AdminPanel/AdminAnalytics.tsx @@ -1,6 +1,17 @@ import { useEffect, useState, useMemo, useCallback } from 'react' -import { Select, Loader, Flex, Box, Text, Badge, ActionIcon, Stack, Group } from '@mantine/core' -import { BaseAnalyticsData, AnalyticsPeriod, Timeframe, AnalyticsType, SSPs } from 'types' +import { + Select, + Loader, + Flex, + Box, + Text, + Badge, + ActionIcon, + Stack, + Group, + Alert +} from '@mantine/core' +import { AnalyticsPeriod, Timeframe, AnalyticsType, SSPs } from 'types' import useCampaignAnalytics from 'hooks/useCampaignAnalytics' import CustomTable from 'components/common/CustomTable' import { CountryData } from 'helpers/countries' @@ -124,7 +135,7 @@ const AdminAnalytics = () => { [analyticsData, analyticsKey] ) - const adminMappedAnalytics: BaseAnalyticsData[] | undefined = useMemo( + const adminMappedAnalytics = useMemo( () => mappedAnalytics.get(analyticsKey?.key || ''), [analyticsKey, mappedAnalytics] ) @@ -132,7 +143,8 @@ const AdminAnalytics = () => { useEffect(() => { console.log({ analytics }) console.log({ mappedAnalytics }) - }, [analytics, mappedAnalytics]) + console.log({ adminMappedAnalytics }) + }, [analytics, mappedAnalytics, adminMappedAnalytics]) useEffect(() => { setAnalyticsKey(undefined) @@ -159,21 +171,26 @@ const AdminAnalytics = () => { checkAnalytics() }, [analType, getAnalyticsKeyAndUpdate, ssp, startDate, timeframe]) - const loading = useMemo( - () => !analyticsKey || !adminMappedAnalytics, - [analyticsKey, adminMappedAnalytics] - ) + const loading = useMemo(() => adminMappedAnalytics?.status === 'loading', [adminMappedAnalytics]) const data = useMemo(() => { - const paid = adminMappedAnalytics?.reduce((sum, i) => sum + i.paid, 0) || 0 - const imps = adminMappedAnalytics?.reduce((sum, i) => sum + i.impressions, 0) || 0 - const clicks = adminMappedAnalytics?.reduce((sum, i) => sum + i.clicks, 0) || 0 + if (!adminMappedAnalytics?.data || adminMappedAnalytics?.status !== 'processed') + return { + paid: 'N/A', + imps: 'N/A', + clicks: 'N/A', + elements: [] + } + + const paid = adminMappedAnalytics.data.reduce((sum, i) => sum + i.paid, 0) || 0 + const imps = adminMappedAnalytics.data.reduce((sum, i) => sum + i.impressions, 0) || 0 + const clicks = adminMappedAnalytics.data.reduce((sum, i) => sum + i.clicks, 0) || 0 return { paid, imps, clicks, elements: - adminMappedAnalytics?.map((item) => ({ + adminMappedAnalytics.data.map((item) => ({ id: item.segment.toString(), segment: mapSegmentLabel(analType, item.segment).segementLabel, share: `${((item.paid / (paid || 1)) * 100).toFixed(2)} %`, @@ -270,7 +287,9 @@ const AdminAnalytics = () => { } size="lg" > - {Number(data.paid.toFixed(2)).toLocaleString()} + {typeof data.paid === 'number' + ? Number(data.paid.toFixed(2)).toLocaleString() + : data.paid} { + {adminMappedAnalytics?.status === 'error' && ( + + )} + analyticsData: Map // TODO: all campaigns event aggregations by account getAnalyticsKeyAndUpdate: ( analyticsType: AnalyticsType, @@ -156,7 +156,7 @@ interface ICampaignsAnalyticsContext { ssp?: SSPs ) => Promise<{ key: string; period: AnalyticsPeriod } | undefined> initialAnalyticsLoading: boolean - mappedAnalytics: Map + mappedAnalytics: Map } const CampaignsAnalyticsContext = createContext(null) @@ -169,16 +169,13 @@ const CampaignsAnalyticsProvider: FC = ({ children }) => { const [initialAnalyticsLoading, setInitialAnalyticsLoading] = useState(true) const [mappedAnalytics, setMappedAnalytics] = useState< ICampaignsAnalyticsContext['mappedAnalytics'] - >(new Map()) - const [dataToMapStatus, setDataToMapStatus] = useState>( - new Map() - ) + >(new Map()) const [analyticsData, setAnalyticsData] = useState( - new Map() + new Map() ) - const updateAnalytics = useCallback( + const updateCampaignAnalyticsByQuery = useCallback( async (params: AnalyticsDataQuery, dataKey: string, forAdmin?: boolean) => { try { const analyticsDataRes = await adexServicesRequest('validator', { @@ -198,19 +195,29 @@ const CampaignsAnalyticsProvider: FC = ({ children }) => { console.log({ analyticsDataRes }) - // if (!analyticsDataRes?.aggr) { - // throw new Error('invalid analytics data response') - // } + if (!analyticsDataRes?.aggr) { + throw new Error('invalid analytics data response') + } setAnalyticsData((prev) => { const next = new Map(prev) - const nextAggr = analyticsDataRes?.aggr || prev.get(dataKey) || [] + const nextAggr: { status: DataStatus; data: AnalyticsData[] } = + { status: 'processed', data: analyticsDataRes?.aggr } || prev.get(dataKey) next.set(dataKey, nextAggr) return next }) } catch (err: any) { console.log(err) // TODO: see how to use campaignId out of segment + setAnalyticsData((prev) => { + const next = new Map(prev) + const nextAggr: { status: DataStatus; data: AnalyticsData[] } = { + status: 'error', + data: prev.get(dataKey)?.data || [] + } + next.set(dataKey, nextAggr) + return next + }) showNotification( 'error', err?.message || err.toString(), @@ -223,13 +230,6 @@ const CampaignsAnalyticsProvider: FC = ({ children }) => { [adexServicesRequest, showNotification] ) - const updateCampaignAnalyticsByQuery = useCallback( - async (query: AnalyticsDataQuery, dataKey: string, forAdmin?: boolean): Promise => { - await updateAnalytics(query, dataKey, forAdmin) - }, - [updateAnalytics] - ) - const getAnalyticsKeyAndUpdate = useCallback( async ( analyticsType: AnalyticsType, @@ -298,30 +298,36 @@ const CampaignsAnalyticsProvider: FC = ({ children }) => { // { ...baseQuery, eventType: 'CLICK', metric: 'paid' } ] - const keys: string[] = queries.map((q) => getAnalyticsKeyFromQuery(q)) - const dataStatusKey = keys.join(keySeparator) + const aggrKeys: string[] = queries.map((q) => getAnalyticsKeyFromQuery(q)) + const mappedDataKey = getAnalyticsKeyFromQuery(baseQuery) - if (!dataToMapStatus.get(dataStatusKey)) { + if (!mappedAnalytics.get(mappedDataKey)) { + setMappedAnalytics((prev) => { + const next = new Map(prev) + const nextMapped: MappedAnalyticsRecord = { + status: 'loading', + analyticsType, + aggrKeys, + data: [] + } + next.set(mappedDataKey, nextMapped) + console.log({ next }) + return next + }) // NOTE: use in case to call the queries in some intervals // eslint-disable-next-line no-restricted-syntax for (const [i, q] of queries.entries()) { - const update = updateCampaignAnalyticsByQuery(q, keys[i], forAdmin) + const update = updateCampaignAnalyticsByQuery(q, aggrKeys[i], forAdmin) if (i < queries.length) { // eslint-disable-next-line no-await-in-loop await Promise.race([update, timeout(69)]) } } - - setDataToMapStatus((prev) => { - const next = new Map(prev) - next.set(dataStatusKey, { dataStatus: 'loading', analyticsType }) - return next - }) } - return { key: dataStatusKey, period } + return { key: mappedDataKey, period } }, - [dataToMapStatus, updateCampaignAnalyticsByQuery] + [mappedAnalytics, updateCampaignAnalyticsByQuery] ) useEffect(() => { @@ -332,60 +338,39 @@ const CampaignsAnalyticsProvider: FC = ({ children }) => { updateCampaigns() } else { - setAnalyticsData(new Map()) + setAnalyticsData(new Map()) setInitialAnalyticsLoading(false) } }, [authenticated]) useEffect(() => { - setDataToMapStatus((prev) => { - let update = false - const nextMappedData = new Map() - const nextMapStatuses = new Map(prev) - - prev.forEach((status, statusKey) => { - const analyticsKeys = statusKey.split(keySeparator) - - if (status.dataStatus === 'loading') { - const isLoaded = analyticsKeys.every((aKey) => !!analyticsData.get(aKey)) - if (isLoaded) { - const analyticsDataAggregates = analyticsKeys.map( - (key) => analyticsData.get(key) as unknown as AnalyticsData[] - ) - - const mapped = analyticsDataToMappedAnalytics( - analyticsDataAggregates, - status.analyticsType - ) - - if (mapped) { - // TODO: map data - nextMappedData.set(statusKey, mapped) - - nextMapStatuses.set(statusKey, { ...status, dataStatus: 'processed' }) - update = true - } + setMappedAnalytics((prev) => { + const nextMappedData = new Map(prev) + + prev.forEach(({ aggrKeys, analyticsType, data }, mappedDataKey) => { + if (aggrKeys.every((aKey) => analyticsData.get(aKey)?.status === 'processed')) { + const analyticsDataAggregates = aggrKeys.map((key) => analyticsData.get(key)!.data) + + const mapped = analyticsDataToMappedAnalytics(analyticsDataAggregates, analyticsType) + + if (mapped) { + nextMappedData.set(mappedDataKey, { + aggrKeys, + analyticsType, + status: 'processed', + data: mapped + }) + } else { + nextMappedData.set(mappedDataKey, { data, aggrKeys, analyticsType, status: 'error' }) } + } else if (aggrKeys.some((aKey) => analyticsData.get(aKey)?.status === 'error')) { + nextMappedData.set(mappedDataKey, { data, aggrKeys, analyticsType, status: 'error' }) } }) - if (update) { - // TODO: process data - map to dev friendly format - setMappedAnalytics((prevMapped) => { - const nextMapped = new Map(prevMapped) - nextMappedData.forEach((value, key) => { - nextMapped.set(key, value) - }) - - return nextMapped - }) - - return new Map(nextMapStatuses) - } - - return prev + return nextMappedData }) - }, [analyticsData, dataToMapStatus]) + }, [analyticsData]) const contextValue = useMemo( () => ({ diff --git a/src/hooks/useCampaignAnalytics/useCampaignAnalyticsData.ts b/src/hooks/useCampaignAnalytics/useCampaignAnalyticsData.ts index 6b441b06..45f50c33 100644 --- a/src/hooks/useCampaignAnalytics/useCampaignAnalyticsData.ts +++ b/src/hooks/useCampaignAnalytics/useCampaignAnalyticsData.ts @@ -14,7 +14,8 @@ export function useCampaignsAnalyticsData({ analyticsType: AnalyticsType forAdmin?: boolean }) { - const { getAnalyticsKeyAndUpdate, mappedAnalytics } = useCampaignAnalytics() + const { getAnalyticsKeyAndUpdate, mappedAnalytics, initialAnalyticsLoading } = + useCampaignAnalytics() const { campaignsData, updateCampaignDataById } = useCampaignsData() const [analyticsKey, setAnalyticsKey] = useState< | { @@ -49,7 +50,7 @@ export function useCampaignsAnalyticsData({ ) const campaignMappedAnalytics: BaseAnalyticsData[] | undefined = useMemo( - () => mappedAnalytics.get(analyticsKey?.key || ''), + () => mappedAnalytics.get(analyticsKey?.key || '')?.data, [analyticsKey, mappedAnalytics] ) @@ -68,8 +69,6 @@ export function useCampaignsAnalyticsData({ if (!campaign?.id) return setAnalyticsKey(undefined) - console.log('KURAMIIIII', { analyticsType, campaign, forAdmin }) - const checkAnalytics = async () => { const key = await getAnalyticsKeyAndUpdate(analyticsType, campaign, !!forAdmin) setAnalyticsKey(key) @@ -80,8 +79,8 @@ export function useCampaignsAnalyticsData({ }, [analyticsType, campaign, getAnalyticsKeyAndUpdate, forAdmin]) const loading = useMemo( - () => !analyticsKey || !campaignMappedAnalytics, - [analyticsKey, campaignMappedAnalytics] + () => initialAnalyticsLoading || !analyticsKey || !campaignMappedAnalytics, + [analyticsKey, campaignMappedAnalytics, initialAnalyticsLoading] ) return { campaignMappedAnalytics, totalPaid, campaign, loading, currencyName, analyticsKey } From 2547be92a613a5ea6c791239a1050948483faebd Mon Sep 17 00:00:00 2001 From: IvoPaunov Date: Thu, 5 Sep 2024 10:11:36 +0300 Subject: [PATCH 29/84] add error to useCampaignsAnalyticsData --- .../useCampaignAnalyticsData.ts | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/src/hooks/useCampaignAnalytics/useCampaignAnalyticsData.ts b/src/hooks/useCampaignAnalytics/useCampaignAnalyticsData.ts index 45f50c33..35672d36 100644 --- a/src/hooks/useCampaignAnalytics/useCampaignAnalyticsData.ts +++ b/src/hooks/useCampaignAnalytics/useCampaignAnalyticsData.ts @@ -83,5 +83,18 @@ export function useCampaignsAnalyticsData({ [analyticsKey, campaignMappedAnalytics, initialAnalyticsLoading] ) - return { campaignMappedAnalytics, totalPaid, campaign, loading, currencyName, analyticsKey } + const error = useMemo( + () => mappedAnalytics.get(analyticsKey?.key || '')?.status === 'error', + [analyticsKey?.key, mappedAnalytics] + ) + + return { + campaignMappedAnalytics, + totalPaid, + campaign, + loading, + currencyName, + analyticsKey, + error + } } From 87933fa16533736e74f2c3a0953ca4a5919a5233 Mon Sep 17 00:00:00 2001 From: IvoPaunov Date: Thu, 5 Sep 2024 10:11:55 +0300 Subject: [PATCH 30/84] add error to CustomTable --- src/components/common/CustomTable/CustomTable.tsx | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/src/components/common/CustomTable/CustomTable.tsx b/src/components/common/CustomTable/CustomTable.tsx index 0215ad1c..8b60ee0e 100644 --- a/src/components/common/CustomTable/CustomTable.tsx +++ b/src/components/common/CustomTable/CustomTable.tsx @@ -17,7 +17,8 @@ import { LoadingOverlay, Checkbox, // CheckIcon, - Button + Button, + Alert } from '@mantine/core' import { useSet, useMediaQuery } from '@mantine/hooks' import usePagination from 'hooks/usePagination' @@ -50,6 +51,7 @@ export type CustomTableProps = PropsWithChildren & loading?: boolean selectedActions?: TableRowAction[] tableActions?: ReactNode + error?: string | boolean } const getLabel = (label: TableRowAction['label'], actionData?: TableElement['actionData']) => { @@ -77,6 +79,7 @@ export const CustomTable = ({ loading, selectedActions, tableActions, + error, ...tableProps }: CustomTableProps) => { const isMobile = useMediaQuery('(max-width: 75rem)') @@ -291,12 +294,22 @@ export const CustomTable = ({ } return {cols} }) + // eslint-disable-next-line react-hooks/exhaustive-deps }, [list, actions, isMobile, columns, selectedElements, selectedElements.size, headings]) return ( + {error && ( + + )} + + {!error && !rows.length && } {selectedElements.size && masterActionMenu} {tableActions} From 1e28986fb78fefab8caf0e3e6a567a3a175a657b Mon Sep 17 00:00:00 2001 From: IvoPaunov Date: Thu, 5 Sep 2024 10:12:11 +0300 Subject: [PATCH 31/84] add error to analytics tables --- .../CampaignAnalytics/Creatives.tsx | 3 +- .../CampaignAnalytics/Placements.tsx | 3 +- src/components/CampaignAnalytics/Regions.tsx | 3 +- src/components/CampaignAnalytics/SSPs.tsx | 4 +- .../CampaignAnalytics/TimeFrame.tsx | 147 +++++++----------- 5 files changed, 68 insertions(+), 92 deletions(-) diff --git a/src/components/CampaignAnalytics/Creatives.tsx b/src/components/CampaignAnalytics/Creatives.tsx index 92ad8632..f248d12c 100644 --- a/src/components/CampaignAnalytics/Creatives.tsx +++ b/src/components/CampaignAnalytics/Creatives.tsx @@ -22,7 +22,7 @@ const csvHeaders = { } const Creatives = ({ forAdmin, campaignId }: { forAdmin: boolean; campaignId: string }) => { - const { campaignMappedAnalytics, currencyName, campaign, analyticsKey, loading } = + const { campaignMappedAnalytics, currencyName, campaign, analyticsKey, loading, error } = useCampaignsAnalyticsData({ campaignId, forAdmin, @@ -62,6 +62,7 @@ const Creatives = ({ forAdmin, campaignId }: { forAdmin: boolean; campaignId: st return ( & { } const Placements = ({ forAdmin, campaignId }: { forAdmin: boolean; campaignId: string }) => { - const { campaignMappedAnalytics, campaign, currencyName, loading, analyticsKey } = + const { campaignMappedAnalytics, campaign, currencyName, loading, analyticsKey, error } = useCampaignsAnalyticsData({ campaignId, forAdmin, @@ -165,6 +165,7 @@ const Placements = ({ forAdmin, campaignId }: { forAdmin: boolean; campaignId: s return ( { const [isMapVisible, setIsMapVisible] = useState(false) - const { campaignMappedAnalytics, currencyName, analyticsKey, loading } = + const { campaignMappedAnalytics, currencyName, analyticsKey, loading, error } = useCampaignsAnalyticsData({ campaignId, forAdmin, @@ -52,6 +52,7 @@ const Regions = ({ forAdmin, campaignId }: { forAdmin: boolean; campaignId: stri return ( { - const { campaignMappedAnalytics, currencyName } = useCampaignsAnalyticsData({ + const { campaignMappedAnalytics, currencyName, error, loading } = useCampaignsAnalyticsData({ campaignId, forAdmin, analyticsType: 'ssp' @@ -28,7 +28,7 @@ const SSPs = ({ forAdmin, campaignId }: { forAdmin: boolean; campaignId: string if (!campaignMappedAnalytics?.length) { return
No placement found
} - return + return } export default SSPs diff --git a/src/components/CampaignAnalytics/TimeFrame.tsx b/src/components/CampaignAnalytics/TimeFrame.tsx index ee5fa616..bc176e3a 100644 --- a/src/components/CampaignAnalytics/TimeFrame.tsx +++ b/src/components/CampaignAnalytics/TimeFrame.tsx @@ -1,42 +1,12 @@ import { useCallback, useEffect, useMemo, useState } from 'react' -import { - Grid, - Title, - Flex, - Text, - lighten, - MantineTheme, - getPrimaryShade, - Box, - LoadingOverlay -} from '@mantine/core' -import { createStyles } from '@mantine/emotion' +import { Title, Flex, Text, Box, LoadingOverlay, Alert, Stack, Group, Paper } from '@mantine/core' import TimeFrameChart from 'components/common/Chart/TimeFrameChart' import { BaseAnalyticsData, FilteredAnalytics, MetricsToShow } from 'types' import { formatCurrency } from 'helpers' -import { useViewportSize, useColorScheme } from '@mantine/hooks' +import { useViewportSize } from '@mantine/hooks' import { useCampaignsAnalyticsData } from 'hooks/useCampaignAnalytics/useCampaignAnalyticsData' import ChartControlBtn from './ChartControlBtn' -const useStyles = createStyles((theme: MantineTheme) => { - const colorScheme = useColorScheme() - const primaryShade = getPrimaryShade(theme, colorScheme) - - return { - wrapper: { - background: theme.colors.mainBackground[primaryShade], - borderRadius: theme.radius.md, - padding: theme.spacing.lg, - marginBottom: theme.spacing.md, - boxShadow: theme.shadows.sm - }, - lighterGray: { - color: lighten(theme.colors.mainText[primaryShade], theme.other.shades.lighten.lighter), - fontSize: theme.fontSizes.sm - } - } -}) - function sumArrayProperties(analytics: BaseAnalyticsData[]) { const sums = analytics.reduce( (sum, x) => { @@ -58,9 +28,7 @@ function sumArrayProperties(analytics: BaseAnalyticsData[]) { } export const TimeFrame = ({ forAdmin, campaignId }: { forAdmin: boolean; campaignId: string }) => { - const { classes } = useStyles() - - const { campaignMappedAnalytics, analyticsKey, currencyName, loading } = + const { campaignMappedAnalytics, analyticsKey, currencyName, loading, error } = useCampaignsAnalyticsData({ campaignId, forAdmin, @@ -119,51 +87,60 @@ export const TimeFrame = ({ forAdmin, campaignId }: { forAdmin: boolean; campaig return ( - - - - - handleMetricClick(v, 'impressions')} - whiteFontColor - /> - - - handleMetricClick(v, 'clicks')} - whiteFontColor - /> - - - handleMetricClick(v, 'avgCpm')} - whiteFontColor - /> - - - handleMetricClick(v, 'paid')} - /> - - - - + + + {error && ( + + )} + {!error && !filteredData.length && ( + + )} + + + + handleMetricClick(v, 'impressions')} + whiteFontColor + /> + + handleMetricClick(v, 'clicks')} + whiteFontColor + /> + + handleMetricClick(v, 'avgCpm')} + whiteFontColor + /> + + handleMetricClick(v, 'paid')} + /> + + + + Chart + = 768 ? windowWidth - 315 : windowWidth - 100} height={420} @@ -172,16 +149,12 @@ export const TimeFrame = ({ forAdmin, campaignId }: { forAdmin: boolean; campaig /> {analyticsKey?.period && ( - - Starts: {analyticsKey?.period.start?.toString()} - - - Ends: {analyticsKey?.period.end?.toString()} - + Starts: {analyticsKey?.period.start?.toString()} + Ends: {analyticsKey?.period.end?.toString()} )} - - + +
) } From 6e2566fa3a5faff4e840ab6ce8ea773e9368226c Mon Sep 17 00:00:00 2001 From: IvoPaunov Date: Thu, 5 Sep 2024 10:12:54 +0300 Subject: [PATCH 32/84] admin analytics add error to data table --- src/components/AdminPanel/AdminAnalytics.tsx | 22 ++++++-------------- 1 file changed, 6 insertions(+), 16 deletions(-) diff --git a/src/components/AdminPanel/AdminAnalytics.tsx b/src/components/AdminPanel/AdminAnalytics.tsx index f26093b4..9ff90ab1 100644 --- a/src/components/AdminPanel/AdminAnalytics.tsx +++ b/src/components/AdminPanel/AdminAnalytics.tsx @@ -1,16 +1,5 @@ import { useEffect, useState, useMemo, useCallback } from 'react' -import { - Select, - Loader, - Flex, - Box, - Text, - Badge, - ActionIcon, - Stack, - Group, - Alert -} from '@mantine/core' +import { Select, Loader, Flex, Box, Text, Badge, ActionIcon, Stack, Group } from '@mantine/core' import { AnalyticsPeriod, Timeframe, AnalyticsType, SSPs } from 'types' import useCampaignAnalytics from 'hooks/useCampaignAnalytics' import CustomTable from 'components/common/CustomTable' @@ -321,11 +310,12 @@ const AdminAnalytics = () => { disabled={loading} />
- {adminMappedAnalytics?.status === 'error' && ( - - )} - Date: Thu, 5 Sep 2024 10:17:28 +0300 Subject: [PATCH 33/84] v0.69.53 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 564aca20..71374f20 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "adex-interface", - "version": "0.69.52", + "version": "0.69.53", "private": true, "dependencies": { "@ambire/login-sdk-core": "^0.0.21", From 666430469783c370efc715382f2a5fbcaf0a16b6 Mon Sep 17 00:00:00 2001 From: IvoPaunov Date: Thu, 5 Sep 2024 10:55:20 +0300 Subject: [PATCH 34/84] update and fix loading overlay styles and logic --- src/components/AdminPanel/Accounts.tsx | 17 ++- src/components/AdminPanel/AdminAnalytics.tsx | 128 ++++++++++-------- .../CampaignAnalytics/TimeFrame.tsx | 2 +- .../common/CustomTable/CustomTable.tsx | 4 +- .../useCampaignAnalyticsData.ts | 8 +- src/themes/base.tsx | 11 +- 6 files changed, 99 insertions(+), 71 deletions(-) diff --git a/src/components/AdminPanel/Accounts.tsx b/src/components/AdminPanel/Accounts.tsx index 6a214f45..37793442 100644 --- a/src/components/AdminPanel/Accounts.tsx +++ b/src/components/AdminPanel/Accounts.tsx @@ -1,6 +1,6 @@ import { useEffect, useMemo, useCallback, useState } from 'react' import { useNavigate } from 'react-router-dom' -import { Loader, Box, Badge, TextInput, Stack, Group } from '@mantine/core' +import { Box, Badge, TextInput, Stack, Group, LoadingOverlay } from '@mantine/core' import CustomTable from 'components/common/CustomTable' import useAdmin from 'hooks/useAdmin' @@ -125,11 +125,10 @@ const AdminAnalytics = () => { ] }, [handlePreview]) - return initialDataLoading ? ( - - ) : ( + return ( - + + Totals: ({data.totalAccounts}) @@ -148,7 +147,13 @@ const AdminAnalytics = () => { miw={420} /> - + ) } diff --git a/src/components/AdminPanel/AdminAnalytics.tsx b/src/components/AdminPanel/AdminAnalytics.tsx index 9ff90ab1..18225c5b 100644 --- a/src/components/AdminPanel/AdminAnalytics.tsx +++ b/src/components/AdminPanel/AdminAnalytics.tsx @@ -1,5 +1,15 @@ import { useEffect, useState, useMemo, useCallback } from 'react' -import { Select, Loader, Flex, Box, Text, Badge, ActionIcon, Stack, Group } from '@mantine/core' +import { + Select, + Flex, + Box, + Text, + Badge, + ActionIcon, + Stack, + Group, + LoadingOverlay +} from '@mantine/core' import { AnalyticsPeriod, Timeframe, AnalyticsType, SSPs } from 'types' import useCampaignAnalytics from 'hooks/useCampaignAnalytics' import CustomTable from 'components/common/CustomTable' @@ -262,67 +272,65 @@ const AdminAnalytics = () => { /> - {loading ? ( - - ) : ( - - - Totals: - - - - } - size="lg" - > - {typeof data.paid === 'number' - ? Number(data.paid.toFixed(2)).toLocaleString() - : data.paid} - - - - - - } - > - {data.imps.toLocaleString()} - - - - - - } - > - {data.clicks.toLocaleString()} - - - - - + + + Totals: + + + } - headings={headings} - elements={data.elements} - pageSize={10} - actions={analType === 'campaignId' ? actions : undefined} + size="lg" + > + {typeof data.paid === 'number' + ? Number(data.paid.toFixed(2)).toLocaleString() + : data.paid} + + + + + + } + > + {data.imps.toLocaleString()} + + + + + + } + > + {data.clicks.toLocaleString()} + + + - - )} + + +
) } diff --git a/src/components/CampaignAnalytics/TimeFrame.tsx b/src/components/CampaignAnalytics/TimeFrame.tsx index bc176e3a..2024fa67 100644 --- a/src/components/CampaignAnalytics/TimeFrame.tsx +++ b/src/components/CampaignAnalytics/TimeFrame.tsx @@ -96,7 +96,7 @@ export const TimeFrame = ({ forAdmin, campaignId }: { forAdmin: boolean; campaig title={typeof error === 'string' ? error : 'Error loading data'} /> )} - {!error && !filteredData.length && ( + {!error && !loading && !filteredData.length && ( )} diff --git a/src/components/common/CustomTable/CustomTable.tsx b/src/components/common/CustomTable/CustomTable.tsx index 8b60ee0e..ae9c37ce 100644 --- a/src/components/common/CustomTable/CustomTable.tsx +++ b/src/components/common/CustomTable/CustomTable.tsx @@ -309,7 +309,9 @@ export const CustomTable = ({ /> )} - {!error && !rows.length && } + {!error && !loading && !rows.length && ( + + )} {selectedElements.size && masterActionMenu} {tableActions} diff --git a/src/hooks/useCampaignAnalytics/useCampaignAnalyticsData.ts b/src/hooks/useCampaignAnalytics/useCampaignAnalyticsData.ts index 35672d36..3eafedbf 100644 --- a/src/hooks/useCampaignAnalytics/useCampaignAnalyticsData.ts +++ b/src/hooks/useCampaignAnalytics/useCampaignAnalyticsData.ts @@ -79,8 +79,12 @@ export function useCampaignsAnalyticsData({ }, [analyticsType, campaign, getAnalyticsKeyAndUpdate, forAdmin]) const loading = useMemo( - () => initialAnalyticsLoading || !analyticsKey || !campaignMappedAnalytics, - [analyticsKey, campaignMappedAnalytics, initialAnalyticsLoading] + () => + initialAnalyticsLoading || + !analyticsKey || + !campaignMappedAnalytics || + mappedAnalytics.get(analyticsKey?.key)?.status === 'loading', + [analyticsKey, campaignMappedAnalytics, initialAnalyticsLoading, mappedAnalytics] ) const error = useMemo( diff --git a/src/themes/base.tsx b/src/themes/base.tsx index 38688b97..115cba0b 100644 --- a/src/themes/base.tsx +++ b/src/themes/base.tsx @@ -17,7 +17,8 @@ import { Paper, defaultVariantColorsResolver, VariantColorsResolver, - parseThemeColor + parseThemeColor, + LoadingOverlay } from '@mantine/core' import { Dropzone } from '@mantine/dropzone' import DownArrowIcon from 'resources/icons/DownArrow' @@ -279,6 +280,14 @@ const themeOverride: MantineThemeOverride = createTheme({ defaultProps: { shadow: 'none' } + }), + LoadingOverlay: LoadingOverlay.extend({ + defaultProps: { + overlayProps: { + backgroundOpacity: 0, + blur: 2 + } + } }) }, primaryShade: { light: 3, dark: 4 }, From 666550b1241976c1e728fc489968c531e6daf689 Mon Sep 17 00:00:00 2001 From: Ivo Paunov Date: Thu, 5 Sep 2024 10:59:32 +0300 Subject: [PATCH 35/84] v0.69.54 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 71374f20..8ca71a94 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "adex-interface", - "version": "0.69.53", + "version": "0.69.54", "private": true, "dependencies": { "@ambire/login-sdk-core": "^0.0.21", From 352d06b020bb37408222ebc613a6f0f748bdd571 Mon Sep 17 00:00:00 2001 From: IvoPaunov Date: Thu, 5 Sep 2024 17:49:59 +0300 Subject: [PATCH 36/84] allow robots v 0.69.55 --- package.json | 2 +- public/robots.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 8ca71a94..87d59f67 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "adex-interface", - "version": "0.69.54", + "version": "0.69.55", "private": true, "dependencies": { "@ambire/login-sdk-core": "^0.0.21", diff --git a/public/robots.txt b/public/robots.txt index b21f0887..349360ba 100644 --- a/public/robots.txt +++ b/public/robots.txt @@ -1,3 +1,3 @@ # https://www.robotstxt.org/robotstxt.html User-agent: * -Disallow: / +Allow: / From e5322f6ec62a65ceb10163668c282b752ce28fd5 Mon Sep 17 00:00:00 2001 From: IvoPaunov Date: Mon, 9 Sep 2024 11:57:05 +0300 Subject: [PATCH 37/84] wip: cleanup and fix billing pdf-s --- src/components/Billing/BillingPDF.tsx | 258 ++++++++++++-------------- 1 file changed, 117 insertions(+), 141 deletions(-) diff --git a/src/components/Billing/BillingPDF.tsx b/src/components/Billing/BillingPDF.tsx index 1ecfc76a..4c684f39 100644 --- a/src/components/Billing/BillingPDF.tsx +++ b/src/components/Billing/BillingPDF.tsx @@ -1,4 +1,17 @@ -import { Flex, Grid, MantineTheme, Space, Table, Text, Box, getPrimaryShade } from '@mantine/core' +import { + Flex, + Grid, + MantineTheme, + Space, + Table, + Text, + Box, + getPrimaryShade, + Group, + Stack, + Title, + Divider +} from '@mantine/core' import { createStyles } from '@mantine/emotion' import { Placement } from 'adex-common' import { @@ -8,8 +21,6 @@ import { parseBigNumTokenAmountToDecimal, formatCurrency } from 'helpers' -// TODO: delete mock data -// import { invoiceDetails } from 'components/Billing/mockedData' import { PropsWithChildren, ReactNode } from 'react' import AdExLogo from 'resources/logos/AdExLogo' import { IInvoiceDetails, InvoiceCompanyDetails, OperationEntry, StatementData } from 'types' @@ -40,52 +51,13 @@ const useStyles = createStyles((theme: MantineTheme) => { const colorScheme = useColorScheme() const primaryShade = getPrimaryShade(theme, colorScheme) return { - wrapper: { - color: theme.colors.secondaryText[primaryShade], - fontSize: theme.fontSizes.xs, - span: { - display: 'block', - wordBreak: 'break-word' - } - }, - title: { - fontSize: theme.fontSizes.sm, - fontWeight: theme.other.fontWeights.bold - }, - right: { - textAlign: 'end' - }, - bold: { - fontWeight: theme.other.fontWeights.bold, - color: theme.colors.mainText[primaryShade] - }, - italic: { - fontStyle: 'italic' - }, wrap: { wordBreak: 'break-word' }, noWrap: { whiteSpace: 'nowrap' }, - smallFontSize: { - fontSize: theme.fontSizes.xs - }, - borderBottom: { borderBottom: '1px solid black', width: '100%', height: '70%' }, - signature: { display: 'flex', justifyContent: 'center', fontSize: theme.fontSizes.xs }, - rightAlignedText: { - textAlign: 'end' - }, - head: { - background: theme.black, - padding: theme.spacing.xl, - color: 'white', - textAlign: 'end' - }, - footer: { - borderTop: '1px solid', - borderColor: theme.colors.decorativeBorders[primaryShade] - }, + tableHeader: { backgroundColor: theme.colors.alternativeBackground[primaryShade] }, @@ -100,73 +72,72 @@ const useStyles = createStyles((theme: MantineTheme) => { }) const BillingBlank = ({ children, header, seller, buyer, title }: DetailsProps) => { - const { classes, cx } = useStyles() + const { classes } = useStyles() return ( - - - - - - - {/* TODO: fix the size to be without px */} - - {title} - - - - - - -
+ + + + + + {/* TODO: fix the size to be without px */} + + {title} + + + + + + + {`${title} to:`} {buyer.companyName} - + {buyer.companyAddress} {buyer.companyCity} {buyer.companyCountry} -
- -
+ + + Reg. No.: {buyer.companyNumber} VAT Reg. No.: {buyer.companyNumberPrim} ETH Address: {buyer.ethAddress} -
-
- {header} - - - {children} - - - - -
- {seller.companyName} - - {seller.companyAddress} - {seller.companyCity} - {seller.companyCountry} -
-
- -
- - Email: {seller.email} - Website: {seller.website} - - - Reg. No.: {seller.companyNumber} - VAT Reg. No.: {seller.companyNumberPrim} - ETH Address: {seller.ethAddress} -
-
-
- -
-
-
-
+
+ + +
{header}
+ + + + {children} + + + + + + + {seller.companyName} + + + + {seller.companyAddress} + {seller.companyCity} + {seller.companyCountry} + + + + Email: {seller.email} + Website: {seller.website} + + + Reg. No.: {seller.companyNumber} + VAT Reg. No.: {seller.companyNumberPrim} + ETH Address: {seller.ethAddress} + + + + + ) } @@ -181,31 +152,38 @@ export const InvoicesPDF = ({ invoiceDetails, placement }: InvoicesPDFProps) => seller={invoiceDetails.seller} buyer={invoiceDetails.buyer} header={ - <> - + - Invoice No.: - {invoiceDetails.invoiceId} - - + + Invoice No.: + + + {invoiceDetails.invoiceId} + + + + Invoice Date: {invoiceDetails.invoiceDate ? formatDate(invoiceDetails.invoiceDate) : 'N/A'} - - + + Payment Date: {invoiceDetails.paymentDate ? formatDate(invoiceDetails.paymentDate) : 'N/A'} - - + + } > - <> +
- - - - Subtotal - - - {invoiceDetails.amount.toFixed(2)} - - - {`VAT ${invoiceDetails.vatPercentageInUSD} %`} - - + + + + Subtotal + {invoiceDetails.amount.toFixed(2)} + + + {`VAT ${invoiceDetails.vatPercentageInUSD} %`} + {`${calculatedVatValue.toFixed(2)}${ invoiceDetails.vatPercentageInUSD === 0 ? '*' : '' }`} - - - {`Invoice total (${invoiceDetails.currencyName})`} - - - {invoiceTotal.toFixed(2)} - - {invoiceDetails.vatPercentageInUSD === 0 && ( - - * Services subject to reverse charge-VAT to be accounted for by the recipient as per - Art.196 Council Directive 2006/112/EC - - )} - - - + + + + + {`Invoice total (${invoiceDetails.currencyName})`} + + {invoiceTotal.toFixed(2)}{' '} + + +
+ {invoiceDetails.vatPercentageInUSD === 0 && ( + + * Services subject to reverse charge-VAT to be accounted for by the recipient as per + Art.196 Council Directive 2006/112/EC + + )} + ) } @@ -365,7 +341,7 @@ export const StatementsPDF = ({ statement, seller, buyer }: StatementsPDFProps) -
+ This is not a bill. From 6406148816b414432fa2842f3da7ad9b4716fe6f Mon Sep 17 00:00:00 2001 From: IvoPaunov Date: Mon, 9 Sep 2024 11:58:13 +0300 Subject: [PATCH 38/84] fix billing printable styles --- src/App.tsx | 7 +- .../Billing/BillingDetailsModal.tsx | 100 +++++++++++------- src/themes/base.tsx | 21 +--- 3 files changed, 64 insertions(+), 64 deletions(-) diff --git a/src/App.tsx b/src/App.tsx index 7bef28f3..f34dd163 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -29,11 +29,8 @@ function GlobalStyles() { ({ [theme.other.media.print]: { - body: { - visibility: 'hidden' - }, - 'body #printable': { - visibility: 'visible' + '#root': { + display: 'none' } } })} diff --git a/src/components/Billing/BillingDetailsModal.tsx b/src/components/Billing/BillingDetailsModal.tsx index 4084f2d9..332d7e4e 100644 --- a/src/components/Billing/BillingDetailsModal.tsx +++ b/src/components/Billing/BillingDetailsModal.tsx @@ -1,5 +1,16 @@ import { PropsWithChildren } from 'react' -import { Button, Flex, Group, Loader, MantineTheme, Modal, getPrimaryShade } from '@mantine/core' +import { + Button, + Center, + Group, + Loader, + MantineTheme, + Modal, + getPrimaryShade, + ScrollArea, + Portal, + Box +} from '@mantine/core' import { createStyles } from '@mantine/emotion' import { useColorScheme } from '@mantine/hooks' @@ -32,16 +43,16 @@ const useStyles = createStyles((theme: MantineTheme) => { close: { color: theme.colors.mainText[primaryShade] }, + printableModal: { + [theme.other.media.print]: { + display: 'none' + } + }, printable: { + display: 'none', [theme.other.media.print]: { - // NOTE: it's not fixed/absolute to body but modal.inner - overflow: 'visible', - position: 'absolute', - top: 0, - left: 0, - bottom: 0, - width: '100%' - // padding: theme.spacing.xl + // border: '1px solid yellow', + display: 'block' } } } @@ -50,40 +61,49 @@ const useStyles = createStyles((theme: MantineTheme) => { export const BillingDetailsModal = ({ children, loading, title, opened, close }: DetailsProps) => { const { classes } = useStyles() return ( - -
+ <> + + + {title} + + } + size="xl" + opened={opened} + onClose={close} + centered + radius="sm" + padding="md" + classNames={{ + header: classes.header, + title: classes.title, + close: classes.close + }} + scrollAreaComponent={ScrollArea.Autosize} + > {loading ? ( - +
- +
) : ( - <> - - - - -
-
- {children} -
-
- + {children} )} -
-
+ + + + {children} + + + ) } diff --git a/src/themes/base.tsx b/src/themes/base.tsx index 115cba0b..6c880961 100644 --- a/src/themes/base.tsx +++ b/src/themes/base.tsx @@ -229,26 +229,9 @@ const themeOverride: MantineThemeOverride = createTheme({ } }), Modal: Modal.extend({ - styles: (theme) => ({ + styles: () => ({ root: { - padding: 0, - [theme.other.media.print]: { - overflow: 'visible' - } - }, - inner: { - [theme.other.media.print]: { - overflow: 'visible', - // Fixes double print, no idea why with fixed it prints twice - position: 'absolute', - // Fix if used with "centered" modal prop - alignItems: 'flex-start' - } - }, - content: { - [theme.other.media.print]: { - overflow: 'visible' - } + padding: 0 } }) }), From 8b9117ec1eff4d10731841ece8cb8f3efe54bd75 Mon Sep 17 00:00:00 2001 From: IvoPaunov Date: Tue, 10 Sep 2024 11:31:21 +0300 Subject: [PATCH 39/84] update default Text to span --- src/themes/base.tsx | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/themes/base.tsx b/src/themes/base.tsx index 6c880961..9ef0f21e 100644 --- a/src/themes/base.tsx +++ b/src/themes/base.tsx @@ -18,7 +18,8 @@ import { defaultVariantColorsResolver, VariantColorsResolver, parseThemeColor, - LoadingOverlay + LoadingOverlay, + Text } from '@mantine/core' import { Dropzone } from '@mantine/dropzone' import DownArrowIcon from 'resources/icons/DownArrow' @@ -271,6 +272,11 @@ const themeOverride: MantineThemeOverride = createTheme({ blur: 2 } } + }), + Text: Text.extend({ + defaultProps: { + span: true + } }) }, primaryShade: { light: 3, dark: 4 }, From 0e009f6c77b3cdf30eebf91fda9895ac09e9c64f Mon Sep 17 00:00:00 2001 From: IvoPaunov Date: Tue, 10 Sep 2024 11:31:42 +0300 Subject: [PATCH 40/84] wip: cleanup billing pdfs --- src/components/Billing/BillingPDF.tsx | 180 ++++++++------------------ 1 file changed, 53 insertions(+), 127 deletions(-) diff --git a/src/components/Billing/BillingPDF.tsx b/src/components/Billing/BillingPDF.tsx index 4c684f39..ff3f70e5 100644 --- a/src/components/Billing/BillingPDF.tsx +++ b/src/components/Billing/BillingPDF.tsx @@ -1,18 +1,4 @@ -import { - Flex, - Grid, - MantineTheme, - Space, - Table, - Text, - Box, - getPrimaryShade, - Group, - Stack, - Title, - Divider -} from '@mantine/core' -import { createStyles } from '@mantine/emotion' +import { Space, Table, Text, Box, Group, Stack, Title, Divider } from '@mantine/core' import { Placement } from 'adex-common' import { formatDate, @@ -25,7 +11,6 @@ import { PropsWithChildren, ReactNode } from 'react' import AdExLogo from 'resources/logos/AdExLogo' import { IInvoiceDetails, InvoiceCompanyDetails, OperationEntry, StatementData } from 'types' import { networks } from 'lib/Icons' -import { useColorScheme } from '@mantine/hooks' type InvoicesPDFProps = { invoiceDetails: IInvoiceDetails; placement: Placement } type SidesDetails = { @@ -47,40 +32,13 @@ const formatTokenAmount = (amount: bigint, token: OperationEntry['token']): stri return `${formatCurrency(parseBigNumTokenAmountToDecimal(amount, token.decimals), 2)}` } -const useStyles = createStyles((theme: MantineTheme) => { - const colorScheme = useColorScheme() - const primaryShade = getPrimaryShade(theme, colorScheme) - return { - wrap: { - wordBreak: 'break-word' - }, - noWrap: { - whiteSpace: 'nowrap' - }, - - tableHeader: { - backgroundColor: theme.colors.alternativeBackground[primaryShade] - }, - tableBody: { - backgroundColor: theme.colors.lightBackground[primaryShade] - }, - tableWrapper: { - borderRadius: theme.radius.sm, - overflow: 'hidden' - } - } -}) - const BillingBlank = ({ children, header, seller, buyer, title }: DetailsProps) => { - const { classes } = useStyles() - return ( - + - {/* TODO: fix the size to be without px */} {title} @@ -91,7 +49,7 @@ const BillingBlank = ({ children, header, seller, buyer, title }: DetailsProps) {`${title} to:`} - {buyer.companyName} + {buyer.companyName} {buyer.companyAddress} {buyer.companyCity} @@ -105,15 +63,15 @@ const BillingBlank = ({ children, header, seller, buyer, title }: DetailsProps) -
{header}
+ {header}
{children} - - + + {seller.companyName} @@ -125,7 +83,7 @@ const BillingBlank = ({ children, header, seller, buyer, title }: DetailsProps) {seller.companyCountry} - + Email: {seller.email} Website: {seller.website} @@ -142,8 +100,6 @@ const BillingBlank = ({ children, header, seller, buyer, title }: DetailsProps) } export const InvoicesPDF = ({ invoiceDetails, placement }: InvoicesPDFProps) => { - const { classes, cx } = useStyles() - const calculatedVatValue = invoiceDetails.amount * (invoiceDetails.vatPercentageInUSD / 100) const invoiceTotal = invoiceDetails.amount + calculatedVatValue return ( @@ -153,17 +109,11 @@ export const InvoicesPDF = ({ invoiceDetails, placement }: InvoicesPDFProps) => buyer={invoiceDetails.buyer} header={ - - - Invoice No.: + + + Invoice: - + {invoiceDetails.invoiceId} @@ -184,45 +134,33 @@ export const InvoicesPDF = ({ invoiceDetails, placement }: InvoicesPDFProps) => } > - - +
+ Placement - Impressions - Clicks - CTR % - + Impressions + Clicks + CTR % + Average CPM
({invoiceDetails.currencyName})
- + Spent
({invoiceDetails.currencyName})
- + - {placement} - - {invoiceDetails.impressions.toLocaleString()} - - - {invoiceDetails.clicks.toLocaleString()} - - {invoiceDetails.ctr} - {invoiceDetails.avgCpm} - - {invoiceDetails.amount.toFixed(4)} - + {placement} + {invoiceDetails.impressions.toLocaleString()} + {invoiceDetails.clicks.toLocaleString()} + {invoiceDetails.ctr} + {invoiceDetails.avgCpm} + {invoiceDetails.amount.toFixed(4)}
@@ -260,65 +198,53 @@ export const InvoicesPDF = ({ invoiceDetails, placement }: InvoicesPDFProps) => } export const StatementsPDF = ({ statement, seller, buyer }: StatementsPDFProps) => { - const { classes, cx } = useStyles() - return ( - + + Statement Period: {getMonthRangeString(monthPeriodIndexToDate(statement.periodIndex))} - - +
+ Currency / Token: {statement.token.name} (chain: {networks[statement.token.chainId]}) - - + + + Start balance: - + {formatTokenAmount(statement.startBalance, statement.token)} - - + + End balance: - + {formatTokenAmount(statement.endBalance, statement.token)} - - + +
} > <> - {/* */} -
- +
+ # Date Type Description - amount ({statement.token.name}) + amount ({statement.token.name}) - + {statement.operations.map((e, index) => ( // eslint-disable-next-line @@ -327,8 +253,8 @@ export const StatementsPDF = ({ statement, seller, buyer }: StatementsPDFProps) {e.name} - {e.id} - + {e.id} + {`${e.type === 'campaignOpen' ? '-' : '+'} ${formatTokenAmount( e.amount, statement.token @@ -338,15 +264,15 @@ export const StatementsPDF = ({ statement, seller, buyer }: StatementsPDFProps) ))}
- - - - - - This is not a bill. - - This is a summary of account activity for the time period stated above - + + + + This is not a bill. + + + This is a summary of account activity for the time period stated above + + ) From 1dbf6576ba735c38ae23c5b17da08d2a38dcf645 Mon Sep 17 00:00:00 2001 From: IvoPaunov Date: Tue, 10 Sep 2024 11:32:16 +0300 Subject: [PATCH 41/84] cleanup and fix invoices --- src/components/Billing/Invoices.tsx | 29 ++++++++++++++++------------- 1 file changed, 16 insertions(+), 13 deletions(-) diff --git a/src/components/Billing/Invoices.tsx b/src/components/Billing/Invoices.tsx index 6aa8e03c..02d8a9bf 100644 --- a/src/components/Billing/Invoices.tsx +++ b/src/components/Billing/Invoices.tsx @@ -1,11 +1,8 @@ -import { Title } from '@mantine/core' import CustomTable from 'components/common/CustomTable' import { useDisclosure } from '@mantine/hooks' -import { useCallback, useMemo, useState } from 'react' +import { useCallback, useEffect, useMemo, useState } from 'react' import { CampaignStatus } from 'adex-common' import { useCampaignsData } from 'hooks/useCampaignsData' -// TODO: Delete mock data -// import { invoiceElements } from './mockedData' import useAccount from 'hooks/useAccount' import { formatDateShort } from 'helpers' import VisibilityIcon from 'resources/icons/Visibility' @@ -15,7 +12,7 @@ const columnTitles = ['Company Name', 'Campaign', 'Campaign Period'] const Invoices = () => { const [opened, { open, close }] = useDisclosure(false) - const { campaignsData } = useCampaignsData() + const { campaignsData, updateAllCampaignsData, initialDataLoading } = useCampaignsData() const campaigns = useMemo(() => Array.from(campaignsData.values()), [campaignsData]) const { adexAccount: { @@ -28,10 +25,13 @@ const Invoices = () => { const invoiceElements = useMemo( () => campaigns - .filter((c) => - [CampaignStatus.expired, CampaignStatus.closedByUser, CampaignStatus.exhausted].includes( - c.campaign.status - ) + .filter( + (c) => + [ + CampaignStatus.expired, + CampaignStatus.closedByUser, + CampaignStatus.exhausted + ].includes(c.campaign.status) && c.paid > 0 ) .sort((a, b) => Number(b.campaign.activeFrom) - Number(a.campaign.activeFrom)) .map((campaign) => { @@ -51,6 +51,11 @@ const Invoices = () => { [campaigns, companyName] ) + useEffect(() => { + updateAllCampaignsData(true) + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []) + const handlePreview = useCallback( (item: { id: string }) => { setSelectedCampaignId(item.id) @@ -69,19 +74,17 @@ const Invoices = () => { ] }, [handlePreview]) - return invoiceElements && invoiceElements.length ? ( + return ( <> - ) : ( - // TODO: needs to be style - No invoices found. ) } From 5d629ad4288839c5a0f53ce1f1fa561bb30ba6b9 Mon Sep 17 00:00:00 2001 From: IvoPaunov Date: Tue, 10 Sep 2024 11:38:14 +0300 Subject: [PATCH 42/84] cleanup statement when no data --- src/components/Billing/AccountStatements.tsx | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/src/components/Billing/AccountStatements.tsx b/src/components/Billing/AccountStatements.tsx index 713e0b41..4c826e6c 100644 --- a/src/components/Billing/AccountStatements.tsx +++ b/src/components/Billing/AccountStatements.tsx @@ -1,5 +1,4 @@ import { useMemo, useCallback, useState, SetStateAction } from 'react' -import { Title } from '@mantine/core' import CustomTable from 'components/common/CustomTable' import { useDisclosure } from '@mantine/hooks' import useAccount from 'hooks/useAccount' @@ -29,6 +28,7 @@ const getTokenIndex = (token: OperationEntry['token']): string => const Statements = () => { const [opened, { open, close }] = useDisclosure(false) const { + isLoading, adexAccount: { address, billingDetails, fundsDeposited, fundsOnCampaigns, refundsFromCampaigns } } = useAccount() @@ -135,10 +135,6 @@ const Statements = () => { ] }, [onPreview]) - if (!statements) { - return No AccountStatements found. - } - return ( <> @@ -150,7 +146,13 @@ const Statements = () => { /> )} - + ) } From 9bd6f2eb971ba389dde52d41b38ffbc6fa4b9c8b Mon Sep 17 00:00:00 2001 From: IvoPaunov Date: Tue, 10 Sep 2024 11:43:49 +0300 Subject: [PATCH 43/84] update admin analytics layout --- src/components/AdminPanel/AdminAnalytics.tsx | 24 ++++++-------------- 1 file changed, 7 insertions(+), 17 deletions(-) diff --git a/src/components/AdminPanel/AdminAnalytics.tsx b/src/components/AdminPanel/AdminAnalytics.tsx index 18225c5b..412f61be 100644 --- a/src/components/AdminPanel/AdminAnalytics.tsx +++ b/src/components/AdminPanel/AdminAnalytics.tsx @@ -1,15 +1,5 @@ import { useEffect, useState, useMemo, useCallback } from 'react' -import { - Select, - Flex, - Box, - Text, - Badge, - ActionIcon, - Stack, - Group, - LoadingOverlay -} from '@mantine/core' +import { Select, Box, Text, Badge, ActionIcon, Stack, Group, LoadingOverlay } from '@mantine/core' import { AnalyticsPeriod, Timeframe, AnalyticsType, SSPs } from 'types' import useCampaignAnalytics from 'hooks/useCampaignAnalytics' import CustomTable from 'components/common/CustomTable' @@ -222,17 +212,17 @@ const AdminAnalytics = () => { }, [handlePreview]) return ( - <> - + + * This analytics are for the actual user campaign, representing placed impressions, clicks, etc. (NOT the stats form received requests form the SSPs) - + * Amounts include AdEx validator fees 7% (total amounts paid by the users). For amounts payed to ssp divide by 1.07 (for records after 22.06.24) - +