Cleanup - unused files / unused exports / duplicate exports
[ProtonMail-WebClient.git] / packages / components / containers / account / DeleteAccountModal.tsx
bloba28ba6ade863a0b11efd76b1754c207527344e6c
1 import type { ChangeEvent, FormEvent } from 'react';
2 import { useState } from 'react';
4 import { c } from 'ttag';
6 import { useGetOrganization } from '@proton/account/organization/hooks';
7 import { useUser } from '@proton/account/user/hooks';
8 import { Button, Href } from '@proton/atoms';
9 import Alert from '@proton/components/components/alert/Alert';
10 import ErrorButton from '@proton/components/components/button/ErrorButton';
11 import Form from '@proton/components/components/form/Form';
12 import Checkbox from '@proton/components/components/input/Checkbox';
13 import type { ModalProps } from '@proton/components/components/modalTwo/Modal';
14 import ModalTwo from '@proton/components/components/modalTwo/Modal';
15 import ModalTwoContent from '@proton/components/components/modalTwo/ModalContent';
16 import ModalTwoFooter from '@proton/components/components/modalTwo/ModalFooter';
17 import ModalTwoHeader from '@proton/components/components/modalTwo/ModalHeader';
18 import useModalState from '@proton/components/components/modalTwo/useModalState';
19 import Option from '@proton/components/components/option/Option';
20 import SelectTwo from '@proton/components/components/selectTwo/SelectTwo';
21 import InputFieldTwo from '@proton/components/components/v2/field/InputField';
22 import TextAreaTwo from '@proton/components/components/v2/input/TextArea';
23 import useFormErrors from '@proton/components/components/v2/useFormErrors';
24 import AuthModal from '@proton/components/containers/password/AuthModal';
25 import useApi from '@proton/components/hooks/useApi';
26 import useAuthentication from '@proton/components/hooks/useAuthentication';
27 import useConfig from '@proton/components/hooks/useConfig';
28 import useEventManager from '@proton/components/hooks/useEventManager';
29 import useNotifications from '@proton/components/hooks/useNotifications';
30 import { useLoading } from '@proton/hooks';
31 import { leaveOrganisation } from '@proton/shared/lib/api/organization';
32 import { canDelete, deleteUser, unlockPasswordChanges } from '@proton/shared/lib/api/user';
33 import { handleLogout } from '@proton/shared/lib/authentication/logout';
34 import { ACCOUNT_DELETION_REASONS, BRAND_NAME } from '@proton/shared/lib/constants';
35 import { minLengthValidator, requiredValidator } from '@proton/shared/lib/helpers/formValidators';
36 import { wait } from '@proton/shared/lib/helpers/promise';
37 import { getKnowledgeBaseUrl } from '@proton/shared/lib/helpers/url';
38 import { getOrganizationDenomination } from '@proton/shared/lib/organization/helper';
39 import isTruthy from '@proton/utils/isTruthy';
40 import noop from '@proton/utils/noop';
42 const { DIFFERENT_ACCOUNT, TOO_EXPENSIVE, MISSING_FEATURE, USE_OTHER_SERVICE, OTHER } = ACCOUNT_DELETION_REASONS;
44 interface Props extends Omit<ModalProps<'form'>, 'as'> {
45     onSuccess?: () => Promise<void>;
46     hideHeader?: boolean;
49 const SupportParagraph = () => {
50     {
51         /* translator: Full sentence: "This message won't reach the support team, if you have an issue with our service or need further action from our side please open a support ticket." */
52     }
53     const supportLink = (
54         <Href key="link-to-support" href="https://proton.me/support/contact-product">{c('Info')
55             .t`please open a support ticket`}</Href>
56     );
58     return (
59         <p className="text-sm color-weak mt-0">
60             {/* translator: Full sentence: "This message won't reach the support team, if you have an issue with our service or need further action from our side please open a support ticket." */}
61             {c('Info')
62                 .jt`This message won't reach the support team, if you have an issue with our service or need further action from our side ${supportLink}.`}
63         </p>
64     );
67 const DeleteAccountModal = (props: Props) => {
68     const [authModalProps, setAuthModalOpen, renderAuthModal] = useModalState();
69     const { createNotification } = useNotifications();
71     const defaultOnSuccess = async () => {
72         createNotification({ text: c('Success').t`Account deleted. Signing out...` });
74         // Add an artificial delay to show the notification.
75         await wait(2500);
76     };
78     const {
79         onSuccess = defaultOnSuccess,
80         hideHeader = false,
81         onClose,
82         disableCloseOnEscape,
83         size = 'large',
84         ...rest
85     } = props;
86     const eventManager = useEventManager();
87     const api = useApi();
88     const authentication = useAuthentication();
89     const [{ isAdmin }] = useUser();
90     const getOrganization = useGetOrganization();
91     const [loading, withLoading] = useLoading();
92     const [model, setModel] = useState({
93         check: false,
94         reason: '',
95         feedback: '',
96     });
97     const { validator, onFormSubmit } = useFormErrors();
98     const { APP_NAME } = useConfig();
100     const reasons = [
101         <Option
102             title={c('Option').t`I use a different ${BRAND_NAME} account`}
103             value={DIFFERENT_ACCOUNT}
104             key={DIFFERENT_ACCOUNT}
105         />,
106         isAdmin && <Option title={c('Option').t`It's too expensive`} value={TOO_EXPENSIVE} key={TOO_EXPENSIVE} />,
107         <Option
108             title={c('Option').t`It's missing a key feature that I need`}
109             value={MISSING_FEATURE}
110             key={MISSING_FEATURE}
111         />,
112         <Option
113             title={c('Option').t`I found another service that I like better`}
114             value={USE_OTHER_SERVICE}
115             key={USE_OTHER_SERVICE}
116         />,
117         <Option title={c('Option').t`My reason isn't listed`} value={OTHER} key={OTHER} />,
118     ].filter(isTruthy);
120     const handleSubmit = async () => {
121         try {
122             eventManager.stop();
124             await api(canDelete());
126             const organization = await getOrganization();
127             // If a user is part of a family plan or duo plan we first need to leave the organization before deleting the account.
128             // Refreshing the event manager is necessary to update the organization state
129             if (getOrganizationDenomination(organization) === 'familyGroup' && !isAdmin) {
130                 eventManager.start();
131                 await api(leaveOrganisation());
132                 await eventManager.call();
133                 eventManager.stop();
134             }
136             await api(
137                 deleteUser({
138                     Reason: model.reason,
139                     Feedback: model.feedback,
140                 })
141             );
143             await onSuccess?.();
144             onClose?.();
145             handleLogout({ appName: APP_NAME, authentication, clearDeviceRecoveryData: true, type: 'full' });
146         } catch (error: any) {
147             eventManager.start();
148             throw error;
149         }
150     };
152     return (
153         <>
154             {renderAuthModal && (
155                 <AuthModal
156                     scope="password"
157                     config={unlockPasswordChanges()}
158                     {...authModalProps}
159                     onCancel={undefined}
160                     onSuccess={() => {
161                         withLoading(handleSubmit());
162                     }}
163                 />
164             )}
165             <ModalTwo
166                 as={Form}
167                 onClose={hideHeader ? undefined : onClose}
168                 disableCloseOnEscape={disableCloseOnEscape || loading}
169                 size={size}
170                 onSubmit={
171                     loading
172                         ? noop
173                         : (event: FormEvent<HTMLFormElement>) => {
174                               if (!onFormSubmit(event.currentTarget)) {
175                                   return;
176                               }
177                               setAuthModalOpen(true);
178                           }
179                 }
180                 {...rest}
181             >
182                 {!hideHeader && <ModalTwoHeader title={c('Title').t`Delete account`} />}
183                 <ModalTwoContent>
184                     <Alert className="mb-4" type="warning">
185                         <div className="text-bold text-uppercase">
186                             {c('Info')
187                                 .t`Warning: deletion is permanent. This also removes access to all connected services and deletes all of your contacts.`}
188                         </div>
189                         <div>
190                             {c('Info').t`If you wish to combine this account with another one, do NOT delete it.`}
191                         </div>
192                         <div>
193                             <Href href={getKnowledgeBaseUrl('/combine-accounts')}>{c('Link').t`Learn more`}</Href>
194                         </div>
195                     </Alert>
196                     <InputFieldTwo
197                         as={SelectTwo}
198                         label={c('Label').t`What is the main reason you are deleting your account?`}
199                         placeholder={c('Placeholder').t`Select a reason`}
200                         id="reason"
201                         autoFocus
202                         value={model.reason}
203                         onValue={(value: unknown) => setModel({ ...model, reason: value as string })}
204                         error={validator([requiredValidator(model.reason)])}
205                         disabled={loading}
206                     >
207                         {reasons}
208                     </InputFieldTwo>
210                     <InputFieldTwo
211                         id="feedback"
212                         as={TextAreaTwo}
213                         rows={3}
214                         label={c('Label')
215                             .t`We are sorry to see you go. Please explain why you are leaving to help us improve.`}
216                         placeholder={c('Placeholder').t`Feedback`}
217                         value={model.feedback}
218                         onValue={(value: string) => setModel({ ...model, feedback: value })}
219                         error={validator([requiredValidator(model.feedback), minLengthValidator(model.feedback, 10)])}
220                         disabled={loading}
221                     />
223                     <InputFieldTwo
224                         id="check"
225                         as={Checkbox}
226                         error={validator([!model.check ? requiredValidator(undefined) : ''])}
227                         checked={model.check}
228                         onChange={({ target }: ChangeEvent<HTMLInputElement>) =>
229                             setModel({ ...model, check: target.checked })
230                         }
231                         disabled={loading}
232                     >
233                         {c('Label').t`Yes, I want to permanently delete this account and all its data.`}
234                     </InputFieldTwo>
236                     <SupportParagraph />
237                 </ModalTwoContent>
238                 <ModalTwoFooter>
239                     <Button onClick={onClose} disabled={loading}>{c('Action').t`Cancel`}</Button>
240                     <ErrorButton loading={loading} type="submit">
241                         {c('Action').t`Delete`}
242                     </ErrorButton>
243                 </ModalTwoFooter>
244             </ModalTwo>
245         </>
246     );
249 export default DeleteAccountModal;