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;
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) => ({
51 orderedContacts: model.orderedContacts
52 .map((group) => group.filter(({ ID }) => !beRemovedIDs.includes(ID)))
53 .filter((group) => group.length > 1),
58 const mergeContacts = async () => {
60 const requests = beMergedIDs.map((ID: string) => async () => {
61 const Contact = await getContact(ID);
62 const { vCardContact, errors } = await prepareVCardContact(Contact, { privateKeys, publicKeys });
64 setModel({ ...model, errorOnLoad: true });
65 throw new Error('Error decrypting contact');
69 const beMergedContacts = await processApiRequestsSafe(requests);
70 const mergedVCardContact = merge(beMergedContacts.map((vCardContact) => vCardContact));
71 setModel({ ...model, mergedVCardContact });
73 setModel({ ...model, errorOnMerge: true });
77 void withLoading(mergeContacts());
80 const hasError = model.errorOnLoad || model.errorOnMerge;
82 const handleStartMerge = () => {
86 const handleMergingFinish = async () => {
89 setMergeFinished(true);
93 <ModalTwo size="large" className="contacts-modal" {...rest}>
95 <ContactMergeErrorContent model={model} onClose={rest.onClose} />
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}
111 <ContactMergeViewContent
112 contact={model.mergedVCardContact}
114 beMergedIDs={beMergedIDs}
115 onSubmit={handleStartMerge}
116 onClose={rest.onClose}
125 export default ContactMergePreviewModal;