Skip to content

Commit c5e6ffe

Browse files
author
MargeBot
committed
Merge branch 'open-contact-in-drawer-based-on-url' into 'main'
Open contacts in drawer, edit and create contacts and contacts groups based on URL content See merge request web/clients!18803
2 parents 00a1f20 + e0839a7 commit c5e6ffe

File tree

6 files changed

+143
-0
lines changed

6 files changed

+143
-0
lines changed
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import { useEffect } from 'react';
2+
import { useLocation } from 'react-router-dom';
3+
4+
import useDrawer from '@proton/components/hooks/drawer/useDrawer';
5+
import { isContactSearchParams } from '@proton/mail/hooks/autoOpenContacts/helper';
6+
import { DRAWER_NATIVE_APPS } from '@proton/shared/lib/drawer/interfaces';
7+
8+
const useAutoOpenContactsDrawer = () => {
9+
const location = useLocation();
10+
11+
const { setAppInView } = useDrawer();
12+
13+
useEffect(() => {
14+
if (isContactSearchParams(location)) {
15+
setAppInView(DRAWER_NATIVE_APPS.CONTACTS);
16+
}
17+
}, []);
18+
};
19+
20+
export default useAutoOpenContactsDrawer;

applications/mail/src/app/router/sideEffects/useMailboxContainerSideEffects.test.tsx

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,11 @@ jest.mock('proton-mail/hooks/mailbox/usePreLoadElements', () => ({
4949
default: jest.fn(),
5050
}));
5151

52+
jest.mock('proton-mail/hooks/drawer/useAutoOpenContactsDrawer', () => ({
53+
__esModule: true,
54+
default: jest.fn(),
55+
}));
56+
5257
const props = {
5358
labelID: 'inbox',
5459
isSearch: false,

applications/mail/src/app/router/sideEffects/useMailboxContainerSideEffects.tsx

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { useCalendarUserSettings } from '@proton/calendar/calendarUserSettings/h
22
import { useCalendars } from '@proton/calendar/calendars/hooks';
33
import { useInboxDesktopBadgeCount } from '@proton/components';
44

5+
import useAutoOpenContactsDrawer from 'proton-mail/hooks/drawer/useAutoOpenContactsDrawer';
56
import useNewEmailNotification from 'proton-mail/hooks/mailbox/notifications/useNewEmailNotification';
67
import { type EncryptedSearchParams, useApplyEncryptedSearch } from 'proton-mail/hooks/mailbox/useApplyEncryptedSearch';
78
import { useMailboxFavicon } from 'proton-mail/hooks/mailbox/useMailboxFavicon';
@@ -47,6 +48,9 @@ export const useMailboxContainerSideEffects = ({
4748

4849
useNewEmailNotification(() => handleCheckAll(false));
4950

51+
// When URL contains a contact route, we need to open the contact drawer app
52+
useAutoOpenContactsDrawer();
53+
5054
// Launch two calendar-specific API calls here to boost calendar widget performance
5155
useCalendars();
5256
useCalendarUserSettings();

packages/components/components/drawer/views/DrawerContactView.tsx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import ContactsWidgetGroupsContainer from '@proton/components/containers/contact
1818
import type { CustomAction } from '@proton/components/containers/contacts/widget/types';
1919
import { CONTACT_WIDGET_TABS } from '@proton/components/containers/contacts/widget/types';
2020
import useDrawerContactFocus from '@proton/components/hooks/useDrawerContactFocus';
21+
import useContactsDrawerFromURL from '@proton/mail/hooks/autoOpenContacts/useContactsDrawerFromURL';
2122
import type { Recipient } from '@proton/shared/lib/interfaces';
2223
import noop from '@proton/utils/noop';
2324

@@ -58,6 +59,8 @@ const DrawerContactView = ({ onCompose, onMailTo = noop, customActions = [] }: P
5859
onLimitReached,
5960
} = useContactModals({ onMailTo, onCompose });
6061

62+
useContactsDrawerFromURL({ onEdit, onGroupEdit, onSelectGroupTab: () => setTab(options[1]) });
63+
6164
const handleDetails = (contactID: string) => {
6265
void onDetails(contactID);
6366
};
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
import type { Location } from 'history';
2+
3+
import { getSearchParams } from '@proton/shared/lib/helpers/url';
4+
5+
export enum CONTACT_SEARCH_PARAMS {
6+
CREATE_CONTACT = 'create-contact',
7+
EDIT_CONTACT = 'edit-contact',
8+
CREATE_CONTACT_GROUP = 'create-contact-group',
9+
EDIT_CONTACT_GROUP = 'edit-contact-group',
10+
}
11+
12+
export const contactSearchParams: CONTACT_SEARCH_PARAMS[] = Object.values(CONTACT_SEARCH_PARAMS);
13+
14+
export const isContactSearchParams = (location: Location) => {
15+
if (!location.hash) {
16+
return false;
17+
}
18+
19+
const params = getSearchParams(location.hash);
20+
21+
const res = Object.keys(params).some((key) => contactSearchParams.includes(key as CONTACT_SEARCH_PARAMS));
22+
23+
return res;
24+
};
Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
import { useEffect } from 'react';
2+
import { useHistory, useLocation } from 'react-router-dom';
3+
4+
import { c } from 'ttag';
5+
6+
import { useUserKeys } from '@proton/account/userKeys/hooks';
7+
import type { ContactEditProps } from '@proton/components/containers/contacts/edit/ContactEditModal';
8+
import type { ContactGroupEditProps } from '@proton/components/containers/contacts/group/ContactGroupEditModal';
9+
import useConfig from '@proton/components/hooks/useConfig';
10+
import useNotifications from '@proton/components/hooks/useNotifications';
11+
import type { PrivateKeyReference } from '@proton/crypto';
12+
import { CONTACT_SEARCH_PARAMS, isContactSearchParams } from '@proton/mail/hooks/autoOpenContacts/helper';
13+
import { useGetContact } from '@proton/mail/store/contacts/contactHooks';
14+
import { APPS } from '@proton/shared/lib/constants';
15+
import { prepareVCardContact } from '@proton/shared/lib/contacts/decrypt';
16+
import { getSearchParams } from '@proton/shared/lib/helpers/url';
17+
import type { DecryptedKey } from '@proton/shared/lib/interfaces';
18+
import { splitKeys } from '@proton/shared/lib/keys';
19+
20+
interface Props {
21+
onEdit: (props: ContactEditProps) => void;
22+
onGroupEdit: (props: ContactGroupEditProps) => void;
23+
onSelectGroupTab: () => void;
24+
}
25+
26+
/**
27+
* This hook allows ET app to open the contact drawer on the mail app using a deeplink
28+
*/
29+
const useContactsDrawerFromURL = ({ onEdit, onGroupEdit, onSelectGroupTab }: Props) => {
30+
const { APP_NAME } = useConfig();
31+
const history = useHistory();
32+
const location = useLocation();
33+
const [userKeysList] = useUserKeys();
34+
const getContact = useGetContact();
35+
const { createNotification } = useNotifications();
36+
37+
const handleEditContact = async (contactID: string, userKeysList: DecryptedKey<PrivateKeyReference>[]) => {
38+
// To edit a contact we first need to get the contact vCard
39+
const contact = await getContact(contactID);
40+
const { publicKeys, privateKeys } = splitKeys(userKeysList);
41+
42+
const { vCardContact, errors } = await prepareVCardContact(contact, {
43+
publicKeys,
44+
privateKeys,
45+
});
46+
47+
if (errors.length > 0) {
48+
createNotification({ type: 'error', text: c('Error').t`Failed to open to contact` });
49+
return;
50+
}
51+
52+
onEdit({ contactID: contactID, vCardContact });
53+
};
54+
55+
useEffect(() => {
56+
if (APP_NAME !== APPS.PROTONMAIL || !isContactSearchParams(location) || !userKeysList) {
57+
return;
58+
}
59+
60+
const params = getSearchParams(location.hash);
61+
62+
const contactCreateMatch = CONTACT_SEARCH_PARAMS.CREATE_CONTACT in params;
63+
const contactEditMatch = CONTACT_SEARCH_PARAMS.EDIT_CONTACT in params;
64+
const contactGroupCreateMatch = CONTACT_SEARCH_PARAMS.CREATE_CONTACT_GROUP in params;
65+
const contactGroupEditMatch = CONTACT_SEARCH_PARAMS.EDIT_CONTACT_GROUP in params;
66+
67+
// Check for group-related routes first to set the correct tab
68+
if (contactGroupCreateMatch || contactGroupEditMatch) {
69+
onSelectGroupTab();
70+
}
71+
72+
if (contactCreateMatch) {
73+
onEdit({});
74+
} else if (contactGroupCreateMatch) {
75+
onGroupEdit({});
76+
} else if (contactEditMatch && params[CONTACT_SEARCH_PARAMS.EDIT_CONTACT]) {
77+
void handleEditContact(params[CONTACT_SEARCH_PARAMS.EDIT_CONTACT], userKeysList);
78+
} else if (contactGroupEditMatch && params[CONTACT_SEARCH_PARAMS.EDIT_CONTACT_GROUP]) {
79+
onGroupEdit({ contactGroupID: params[CONTACT_SEARCH_PARAMS.EDIT_CONTACT_GROUP] });
80+
}
81+
82+
// remove hash params so that closing the modal will not trigger a modal reopen
83+
history.replace({ pathname: location.pathname, search: location.search });
84+
}, [location.hash, handleEditContact, userKeysList, APP_NAME]);
85+
};
86+
87+
export default useContactsDrawerFromURL;

0 commit comments

Comments
 (0)