From 37c3922ed58ef8685e0a4d40c5f456361b317949 Mon Sep 17 00:00:00 2001 From: Theophile Sandoz Date: Thu, 23 Nov 2023 15:31:58 +0100 Subject: [PATCH] =?UTF-8?q?=F0=9F=A7=BD=20Notifications=20MVP=20last=20fix?= =?UTF-8?q?es=20(#4641)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Make the custom backend and faucet links optional * Fix custom network form minor style issues * Only show notification tab when the backend is set * Fix setting page styles * Do not show registration modal to registered members * Refetch queries when the token changes * Fix test * Fix tests again --- packages/ui/src/app/hooks/usePageTabs.ts | 2 +- .../src/app/pages/Settings/SettingsLayout.tsx | 17 +- .../app/pages/Settings/SettingsNetworkTab.tsx | 193 ++++++++++-------- .../SettingsNotificationsTab.stories.tsx | 26 +-- .../Settings/SettingsNotificationsTab.tsx | 21 +- .../ui/src/app/providers/backend/context.ts | 1 + .../ui/src/app/providers/backend/provider.tsx | 10 +- packages/ui/src/common/components/Loading.tsx | 5 +- .../components/NetworkInfo/NetworkInfo.tsx | 7 +- .../components/forms/InputComponent.tsx | 1 + .../ui/src/common/components/icons/Loader.tsx | 26 +-- .../hooks/useNotificationSettings.ts | 3 +- .../EmaiSubscriptionFormModal.tsx | 2 +- .../EmailSubscriptionModal.tsx | 8 +- packages/ui/src/mocks/providers/backend.tsx | 2 + packages/ui/src/mocks/providers/gql.tsx | 4 +- .../src/services/i18n/dict/en/settings.json | 1 + 17 files changed, 180 insertions(+), 149 deletions(-) diff --git a/packages/ui/src/app/hooks/usePageTabs.ts b/packages/ui/src/app/hooks/usePageTabs.ts index 2f7b3bbe31..49d2b9ba93 100644 --- a/packages/ui/src/app/hooks/usePageTabs.ts +++ b/packages/ui/src/app/hooks/usePageTabs.ts @@ -9,7 +9,7 @@ interface Options { hasChanges?: boolean } -export type TabsDefinition = [string, Path] | [string, Path, number] | [string, Path, Options] +export type TabsDefinition = readonly [string, Path] | [string, Path, number] | [string, Path, Options] export const usePageTabs = (tabs: TabsDefinition[]) => { const history = useHistory() diff --git a/packages/ui/src/app/pages/Settings/SettingsLayout.tsx b/packages/ui/src/app/pages/Settings/SettingsLayout.tsx index 77c44966e5..1a76ff8be7 100644 --- a/packages/ui/src/app/pages/Settings/SettingsLayout.tsx +++ b/packages/ui/src/app/pages/Settings/SettingsLayout.tsx @@ -1,10 +1,11 @@ -import React, { ReactNode } from 'react' +import React, { ReactNode, useContext } from 'react' import { useTranslation } from 'react-i18next' import styled from 'styled-components' import { PageHeader } from '@/app/components/PageHeader' import { PageLayout } from '@/app/components/PageLayout' import { usePageTabs } from '@/app/hooks/usePageTabs' +import { BackendContext } from '@/app/providers/backend/context' import { ButtonPrimary } from '@/common/components/buttons' import { MainPanel } from '@/common/components/page/PageContent' import { Tabs } from '@/common/components/Tabs' @@ -17,15 +18,19 @@ export type SettingsLayoutProps = { disabled: boolean onClick: () => void } + fullWidth?: boolean children?: ReactNode } -export const SettingsLayout = ({ saveButton, children }: SettingsLayoutProps) => { +export const SettingsLayout = ({ saveButton, fullWidth, children }: SettingsLayoutProps) => { const { t } = useTranslation('settings') + const backendContext = useContext(BackendContext) + const notificationsTab = [t('notifications'), SettingsRoutes.notifications] as const const tabs = usePageTabs([ [t('network'), SettingsRoutes.settings], - [t('notifications'), SettingsRoutes.notifications], + ...(backendContext.backendClient ? [notificationsTab] : []), ]) + return ( /> } main={ - + {children} } @@ -50,6 +55,6 @@ export const SettingsLayout = ({ saveButton, children }: SettingsLayoutProps) => ) } -export const Container = styled.div` - max-width: 690px; +export const Container = styled.div<{ fullWidth?: boolean }>` + max-width: ${({ fullWidth = false }) => (fullWidth ? 'auto' : '690px')}; ` diff --git a/packages/ui/src/app/pages/Settings/SettingsNetworkTab.tsx b/packages/ui/src/app/pages/Settings/SettingsNetworkTab.tsx index a2c0046fb9..e5b56843f6 100644 --- a/packages/ui/src/app/pages/Settings/SettingsNetworkTab.tsx +++ b/packages/ui/src/app/pages/Settings/SettingsNetworkTab.tsx @@ -1,6 +1,7 @@ import React, { useState, useEffect } from 'react' import { useForm, FormProvider } from 'react-hook-form' import { useTranslation } from 'react-i18next' +import styled from 'styled-components' import { useApi } from '@/api/hooks/useApi' import { NetworkType, NetworkEndpoints } from '@/app/config' @@ -14,6 +15,7 @@ import { PolkadotAppInfo } from '@/common/components/PolkadotAppInfo' import { SimpleSelect } from '@/common/components/selects' import { SettingsInformation, SettingsWarningInformation } from '@/common/components/SettingsInformation' import { TextMedium } from '@/common/components/typography' +import { Colors } from '@/common/constants' import { useLocalStorage } from '@/common/hooks/useLocalStorage' import { useNetwork } from '@/common/hooks/useNetwork' import { useNetworkEndpoints } from '@/common/hooks/useNetworkEndpoints' @@ -57,75 +59,25 @@ export const SettingsNetworkTab = () => { if (network === 'custom') { form.setValue('settings.customRpcEndpoint', endpoints.nodeRpcEndpoint) form.setValue('settings.customQueryEndpoint', endpoints.queryNodeEndpoint) - form.setValue('settings.customFaucetEndpoint', endpoints.membershipFaucetEndpoint) - form.setValue('settings.customBackendEndpoint', endpoints.backendEndpoint) + form.setValue('settings.customFaucetEndpoint', endpoints.membershipFaucetEndpoint ?? '') + form.setValue('settings.customBackendEndpoint', endpoints.backendEndpoint ?? '') } }, [network, endpoints]) - const checkFaucetEndpoint = async () => { - // check faucet endpoint - try { - const faucetStatusEndpoint = customFaucetEndpoint.replace(new RegExp('register$'), 'status') - const response = await fetch(faucetStatusEndpoint) - const succeeded = response.status < 400 - setIsValidFaucetEndpoint(succeeded) - return succeeded - } catch { - setIsValidFaucetEndpoint(false) - return false - } - } - - const checkRpcEndpoint = async () => { - // check RPC endpoint - try { - return await new Promise((resolve) => { - const ws = new WebSocket(customRpcEndpoint) - const willResolveTo = (succeeded: boolean, timeout?: any) => () => { - if (timeout) clearTimeout(timeout) - - ws.close() - setIsValidRpcEndpoint(succeeded) - resolve(succeeded) - } - - const timeout = setTimeout(willResolveTo(false), 3000) - ws.onopen = willResolveTo(true, timeout) - ws.onerror = willResolveTo(false, timeout) - }) - } catch { - setIsValidRpcEndpoint(false) - return false - } - } - - const checkGQLEndpoint = async (endpoint: string, setIsValidEndpoint: (v: boolean) => void) => { - // check GraphQL endpoint - try { - const response = await fetch(endpoint + '?query=%7B__typename%7D') - const succeeded = response.status < 400 && (await response.json()).data['__typename'] === 'Query' - setIsValidEndpoint(succeeded) - return succeeded - } catch { - setIsValidEndpoint(false) - return false - } - } - useEffect(() => { if ( - isValidFaucetEndpoint && isValidRpcEndpoint && isValidQueryEndpoint && + isValidFaucetEndpoint && isValidBackendEndpoint && customSaveStatus === 'Done' ) { storeCustomEndpoints({ nodeRpcEndpoint: customRpcEndpoint, queryNodeEndpoint: customQueryEndpoint, - membershipFaucetEndpoint: customFaucetEndpoint, queryNodeEndpointSubscription: customQueryEndpoint.replace(/^http?/, 'ws'), - backendEndpoint: customBackendEndpoint, + membershipFaucetEndpoint: customFaucetEndpoint || undefined, + backendEndpoint: customBackendEndpoint || undefined, configEndpoint: undefined, }) window.location.reload() @@ -134,10 +86,10 @@ export const SettingsNetworkTab = () => { const saveSettings = async () => { if ( - !isValidUrl(customFaucetEndpoint) || - !isValidUrl(customRpcEndpoint, 'wss?') || - !isValidUrl(customQueryEndpoint) || - !isValidUrl(customBackendEndpoint) + !isValidRPCUrl(customRpcEndpoint) || + !isValidQNUrl(customQueryEndpoint) || + !isValidFaucetUrl(customFaucetEndpoint) || + !isValidBackendUrl(customBackendEndpoint) ) { return } @@ -145,10 +97,10 @@ export const SettingsNetworkTab = () => { setCustomSaveStatus('Saving') await Promise.all([ - checkFaucetEndpoint(), - checkRpcEndpoint(), - checkGQLEndpoint(customQueryEndpoint, setIsValidQueryEndpoint), - checkGQLEndpoint(customBackendEndpoint, setIsValidBackendEndpoint), + checkEndpoint(customRpcEndpoint, checkRpcEndpoint, setIsValidRpcEndpoint), + checkEndpoint(customQueryEndpoint, checkGQLEndpoint, setIsValidQueryEndpoint), + checkEndpoint(customFaucetEndpoint, checkFaucetEndpoint, setIsValidFaucetEndpoint), + checkEndpoint(customBackendEndpoint, checkGQLEndpoint, setIsValidBackendEndpoint), ]) setCustomSaveStatus('Done') @@ -173,25 +125,12 @@ export const SettingsNetworkTab = () => { - !isValidUrl(customFaucetEndpoint), 'This Faucet endpoint must start with http or https'], - [() => !isValidFaucetEndpoint, 'Connection Error'] - )} - > - - + !isValidUrl(customRpcEndpoint, 'wss?'), 'This RPC endpoint must start with ws or wss'], + [() => !isValidRPCUrl(customRpcEndpoint), 'This RPC endpoint must start with ws or wss'], [ () => !isValidRpcEndpoint, 'Connection Error. Sometimes it fails due to network speed. Please try to check once more', @@ -200,11 +139,12 @@ export const SettingsNetworkTab = () => { > + !isValidUrl(customQueryEndpoint), 'This Query endpoint must start with http or https'], + [() => !isValidQNUrl(customQueryEndpoint), 'This Query endpoint must start with http or https'], [() => !isValidQueryEndpoint, 'Connection Error'] )} > @@ -214,11 +154,30 @@ export const SettingsNetworkTab = () => { name="settings.customQueryEndpoint" /> + + !isValidFaucetUrl(customFaucetEndpoint), 'This Faucet endpoint must start with http or https'], + [() => !isValidFaucetEndpoint, 'Connection Error'] + )} + > + + + !isValidUrl(customBackendEndpoint), 'This Backend endpoint must start with http or https'], + [ + () => !isValidBackendUrl(customBackendEndpoint), + 'This Backend endpoint must start with http or https', + ], [() => !isValidBackendEndpoint, 'Connection Error'] )} > @@ -228,9 +187,10 @@ export const SettingsNetworkTab = () => { name="settings.customBackendEndpoint" /> + Save settings - {customSaveStatus === 'Saving' && } + {customSaveStatus === 'Saving' && } )} @@ -244,6 +204,7 @@ export const SettingsNetworkTab = () => { networkAddress={endpoints.nodeRpcEndpoint} queryNodeAddress={endpoints.queryNodeEndpoint} faucetAddress={endpoints.membershipFaucetEndpoint} + backendAddress={endpoints.backendEndpoint} /> } title={t('chainInfo')}> @@ -261,4 +222,66 @@ export const SettingsNetworkTab = () => { ) } -const isValidUrl = (url: string, prefix = 'https?') => RegExp(String.raw`${prefix}://\w+/?`, 'i').test(url) +type IsValidOptions = { prefix?: 'https?' | 'wss?'; isRequired?: boolean } +const isValid = (url: string, { prefix = 'https?', isRequired = true }: IsValidOptions = {}) => + (isRequired === false && url === '') || RegExp(String.raw`${prefix}://\w+/?`, 'i').test(url) + +const isValidRPCUrl = (url: string) => isValid(url, { prefix: 'wss?' }) +const isValidQNUrl = (url: string) => isValid(url) +const isValidFaucetUrl = (url: string) => isValid(url, { isRequired: false }) +const isValidBackendUrl = (url: string) => isValid(url, { isRequired: false }) + +const checkEndpoint = async ( + endpoint: string, + check: (endpoint: string) => Promise, + setEndpointIsValid: (isValid: boolean) => void +) => { + const isValid = endpoint ? await check(endpoint) : true + setEndpointIsValid(isValid) + return isValid +} + +const checkRpcEndpoint = async (endpoint: string) => { + // check RPC endpoint + try { + return await new Promise((resolve) => { + const ws = new WebSocket(endpoint) + const willResolveTo = (succeeded: boolean, timeout?: any) => () => { + if (timeout) clearTimeout(timeout) + ws.close() + resolve(succeeded) + } + + const timeout = setTimeout(willResolveTo(false), 3000) + ws.onopen = willResolveTo(true, timeout) + ws.onerror = willResolveTo(false, timeout) + }) + } catch { + return false + } +} + +const checkGQLEndpoint = async (endpoint: string) => { + // check GraphQL endpoint + try { + const response = await fetch(endpoint + '?query=%7B__typename%7D') + return response.status < 400 && (await response.json()).data['__typename'] === 'Query' + } catch { + return false + } +} + +const checkFaucetEndpoint = async (endpoint: string) => { + // check faucet endpoint + try { + const faucetStatusEndpoint = endpoint.replace(new RegExp('register$'), 'status') + const response = await fetch(faucetStatusEndpoint) + return response.status < 400 + } catch { + return false + } +} + +const CustomLoading = styled(Loading)` + fill: ${Colors.White}; +` diff --git a/packages/ui/src/app/pages/Settings/SettingsNotificationsTab.stories.tsx b/packages/ui/src/app/pages/Settings/SettingsNotificationsTab.stories.tsx index 1ebd3d4143..713bdb5282 100644 --- a/packages/ui/src/app/pages/Settings/SettingsNotificationsTab.stories.tsx +++ b/packages/ui/src/app/pages/Settings/SettingsNotificationsTab.stories.tsx @@ -81,16 +81,14 @@ export default { }, { query: GetBackendMeDocument, - data: args.isAuthorized - ? { - me: { - email: args.isEmailConfirmed ? email : null, - unverifiedEmail: args.isEmailConfirmed ? null : email, - receiveEmails: true, - name: 'test', - }, - } - : undefined, + data: { + me: { + email: args.isEmailConfirmed ? email : null, + unverifiedEmail: args.isEmailConfirmed ? null : email, + receiveEmails: true, + name: 'test', + }, + }, error: args.isAuthorized ? undefined : new Error('Unauthorized'), }, ], @@ -110,14 +108,8 @@ export default { }, backend: { - notificationsSettingsMap: args.isAuthorized - ? { - [alice.id]: { - accessToken: 'token', - }, - } - : {}, onSetMemberSettings: (...settingsArgs: any[]) => args.onSetMemberSettings(...settingsArgs), + authToken: SIGNIN_TOKEN, }, } }, diff --git a/packages/ui/src/app/pages/Settings/SettingsNotificationsTab.tsx b/packages/ui/src/app/pages/Settings/SettingsNotificationsTab.tsx index 6ce6ebc7ed..38159dc2f0 100644 --- a/packages/ui/src/app/pages/Settings/SettingsNotificationsTab.tsx +++ b/packages/ui/src/app/pages/Settings/SettingsNotificationsTab.tsx @@ -48,7 +48,7 @@ export const SettingsNotificationsTab: FC = () => { activeMemberExistBackendLoading, activeMemberExistBackendRefetch, backendClient, - activeMemberSettings, + authToken, setMemberSettings, } = useNotificationSettings() const { showModal } = useModal() @@ -79,10 +79,12 @@ export const SettingsNotificationsTab: FC = () => { receiveEmailNotifications: data.me?.receiveEmails ?? true, }) }, - skip: !activeMemberSettings?.accessToken, + skip: !authToken, }) - const isUnauthorized = - (!!activeMember && !activeMemberSettings?.accessToken) || meError?.message.includes('Unauthorized') + + const isRegistered = activeMemberExistBackendData?.memberExist ?? false + + const isUnauthorized = (!!activeMember && !authToken) || meError?.message.includes('Unauthorized') const [sendUpdateMemberMutation, { error: mutationError }] = useUpdateBackendMemberMutation({ client: backendClient, @@ -95,8 +97,6 @@ export const SettingsNotificationsTab: FC = () => { const [reauthorizationStatus, setReauthorizationStatus] = useState(null) const handleSubscribeClick = () => { - // TODO: this will show member select if there is no active membership. - // However, if the user selects a membership that's already registered, the subscription modal will still show. showModal({ modal: 'EmailSubscriptionModal', data: { @@ -239,7 +239,7 @@ You can customize what kind of notifications you receive anytime in settings." ) } - const mainContent = ( + const mainContent = () => ( @@ -277,11 +277,11 @@ You can customize what kind of notifications you receive anytime in settings." return errorContent } - if (activeMemberExistBackendData?.memberExist === false) { - return unregisteredContent + if (isRegistered) { + return mainContent() } - return mainContent + return unregisteredContent } return ( @@ -293,6 +293,7 @@ You can customize what kind of notifications you receive anytime in settings." setShowSettingsUpdatedModal(false)} /> ))} notificationsSettingsMap?: MemberNotificationsRecord setMemberSettings: (memberId: string, settings: Partial) => void + authToken?: string } export const BackendContext = createContext({ setMemberSettings: () => null }) diff --git a/packages/ui/src/app/providers/backend/provider.tsx b/packages/ui/src/app/providers/backend/provider.tsx index c719c58985..63b2de21a9 100644 --- a/packages/ui/src/app/providers/backend/provider.tsx +++ b/packages/ui/src/app/providers/backend/provider.tsx @@ -1,4 +1,4 @@ -import { ApolloClient, ApolloLink, HttpLink, InMemoryCache, makeVar } from '@apollo/client' +import { ApolloClient, ApolloLink, HttpLink, InMemoryCache, makeVar, useReactiveVar } from '@apollo/client' import React, { ReactNode, useCallback, useEffect, useState } from 'react' import { useLocalStorage } from '@/common/hooks/useLocalStorage' @@ -48,6 +48,13 @@ export const BackendProvider = (props: { children: ReactNode }) => { backendAuthTokenVar(activeMemberSettings.accessToken) }, [backendClient, activeMemberSettings?.accessToken]) + const authToken = useReactiveVar(backendAuthTokenVar) + + // Refetch backend queries after Apollo client auth token changes (warning: the changes are async). + useEffect(() => { + backendClient?.refetchQueries({ include: 'active' }) + }, [authToken]) + const setMemberSettings = useCallback( (memberId: string, settings: Partial) => { setNotificationsSettingsMap((prev) => ({ @@ -71,6 +78,7 @@ export const BackendProvider = (props: { children: ReactNode }) => { backendClient, notificationsSettingsMap, setMemberSettings, + authToken: authToken ?? undefined, }} {...props} /> diff --git a/packages/ui/src/common/components/Loading.tsx b/packages/ui/src/common/components/Loading.tsx index 013888e648..f20ad26f60 100644 --- a/packages/ui/src/common/components/Loading.tsx +++ b/packages/ui/src/common/components/Loading.tsx @@ -8,12 +8,13 @@ import { TextInlineSmall } from './typography' interface Props { text?: string withoutMargin?: boolean + className?: string } -export const Loading = ({ text, withoutMargin }: Props) => { +export const Loading = ({ text, withoutMargin, className }: Props) => { return ( - + {text && ( {text} diff --git a/packages/ui/src/common/components/NetworkInfo/NetworkInfo.tsx b/packages/ui/src/common/components/NetworkInfo/NetworkInfo.tsx index 6854c097dc..9730b6139b 100644 --- a/packages/ui/src/common/components/NetworkInfo/NetworkInfo.tsx +++ b/packages/ui/src/common/components/NetworkInfo/NetworkInfo.tsx @@ -13,10 +13,11 @@ export interface NetworkInfoProps { networkAddress: string queryNodeAddress: string faucetAddress?: string + backendAddress?: string } const NetworkInfo: React.FC = React.memo( - ({ detailsTitle, networkAddress, queryNodeAddress, faucetAddress }) => { + ({ detailsTitle, networkAddress, queryNodeAddress, faucetAddress, backendAddress }) => { const { t } = useTranslation('settings') return ( }> @@ -32,6 +33,10 @@ const NetworkInfo: React.FC = React.memo( {t('faucet')} + + {t('backend')} + + ) } diff --git a/packages/ui/src/common/components/forms/InputComponent.tsx b/packages/ui/src/common/components/forms/InputComponent.tsx index 642dd660d2..16b83199a4 100644 --- a/packages/ui/src/common/components/forms/InputComponent.tsx +++ b/packages/ui/src/common/components/forms/InputComponent.tsx @@ -467,6 +467,7 @@ export const InputNotification = styled.div` grid-auto-flow: column; grid-column-gap: 4px; align-items: center; + justify-content: start; width: 100%; color: ${({ validation }) => { switch (validation) { diff --git a/packages/ui/src/common/components/icons/Loader.tsx b/packages/ui/src/common/components/icons/Loader.tsx index b78df10a5d..aaa62c3bda 100644 --- a/packages/ui/src/common/components/icons/Loader.tsx +++ b/packages/ui/src/common/components/icons/Loader.tsx @@ -10,56 +10,40 @@ interface LoaderProps { export function Loader({ className, withoutMargin }: LoaderProps) { return ( - - - + + + ) } -export const LoaderComponent = styled.svg` +const LoaderComponent = styled.svg` height: 24px; border-radius: ${BorderRad.round}; animation: spinLoader 1s infinite linear; justify-self: start; place-self: center; + fill: ${Colors.Black[500]}; @keyframes spinLoader { 0% { diff --git a/packages/ui/src/memberships/hooks/useNotificationSettings.ts b/packages/ui/src/memberships/hooks/useNotificationSettings.ts index 4b5b3aef07..4c2195372e 100644 --- a/packages/ui/src/memberships/hooks/useNotificationSettings.ts +++ b/packages/ui/src/memberships/hooks/useNotificationSettings.ts @@ -10,7 +10,7 @@ export const useNotificationSettings = () => { const { active: activeMember } = useMyMemberships() const backendContext = useContext(BackendContext) if (!backendContext) throw new Error('Missing backend context') - const { backendClient, notificationsSettingsMap, setMemberSettings } = backendContext + const { backendClient, notificationsSettingsMap, setMemberSettings, authToken } = backendContext const memberExistsQueryEnabled = !!backendClient && !!activeMember?.id const { data, error, loading, refetch } = useGetBackendMemberExistsQuery({ client: backendClient, @@ -28,5 +28,6 @@ export const useNotificationSettings = () => { activeMemberExistBackendRefetch: refetch, setMemberSettings, backendClient, + authToken, } } diff --git a/packages/ui/src/memberships/modals/EmailSubscriptionModal/EmaiSubscriptionFormModal.tsx b/packages/ui/src/memberships/modals/EmailSubscriptionModal/EmaiSubscriptionFormModal.tsx index 9b4fb7c5ab..9421501433 100644 --- a/packages/ui/src/memberships/modals/EmailSubscriptionModal/EmaiSubscriptionFormModal.tsx +++ b/packages/ui/src/memberships/modals/EmailSubscriptionModal/EmaiSubscriptionFormModal.tsx @@ -77,5 +77,5 @@ export const EmailSubscriptionFormModal = ({ onClose, onSubmit }: Props) => { } const StyledFooter = styled(ModalTransactionFooter)` - grid-column-gap: 20px; + grid-column-gap: 0; ` diff --git a/packages/ui/src/memberships/modals/EmailSubscriptionModal/EmailSubscriptionModal.tsx b/packages/ui/src/memberships/modals/EmailSubscriptionModal/EmailSubscriptionModal.tsx index 7fb07e8d3c..ef5ab035d1 100644 --- a/packages/ui/src/memberships/modals/EmailSubscriptionModal/EmailSubscriptionModal.tsx +++ b/packages/ui/src/memberships/modals/EmailSubscriptionModal/EmailSubscriptionModal.tsx @@ -28,7 +28,7 @@ export const EmailSubscriptionModal = () => { const { wallet } = useMyAccounts() const [state, send] = useMachine(EmailSubscriptionMachine) - const { setMemberSettings, backendClient } = useNotificationSettings() + const { activeMemberExistBackendData, setMemberSettings, backendClient } = useNotificationSettings() const [sendRegisterbackendMemberMutation] = useRegisterBackendMemberMutation({ client: backendClient, }) @@ -70,6 +70,12 @@ export const EmailSubscriptionModal = () => { } }, [state, sendRegisterbackendMemberMutation, member]) + useEffect(() => { + if (state.matches('prepare') && activeMemberExistBackendData?.memberExist) { + hideModal() + } + }, [state, activeMemberExistBackendData]) + useEffect(() => { if (state.matches('signature')) { generateBackendAuthSignature() diff --git a/packages/ui/src/mocks/providers/backend.tsx b/packages/ui/src/mocks/providers/backend.tsx index 2ff58dbc07..e0bb297eb8 100644 --- a/packages/ui/src/mocks/providers/backend.tsx +++ b/packages/ui/src/mocks/providers/backend.tsx @@ -6,6 +6,7 @@ export type MockBackendProps = { backend?: { notificationsSettingsMap?: BackendContextValue['notificationsSettingsMap'] onSetMemberSettings?: (memberId: string, settings: any) => void + authToken?: string } } @@ -16,6 +17,7 @@ export const MockBackendProvider: FC = ({ children, backend }) backendClient: { mocked: true } as any, // so that backendClient checks don't skip queries notificationsSettingsMap: backend?.notificationsSettingsMap, setMemberSettings: (memberId, settings) => backend?.onSetMemberSettings?.(memberId, settings), + authToken: backend?.authToken, }} > {children} diff --git a/packages/ui/src/mocks/providers/gql.tsx b/packages/ui/src/mocks/providers/gql.tsx index 5bf6d8056b..8b6c50f3fa 100644 --- a/packages/ui/src/mocks/providers/gql.tsx +++ b/packages/ui/src/mocks/providers/gql.tsx @@ -100,9 +100,9 @@ export const MockGqlProvider: FC = ({ children, queries, mutations const queriesMap = useMemo( () => new Map( - queries?.map<[DocumentNode, Resolver]>(({ query, data, resolver }) => [ + queries?.map<[DocumentNode, Resolver]>(({ query, data, error, resolver }) => [ query, - (options) => resolver?.(options) ?? { loading: false, data }, + (options) => resolver?.(options) ?? { loading: false, data: error ? undefined : data, error }, ]) ?? [] ), [queries] diff --git a/packages/ui/src/services/i18n/dict/en/settings.json b/packages/ui/src/services/i18n/dict/en/settings.json index 8f6709626b..73104a6d1c 100644 --- a/packages/ui/src/services/i18n/dict/en/settings.json +++ b/packages/ui/src/services/i18n/dict/en/settings.json @@ -7,6 +7,7 @@ "networkAddress": "Network: ", "QueryNodeAddress": "Query Node: ", "faucet": "Faucet: ", + "backend": "Backend: ", "chainInfo": "Chain Information", "rpcBlockheight": "RPC Block Height: ", "qnBlockheight": "Query Node Block Height: ",