Skip to content

Commit b11518c

Browse files
Add Close account to front end (canada-ca#2768)
* add close account schema * add close account mutation * add close account imports * add close account button * add modal * use redirect * button alignment * grid break on md in place of large * add isLoading to confirm button * add Confirmation Field * fix typos * CloseAccount userID not required * closeAccount userPage shouldn't supply userId
1 parent 262275e commit b11518c

File tree

3 files changed

+223
-4
lines changed

3 files changed

+223
-4
lines changed

frontend/mocking/faked_schema.js

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2518,6 +2518,9 @@ export const getTypeNames = () => gql`
25182518
# This mutation allows users to leave a given organization.
25192519
leaveOrganization(input: LeaveOrganizationInput!): LeaveOrganizationPayload
25202520
2521+
# This mutation allows users to close their account.
2522+
closeAccount(input: CloseAccountInput!): CloseAccountPayload
2523+
25212524
# This mutation allows admins or higher to remove users from any organizations they belong to.
25222525
removeUserFromOrg(input: RemoveUserFromOrgInput!): RemoveUserFromOrgPayload
25232526
@@ -2655,7 +2658,7 @@ export const getTypeNames = () => gql`
26552658
# This union is used with the \`leaveOrganization\` mutation, allowing for users to leave a given organization, and support any errors that may occur.
26562659
union LeaveOrganizationUnion = AffiliationError | LeaveOrganizationResult
26572660
2658-
# This object is used to inform the user that they successful left a given organization.
2661+
# This object is used to inform the user that they successfully left a given organization.
26592662
type LeaveOrganizationResult {
26602663
# Status message confirming the user left the org.
26612664
status: String
@@ -2667,6 +2670,35 @@ export const getTypeNames = () => gql`
26672670
clientMutationId: String
26682671
}
26692672
2673+
type CloseAccountPayload {
2674+
# \`CloseAccountUnion\` resolving to either a \`CloseAccountResult\` or \`CloseAccountError\`.
2675+
result: CloseAccountUnion
2676+
clientMutationId: String
2677+
}
2678+
2679+
# This union is used with the \`CloseAccount\` mutation, allowing for users to close their account, and support any errors that may occur.
2680+
union CloseAccountUnion = CloseAccountError | CloseAccountResult
2681+
2682+
# This object is used to inform the user if any errors occurred during closure of an account.
2683+
type CloseAccountError {
2684+
# Error code to inform user what the issue is related to.
2685+
code: Int
2686+
2687+
# Description of the issue that was encountered.
2688+
description: String
2689+
}
2690+
2691+
# This object is used to inform the user that they successfully closed their account.
2692+
type CloseAccountResult {
2693+
# Status message confirming the user has closed their account.
2694+
status: String
2695+
}
2696+
2697+
input CloseAccountInput {
2698+
# Id of the user who is closing their account.
2699+
userId: ID!
2700+
}
2701+
26702702
type RemoveUserFromOrgPayload {
26712703
# \`RemoveUserFromOrgUnion\` returning either a \`RemoveUserFromOrgResult\`, or \`RemoveUserFromOrgError\` object.
26722704
result: RemoveUserFromOrgUnion

frontend/src/UserPage.js

Lines changed: 174 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,27 @@
11
import React, { useState } from 'react'
22
import { string } from 'prop-types'
3-
import { Button, Divider, SimpleGrid, Stack, useToast } from '@chakra-ui/react'
3+
import {
4+
Button,
5+
Divider,
6+
Modal,
7+
ModalBody,
8+
ModalCloseButton,
9+
ModalContent,
10+
ModalFooter,
11+
ModalHeader,
12+
ModalOverlay,
13+
SimpleGrid,
14+
Stack,
15+
Text,
16+
useDisclosure,
17+
useToast,
18+
} from '@chakra-ui/react'
419
import { EmailIcon } from '@chakra-ui/icons'
520
import { useMutation, useQuery } from '@apollo/client'
621
import { QUERY_CURRENT_USER } from './graphql/queries'
22+
import { useHistory } from 'react-router-dom'
723
import { t, Trans } from '@lingui/macro'
24+
import { useLingui } from '@lingui/react'
825
import EditableUserLanguage from './EditableUserLanguage'
926
import EditableUserDisplayName from './EditableUserDisplayName'
1027
import EditableUserEmail from './EditableUserEmail'
@@ -13,10 +30,16 @@ import { LoadingMessage } from './LoadingMessage'
1330
import { ErrorFallbackMessage } from './ErrorFallbackMessage'
1431
import EditableUserTFAMethod from './EditableUserTFAMethod'
1532
import EditableUserPhoneNumber from './EditableUserPhoneNumber'
16-
import { SEND_EMAIL_VERIFICATION } from './graphql/mutations'
33+
import { SEND_EMAIL_VERIFICATION, CLOSE_ACCOUNT } from './graphql/mutations'
34+
import { Formik } from 'formik'
35+
import FormField from './FormField'
36+
import { object, string as yupString } from 'yup'
37+
import { fieldRequirements } from './fieldRequirements'
1738

1839
export default function UserPage() {
1940
const toast = useToast()
41+
const history = useHistory()
42+
const { i18n } = useLingui()
2043
const [emailSent, setEmailSent] = useState(false)
2144
const [sendEmailVerification, { error }] = useMutation(
2245
SEND_EMAIL_VERIFICATION,
@@ -45,6 +68,63 @@ export default function UserPage() {
4568
},
4669
)
4770

71+
const [closeAccount, { loading: loadingCloseAccount }] = useMutation(
72+
CLOSE_ACCOUNT,
73+
{
74+
onError(error) {
75+
toast({
76+
title: i18n._(t`An error occurred.`),
77+
description: error.message,
78+
status: 'error',
79+
duration: 9000,
80+
isClosable: true,
81+
position: 'top-left',
82+
})
83+
},
84+
onCompleted({ closeAccount }) {
85+
if (closeAccount.result.__typename === 'CloseAccountResult') {
86+
toast({
87+
title: i18n._(t`Account Closed Successfully`),
88+
description: i18n._(
89+
t`Tracker account has been successfully closed.`,
90+
),
91+
status: 'success',
92+
duration: 9000,
93+
isClosable: true,
94+
position: 'top-left',
95+
})
96+
closeAccountOnClose()
97+
history.push('/')
98+
} else if (closeAccount.result.__typename === 'CloseAccountError') {
99+
toast({
100+
title: i18n._(t`Unable to close the account.`),
101+
description: closeAccount.result.description,
102+
status: 'error',
103+
duration: 9000,
104+
isClosable: true,
105+
position: 'top-left',
106+
})
107+
} else {
108+
toast({
109+
title: i18n._(t`Incorrect send method received.`),
110+
description: i18n._(t`Incorrect closeAccount.result typename.`),
111+
status: 'error',
112+
duration: 9000,
113+
isClosable: true,
114+
position: 'top-left',
115+
})
116+
console.log('Incorrect closeAccount.result typename.')
117+
}
118+
},
119+
},
120+
)
121+
122+
const {
123+
isOpen: closeAccountIsOpen,
124+
onOpen: closeAccountOnOpen,
125+
onClose: closeAccountOnClose,
126+
} = useDisclosure()
127+
48128
const {
49129
loading: queryUserLoading,
50130
error: queryUserError,
@@ -73,8 +153,14 @@ export default function UserPage() {
73153
phoneValidated,
74154
} = queryUserData?.userPage
75155

156+
const closeAccountValidationSchema = object().shape({
157+
userNameConfirm: yupString()
158+
.required(i18n._(fieldRequirements.field.required.message))
159+
.matches(userName, t`User email does not match.`),
160+
})
161+
76162
return (
77-
<SimpleGrid columns={{ md: 1, lg: 2 }} width="100%">
163+
<SimpleGrid columns={{ base: 1, md: 2 }} width="100%">
78164
<Stack py={25} px="4">
79165
<EditableUserDisplayName detailValue={displayName} />
80166

@@ -114,7 +200,92 @@ export default function UserPage() {
114200
<Trans>Verify Email</Trans>
115201
</Button>
116202
)}
203+
204+
<Divider />
205+
206+
<Button
207+
variant="danger"
208+
onClick={() => {
209+
closeAccountOnOpen()
210+
}}
211+
ml="auto"
212+
w={{ base: '100%', md: 'auto' }}
213+
mb={2}
214+
alignSelf="flex-end"
215+
>
216+
<Trans> Close Account </Trans>
217+
</Button>
117218
</Stack>
219+
220+
<Modal
221+
isOpen={closeAccountIsOpen}
222+
onClose={closeAccountOnClose}
223+
motionPreset="slideInBottom"
224+
>
225+
<Formik
226+
validateOnBlur={false}
227+
initialValues={{
228+
userNameConfirm: '',
229+
}}
230+
initialTouched={{
231+
userNameConfirm: true,
232+
}}
233+
validationSchema={closeAccountValidationSchema}
234+
onSubmit={async () => {
235+
await closeAccount({})
236+
}}
237+
>
238+
{({ handleSubmit }) => (
239+
<form onSubmit={handleSubmit}>
240+
<ModalOverlay />
241+
<ModalContent pb={4}>
242+
<ModalHeader>
243+
<Trans>Close Account</Trans>
244+
</ModalHeader>
245+
<ModalCloseButton />
246+
<ModalBody>
247+
<Trans>
248+
This action CANNOT be reversed, are you sure you wish to to
249+
close the account {displayName}?
250+
</Trans>
251+
252+
<Text mb="1rem">
253+
<Trans>
254+
Enter "{userName}" below to confirm removal. This field is
255+
case-sensitive.
256+
</Trans>
257+
</Text>
258+
259+
<FormField
260+
name="userNameConfirm"
261+
label={t`User Email`}
262+
placeholder={userName}
263+
/>
264+
</ModalBody>
265+
266+
<ModalFooter>
267+
<Button
268+
variant="primaryOutline"
269+
mr="4"
270+
onClick={closeAccountOnClose}
271+
>
272+
<Trans>Cancel</Trans>
273+
</Button>
274+
275+
<Button
276+
variant="primary"
277+
mr="4"
278+
type="submit"
279+
isLoading={loadingCloseAccount}
280+
>
281+
<Trans>Confirm</Trans>
282+
</Button>
283+
</ModalFooter>
284+
</ModalContent>
285+
</form>
286+
)}
287+
</Formik>
288+
</Modal>
118289
</SimpleGrid>
119290
)
120291
}

frontend/src/graphql/mutations.js

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -409,6 +409,22 @@ export const VERIFY_ACCOUNT = gql`
409409
}
410410
`
411411

412+
export const CLOSE_ACCOUNT = gql`
413+
mutation CloseAccount($userId: ID) {
414+
closeAccount(input: { userId: $userId }) {
415+
result {
416+
... on CloseAccountError {
417+
code
418+
description
419+
}
420+
... on CloseAccountResult {
421+
status
422+
}
423+
}
424+
}
425+
}
426+
`
427+
412428
export const CREATE_ORGANIZATION = gql`
413429
mutation CreateOrganization(
414430
$acronymEN: Acronym!

0 commit comments

Comments
 (0)