Add confirmation when deleting or trashing many items
[ProtonMail-WebClient.git] / packages / pass / components / Item / ItemActionsProvider.tsx
blobe4546b09ae44daee6eb41add8e266a303aa5f3a3
1 import { type FC, type PropsWithChildren, createContext, useCallback, useContext, useMemo } from 'react';
2 import { useDispatch, useStore } from 'react-redux';
4 import { c } from 'ttag';
6 import { useBulkSelect } from '@proton/pass/components/Bulk/BulkSelectProvider';
7 import { usePassCore } from '@proton/pass/components/Core/PassCoreProvider';
8 import { ConfirmTrashAlias } from '@proton/pass/components/Item/Actions/ConfirmAliasActions';
9 import {
10     ConfirmDeleteManyItems,
11     ConfirmMoveManyItems,
12     ConfirmTrashManyItems,
13 } from '@proton/pass/components/Item/Actions/ConfirmBulkActions';
14 import { ConfirmDeleteItem, ConfirmMoveItem } from '@proton/pass/components/Item/Actions/ConfirmItemActions';
15 import { VaultSelect, VaultSelectMode, useVaultSelectModalHandles } from '@proton/pass/components/Vault/VaultSelect';
16 import { useConfirm } from '@proton/pass/hooks/useConfirm';
17 import { isAliasDisabled, isAliasItem } from '@proton/pass/lib/items/item.predicates';
18 import {
19     itemBulkDeleteIntent,
20     itemBulkMoveIntent,
21     itemBulkRestoreIntent,
22     itemBulkTrashIntent,
23     itemDeleteIntent,
24     itemMoveIntent,
25     itemRestoreIntent,
26     itemTrashIntent,
27 } from '@proton/pass/store/actions';
28 import { selectLoginItemByEmail } from '@proton/pass/store/selectors';
29 import type { State } from '@proton/pass/store/types';
30 import { type BulkSelectionDTO, type ItemRevision, type MaybeNull, OnboardingMessage } from '@proton/pass/types';
31 import { uniqueId } from '@proton/pass/utils/string/unique-id';
33 /** Ongoing: move every item action definition to this
34  * context object. This context should be loosely connected */
35 type ItemActionsContextType = {
36     delete: (item: ItemRevision) => void;
37     deleteMany: (items: BulkSelectionDTO) => void;
38     move: (item: ItemRevision, mode: VaultSelectMode) => void;
39     moveMany: (items: BulkSelectionDTO) => void;
40     moveFromDragAndDrop: (selected: BulkSelectionDTO, shareId: string) => void;
41     restore: (item: ItemRevision) => void;
42     restoreMany: (items: BulkSelectionDTO) => void;
43     trash: (item: ItemRevision) => void;
44     trashMany: (items: BulkSelectionDTO) => void;
47 const ItemActionsContext = createContext<MaybeNull<ItemActionsContextType>>(null);
49 export const ItemActionsProvider: FC<PropsWithChildren> = ({ children }) => {
50     const core = usePassCore();
51     const bulk = useBulkSelect();
52     const dispatch = useDispatch();
53     const store = useStore<State>();
55     const { closeVaultSelect, openVaultSelect, modalState } = useVaultSelectModalHandles();
57     const moveItem = useConfirm(
58         useCallback(
59             (options: { item: ItemRevision; shareId: string }) =>
60                 dispatch(
61                     itemMoveIntent({
62                         ...options,
63                         optimisticId: uniqueId(),
64                     })
65                 ),
66             []
67         )
68     );
70     const moveManyItems = useConfirm(
71         useCallback(
72             (options: { selected: BulkSelectionDTO; shareId: string }) => {
73                 dispatch(itemBulkMoveIntent(options));
74                 bulk.disable();
75             },
76             [bulk]
77         )
78     );
80     const trashItem = useConfirm(
81         useCallback(
82             (item: ItemRevision) =>
83                 dispatch(
84                     itemTrashIntent({
85                         itemId: item.itemId,
86                         shareId: item.shareId,
87                         item,
88                     })
89                 ),
90             []
91         )
92     );
94     const trashManyItems = useConfirm(
95         useCallback((selected: BulkSelectionDTO) => {
96             dispatch(itemBulkTrashIntent({ selected }));
97             bulk.disable();
98         }, [])
99     );
101     const deleteItem = useConfirm(
102         useCallback((item: ItemRevision) => {
103             dispatch(
104                 itemDeleteIntent({
105                     itemId: item.itemId,
106                     shareId: item.shareId,
107                     item,
108                 })
109             );
110         }, [])
111     );
113     const deleteManyItems = useConfirm(
114         useCallback((selected: BulkSelectionDTO) => {
115             dispatch(itemBulkDeleteIntent({ selected }));
116             bulk.disable();
117         }, [])
118     );
120     const restoreItem = useCallback(
121         (item: ItemRevision) =>
122             dispatch(
123                 itemRestoreIntent({
124                     itemId: item.itemId,
125                     shareId: item.shareId,
126                     item,
127                 })
128             ),
129         []
130     );
132     const restoreManyItems = useCallback((selected: BulkSelectionDTO) => {
133         dispatch(itemBulkRestoreIntent({ selected }));
134         bulk.disable();
135     }, []);
137     const context = useMemo<ItemActionsContextType>(() => {
138         return {
139             move: (item, mode) =>
140                 openVaultSelect({
141                     mode,
142                     shareId: item.shareId,
143                     onSubmit: (shareId) => {
144                         moveItem.prompt({ item, shareId });
145                         closeVaultSelect();
146                     },
147                 }),
148             moveMany: (selected) =>
149                 openVaultSelect({
150                     mode: VaultSelectMode.Writable,
151                     shareId: '' /* allow all vaults */,
152                     onSubmit: (shareId) => {
153                         moveManyItems.prompt({ selected, shareId });
154                         closeVaultSelect();
155                     },
156                 }),
157             moveFromDragAndDrop: (selected, shareId) => moveManyItems.prompt({ selected, shareId }),
158             trash: (item) => {
159                 if (isAliasItem(item.data)) {
160                     const aliasEmail = item.aliasEmail!;
161                     const relatedLogin = selectLoginItemByEmail(aliasEmail)(store.getState());
162                     if (isAliasDisabled(item) && !relatedLogin) trashItem.call(item);
163                     else {
164                         Promise.resolve(core.onboardingCheck?.(OnboardingMessage.ALIAS_TRASH_CONFIRM) ?? false)
165                             .then((prompt) => (prompt ? trashItem.prompt(item) : trashItem.call(item)))
166                             .catch(() => trashItem.call(item));
167                     }
168                 }
170                 trashItem.call(item);
171             },
172             trashMany: trashManyItems.prompt,
173             delete: deleteItem.prompt,
174             deleteMany: deleteManyItems.prompt,
175             restore: restoreItem,
176             restoreMany: restoreManyItems,
177         };
178     }, [bulk]);
180     return (
181         <ItemActionsContext.Provider value={context}>
182             {children}
183             <VaultSelect
184                 downgradeMessage={c('Info')
185                     .t`You have exceeded the number of vaults included in your subscription. Items can only be moved to your first two vaults. To move items between all vaults upgrade your subscription.`}
186                 onClose={closeVaultSelect}
187                 {...modalState}
188             />
190             {moveItem.pending && (
191                 <ConfirmMoveItem
192                     open
193                     item={moveItem.param.item}
194                     shareId={moveItem.param.shareId}
195                     onCancel={moveItem.cancel}
196                     onConfirm={moveItem.confirm}
197                 />
198             )}
200             {moveManyItems.pending && (
201                 <ConfirmMoveManyItems
202                     open
203                     selected={moveManyItems.param.selected}
204                     shareId={moveManyItems.param.shareId}
205                     onConfirm={moveManyItems.confirm}
206                     onCancel={moveManyItems.cancel}
207                 />
208             )}
210             {trashItem.pending && (
211                 <ConfirmTrashAlias
212                     open
213                     onCancel={trashItem.cancel}
214                     onConfirm={trashItem.confirm}
215                     item={trashItem.param}
216                 />
217             )}
219             {trashManyItems.pending && (
220                 <ConfirmTrashManyItems
221                     open
222                     onCancel={trashManyItems.cancel}
223                     onConfirm={trashManyItems.confirm}
224                     selected={trashManyItems.param}
225                 />
226             )}
228             {deleteItem.pending && (
229                 <ConfirmDeleteItem
230                     open
231                     onCancel={deleteItem.cancel}
232                     onConfirm={deleteItem.confirm}
233                     item={deleteItem.param}
234                 />
235             )}
237             {deleteManyItems.pending && (
238                 <ConfirmDeleteManyItems
239                     open
240                     onCancel={deleteItem.cancel}
241                     onConfirm={deleteItem.confirm}
242                     selected={deleteManyItems.param}
243                 />
244             )}
245         </ItemActionsContext.Provider>
246     );
249 export const useItemsActions = (): ItemActionsContextType => useContext(ItemActionsContext)!;