1 import { useEffect, useMemo, useRef } from 'react';
3 import { c } from 'ttag';
5 import { useAddresses } from '@proton/account/addresses/hooks';
6 import { useUserKeys } from '@proton/account/userKeys/hooks';
7 import { Button } from '@proton/atoms';
8 import Icon from '@proton/components/components/icon/Icon';
9 import Loader from '@proton/components/components/loader/Loader';
10 import type { ModalProps } from '@proton/components/components/modalTwo/Modal';
11 import ModalTwo from '@proton/components/components/modalTwo/Modal';
12 import ModalTwoContent from '@proton/components/components/modalTwo/ModalContent';
13 import ModalTwoFooter from '@proton/components/components/modalTwo/ModalFooter';
14 import ModalTwoHeader from '@proton/components/components/modalTwo/ModalHeader';
15 import Tooltip from '@proton/components/components/tooltip/Tooltip';
16 import useConfig from '@proton/components/hooks/useConfig';
17 import useNotifications from '@proton/components/hooks/useNotifications';
18 import { useContactGroups } from '@proton/mail';
19 import { useContact } from '@proton/mail/contacts/contactHooks';
20 import { useMailSettings } from '@proton/mail/mailSettings/hooks';
21 import { APPS } from '@proton/shared/lib/constants';
22 import { CRYPTO_PROCESSING_TYPES } from '@proton/shared/lib/contacts/constants';
23 import { singleExport } from '@proton/shared/lib/contacts/helpers/export';
24 import { textToClipboard } from '@proton/shared/lib/helpers/browser';
25 import { toMap } from '@proton/shared/lib/helpers/object';
26 import type { ContactEmail } from '@proton/shared/lib/interfaces/contacts';
27 import type { VCardContact } from '@proton/shared/lib/interfaces/contacts/VCard';
29 import { useLinkHandler } from '../../../hooks/useLinkHandler';
30 import ErrorBoundary from '../../app/ErrorBoundary';
31 import GenericError from '../../error/GenericError';
32 import type { ContactEditProps } from '../edit/ContactEditModal';
33 import type { ContactEmailSettingsProps } from '../email/ContactEmailSettingsModal';
34 import type { ContactGroupEditProps } from '../group/ContactGroupEditModal';
35 import useContactList from '../hooks/useContactList';
36 import useVCardContact from '../hooks/useVCardContact';
37 import type { ContactDeleteProps } from '../modals/ContactDeleteModal';
38 import ContactView from './ContactView';
40 export interface ContactDetailsProps {
42 onMailTo?: (src: string) => void;
43 onEdit: (props: ContactEditProps) => void;
44 onDelete: (props: ContactDeleteProps) => void;
45 onEmailSettings: (props: ContactEmailSettingsProps) => void;
46 onGroupDetails: (contactGroupID: string) => void;
47 onGroupEdit: (props: ContactGroupEditProps) => void;
48 onUpgrade: () => void;
49 onSignatureError: (contactID: string) => void;
50 onDecryptionError: (contactID: string) => void;
53 type Props = ContactDetailsProps & ModalProps;
55 const ContactDetailsModal = ({
68 const { onClose } = rest;
70 const [mailSettings] = useMailSettings();
71 const [contactGroups = [], loadingContactGroups] = useContactGroups();
72 const [userKeysList, loadingUserKeys] = useUserKeys();
73 const [addresses = [], loadingAddresses] = useAddresses();
74 const { loading: loadingContacts, contactEmailsMap } = useContactList({});
75 const [contact, loadingContact] = useContact(contactID);
76 const modalRef = useRef<HTMLDivElement>(null);
77 const { createNotification } = useNotifications();
79 const { APP_NAME } = useConfig();
81 const { modal: linkModal } = useLinkHandler(modalRef, mailSettings, { onMailTo });
85 isLoading: loadingVCard,
89 } = useVCardContact({ contact, userKeysList });
91 // Close the modal on a click on a mailto, useLinkHandler will open the composer
93 const handleClick = (event: MouseEvent) => {
94 const link = (event.target as Element).closest('a');
95 const src = link?.getAttribute('href');
96 if (src?.startsWith('mailto:')) {
97 if (APP_NAME === APPS.PROTONMAIL) {
100 textToClipboard(src?.replace('mailto:', ''));
102 text: c('Success').t`Email address copied to clipboard`,
108 modalRef.current?.addEventListener('click', handleClick);
109 return () => modalRef.current?.removeEventListener('click', handleClick);
112 const handleEdit = (newField?: string) => {
113 onEdit({ contactID, vCardContact, newField });
116 const handleDelete = () => {
117 onDelete({ contactIDs: [contactID] });
121 const handleExport = () => {
122 const hasError = errors?.some(
123 (error) => error instanceof Error || error.type !== CRYPTO_PROCESSING_TYPES.SIGNATURE_NOT_VERIFIED
127 createNotification({ text: c('Error').t`Cannot export this contact`, type: 'error' });
131 return singleExport(vCardContact as VCardContact);
134 const ownAddresses = useMemo(() => addresses.map(({ Email }) => Email), [addresses]);
135 const contactGroupsMap = useMemo(() => toMap(contactGroups), [contactGroups]);
139 loadingContactGroups ||
146 <ModalTwo size="large" className="contacts-modal" data-testid="contact-details-modal" {...rest}>
149 <Tooltip title={c('Action').t`Edit`}>
154 onClick={() => handleEdit()}
155 className="inline-flex ml-2"
156 data-testid="contact-details:edit"
158 <Icon name="pen" alt={c('Action').t`Edit`} />
161 <Tooltip title={c('Action').t`Export`}>
166 onClick={handleExport}
167 className="inline-flex ml-2"
168 data-testid="contact-details:export"
170 <Icon name="arrow-up-from-square" alt={c('Action').t`Export`} />
173 <Tooltip title={c('Action').t`Delete`}>
178 onClick={handleDelete}
179 className="inline-flex ml-2"
180 data-testid="contact-details:delete"
182 <Icon name="trash" alt={c('Action').t`Delete`} />
188 <ErrorBoundary key={contactID} component={<GenericError className="pt-7 view-column-detail flex-1" />}>
194 vCardContact={vCardContact as VCardContact}
195 contactID={contactID}
196 contactEmails={contactEmailsMap[contactID] as ContactEmail[]}
197 contactGroupsMap={contactGroupsMap}
198 ownAddresses={ownAddresses}
200 isSignatureVerified={isVerified}
203 onEmailSettings={onEmailSettings}
204 onGroupDetails={onGroupDetails}
205 onGroupEdit={onGroupEdit}
206 onUpgrade={onUpgrade}
207 onSignatureError={onSignatureError}
208 onDecryptionError={onDecryptionError}
216 <Button onClick={onClose}>{c('Action').t`Close`}</Button>
222 export default ContactDetailsModal;