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';
10 ConfirmDeleteManyItems,
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';
21 itemBulkRestoreIntent,
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(
59 (options: { item: ItemRevision; shareId: string }) =>
63 optimisticId: uniqueId(),
70 const moveManyItems = useConfirm(
72 (options: { selected: BulkSelectionDTO; shareId: string }) => {
73 dispatch(itemBulkMoveIntent(options));
80 const trashItem = useConfirm(
82 (item: ItemRevision) =>
86 shareId: item.shareId,
94 const trashManyItems = useConfirm(
95 useCallback((selected: BulkSelectionDTO) => {
96 dispatch(itemBulkTrashIntent({ selected }));
101 const deleteItem = useConfirm(
102 useCallback((item: ItemRevision) => {
106 shareId: item.shareId,
113 const deleteManyItems = useConfirm(
114 useCallback((selected: BulkSelectionDTO) => {
115 dispatch(itemBulkDeleteIntent({ selected }));
120 const restoreItem = useCallback(
121 (item: ItemRevision) =>
125 shareId: item.shareId,
132 const restoreManyItems = useCallback((selected: BulkSelectionDTO) => {
133 dispatch(itemBulkRestoreIntent({ selected }));
137 const context = useMemo<ItemActionsContextType>(() => {
139 move: (item, mode) =>
142 shareId: item.shareId,
143 onSubmit: (shareId) => {
144 moveItem.prompt({ item, shareId });
148 moveMany: (selected) =>
150 mode: VaultSelectMode.Writable,
151 shareId: '' /* allow all vaults */,
152 onSubmit: (shareId) => {
153 moveManyItems.prompt({ selected, shareId });
157 moveFromDragAndDrop: (selected, shareId) => moveManyItems.prompt({ selected, shareId }),
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);
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));
170 trashItem.call(item);
172 trashMany: trashManyItems.prompt,
173 delete: deleteItem.prompt,
174 deleteMany: deleteManyItems.prompt,
175 restore: restoreItem,
176 restoreMany: restoreManyItems,
181 <ItemActionsContext.Provider value={context}>
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}
190 {moveItem.pending && (
193 item={moveItem.param.item}
194 shareId={moveItem.param.shareId}
195 onCancel={moveItem.cancel}
196 onConfirm={moveItem.confirm}
200 {moveManyItems.pending && (
201 <ConfirmMoveManyItems
203 selected={moveManyItems.param.selected}
204 shareId={moveManyItems.param.shareId}
205 onConfirm={moveManyItems.confirm}
206 onCancel={moveManyItems.cancel}
210 {trashItem.pending && (
213 onCancel={trashItem.cancel}
214 onConfirm={trashItem.confirm}
215 item={trashItem.param}
219 {trashManyItems.pending && (
220 <ConfirmTrashManyItems
222 onCancel={trashManyItems.cancel}
223 onConfirm={trashManyItems.confirm}
224 selected={trashManyItems.param}
228 {deleteItem.pending && (
231 onCancel={deleteItem.cancel}
232 onConfirm={deleteItem.confirm}
233 item={deleteItem.param}
237 {deleteManyItems.pending && (
238 <ConfirmDeleteManyItems
240 onCancel={deleteItem.cancel}
241 onConfirm={deleteItem.confirm}
242 selected={deleteManyItems.param}
245 </ItemActionsContext.Provider>
249 export const useItemsActions = (): ItemActionsContextType => useContext(ItemActionsContext)!;