1 import { useCallback } from 'react';
3 import { c } from 'ttag';
5 import { useModalTwoStatic } from '@proton/components/components/modalTwo/useModalTwo';
6 import useApi from '@proton/components/hooks/useApi';
7 import useEventManager from '@proton/components/hooks/useEventManager';
8 import useNotifications from '@proton/components/hooks/useNotifications';
9 import { useContactGroups } from '@proton/mail';
10 import { useContactEmails } from '@proton/mail/contactEmails/hooks';
11 import { useContacts } from '@proton/mail/contacts/hooks';
12 import { useMailSettings } from '@proton/mail/mailSettings/hooks';
13 import { labelContactEmails, unLabelContactEmails } from '@proton/shared/lib/api/contacts';
14 import { hasReachedContactGroupMembersLimit } from '@proton/shared/lib/contacts/helpers/contactGroup';
15 import type { Contact, ContactEmail } from '@proton/shared/lib/interfaces/contacts';
17 import ContactGroupLimitReachedModal from '../modals/ContactGroupLimitReachedModal';
18 import type { SelectEmailsProps } from '../modals/SelectEmailsModal';
21 * Collect contacts having multiple emails
22 * Used for <SelectEmailsModal />
24 export const collectContacts = (contactEmails: ContactEmail[] = [], contacts: Contact[]) => {
25 return contactEmails.reduce(
26 (acc, { ContactID }) => {
27 acc.duplicate[ContactID] = (acc.duplicate[ContactID] || 0) + 1;
29 if (acc.duplicate[ContactID] === 2) {
30 const contact = contacts.find(({ ID }: { ID: string }) => ID === ContactID);
32 acc.contacts.push(contact);
39 contacts: [] as Contact[],
40 duplicate: Object.create(null),
46 * Returns a reusable action to apply or remove groups to a list of contact emails
48 const useApplyGroups = (
49 onLock?: (lock: boolean) => void,
50 setLoading?: (loading: boolean) => void,
51 onSelectEmails?: (props: SelectEmailsProps) => Promise<ContactEmail[]>
53 const [mailSettings] = useMailSettings();
54 const { createNotification } = useNotifications();
55 const { call } = useEventManager();
57 const contacts = useContacts()[0] || [];
58 const userContactEmails = useContactEmails()[0] || [];
59 const [groups = []] = useContactGroups();
61 const [contactGroupLimitReachedModal, handleShowContactGroupLimitReachedModal] =
62 useModalTwoStatic(ContactGroupLimitReachedModal);
64 const applyGroups = useCallback(
65 async (contactEmails: ContactEmail[], changes: { [groupID: string]: boolean }, preventNotification = false) => {
66 const { contacts: collectedContacts } = collectContacts(contactEmails, contacts);
68 // contact emails in contacts with only one email (and then, skipping the modal)
69 const simpleEmails = contactEmails.filter(
70 (contactEmail) => !collectedContacts.find((contact) => contactEmail.ContactID === contact.ID)
73 // contact emails in contacts with multiple email (and then, passing through the modal)
74 let selectedEmails: ContactEmail[] = [];
76 if (collectedContacts.length) {
77 const groupIDs = Object.entries(changes)
78 .filter(([, isChecked]) => isChecked)
79 .map(([groupID]) => groupID);
81 if (groupIDs.length) {
83 selectedEmails = (await onSelectEmails?.({ groupIDs, contacts: collectedContacts, onLock })) || [];
88 // When removing a group, we remove it for all emails selected
89 const listForRemoving = [...contactEmails];
91 // When adding a group, we do it only for the selected ones
92 const listForAdding = [...simpleEmails, ...selectedEmails];
94 const groupEntries = Object.entries(changes);
96 const cannotAddContactInGroupIDs: string[] = [];
99 groupEntries.map(([groupID, isChecked]) => {
100 const contactGroup = groups.find((group) => group.ID === groupID);
101 const contactGroupName = contactGroup?.Name;
104 const groupExistingMembers =
106 userContactEmails.filter(({ LabelIDs = [] }: { LabelIDs: string[] }) =>
107 LabelIDs.includes(groupID)
110 const toLabel = listForAdding
111 .filter(({ LabelIDs = [] }) => !LabelIDs.includes(groupID))
112 .map(({ ID }) => ID);
114 if (!toLabel.length) {
115 return Promise.resolve();
118 // Cannot add more than 100 contacts in a contact group
119 const canAddContact = hasReachedContactGroupMembersLimit(
120 groupExistingMembers.length + toLabel.length,
125 if (!canAddContact) {
126 cannotAddContactInGroupIDs.push(groupID);
127 return Promise.resolve();
130 if (!preventNotification && contactGroupName) {
131 const notificationText = c('Info').t`Contact assigned to group ${contactGroupName}`;
132 createNotification({ text: notificationText });
134 return api(labelContactEmails({ LabelID: groupID, ContactEmailIDs: toLabel }));
137 const toUnlabel = listForRemoving
138 .filter(({ LabelIDs = [] }) => LabelIDs.includes(groupID))
139 .map(({ ID }) => ID);
141 if (!toUnlabel.length) {
142 return Promise.resolve();
145 if (!preventNotification && contactGroupName) {
146 const notificationText = c('Info').t`Contact unassigned from group ${contactGroupName}`;
147 createNotification({ text: notificationText });
150 return api(unLabelContactEmails({ LabelID: groupID, ContactEmailIDs: toUnlabel }));
154 if (cannotAddContactInGroupIDs.length > 0) {
155 void handleShowContactGroupLimitReachedModal({ groupIDs: cannotAddContactInGroupIDs });
160 [contacts, userContactEmails]
163 return { applyGroups, contactGroupLimitReachedModal };
166 export default useApplyGroups;