Merge branch 'DRVDOC-1266' into 'main'
[ProtonMail-WebClient.git] / packages / pass / components / Item / ItemActionsProvider.tsx
blob5abdf60d3caa23f274894721871460cd78fd23e2
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 { ConfirmTrashAlias } from '@proton/pass/components/Item/Actions/ConfirmAliasActions';
8 import {
9     ConfirmDeleteManyItems,
10     ConfirmMoveManyItems,
11     ConfirmTrashManyItems,
12 } from '@proton/pass/components/Item/Actions/ConfirmBulkActions';
13 import { ConfirmDeleteItem, ConfirmMoveItem } from '@proton/pass/components/Item/Actions/ConfirmItemActions';
14 import { VaultSelect, VaultSelectMode, useVaultSelectModalHandles } from '@proton/pass/components/Vault/VaultSelect';
15 import { useConfirm } from '@proton/pass/hooks/useConfirm';
16 import { isAliasDisabled, isAliasItem } from '@proton/pass/lib/items/item.predicates';
17 import {
18     itemBulkDeleteIntent,
19     itemBulkMoveIntent,
20     itemBulkRestoreIntent,
21     itemBulkTrashIntent,
22     itemDeleteIntent,
23     itemMoveIntent,
24     itemRestoreIntent,
25     itemTrashIntent,
26 } from '@proton/pass/store/actions';
27 import { selectAliasTrashAcknowledged, selectLoginItemByEmail } from '@proton/pass/store/selectors';
28 import type { State } from '@proton/pass/store/types';
29 import { type BulkSelectionDTO, type ItemRevision, type MaybeNull } from '@proton/pass/types';
30 import { uniqueId } from '@proton/pass/utils/string/unique-id';
32 /** Ongoing: move every item action definition to this
33  * context object. This context should be loosely connected */
34 type ItemActionsContextType = {
35     delete: (item: ItemRevision) => void;
36     deleteMany: (items: BulkSelectionDTO) => void;
37     move: (item: ItemRevision, mode: VaultSelectMode) => void;
38     moveMany: (items: BulkSelectionDTO, shareId?: string) => void;
39     restore: (item: ItemRevision) => void;
40     restoreMany: (items: BulkSelectionDTO) => void;
41     trash: (item: ItemRevision) => void;
42     trashMany: (items: BulkSelectionDTO) => void;
45 const ItemActionsContext = createContext<MaybeNull<ItemActionsContextType>>(null);
47 export const ItemActionsProvider: FC<PropsWithChildren> = ({ children }) => {
48     const bulk = useBulkSelect();
49     const dispatch = useDispatch();
50     const store = useStore<State>();
52     const { closeVaultSelect, openVaultSelect, modalState } = useVaultSelectModalHandles();
54     const moveItem = useConfirm(
55         useCallback(
56             (options: { item: ItemRevision; shareId: string }) =>
57                 dispatch(
58                     itemMoveIntent({
59                         ...options,
60                         optimisticId: uniqueId(),
61                     })
62                 ),
63             []
64         )
65     );
67     const moveManyItems = useConfirm(
68         useCallback(
69             (options: { selected: BulkSelectionDTO; shareId: string }) => {
70                 dispatch(itemBulkMoveIntent(options));
71                 bulk.disable();
72             },
73             [bulk]
74         )
75     );
77     const trashItem = useConfirm(
78         useCallback(
79             (item: ItemRevision) =>
80                 dispatch(
81                     itemTrashIntent({
82                         itemId: item.itemId,
83                         shareId: item.shareId,
84                         item,
85                     })
86                 ),
87             []
88         )
89     );
91     const trashManyItems = useConfirm(
92         useCallback(
93             (selected: BulkSelectionDTO) => {
94                 dispatch(itemBulkTrashIntent({ selected }));
95                 bulk.disable();
96             },
97             [bulk]
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(
115             (selected: BulkSelectionDTO) => {
116                 dispatch(itemBulkDeleteIntent({ selected }));
117                 bulk.disable();
118             },
119             [bulk]
120         )
121     );
123     const restoreItem = useCallback(
124         (item: ItemRevision) =>
125             dispatch(
126                 itemRestoreIntent({
127                     itemId: item.itemId,
128                     shareId: item.shareId,
129                     item,
130                 })
131             ),
132         []
133     );
135     const restoreManyItems = useCallback(
136         (selected: BulkSelectionDTO) => {
137             dispatch(itemBulkRestoreIntent({ selected }));
138             bulk.disable();
139         },
140         [bulk]
141     );
143     const context = useMemo<ItemActionsContextType>(() => {
144         return {
145             move: (item, mode) =>
146                 openVaultSelect({
147                     mode,
148                     shareId: item.shareId,
149                     onSubmit: (shareId) => {
150                         moveItem.prompt({ item, shareId });
151                         closeVaultSelect();
152                     },
153                 }),
154             moveMany: (selected, shareId) =>
155                 shareId
156                     ? moveManyItems.prompt({ selected, shareId })
157                     : openVaultSelect({
158                           mode: VaultSelectMode.Writable,
159                           shareId: '' /* allow all vaults */,
160                           onSubmit: (shareId) => {
161                               moveManyItems.prompt({ selected, shareId });
162                               closeVaultSelect();
163                           },
164                       }),
165             trash: (item) => {
166                 if (isAliasItem(item.data)) {
167                     const state = store.getState();
168                     const aliasEmail = item.aliasEmail!;
169                     const relatedLogin = selectLoginItemByEmail(aliasEmail)(state);
170                     const ack = selectAliasTrashAcknowledged(state);
171                     if ((isAliasDisabled(item) || ack) && !relatedLogin) trashItem.call(item);
172                     else trashItem.prompt(item);
173                 } else trashItem.call(item);
174             },
175             trashMany: trashManyItems.prompt,
176             delete: deleteItem.prompt,
177             deleteMany: deleteManyItems.prompt,
178             restore: restoreItem,
179             restoreMany: restoreManyItems,
180         };
181     }, [bulk]);
183     return (
184         <ItemActionsContext.Provider value={context}>
185             {children}
186             <VaultSelect
187                 downgradeMessage={c('Info')
188                     .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.`}
189                 onClose={closeVaultSelect}
190                 {...modalState}
191             />
193             {moveItem.pending && (
194                 <ConfirmMoveItem
195                     item={moveItem.param.item}
196                     shareId={moveItem.param.shareId}
197                     onCancel={moveItem.cancel}
198                     onConfirm={moveItem.confirm}
199                 />
200             )}
202             {moveManyItems.pending && (
203                 <ConfirmMoveManyItems
204                     selected={moveManyItems.param.selected}
205                     shareId={moveManyItems.param.shareId}
206                     onConfirm={moveManyItems.confirm}
207                     onCancel={moveManyItems.cancel}
208                 />
209             )}
211             {trashItem.pending && (
212                 <ConfirmTrashAlias onCancel={trashItem.cancel} onConfirm={trashItem.confirm} item={trashItem.param} />
213             )}
215             {trashManyItems.pending && (
216                 <ConfirmTrashManyItems
217                     onCancel={trashManyItems.cancel}
218                     onConfirm={trashManyItems.confirm}
219                     selected={trashManyItems.param}
220                 />
221             )}
223             {deleteItem.pending && (
224                 <ConfirmDeleteItem
225                     onCancel={deleteItem.cancel}
226                     onConfirm={deleteItem.confirm}
227                     item={deleteItem.param}
228                 />
229             )}
231             {deleteManyItems.pending && (
232                 <ConfirmDeleteManyItems
233                     onCancel={deleteManyItems.cancel}
234                     onConfirm={deleteManyItems.confirm}
235                     selected={deleteManyItems.param}
236                 />
237             )}
238         </ItemActionsContext.Provider>
239     );
242 export const useItemsActions = (): ItemActionsContextType => useContext(ItemActionsContext)!;