1 import { useEffect, useMemo, useState } from 'react';
3 import type { ModalProps } from '@proton/components/components/modalTwo/Modal';
4 import ModalTwo from '@proton/components/components/modalTwo/Modal';
5 import useEventManager from '@proton/components/hooks/useEventManager';
6 import type { ContactFormatted, ContactMergeModel } from '@proton/shared/lib/interfaces/contacts';
8 import type { ContactMergePreviewModalProps } from './ContactMergePreviewModal';
9 import ContactMergeTableContent from './ContactMergeTableContent';
10 import ContactMergingContent from './ContactMergingContent';
12 export interface ContactMergeProps {
13 contacts: ContactFormatted[][];
17 export interface ContactMergeModalProps {
18 onMergeDetails: (contactID: string) => void;
19 onMergePreview: (props: ContactMergePreviewModalProps) => void;
22 type Props = ContactMergeProps & ContactMergeModalProps & ModalProps;
24 const ContactMergeModal = ({ contacts, onMerged, onMergeDetails, onMergePreview, ...rest }: Props) => {
25 const { call } = useEventManager();
27 const [isMerging, setIsMerging] = useState(false);
28 const [mergeFinished, setMergeFinished] = useState(false);
29 const [model, setModel] = useState<ContactMergeModel>(() => ({
30 orderedContacts: contacts,
31 isChecked: contacts.flat().reduce<{ [ID: string]: boolean }>((acc, { ID }) => {
35 beDeleted: contacts.flat().reduce<{ [ID: string]: boolean }>((acc, { ID }) => {
41 const { orderedContacts, isChecked, beDeleted } = model;
44 // close the modal if all contacts have been merged from preview
45 if (!orderedContacts.flat().length) {
49 }, [orderedContacts]);
51 // beMergedModel = { 'ID of be-merged contact': [IDs to be merged] }
52 // beDeletedModel = { 'ID of be-deleted contact': 'ID to navigate to in case it is the current ID' }
53 const { beMergedModel, beDeletedModel, totalBeMerged, totalBeDeleted } = useMemo(
55 orderedContacts.reduce<{
56 beMergedModel: { [ID: string]: string[] };
57 beDeletedModel: { [ID: string]: string };
58 totalBeMerged: number;
59 totalBeDeleted: number;
62 const groupIDs = group.map(({ ID }) => ID);
63 const beMergedIDs = groupIDs
64 .map((ID) => isChecked[ID] && !beDeleted[ID] && ID)
65 .filter(Boolean) as string[];
66 const beDeletedIDs = groupIDs.map((ID) => beDeleted[ID] && ID).filter(Boolean) as string[];
67 const willBeMerged = beMergedIDs.length > 1;
70 acc.beMergedModel[beMergedIDs[0]] = beMergedIDs;
71 acc.totalBeMerged += beMergedIDs.length;
73 for (const ID of beDeletedIDs) {
74 // route to merged contact or to /contacts if no associated contact is merged
75 acc.beDeletedModel[ID] = willBeMerged ? beMergedIDs[0] : '';
76 acc.totalBeDeleted += 1;
80 { beMergedModel: {}, beDeletedModel: {}, totalBeMerged: 0, totalBeDeleted: 0 }
82 [orderedContacts, isChecked, beDeleted]
85 const handleStartMerge = () => {
89 const handleMergingFinish = async () => {
91 setMergeFinished(true);
94 const handleMerged = () => {
100 <ModalTwo size="large" className="contacts-modal" {...rest}>
102 <ContactMergingContent
103 mergeFinished={mergeFinished}
104 onFinish={handleMergingFinish}
105 onMerged={handleMerged}
106 onClose={rest.onClose}
107 beMergedModel={beMergedModel}
108 beDeletedModel={beDeletedModel}
109 totalBeMerged={totalBeMerged}
110 totalBeDeleted={totalBeDeleted}
113 <ContactMergeTableContent
115 updateModel={setModel}
116 onSubmit={handleStartMerge}
117 onClose={rest.onClose}
118 beMergedModel={beMergedModel}
119 beDeletedModel={beDeletedModel}
120 totalBeMerged={totalBeMerged}
121 totalBeDeleted={totalBeDeleted}
122 onMergeDetails={onMergeDetails}
123 onMergePreview={onMergePreview}
130 export default ContactMergeModal;