1 import { useEffect, useState } from 'react';
2 import { arrayMove } from 'react-sortable-hoc';
4 import { c } from 'ttag';
6 import { useUser } from '@proton/account/user/hooks';
7 import { Button } from '@proton/atoms';
8 import useDebounceInput from '@proton/components/components/input/useDebounceInput';
9 import Loader from '@proton/components/components/loader/Loader';
10 import useModalState from '@proton/components/components/modalTwo/useModalState';
11 import MailUpsellButton from '@proton/components/components/upsell/MailUpsellButton';
12 import LabelsUpsellModal from '@proton/components/components/upsell/modal/types/LabelsUpsellModal';
13 import SettingsSection from '@proton/components/containers/account/SettingsSection';
14 import useApi from '@proton/components/hooks/useApi';
15 import useEventManager from '@proton/components/hooks/useEventManager';
16 import useNotifications from '@proton/components/hooks/useNotifications';
17 import { useLoading } from '@proton/hooks';
18 import { useLabels } from '@proton/mail';
19 import { orderLabels } from '@proton/shared/lib/api/labels';
20 import { MAIL_UPSELL_PATHS } from '@proton/shared/lib/constants';
21 import { hasReachedLabelLimit } from '@proton/shared/lib/helpers/folder';
22 import isDeepEqual from '@proton/shared/lib/helpers/isDeepEqual';
23 import type { Label } from '@proton/shared/lib/interfaces';
25 import LabelSortableList from './LabelSortableList';
26 import EditLabelModal from './modals/EditLabelModal';
28 const DEBOUNCE_VALUE = 1600;
30 const toLabelIDs = (labels: Label[]) => labels.map(({ ID }) => ID).join(',');
32 function LabelsSection() {
33 const [user] = useUser();
34 const [labels = [], loadingLabels] = useLabels();
35 const { call } = useEventManager();
37 const { createNotification } = useNotifications();
38 const [loading, withLoading] = useLoading();
40 const [localLabels, setLocalLabels] = useState(labels);
41 const debouncedLabels = useDebounceInput(localLabels, DEBOUNCE_VALUE);
43 const labelsOrder = toLabelIDs(labels);
44 const debouncedLabelOrder = toLabelIDs(debouncedLabels);
46 const canCreateLabel = !hasReachedLabelLimit(user, labels);
48 const [editLabelProps, setEditLabelModalOpen, renderEditLabelModal] = useModalState();
49 const [upsellModalProps, handleUpsellModalDisplay, renderUpsellModal] = useModalState();
52 * Refresh the list + update API and call event, it can be slow.
53 * We want a responsive UI, if it fails the item will go back to its previous index
54 * @param {Number} oldIndex cf https://github.com/clauderic/react-sortable-hoc#basic-example
55 * @param {Number} newIndex
57 const onSortEnd = async ({ oldIndex, newIndex }: { oldIndex: number; newIndex: number }) => {
58 const newLabels = arrayMove(localLabels, oldIndex, newIndex);
59 setLocalLabels(newLabels);
62 const handleSortLabel = async () => {
63 const newLabels = [...localLabels].sort((a, b) => a.Name.localeCompare(b.Name, undefined, { numeric: true }));
64 setLocalLabels(newLabels);
65 createNotification({ text: c('Success message after sorting labels').t`Labels sorted` });
69 if (!debouncedLabelOrder || debouncedLabelOrder === labelsOrder) {
73 const sync = async () => {
74 await api(orderLabels({ LabelIDs: debouncedLabels.map(({ ID }) => ID) }));
79 }, [debouncedLabels]);
82 if (isDeepEqual(debouncedLabels, labels)) {
85 setLocalLabels(labels);
94 <div className="flex gap-4 mb-7 labels-action">
96 <Button color="norm" onClick={() => setEditLabelModalOpen(true)}>
97 {c('Action').t`Add label`}
101 onClick={() => handleUpsellModalDisplay(true)}
102 text={c('Action').t`Get more labels`}
105 {localLabels.length ? (
108 title={c('Title').t`Sort labels alphabetically`}
110 onClick={() => withLoading(handleSortLabel())}
112 {c('Action').t`Sort`}
116 {localLabels.length ? <LabelSortableList items={localLabels} onSortEnd={onSortEnd} /> : null}
118 {renderEditLabelModal && <EditLabelModal {...editLabelProps} type="label" />}
120 {renderUpsellModal && (
122 modalProps={upsellModalProps}
123 feature={MAIL_UPSELL_PATHS.UNLIMITED_LABELS}
133 export default LabelsSection;