diff --git a/frontend/mocking/faked_schema.js b/frontend/mocking/faked_schema.js index 8d765589a9..bddc931f92 100644 --- a/frontend/mocking/faked_schema.js +++ b/frontend/mocking/faked_schema.js @@ -2518,6 +2518,9 @@ export const getTypeNames = () => gql` # This mutation allows users to leave a given organization. leaveOrganization(input: LeaveOrganizationInput!): LeaveOrganizationPayload + # This mutation allows users to close their account. + closeAccount(input: CloseAccountInput!): CloseAccountPayload + # This mutation allows admins or higher to remove users from any organizations they belong to. removeUserFromOrg(input: RemoveUserFromOrgInput!): RemoveUserFromOrgPayload @@ -2655,7 +2658,7 @@ export const getTypeNames = () => gql` # This union is used with the \`leaveOrganization\` mutation, allowing for users to leave a given organization, and support any errors that may occur. union LeaveOrganizationUnion = AffiliationError | LeaveOrganizationResult - # This object is used to inform the user that they successful left a given organization. + # This object is used to inform the user that they successfully left a given organization. type LeaveOrganizationResult { # Status message confirming the user left the org. status: String @@ -2667,6 +2670,35 @@ export const getTypeNames = () => gql` clientMutationId: String } + type CloseAccountPayload { + # \`CloseAccountUnion\` resolving to either a \`CloseAccountResult\` or \`CloseAccountError\`. + result: CloseAccountUnion + clientMutationId: String + } + + # This union is used with the \`CloseAccount\` mutation, allowing for users to close their account, and support any errors that may occur. + union CloseAccountUnion = CloseAccountError | CloseAccountResult + + # This object is used to inform the user if any errors occurred during closure of an account. + type CloseAccountError { + # Error code to inform user what the issue is related to. + code: Int + + # Description of the issue that was encountered. + description: String + } + + # This object is used to inform the user that they successfully closed their account. + type CloseAccountResult { + # Status message confirming the user has closed their account. + status: String + } + + input CloseAccountInput { + # Id of the user who is closing their account. + userId: ID! + } + type RemoveUserFromOrgPayload { # \`RemoveUserFromOrgUnion\` returning either a \`RemoveUserFromOrgResult\`, or \`RemoveUserFromOrgError\` object. result: RemoveUserFromOrgUnion diff --git a/frontend/src/UserPage.js b/frontend/src/UserPage.js index 5725d0a4fa..0cd9cd04e0 100644 --- a/frontend/src/UserPage.js +++ b/frontend/src/UserPage.js @@ -1,10 +1,27 @@ import React, { useState } from 'react' import { string } from 'prop-types' -import { Button, Divider, SimpleGrid, Stack, useToast } from '@chakra-ui/react' +import { + Button, + Divider, + Modal, + ModalBody, + ModalCloseButton, + ModalContent, + ModalFooter, + ModalHeader, + ModalOverlay, + SimpleGrid, + Stack, + Text, + useDisclosure, + useToast, +} from '@chakra-ui/react' import { EmailIcon } from '@chakra-ui/icons' import { useMutation, useQuery } from '@apollo/client' import { QUERY_CURRENT_USER } from './graphql/queries' +import { useHistory } from 'react-router-dom' import { t, Trans } from '@lingui/macro' +import { useLingui } from '@lingui/react' import EditableUserLanguage from './EditableUserLanguage' import EditableUserDisplayName from './EditableUserDisplayName' import EditableUserEmail from './EditableUserEmail' @@ -13,10 +30,16 @@ import { LoadingMessage } from './LoadingMessage' import { ErrorFallbackMessage } from './ErrorFallbackMessage' import EditableUserTFAMethod from './EditableUserTFAMethod' import EditableUserPhoneNumber from './EditableUserPhoneNumber' -import { SEND_EMAIL_VERIFICATION } from './graphql/mutations' +import { SEND_EMAIL_VERIFICATION, CLOSE_ACCOUNT } from './graphql/mutations' +import { Formik } from 'formik' +import FormField from './FormField' +import { object, string as yupString } from 'yup' +import { fieldRequirements } from './fieldRequirements' export default function UserPage() { const toast = useToast() + const history = useHistory() + const { i18n } = useLingui() const [emailSent, setEmailSent] = useState(false) const [sendEmailVerification, { error }] = useMutation( SEND_EMAIL_VERIFICATION, @@ -45,6 +68,63 @@ export default function UserPage() { }, ) + const [closeAccount, { loading: loadingCloseAccount }] = useMutation( + CLOSE_ACCOUNT, + { + onError(error) { + toast({ + title: i18n._(t`An error occurred.`), + description: error.message, + status: 'error', + duration: 9000, + isClosable: true, + position: 'top-left', + }) + }, + onCompleted({ closeAccount }) { + if (closeAccount.result.__typename === 'CloseAccountResult') { + toast({ + title: i18n._(t`Account Closed Successfully`), + description: i18n._( + t`Tracker account has been successfully closed.`, + ), + status: 'success', + duration: 9000, + isClosable: true, + position: 'top-left', + }) + closeAccountOnClose() + history.push('/') + } else if (closeAccount.result.__typename === 'CloseAccountError') { + toast({ + title: i18n._(t`Unable to close the account.`), + description: closeAccount.result.description, + status: 'error', + duration: 9000, + isClosable: true, + position: 'top-left', + }) + } else { + toast({ + title: i18n._(t`Incorrect send method received.`), + description: i18n._(t`Incorrect closeAccount.result typename.`), + status: 'error', + duration: 9000, + isClosable: true, + position: 'top-left', + }) + console.log('Incorrect closeAccount.result typename.') + } + }, + }, + ) + + const { + isOpen: closeAccountIsOpen, + onOpen: closeAccountOnOpen, + onClose: closeAccountOnClose, + } = useDisclosure() + const { loading: queryUserLoading, error: queryUserError, @@ -73,8 +153,14 @@ export default function UserPage() { phoneValidated, } = queryUserData?.userPage + const closeAccountValidationSchema = object().shape({ + userNameConfirm: yupString() + .required(i18n._(fieldRequirements.field.required.message)) + .matches(userName, t`User email does not match.`), + }) + return ( - + @@ -114,7 +200,92 @@ export default function UserPage() { Verify Email )} + + + + + + + { + await closeAccount({}) + }} + > + {({ handleSubmit }) => ( +
+ + + + Close Account + + + + + This action CANNOT be reversed, are you sure you wish to to + close the account {displayName}? + + + + + Enter "{userName}" below to confirm removal. This field is + case-sensitive. + + + + + + + + + + + + + + )} +
+
) } diff --git a/frontend/src/graphql/mutations.js b/frontend/src/graphql/mutations.js index ff9accfed1..eb62b1be52 100644 --- a/frontend/src/graphql/mutations.js +++ b/frontend/src/graphql/mutations.js @@ -393,6 +393,22 @@ export const VERIFY_ACCOUNT = gql` } ` +export const CLOSE_ACCOUNT = gql` + mutation CloseAccount($userId: ID) { + closeAccount(input: { userId: $userId }) { + result { + ... on CloseAccountError { + code + description + } + ... on CloseAccountResult { + status + } + } + } + } +` + export const CREATE_ORGANIZATION = gql` mutation CreateOrganization( $acronymEN: Acronym!