Merge branch 'pass-lifetime-fixes' into 'main'
[ProtonMail-WebClient.git] / packages / components / containers / contacts / merge / ContactMergePreviewModal.tsx
blob6332dd9baab7b9da4eee7efd9472d328e4577b9c
1 import type { Dispatch, SetStateAction } from 'react';
2 import { useEffect, useMemo, useState } from 'react';
4 import { useUserKeys } from '@proton/account/userKeys/hooks';
5 import type { ModalProps } from '@proton/components/components/modalTwo/Modal';
6 import ModalTwo from '@proton/components/components/modalTwo/Modal';
7 import useEventManager from '@proton/components/hooks/useEventManager';
8 import { useLoading } from '@proton/hooks';
9 import { useGetContact } from '@proton/mail/contacts/contactHooks';
10 import { processApiRequestsSafe } from '@proton/shared/lib/api/helpers/safeApiRequests';
11 import { prepareVCardContact } from '@proton/shared/lib/contacts/decrypt';
12 import { merge } from '@proton/shared/lib/contacts/helpers/merge';
13 import type { ContactMergeModel } from '@proton/shared/lib/interfaces/contacts';
14 import type { VCardContact } from '@proton/shared/lib/interfaces/contacts/VCard';
15 import { splitKeys } from '@proton/shared/lib/keys/keys';
17 import ContactMergeErrorContent from './ContactMergeErrorContent';
18 import ContactMergeViewContent from './ContactMergeViewContent';
19 import MergingModalContent from './ContactMergingContent';
21 export interface ContactMergePreviewModalProps {
22     beMergedModel: { [ID: string]: string[] };
23     beDeletedModel: { [ID: string]: string };
24     updateModel: Dispatch<SetStateAction<ContactMergeModel>>;
27 type Props = ContactMergePreviewModalProps & ModalProps;
29 const ContactMergePreviewModal = ({ beMergedModel, beDeletedModel, updateModel, ...rest }: Props) => {
30     const { call } = useEventManager();
31     const [userKeysList] = useUserKeys();
32     const getContact = useGetContact();
33     const { privateKeys, publicKeys } = useMemo(() => splitKeys(userKeysList), [userKeysList]);
35     const [loading, withLoading] = useLoading(true);
36     const [isMerging, setIsMerging] = useState(false);
37     const [mergeFinished, setMergeFinished] = useState(false);
38     const [model, setModel] = useState<{
39         mergedVCardContact?: VCardContact;
40         errorOnMerge?: boolean;
41         errorOnLoad?: boolean;
42     }>({});
44     const [beMergedIDs] = Object.values(beMergedModel);
45     const beDeletedIDs = Object.keys(beDeletedModel);
47     const handleRemoveMerged = () => {
48         const beRemovedIDs = beMergedIDs.slice(1).concat(beDeletedIDs);
49         updateModel((model) => ({
50             ...model,
51             orderedContacts: model.orderedContacts
52                 .map((group) => group.filter(({ ID }) => !beRemovedIDs.includes(ID)))
53                 .filter((group) => group.length > 1),
54         }));
55     };
57     useEffect(() => {
58         const mergeContacts = async () => {
59             try {
60                 const requests = beMergedIDs.map((ID: string) => async () => {
61                     const Contact = await getContact(ID);
62                     const { vCardContact, errors } = await prepareVCardContact(Contact, { privateKeys, publicKeys });
63                     if (errors.length) {
64                         setModel({ ...model, errorOnLoad: true });
65                         throw new Error('Error decrypting contact');
66                     }
67                     return vCardContact;
68                 });
69                 const beMergedContacts = await processApiRequestsSafe(requests);
70                 const mergedVCardContact = merge(beMergedContacts.map((vCardContact) => vCardContact));
71                 setModel({ ...model, mergedVCardContact });
72             } catch (e: any) {
73                 setModel({ ...model, errorOnMerge: true });
74             }
75         };
77         void withLoading(mergeContacts());
78     }, []);
80     const hasError = model.errorOnLoad || model.errorOnMerge;
82     const handleStartMerge = () => {
83         setIsMerging(true);
84     };
86     const handleMergingFinish = async () => {
87         handleRemoveMerged();
88         await call();
89         setMergeFinished(true);
90     };
92     return (
93         <ModalTwo size="large" className="contacts-modal" {...rest}>
94             {hasError ? (
95                 <ContactMergeErrorContent model={model} onClose={rest.onClose} />
96             ) : (
97                 <>
98                     {isMerging ? (
99                         <MergingModalContent
100                             alreadyMerged={model.mergedVCardContact}
101                             mergeFinished={mergeFinished}
102                             beMergedModel={beMergedModel}
103                             beDeletedModel={beDeletedModel}
104                             totalBeMerged={beMergedIDs.length}
105                             totalBeDeleted={beDeletedIDs.length}
106                             onFinish={handleMergingFinish}
107                             onMerged={rest.onClose}
108                             onClose={rest.onClose}
109                         />
110                     ) : (
111                         <ContactMergeViewContent
112                             contact={model.mergedVCardContact}
113                             loading={loading}
114                             beMergedIDs={beMergedIDs}
115                             onSubmit={handleStartMerge}
116                             onClose={rest.onClose}
117                         />
118                     )}
119                 </>
120             )}
121         </ModalTwo>
122     );
125 export default ContactMergePreviewModal;