Remove payments API routing initialization
[ProtonMail-WebClient.git] / packages / components / containers / recovery / DataRecoverySection.tsx
blob801ad8af0e0c7fc9580488c8eb9c94ce412b33d2
1 import { c } from 'ttag';
3 import { useGetAddresses } from '@proton/account/addresses/hooks';
4 import { useGetUser, useUser } from '@proton/account/user/hooks';
5 import { useGetUserKeys } from '@proton/account/userKeys/hooks';
6 import { useGetUserSettings, useUserSettings } from '@proton/account/userSettings/hooks';
7 import { Button, Href } from '@proton/atoms';
8 import Icon from '@proton/components/components/icon/Icon';
9 import Info from '@proton/components/components/link/Info';
10 import useModalState from '@proton/components/components/modalTwo/useModalState';
11 import Toggle from '@proton/components/components/toggle/Toggle';
12 import useIsRecoveryFileAvailable from '@proton/components/hooks/recoveryFile/useIsRecoveryFileAvailable';
13 import useApi from '@proton/components/hooks/useApi';
14 import useAuthentication from '@proton/components/hooks/useAuthentication';
15 import useConfig from '@proton/components/hooks/useConfig';
16 import useEventManager from '@proton/components/hooks/useEventManager';
17 import useHasOutdatedRecoveryFile from '@proton/components/hooks/useHasOutdatedRecoveryFile';
18 import useIsMnemonicAvailable from '@proton/components/hooks/useIsMnemonicAvailable';
19 import useRecoverySecrets from '@proton/components/hooks/useRecoverySecrets';
20 import { useLoading } from '@proton/hooks';
21 import { updateDeviceRecovery } from '@proton/shared/lib/api/settingsRecovery';
22 import { BRAND_NAME } from '@proton/shared/lib/constants';
23 import { getKnowledgeBaseUrl } from '@proton/shared/lib/helpers/url';
24 import type { UserSettings } from '@proton/shared/lib/interfaces';
25 import { MNEMONIC_STATUS } from '@proton/shared/lib/interfaces';
26 import { syncDeviceRecovery } from '@proton/shared/lib/recoveryFile/deviceRecovery';
28 import useSearchParamsEffect from '../../hooks/useSearchParamsEffect';
29 import SettingsLayout from '../account/SettingsLayout';
30 import SettingsLayoutLeft from '../account/SettingsLayoutLeft';
31 import SettingsLayoutRight from '../account/SettingsLayoutRight';
32 import SettingsParagraph from '../account/SettingsParagraph';
33 import SettingsSection from '../account/SettingsSection';
34 import DisableMnemonicModal from '../mnemonic/DisableMnemonicModal';
35 import GenerateMnemonicModal from '../mnemonic/GenerateMnemonicModal';
36 import ExportRecoveryFileButton from './ExportRecoveryFileButton';
37 import VoidRecoveryFilesModal from './VoidRecoveryFilesModal';
39 export const DataRecoverySection = () => {
40     const [user] = useUser();
41     const [userSettings] = useUserSettings();
42     const { call } = useEventManager();
43     const api = useApi();
44     const { APP_NAME } = useConfig();
45     const authentication = useAuthentication();
47     const getUser = useGetUser();
48     const getUserKeys = useGetUserKeys();
49     const getAddresses = useGetAddresses();
50     const getUserSettings = useGetUserSettings();
51     const [isRecoveryFileAvailable] = useIsRecoveryFileAvailable();
52     const [isMnemonicAvailable, loadingIsMnemonicAvailable] = useIsMnemonicAvailable();
54     const [disableMnemonicModal, setDisableMnemonicModalOpen, renderDisableMnemonicModal] = useModalState();
55     const [generateMnemonicModal, setGenerateMnemonicModalOpen, renderGenerateMnemonicModal] = useModalState();
56     const [generateMnemonicModalButton, setGenerateMnemonicModalButtonOpen, renderGenerateMnemonicModalButton] =
57         useModalState();
58     const [voidRecoveryFilesModal, setVoidRecoveryFilesModalOpen, renderVoidRecoveryFilesModal] = useModalState();
60     const hasOutdatedRecoveryFile = useHasOutdatedRecoveryFile();
61     const recoverySecrets = useRecoverySecrets();
62     const canRevokeRecoveryFiles = recoverySecrets?.length > 0;
64     const [loadingDeviceRecovery, withLoadingDeviceRecovery] = useLoading();
66     useSearchParamsEffect(
67         (params) => {
68             if (!isMnemonicAvailable) {
69                 return;
70             }
72             const actionParam = params.get('action');
73             if (!actionParam) {
74                 return;
75             }
77             if (actionParam === 'generate-recovery-phrase') {
78                 if (user.MnemonicStatus === MNEMONIC_STATUS.SET || user.MnemonicStatus === MNEMONIC_STATUS.OUTDATED) {
79                     setGenerateMnemonicModalButtonOpen(true);
80                 } else {
81                     setGenerateMnemonicModalOpen(true);
82                 }
84                 params.delete('action');
85                 return params;
86             }
87         },
88         [loadingIsMnemonicAvailable]
89     );
91     const syncDeviceRecoveryHelper = async (partialUserSettings: Partial<UserSettings>) => {
92         const [user, userKeys, addresses, userSettings] = await Promise.all([
93             getUser(),
94             getUserKeys(),
95             getAddresses(),
96             getUserSettings(),
97         ]);
98         return syncDeviceRecovery({
99             api,
100             user,
101             addresses,
102             appName: APP_NAME,
103             userSettings: { ...userSettings, ...partialUserSettings },
104             userKeys,
105             authentication,
106         });
107     };
109     const handleChangeDeviceRecoveryToggle = async (checked: boolean) => {
110         const DeviceRecovery = Number(checked) as 0 | 1;
111         if (userSettings.DeviceRecovery === DeviceRecovery) {
112             return;
113         }
114         await api(updateDeviceRecovery({ DeviceRecovery }));
115         await syncDeviceRecoveryHelper({ DeviceRecovery });
116         await call();
117     };
119     return (
120         <>
121             {renderDisableMnemonicModal && <DisableMnemonicModal {...disableMnemonicModal} />}
122             {renderGenerateMnemonicModalButton && (
123                 <GenerateMnemonicModal confirmStep {...generateMnemonicModalButton} />
124             )}
125             {renderGenerateMnemonicModal && <GenerateMnemonicModal {...generateMnemonicModal} />}
126             {renderVoidRecoveryFilesModal && (
127                 <VoidRecoveryFilesModal
128                     onVoid={() => handleChangeDeviceRecoveryToggle(false)}
129                     deviceRecoveryEnabled={Boolean(userSettings.DeviceRecovery)}
130                     {...voidRecoveryFilesModal}
131                 />
132             )}
134             <SettingsSection>
135                 <SettingsParagraph>
136                     {c('Info')
137                         .t`Activate at least one data recovery method to make sure you can continue to access the contents of your ${BRAND_NAME} Account if you lose your password.`}
138                     <br />
139                     <Href href={getKnowledgeBaseUrl('/set-account-recovery-methods#how-to-enable-a-recovery-phrase')}>
140                         {c('Link').t`Learn more about data recovery`}
141                     </Href>
142                 </SettingsParagraph>
144                 {isMnemonicAvailable && (
145                     <>
146                         {user.MnemonicStatus === MNEMONIC_STATUS.OUTDATED && (
147                             <p className="color-danger">
148                                 <Icon className="mr-2 float-left mt-1" name="exclamation-circle-filled" size={3.5} />
149                                 {c('Warning')
150                                     .t`Your recovery phrase is outdated. It can't recover new data if you reset your password again.`}
151                             </p>
152                         )}
154                         <SettingsLayout>
155                             <SettingsLayoutLeft>
156                                 <label className="pt-0 mb-2 md:mb-0 text-semibold" htmlFor="mnemonicToggle">
157                                     <span className="mr-2">{c('label').t`Recovery phrase`}</span>
158                                     <Info
159                                         title={c('Info')
160                                             .t`A recovery phrase lets you access your account and recover your encrypted messages if you forget your password`}
161                                     />
162                                 </label>
163                             </SettingsLayoutLeft>
164                             <SettingsLayoutRight isToggleContainer={user.MnemonicStatus !== MNEMONIC_STATUS.OUTDATED}>
165                                 {user.MnemonicStatus === MNEMONIC_STATUS.OUTDATED ? (
166                                     <Button color="norm" onClick={() => setGenerateMnemonicModalButtonOpen(true)}>
167                                         {c('Action').t`Update recovery phrase`}
168                                     </Button>
169                                 ) : (
170                                     <>
171                                         <div className="flex items-start">
172                                             <Toggle
173                                                 className="mr-2"
174                                                 loading={disableMnemonicModal.open || generateMnemonicModal.open}
175                                                 checked={user.MnemonicStatus === MNEMONIC_STATUS.SET}
176                                                 id="mnemonicToggle"
177                                                 onChange={({ target: { checked } }) => {
178                                                     if (checked) {
179                                                         setGenerateMnemonicModalOpen(true);
180                                                     } else {
181                                                         setDisableMnemonicModalOpen(true);
182                                                     }
183                                                 }}
184                                             />
186                                             <label
187                                                 data-testid="account:recovery:mnemonicToggle"
188                                                 htmlFor="mnemonicToggle"
189                                                 className="flex-1 mt-0.5"
190                                             >
191                                                 {c('Label').t`Allow recovery by recovery phrase`}
192                                             </label>
193                                         </div>
195                                         {user.MnemonicStatus === MNEMONIC_STATUS.SET && (
196                                             <Button
197                                                 className="mt-4"
198                                                 shape="outline"
199                                                 onClick={() => setGenerateMnemonicModalButtonOpen(true)}
200                                             >
201                                                 {c('Action').t`Generate new recovery phrase`}
202                                             </Button>
203                                         )}
204                                     </>
205                                 )}
206                             </SettingsLayoutRight>
207                         </SettingsLayout>
208                     </>
209                 )}
211                 {isMnemonicAvailable && isRecoveryFileAvailable && <hr className="my-8" />}
213                 {isRecoveryFileAvailable && (
214                     <>
215                         <SettingsLayout>
216                             <SettingsLayoutLeft>
217                                 <label className="pt-0 mb-2 md:mb-0 text-semibold" htmlFor="deviceRecoveryToggle">
218                                     <span className="mr-2">{c('label').t`Device-based recovery`}</span>
219                                     <Info
220                                         url={getKnowledgeBaseUrl('/device-data-recovery')}
221                                         title={c('Info')
222                                             .t`We securely store recovery information on your trusted device to prevent you from losing your data`}
223                                     />
224                                 </label>
225                             </SettingsLayoutLeft>
226                             <SettingsLayoutRight isToggleContainer>
227                                 <div className="flex items-start">
228                                     <Toggle
229                                         className="mr-2"
230                                         loading={loadingDeviceRecovery}
231                                         checked={!!userSettings.DeviceRecovery}
232                                         id="deviceRecoveryToggle"
233                                         onChange={({ target: { checked } }) =>
234                                             withLoadingDeviceRecovery(handleChangeDeviceRecoveryToggle(checked))
235                                         }
236                                     />
237                                     <label
238                                         htmlFor="deviceRecoveryToggle"
239                                         className="flex-1 mt-0.5"
240                                         data-testid="account:recovery:trustedDevice"
241                                     >
242                                         {c('Label').t`Allow recovery using a trusted device`}
243                                     </label>
244                                 </div>
245                             </SettingsLayoutRight>
246                         </SettingsLayout>
247                         <SettingsLayout>
248                             <SettingsLayoutLeft>
249                                 <span className="pt-0 mb-2 md:mb-0 text-semibold">
250                                     <span className="mr-2">{c('Title').t`Recovery file`}</span>
251                                     <Info
252                                         title={c('Info')
253                                             .t`A recovery file lets you unlock and view your data after account recovery`}
254                                     />
255                                 </span>
256                             </SettingsLayoutLeft>
257                             <SettingsLayoutRight>
258                                 <ExportRecoveryFileButton className="block" color="norm">
259                                     {hasOutdatedRecoveryFile
260                                         ? c('Action').t`Update recovery file`
261                                         : c('Action').t`Download recovery file`}
262                                 </ExportRecoveryFileButton>
263                                 {canRevokeRecoveryFiles && (
264                                     <Button
265                                         className="mt-4"
266                                         color="danger"
267                                         shape="underline"
268                                         onClick={() => setVoidRecoveryFilesModalOpen(true)}
269                                     >
270                                         {c('Action').t`Void all recovery files`}
271                                     </Button>
272                                 )}
273                             </SettingsLayoutRight>
274                         </SettingsLayout>
275                         {hasOutdatedRecoveryFile && (
276                             <p className="color-danger flex flex-nowrap">
277                                 <Icon className="mr-2 shrink-0 mt-0.5" name="exclamation-circle-filled" size={3.5} />
278                                 <span className="flex-1">{c('Warning')
279                                     .t`Your recovery file is outdated. It can't recover new data if you reset your password again.`}</span>
280                             </p>
281                         )}
282                     </>
283                 )}
284             </SettingsSection>
285         </>
286     );