diff --git a/packages/ui/src/app/App.stories.tsx b/packages/ui/src/app/App.stories.tsx index 15640aaec3..b09248ba0a 100644 --- a/packages/ui/src/app/App.stories.tsx +++ b/packages/ui/src/app/App.stories.tsx @@ -34,6 +34,8 @@ type Args = { isRPCNodeConnected: boolean hasRegisteredEmail: boolean hasBeenAskedForEmail: boolean + subscribeEmailError: boolean + confirmEmailError: boolean onBuyMembership: jest.Mock onTransfer: jest.Mock onSubscribeEmail: jest.Mock @@ -82,6 +84,8 @@ export default { isRPCNodeConnected: true, hasRegisteredEmail: true, hasBeenAskedForEmail: true, + subscribeEmailError: false, + confirmEmailError: false, }, parameters: { @@ -141,12 +145,14 @@ export default { { mutation: RegisterBackendMemberDocument, onSend: (...sendArgs: any[]) => args.onSubscribeEmail(...sendArgs), - data: { signup: '' }, + data: !args.subscribeEmailError ? { signup: '' } : undefined, + error: args.subscribeEmailError ? new Error('error') : undefined, }, { mutation: ConfirmBackendEmailDocument, onSend: (...sendArgs: any[]) => args.onConfirmEmail(...sendArgs), - data: { confirmEmail: '' }, + data: !args.confirmEmailError ? { confirmEmail: '' } : undefined, + error: args.confirmEmailError ? new Error('error') : undefined, }, ], }, @@ -574,10 +580,34 @@ export const EmailSubscriptionModalSubscribe: Story = { }, } +export const EmailSubscriptionModalSubscribeError: Story = { + args: { + isLoggedIn: true, + hasMemberships: true, + hasAccounts: true, + hasFunds: true, + hasWallet: true, + isRPCNodeConnected: true, + hasRegisteredEmail: false, + hasBeenAskedForEmail: false, + subscribeEmailError: true, + }, + play: async ({ canvasElement }) => { + const modal = withinModal(canvasElement) + const button = modal.getByText(/^Sign and Authorize Email/i) + expect(button.closest('button')).toBeDisabled() + const testEmail = 'test@email.com' + await userEvent.type(modal.getByPlaceholderText('Add email for notifications here'), testEmail) + await waitFor(() => expect(button.closest('button')).toBeEnabled()) + await userEvent.click(button) + await waitFor(() => expect(modal.getAllByText(/Unexpected error/i))) + }, +} + // ---------------------------------------------------------------------------- // Test Email Confirmation Modal // ---------------------------------------------------------------------------- -export const EmailConfirmationModal: Story = { +export const EmailConfirmationSuccess: Story = { parameters: { router: { href: `/?${EMAIL_VERIFICATION_TOKEN_SEARCH_PARAM}=${MOCK_VERIFICATION_TOKEN}` } }, play: async ({ canvasElement, args: { onConfirmEmail } }) => { const modal = withinModal(canvasElement) @@ -589,3 +619,17 @@ export const EmailConfirmationModal: Story = { expect(modal.getByText(/Your email has been confirmed/)) }, } + +// ---------------------------------------------------------------------------- +// Email Confirmation Error +// ---------------------------------------------------------------------------- +export const EmailConfirmationError: Story = { + args: { + confirmEmailError: true, + }, + parameters: { router: { href: `/?${EMAIL_VERIFICATION_TOKEN_SEARCH_PARAM}=${MOCK_VERIFICATION_TOKEN}` } }, + play: async ({ canvasElement }) => { + const modal = withinModal(canvasElement) + await waitFor(() => expect(modal.getAllByText(/Unexpected error/i))) + }, +} diff --git a/packages/ui/src/app/pages/Settings/SettingsNotificationsTab.stories.tsx b/packages/ui/src/app/pages/Settings/SettingsNotificationsTab.stories.tsx index 15e868c188..378e3aec20 100644 --- a/packages/ui/src/app/pages/Settings/SettingsNotificationsTab.stories.tsx +++ b/packages/ui/src/app/pages/Settings/SettingsNotificationsTab.stories.tsx @@ -19,6 +19,7 @@ type Args = { isEmailConfirmed: boolean activeMemberExistBackendLoading: boolean activeMemberExistBackendError: boolean + updateMemberError: boolean onMemberUpdate: CallableFunction } type Story = StoryObj> @@ -41,6 +42,7 @@ export default { isEmailConfirmed: true, activeMemberExistBackendLoading: false, activeMemberExistBackendError: false, + updateMemberError: false, }, parameters: { @@ -82,6 +84,7 @@ export default { { mutation: UpdateBackendMemberDocument, onSend: (...sendArgs: any[]) => args.onMemberUpdate(...sendArgs), + error: args.updateMemberError ? new Error('error') : undefined, }, ], }, @@ -227,3 +230,28 @@ export const BackendLoading: Story = { expect(screen.queryByText(/I want to be notified by email/)).toBeNull() }, } + +// ---------------------------------------------------------------------------- +// Notifications settings: update error +// ---------------------------------------------------------------------------- +export const UpdateError: Story = { + args: { + updateMemberError: true, + }, + + play: async ({ canvasElement }) => { + const screen = within(canvasElement) + + expect(screen.getByText(/I want to be notified by email/)).toBeInTheDocument() + + const saveChangesButton = getButtonByText(screen, /Save changes/i) + const _emailInput = screen.getByText(/Email/).parentElement?.querySelector('input') + expect(_emailInput).toBeDefined() + const emailInput = _emailInput as HTMLInputElement + await waitFor(() => expect(emailInput).toHaveValue(email)) + userEvent.type(emailInput, 'm') + expect(saveChangesButton).toBeEnabled() + userEvent.click(saveChangesButton) + await waitFor(() => expect(screen.getByText(/Unexpected error/i)).toBeInTheDocument()) + }, +} diff --git a/packages/ui/src/app/pages/Settings/SettingsNotificationsTab.tsx b/packages/ui/src/app/pages/Settings/SettingsNotificationsTab.tsx index e51cd4175a..0fd845a112 100644 --- a/packages/ui/src/app/pages/Settings/SettingsNotificationsTab.tsx +++ b/packages/ui/src/app/pages/Settings/SettingsNotificationsTab.tsx @@ -11,8 +11,10 @@ import { SuccessModal } from '@/common/components/SuccessModal' import { TextBig, TextMedium } from '@/common/components/typography' import { Warning } from '@/common/components/Warning' import { useModal } from '@/common/hooks/useModal' +import { error } from '@/common/logger' import { useYupValidationResolver } from '@/common/utils/validation' import { useNotificationSettings } from '@/memberships/hooks/useNotificationSettings' +import { BackendErrorModal } from '@/memberships/modals/BackendErrorModal' import { EmailSubscriptionModalCall } from '@/memberships/modals/EmailSubscriptionModal' import { useGetBackendMeQuery, @@ -67,7 +69,7 @@ export const SettingsNotificationsTab: FC = () => { skip: !activeMemberSettings?.accessToken, }) - const [sendUpdateMemberMutation] = useUpdateBackendMemberMutation({ + const [sendUpdateMemberMutation, { error: mutationError }] = useUpdateBackendMemberMutation({ client: backendClient, }) const [newLinkGenerated, setNewLinkGenerated] = useState(false) @@ -97,12 +99,17 @@ export const SettingsNotificationsTab: FC = () => { } const handleSaveChangesClick = handleSubmit(async (data) => { - await sendUpdateMemberMutation({ - variables: { - email: dirtyFields.email ? data.email : undefined, - receiveEmails: dirtyFields.receiveEmailNotifications ? data.receiveEmailNotifications : undefined, - }, - }) + try { + await sendUpdateMemberMutation({ + variables: { + email: dirtyFields.email ? data.email : undefined, + receiveEmails: dirtyFields.receiveEmailNotifications ? data.receiveEmailNotifications : undefined, + }, + }) + } catch (e) { + error(e) + } + setShowSettingsUpdatedModal(true) }) @@ -176,9 +183,12 @@ You can customize what kind of notifications you receive anytime in settings." return ( <> - {showSettingsUpdatedModal && ( - setShowSettingsUpdatedModal(false)} /> - )} + {showSettingsUpdatedModal && + (mutationError ? ( + setShowSettingsUpdatedModal(false)} /> + ) : ( + setShowSettingsUpdatedModal(false)} /> + ))} void +} + +export const BackendErrorModal: FC = ({ onClose }) => { + return ( + + There's been an unexpected error when communicating with notifications service. Please try again later. + + ) +} diff --git a/packages/ui/src/memberships/modals/BackendErrorModal/index.ts b/packages/ui/src/memberships/modals/BackendErrorModal/index.ts new file mode 100644 index 0000000000..91ade18cef --- /dev/null +++ b/packages/ui/src/memberships/modals/BackendErrorModal/index.ts @@ -0,0 +1 @@ +export * from './BackendErrorModal' diff --git a/packages/ui/src/memberships/modals/EmailConfirmationModal/EmailConfirmationModal.tsx b/packages/ui/src/memberships/modals/EmailConfirmationModal/EmailConfirmationModal.tsx index 69ff80dce4..3bc06a9123 100644 --- a/packages/ui/src/memberships/modals/EmailConfirmationModal/EmailConfirmationModal.tsx +++ b/packages/ui/src/memberships/modals/EmailConfirmationModal/EmailConfirmationModal.tsx @@ -9,6 +9,8 @@ import { ModalWithDataCall } from '@/common/providers/modal/types' import { useNotificationSettings } from '@/memberships/hooks/useNotificationSettings' import { useConfirmBackendEmailMutation } from '@/memberships/queries/__generated__/backend.generated' +import { BackendErrorModal } from '../BackendErrorModal' + export type EmailConfirmationModalCall = ModalWithDataCall< 'EmailConfirmationModal', { @@ -49,14 +51,16 @@ export const EmailConfirmationModal = () => { hideModal() } + if (error) { + return + } + return ( {loading ? ( - ) : error ? ( - Unexpected error occurred. Please try again later. ) : ( Your email has been confirmed! You can always adjust your notification preferences in settings. diff --git a/packages/ui/src/memberships/modals/EmailSubscriptionModal/EmailSubscriptionModal.tsx b/packages/ui/src/memberships/modals/EmailSubscriptionModal/EmailSubscriptionModal.tsx index eb7181f5ef..7fb07e8d3c 100644 --- a/packages/ui/src/memberships/modals/EmailSubscriptionModal/EmailSubscriptionModal.tsx +++ b/packages/ui/src/memberships/modals/EmailSubscriptionModal/EmailSubscriptionModal.tsx @@ -1,7 +1,6 @@ import React, { useEffect, useCallback } from 'react' import { useMyAccounts } from '@/accounts/hooks/useMyAccounts' -import { FailureModal } from '@/common/components/FailureModal' import { SuccessModal } from '@/common/components/SuccessModal' import { WaitModal } from '@/common/components/WaitModal' import { useMachine } from '@/common/hooks/useMachine' @@ -13,6 +12,8 @@ import { EmailSubscriptionModalCall } from '@/memberships/modals/EmailSubscripti import { getBackendAuthSignature } from '@/memberships/model/backendAuth' import { useRegisterBackendMemberMutation } from '@/memberships/queries/__generated__/backend.generated' +import { BackendErrorModal } from '../BackendErrorModal' + import { EmailSubscriptionFormModal } from './EmaiSubscriptionFormModal' import { EmailSubscriptionMachine } from './machine' import { EmailSubscriptionForm } from './types' @@ -100,13 +101,7 @@ export const EmailSubscriptionModal = () => { } if (state.matches('error')) { - return ( - - There was a problem registering your email. -
- We could not register your email at the moment! Please, try again later! -
- ) + return } if (state.matches('signature')) { diff --git a/packages/ui/src/mocks/providers/gql.tsx b/packages/ui/src/mocks/providers/gql.tsx index fb91110073..ed1d075e43 100644 --- a/packages/ui/src/mocks/providers/gql.tsx +++ b/packages/ui/src/mocks/providers/gql.tsx @@ -10,7 +10,7 @@ export { ApolloProvider } from '@apollo/client/react' type OptionVariables = { where?: Record; orderBy?: string | string[]; limit?: number; offset?: number } type Options = { variables?: OptionVariables; skip?: boolean; onCompleted?: (data: any) => void } -type Result = { loading: boolean; data: any } +type Result = { loading: boolean; data: any; error?: any } type Resolver = (options?: Options) => Result type GqlQueriesMap = Map @@ -67,6 +67,9 @@ export const useMutation = (mutation: DocumentNode, options?: Options): [() => v (...args: any[]) => { spy?.(...args) setMutationResult(result) + if (result.error) { + return Promise.reject(result.error) + } return Promise.resolve(result.data) }, [JSON.stringify(result)]