Remove option component
[ProtonMail-WebClient.git] / packages / components / containers / contacts / import / steps / ContactImportGroups.tsx
blob58cf992dad5898f997a92b82c6e3d586a5b6d0fc
1 import type { ChangeEvent, Dispatch, FormEvent, SetStateAction } from 'react';
3 import { c, msgid } from 'ttag';
5 import { Button, Input } from '@proton/atoms';
6 import Alert from '@proton/components/components/alert/Alert';
7 import Option from '@proton/components/components/option/Option';
8 import { FORBIDDEN_LABEL_NAMES } from '@proton/shared/lib/constants';
9 import { omit } from '@proton/shared/lib/helpers/object';
10 import { normalize } from '@proton/shared/lib/helpers/string';
11 import type { ContactGroup, ImportContactsModel } from '@proton/shared/lib/interfaces/contacts';
12 import { IMPORT_GROUPS_ACTION } from '@proton/shared/lib/interfaces/contacts';
13 import isTruthy from '@proton/utils/isTruthy';
15 import { ModalTwoContent, ModalTwoFooter, ModalTwoHeader, SelectTwo } from '../../../../components';
16 import { useApi, useEventManager } from '../../../../hooks';
17 import { submitCategories } from '../encryptAndSubmit';
19 interface SelectGroupActionProps {
20     action: IMPORT_GROUPS_ACTION;
21     index: number;
22     canMerge: boolean;
23     onChange: (action: IMPORT_GROUPS_ACTION, index: number) => void;
26 const SelectGroupAction = ({ action, index, canMerge, onChange }: SelectGroupActionProps) => {
27     const actionOptions = [
28         canMerge && { text: c('Option').t`Add to existing group`, value: IMPORT_GROUPS_ACTION.MERGE },
29         { text: c('Option').t`Create new group`, value: IMPORT_GROUPS_ACTION.CREATE },
30         { text: c('Option').t`Ignore group`, value: IMPORT_GROUPS_ACTION.IGNORE },
31     ].filter(isTruthy);
33     return (
34         <SelectTwo
35             id="contact-group-action-select"
36             value={action}
37             onChange={({ value }) => onChange(value as IMPORT_GROUPS_ACTION, index)}
38             title={c('Title').t`Select action to take on contact group`}
39         >
40             {actionOptions.map(({ text, value }) => (
41                 <Option key={value} value={value} title={text} />
42             ))}
43         </SelectTwo>
44     );
47 interface SelectGroupProps {
48     targetGroup: ContactGroup;
49     targetName: string;
50     contactGroups?: ContactGroup[];
51     action: IMPORT_GROUPS_ACTION;
52     index: number;
53     error?: string;
54     onChangeTargetGroup: (targetGroup: ContactGroup, index: number) => void;
55     onChangeTargetName: (targetName: string, index: number) => void;
56     onError: (error: string, index: number) => void;
59 const SelectGroup = ({
60     targetGroup,
61     targetName,
62     contactGroups = [],
63     action,
64     index,
65     error,
66     onChangeTargetGroup,
67     onChangeTargetName,
68     onError,
69 }: SelectGroupProps) => {
70     const groupNames = contactGroups.map(({ Name }) => Name);
71     const groupsOptions = contactGroups.map((group) => ({
72         text: group.Name,
73         value: group,
74     }));
76     const handleChangeGroupName = ({ target }: ChangeEvent<HTMLInputElement>) => {
77         // Clear previous errors
78         onError('', index);
79         const name = target.value;
80         if (!name) {
81             onError(c('Error').t`You must set a name`, index);
82         } else if (groupNames.includes(name)) {
83             onError(c('Error').t`A group with this name already exists`, index);
84         } else if (FORBIDDEN_LABEL_NAMES.includes(normalize(name))) {
85             onError(c('Error').t`Invalid name`, index);
86         }
87         onChangeTargetName(target.value, index);
88     };
90     if (action === IMPORT_GROUPS_ACTION.CREATE) {
91         return (
92             <Input
93                 id="contact-group-create"
94                 placeholder={c('Placeholder').t`Name`}
95                 maxLength={100}
96                 title={c('Title').t`Add contact group name`}
97                 error={error}
98                 value={targetName}
99                 onChange={handleChangeGroupName}
100             />
101         );
102     }
104     if (action === IMPORT_GROUPS_ACTION.MERGE) {
105         return (
106             <SelectTwo
107                 id="contact-group-select"
108                 value={targetGroup}
109                 onChange={({ value }) => onChangeTargetGroup(value, index)}
110                 title={c('Title').t`Select contact group`}
111             >
112                 {groupsOptions.map(({ text, value }) => (
113                     <Option key={value.Name} value={value} title={text} />
114                 ))}
115             </SelectTwo>
116         );
117     }
118     return null;
121 interface Props {
122     model: ImportContactsModel;
123     setModel: Dispatch<SetStateAction<ImportContactsModel>>;
124     onClose?: () => void;
127 const ContactImportGroups = ({ model, setModel, onClose }: Props) => {
128     const api = useApi();
129     const { call } = useEventManager();
131     const { categories } = model;
133     const cannotSave = categories.some(
134         ({ error, action, targetName }) => !!error || (action === IMPORT_GROUPS_ACTION.CREATE && !targetName)
135     );
137     const handleChangeAction = (action: IMPORT_GROUPS_ACTION, index: number) => {
138         setModel((model) => ({
139             ...model,
140             categories: model.categories.map((category, j) => {
141                 if (index !== j) {
142                     return category;
143                 }
144                 return { ...omit(category, ['error']), action };
145             }),
146         }));
147     };
149     const handleChangeTargetGroup = (targetGroup: ContactGroup, index: number) => {
150         setModel((model) => ({
151             ...model,
152             categories: model.categories.map((category, j) => {
153                 if (index !== j) {
154                     return category;
155                 }
156                 return { ...category, targetGroup };
157             }),
158         }));
159     };
161     const handleChangeTargetName = (targetName: string, index: number) => {
162         setModel((model) => ({
163             ...model,
164             categories: model.categories.map((category, j) => {
165                 if (index !== j) {
166                     return category;
167                 }
168                 return { ...category, targetName };
169             }),
170         }));
171     };
173     const handleSetError = (error: string, index: number) => {
174         setModel((model) => ({
175             ...model,
176             categories: model.categories.map((category, j) => {
177                 if (index !== j) {
178                     return category;
179                 }
180                 return { ...category, error };
181             }),
182         }));
183     };
185     const handleSubmit = async (event: FormEvent) => {
186         event.preventDefault();
187         event.stopPropagation();
189         setModel((model) => ({ ...model, loading: true }));
190         await submitCategories(model.categories, api);
191         await call();
192         setModel((model) => ({ ...model, loading: false }));
193         onClose?.();
194     };
196     const rows = categories.map(({ name, totalContacts, action, targetGroup, targetName, error }, index) => {
197         const totalContactsString = c('Import contact groups info').ngettext(
198             msgid`${totalContacts} contact`,
199             `${totalContacts} contacts`,
200             totalContacts
201         );
202         const categoryString = `${name} (${totalContactsString})`;
203         return (
204             <div
205                 key={name}
206                 className="flex flex-nowrap flex-1 items-stretch sm:items-center flex-column sm:flex-row mb-4 gap-2"
207             >
208                 <div className="sm:flex-1 text-ellipsis" title={categoryString}>
209                     {categoryString}
210                 </div>
211                 <div className="sm:flex-1">
212                     <SelectGroupAction
213                         action={action}
214                         index={index}
215                         canMerge={!!model.contactGroups?.length}
216                         onChange={handleChangeAction}
217                     />
218                 </div>
219                 <div className="sm:flex-1 sm:w-3/10">
220                     <SelectGroup
221                         contactGroups={model.contactGroups}
222                         action={action}
223                         targetGroup={targetGroup}
224                         targetName={targetName}
225                         error={error}
226                         index={index}
227                         onChangeTargetGroup={handleChangeTargetGroup}
228                         onChangeTargetName={handleChangeTargetName}
229                         onError={handleSetError}
230                     />
231                 </div>
232             </div>
233         );
234     });
236     return (
237         <form className="modal-two-dialog-container h-full" onSubmit={handleSubmit}>
238             <ModalTwoHeader title={c('Title').t`Import groups`} />
239             <ModalTwoContent>
240                 <Alert className="mb-4">
241                     {c('Description')
242                         .t`It looks like the contact list you are importing contains some groups. Please review how these groups should be imported.`}
243                 </Alert>
244                 {rows}
245             </ModalTwoContent>
246             <ModalTwoFooter>
247                 <Button onClick={onClose}>{c('Action').t`Cancel`}</Button>
248                 <Button color="norm" disabled={cannotSave} loading={model.loading} type="submit">
249                     {c('Action').t`Save`}
250                 </Button>
251             </ModalTwoFooter>
252         </form>
253     );
256 export default ContactImportGroups;