Skip to content

Commit

Permalink
handle backend errors (#4580)
Browse files Browse the repository at this point in the history
* handle backend errors

* use FailureModal in BackendErrorModal
  • Loading branch information
kdembler authored Oct 18, 2023
1 parent 986a41e commit fcd1b4d
Show file tree
Hide file tree
Showing 8 changed files with 124 additions and 24 deletions.
50 changes: 47 additions & 3 deletions packages/ui/src/app/App.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -82,6 +84,8 @@ export default {
isRPCNodeConnected: true,
hasRegisteredEmail: true,
hasBeenAskedForEmail: true,
subscribeEmailError: false,
confirmEmailError: false,
},

parameters: {
Expand Down Expand Up @@ -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,
},
],
},
Expand Down Expand Up @@ -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 = '[email protected]'
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)
Expand All @@ -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)))
},
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ type Args = {
isEmailConfirmed: boolean
activeMemberExistBackendLoading: boolean
activeMemberExistBackendError: boolean
updateMemberError: boolean
onMemberUpdate: CallableFunction
}
type Story = StoryObj<FC<Args>>
Expand All @@ -41,6 +42,7 @@ export default {
isEmailConfirmed: true,
activeMemberExistBackendLoading: false,
activeMemberExistBackendError: false,
updateMemberError: false,
},

parameters: {
Expand Down Expand Up @@ -82,6 +84,7 @@ export default {
{
mutation: UpdateBackendMemberDocument,
onSend: (...sendArgs: any[]) => args.onMemberUpdate(...sendArgs),
error: args.updateMemberError ? new Error('error') : undefined,
},
],
},
Expand Down Expand Up @@ -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())
},
}
30 changes: 20 additions & 10 deletions packages/ui/src/app/pages/Settings/SettingsNotificationsTab.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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)
})

Expand Down Expand Up @@ -176,9 +183,12 @@ You can customize what kind of notifications you receive anytime in settings."

return (
<>
{showSettingsUpdatedModal && (
<SuccessModal text="Settings saved successfully" onClose={() => setShowSettingsUpdatedModal(false)} />
)}
{showSettingsUpdatedModal &&
(mutationError ? (
<BackendErrorModal onClose={() => setShowSettingsUpdatedModal(false)} />
) : (
<SuccessModal text="Settings saved successfully" onClose={() => setShowSettingsUpdatedModal(false)} />
))}
<SettingsLayout
saveButton={{
isVisible: true,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import React, { FC } from 'react'

import { FailureModal } from '@/common/components/FailureModal'

type BackendErrorModalProps = {
onClose: () => void
}

export const BackendErrorModal: FC<BackendErrorModalProps> = ({ onClose }) => {
return (
<FailureModal onClose={onClose}>
There's been an unexpected error when communicating with notifications service. Please try again later.
</FailureModal>
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './BackendErrorModal'
Original file line number Diff line number Diff line change
Expand Up @@ -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',
{
Expand Down Expand Up @@ -49,14 +51,16 @@ export const EmailConfirmationModal = () => {
hideModal()
}

if (error) {
return <BackendErrorModal onClose={closeConfirmationModal} />
}

return (
<Modal onClose={closeConfirmationModal} modalSize="m">
<ModalHeader onClick={closeConfirmationModal} title="Email confirmation" />
<ModalBody>
{loading ? (
<Loading text="Confirming email..." withoutMargin />
) : error ? (
<TextMedium>Unexpected error occurred. Please try again later.</TextMedium>
) : (
<TextMedium>
Your email has been confirmed! You can always adjust your notification preferences in settings.
Expand Down
Original file line number Diff line number Diff line change
@@ -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'
Expand All @@ -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'
Expand Down Expand Up @@ -100,13 +101,7 @@ export const EmailSubscriptionModal = () => {
}

if (state.matches('error')) {
return (
<FailureModal onClose={hideModal}>
There was a problem registering your email.
<br />
We could not register your email at the moment! Please, try again later!
</FailureModal>
)
return <BackendErrorModal onClose={hideModal} />
}

if (state.matches('signature')) {
Expand Down
5 changes: 4 additions & 1 deletion packages/ui/src/mocks/providers/gql.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ export { ApolloProvider } from '@apollo/client/react'

type OptionVariables = { where?: Record<string, any>; 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<DocumentNode, Resolver>
Expand Down Expand Up @@ -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)]
Expand Down

0 comments on commit fcd1b4d

Please sign in to comment.