Update selected item color in Pass menu
[ProtonMail-WebClient.git] / packages / pass / store / actions / creators / item.ts
blob6cb730bd302c366fcbc6a4d495b8c48599909860
1 import { createAction } from '@reduxjs/toolkit';
2 import { c } from 'ttag';
4 import { getItemActionId } from '@proton/pass/lib/items/item.utils';
5 import { withCache, withThrottledCache } from '@proton/pass/store/actions/enhancers/cache';
6 import { type ActionCallback, withCallback } from '@proton/pass/store/actions/enhancers/callback';
7 import { withSynchronousAction } from '@proton/pass/store/actions/enhancers/client';
8 import { withNotification } from '@proton/pass/store/actions/enhancers/notification';
9 import {
10     itemPinRequest,
11     itemRevisionsRequest,
12     itemUnpinRequest,
13     itemsBulkDeleteRequest,
14     itemsBulkMoveRequest,
15     itemsBulkRestoreRequest,
16     itemsBulkTrashRequest,
17     secureLinkCreateRequest,
18     secureLinkOpenRequest,
19     secureLinkRemoveRequest,
20     secureLinksGetRequest,
21     secureLinksRemoveInactiveRequest,
22 } from '@proton/pass/store/actions/requests';
23 import { createOptimisticAction } from '@proton/pass/store/optimistic/action/create-optimistic-action';
24 import type { Draft, DraftBase } from '@proton/pass/store/reducers';
25 import {
26     withRequest,
27     withRequestFailure,
28     withRequestProgress,
29     withRequestSuccess,
30 } from '@proton/pass/store/request/enhancers';
31 import { requestActionsFactory } from '@proton/pass/store/request/flow';
32 import type {
33     BatchItemRevisionIDs,
34     BatchItemRevisions,
35     BulkSelectionDTO,
36     ItemCreateIntent,
37     ItemEditIntent,
38     ItemRevision,
39     ItemRevisionsIntent,
40     ItemRevisionsSuccess,
41     SecureLink,
42     SecureLinkCreationDTO,
43     SecureLinkDeleteDTO,
44     SecureLinkItem,
45     SecureLinkQuery,
46     SelectedItem,
47     UniqueItem,
48 } from '@proton/pass/types';
49 import { getErrorMessage } from '@proton/pass/utils/errors/get-error-message';
50 import { pipe } from '@proton/pass/utils/fp/pipe';
51 import { UNIX_MINUTE } from '@proton/pass/utils/time/constants';
53 export const draftSave = createAction('draft::save', (payload: Draft) => withThrottledCache({ payload }));
54 export const draftDiscard = createAction('draft::discard', (payload: DraftBase) => withThrottledCache({ payload }));
55 export const draftsGarbageCollect = createAction('drafts::gc');
57 export const itemCreationIntent = createOptimisticAction(
58     'item::creation::intent',
59     (
60         payload: ItemCreateIntent,
61         callback?: ActionCallback<ReturnType<typeof itemCreationSuccess> | ReturnType<typeof itemCreationFailure>>
62     ) => pipe(withSynchronousAction, withCallback(callback))({ payload }),
63     ({ payload }) => getItemActionId(payload)
66 export const itemCreationFailure = createOptimisticAction(
67     'item::creation::failure',
68     (payload: { optimisticId: string; shareId: string }, error: unknown) =>
69         withNotification({
70             type: 'error',
71             text: c('Error').t`Item creation failed`,
72             error,
73         })({ payload, error }),
74     ({ payload }) => getItemActionId(payload)
77 export const itemCreationDismiss = createOptimisticAction(
78     'item::creation::dismiss',
79     (payload: { optimisticId: string; shareId: string; item: ItemRevision }) =>
80         withNotification({
81             type: 'info',
82             text: c('Info').t`"${payload.item.data.metadata.name}" item was dismissed`,
83         })({ payload }),
84     ({ payload }) => getItemActionId(payload)
87 export const itemCreationSuccess = createOptimisticAction(
88     'item::creation::success',
89     (payload: { optimisticId: string; shareId: string; item: ItemRevision; alias?: ItemRevision }) =>
90         pipe(
91             withCache,
92             withNotification({
93                 type: 'success',
94                 text: c('Info').t`Item "${payload.item.data.metadata.name}" created`,
95             })
96         )({ payload }),
97     ({ payload }) => getItemActionId(payload)
100 export const itemEditIntent = createOptimisticAction(
101     'item::edit::intent',
102     (
103         payload: ItemEditIntent,
104         callback?: ActionCallback<ReturnType<typeof itemEditSuccess> | ReturnType<typeof itemEditFailure>>
105     ) => pipe(withSynchronousAction, withCallback(callback))({ payload }),
106     ({ payload }) => getItemActionId(payload)
109 export const itemEditFailure = createOptimisticAction(
110     'item::edit::failure',
111     (payload: SelectedItem, error: unknown) =>
112         withNotification({
113             type: 'error',
114             text: c('Error').t`Editing item failed`,
115             error,
116         })({ payload, error }),
117     ({ payload }) => getItemActionId(payload)
120 export const itemEditDismiss = createOptimisticAction(
121     'item::edit::dismiss',
122     (payload: { itemId: string; shareId: string; item: ItemRevision }) =>
123         withNotification({
124             type: 'info',
125             text: c('Info').t`"${payload.item.data.metadata.name}" update was dismissed`,
126         })({ payload }),
127     ({ payload }) => getItemActionId(payload)
130 export const itemEditSuccess = createOptimisticAction(
131     'item::edit::success',
132     (payload: { item: ItemRevision } & SelectedItem) =>
133         pipe(
134             withCache,
135             withNotification({
136                 type: 'success',
137                 text: c('Info').t`Item "${payload.item.data.metadata.name}" updated`,
138             })
139         )({ payload }),
140     ({ payload }) => getItemActionId(payload)
143 export const itemsEditSync = createAction('items::edit::sync', (items: ItemRevision[]) =>
144     withCache({ payload: { items } })
147 export const itemMoveIntent = createOptimisticAction(
148     'item::move::intent',
149     (payload: { item: ItemRevision; shareId: string; optimisticId: string }) => withSynchronousAction({ payload }),
150     ({ payload }) => getItemActionId(payload)
153 export const itemMoveFailure = createOptimisticAction(
154     'item::move::failure',
155     (payload: { optimisticId: string; shareId: string; item: ItemRevision }, error: unknown) =>
156         withNotification({
157             type: 'error',
158             text: c('Error').t`Moving item failed`,
159             error,
160         })({ payload, error }),
161     ({ payload }) => getItemActionId(payload)
164 export const itemMoveSuccess = createOptimisticAction(
165     'item::move::success',
166     (payload: { item: ItemRevision; optimisticId: string; shareId: string }) =>
167         pipe(
168             withCache,
169             withNotification({
170                 type: 'success',
171                 text: c('Info').t`Item successfully moved`,
172             })
173         )({ payload }),
174     ({ payload }) => getItemActionId(payload)
177 export const itemBulkMoveIntent = createAction(
178     'item::bulk::move::intent',
179     (payload: { selected: BulkSelectionDTO; shareId: string }) =>
180         pipe(
181             withRequest({ status: 'start', id: itemsBulkMoveRequest() }),
182             withNotification({
183                 expiration: -1,
184                 type: 'info',
185                 loading: true,
186                 text: c('Info').t`Moving items`,
187             })
188         )({ payload })
191 export const itemBulkMoveFailure = createAction(
192     'item::bulk::move::failure',
193     withRequestFailure((payload: {}, error: unknown) =>
194         withNotification({
195             type: 'error',
196             text: c('Error').t`Failed to move items`,
197             error,
198         })({ payload, error })
199     )
202 export const itemBulkMoveProgress = createAction(
203     'item::bulk::move::progress',
204     withRequestProgress((payload: BatchItemRevisions & { movedItems: ItemRevision[]; destinationShareId: string }) =>
205         withCache({ payload })
206     )
209 export const itemBulkMoveSuccess = createAction(
210     'item::bulk::move::success',
211     withRequestSuccess((payload: {}) =>
212         withNotification({
213             type: 'info',
214             text: c('Info').t`All items successfully moved`,
215         })({ payload })
216     )
219 export const itemTrashIntent = createOptimisticAction(
220     'item::trash::intent',
221     (
222         payload: { item: ItemRevision } & SelectedItem,
223         callback?: ActionCallback<ReturnType<typeof itemTrashSuccess> | ReturnType<typeof itemTrashFailure>>
224     ) => withCallback(callback)({ payload }),
225     ({ payload }) => getItemActionId(payload)
228 export const itemTrashFailure = createOptimisticAction(
229     'item::trash::failure',
230     (payload: SelectedItem, error: unknown) =>
231         withNotification({
232             type: 'error',
233             text: c('Error').t`Trashing item failed`,
234             error,
235         })({ payload, error }),
236     ({ payload }) => getItemActionId(payload)
239 export const itemTrashSuccess = createOptimisticAction(
240     'item::trash::success',
241     (payload: SelectedItem) =>
242         pipe(
243             withCache,
244             withNotification({
245                 type: 'success',
246                 key: getItemActionId(payload),
247                 text: c('Info').t`Item moved to trash`,
248             })
249         )({ payload }),
250     ({ payload }) => getItemActionId(payload)
253 export const itemBulkTrashIntent = createAction(
254     'item::bulk::trash::intent',
255     (payload: { selected: BulkSelectionDTO }) =>
256         pipe(
257             withRequest({ status: 'start', id: itemsBulkTrashRequest() }),
258             withNotification({
259                 expiration: -1,
260                 type: 'info',
261                 loading: true,
262                 text: c('Info').t`Moving items to trash`,
263             })
264         )({ payload })
267 export const itemBulkTrashFailure = createAction(
268     'item::bulk::trash::failure',
269     withRequestFailure((payload: {}, error: unknown) =>
270         withNotification({
271             type: 'error',
272             text: c('Error').t`Failed to move items to trash`,
273             error,
274         })({ payload, error })
275     )
278 export const itemBulkTrashProgress = createAction(
279     'item::bulk::trash::progress',
280     withRequestProgress((payload: BatchItemRevisionIDs) => withCache({ payload }))
283 export const itemBulkTrashSuccess = createAction(
284     'item::bulk::trash::success',
285     withRequestSuccess((payload: {}) =>
286         withNotification({
287             type: 'info',
288             text: c('Info').t`Selected items successfully moved to trash`,
289         })({ payload })
290     )
293 export const itemDeleteIntent = createOptimisticAction(
294     'item::delete::intent',
295     (
296         payload: { item: ItemRevision } & SelectedItem,
297         callback?: ActionCallback<ReturnType<typeof itemDeleteSuccess> | ReturnType<typeof itemDeleteFailure>>
298     ) => withCallback(callback)({ payload }),
299     ({ payload }) => getItemActionId(payload)
302 export const itemDeleteFailure = createOptimisticAction(
303     'item::delete::failure',
304     (payload: SelectedItem, error: unknown) =>
305         withNotification({
306             type: 'error',
307             text: c('Error').t`Deleting item failed`,
308             error,
309         })({ payload, error }),
310     ({ payload }) => getItemActionId(payload)
313 export const itemDeleteSuccess = createOptimisticAction(
314     'item::delete::success',
315     (payload: SelectedItem) =>
316         pipe(
317             withCache,
318             withNotification({
319                 type: 'success',
320                 text: c('Info').t`Item permanently deleted`,
321             })
322         )({ payload }),
323     ({ payload }) => getItemActionId(payload)
326 export const itemBulkDeleteIntent = createAction(
327     'item::bulk::delete::intent',
328     (payload: { selected: BulkSelectionDTO }) =>
329         pipe(
330             withRequest({ status: 'start', id: itemsBulkDeleteRequest() }),
331             withNotification({
332                 expiration: -1,
333                 type: 'info',
334                 loading: true,
335                 text: c('Info').t`Permanently deleting selected items`,
336             })
337         )({ payload })
340 export const itemBulkDeleteFailure = createAction(
341     'item::bulk::delete::failure',
342     withRequestFailure((payload: {}, error: unknown) =>
343         withNotification({
344             type: 'error',
345             text: c('Error').t`Failed to delete selected items`,
346             error,
347         })({ payload, error })
348     )
351 export const itemBulkDeleteProgress = createAction(
352     'item::bulk::delete::progress',
353     withRequestProgress((payload: BatchItemRevisionIDs) => withCache({ payload }))
356 export const itemBulkDeleteSuccess = createAction(
357     'item::bulk::delete::success',
358     withRequestSuccess((payload: {}) =>
359         withNotification({
360             type: 'info',
361             text: c('Info').t`Selected items permanently deleted`,
362         })({ payload })
363     )
366 export const itemsDeleteSync = createAction('items::delete::sync', (shareId: string, itemIds: string[]) =>
367     withCache({ payload: { shareId, itemIds } })
370 export const itemRestoreIntent = createOptimisticAction(
371     'item::restore::intent',
372     (
373         payload: { item: ItemRevision } & SelectedItem,
374         callback?: ActionCallback<ReturnType<typeof itemRestoreSuccess> | ReturnType<typeof itemRestoreFailure>>
375     ) => withCallback(callback)({ payload }),
376     ({ payload }) => getItemActionId(payload)
379 export const itemRestoreFailure = createOptimisticAction(
380     'item::restore::failure',
381     (payload: SelectedItem, error: unknown) =>
382         withNotification({
383             type: 'error',
384             text: c('Error').t`Restoring item failed`,
385             error,
386         })({ payload, error }),
387     ({ payload }) => getItemActionId(payload)
390 export const itemRestoreSuccess = createOptimisticAction(
391     'item::restore::success',
392     (payload: SelectedItem) =>
393         pipe(
394             withCache,
395             withNotification({
396                 type: 'success',
397                 text: c('Info').t`Item restored`,
398             })
399         )({ payload }),
400     ({ payload }) => getItemActionId(payload)
403 export const itemBulkRestoreIntent = createAction(
404     'item::bulk:restore::intent',
405     (payload: { selected: BulkSelectionDTO }) =>
406         pipe(
407             withRequest({ status: 'start', id: itemsBulkRestoreRequest() }),
408             withNotification({
409                 expiration: -1,
410                 type: 'info',
411                 loading: true,
412                 text: c('Info').t`Restoring items from trash`,
413             })
414         )({ payload })
417 export const itemBulkRestoreFailure = createAction(
418     'item::bulk::restore::failure',
419     withRequestFailure((payload: {}, error: unknown) =>
420         withNotification({
421             type: 'error',
422             text: c('Error').t`Failed to restore items from trash`,
423             error,
424         })({ payload, error })
425     )
428 export const itemBulkRestoreProgress = createAction(
429     'item::bulk::restore::progress',
430     withRequestProgress((payload: BatchItemRevisionIDs) => withCache({ payload }))
433 export const itemBulkRestoreSuccess = createAction(
434     'item::bulk::restore::success',
435     withRequestSuccess((payload: {}) =>
436         withNotification({
437             type: 'info',
438             text: c('Info').t`Selected items successfully restored from trash`,
439         })({ payload })
440     )
443 export const itemAutofilled = createAction('item::autofilled', (payload: SelectedItem) => ({ payload }));
445 export const itemsUsedSync = createAction('items::used::sync', (items: (SelectedItem & { lastUseTime: number })[]) =>
446     withCache({ payload: { items } })
449 export const itemPinIntent = createAction('item::pin::intent', (payload: UniqueItem) =>
450     pipe(
451         withRequest({ status: 'start', id: itemPinRequest(payload.shareId, payload.itemId) }),
452         withNotification({
453             type: 'info',
454             text: c('Info').t`Pinning item...`,
455             loading: true,
456             expiration: -1,
457         })
458     )({ payload })
461 export const itemPinSuccess = createAction(
462     'item::pin::success',
463     withRequestSuccess((payload: UniqueItem) =>
464         pipe(
465             withCache,
466             withNotification({
467                 type: 'info',
468                 text: c('Info').t`Item successfully pinned`,
469             })
470         )({ payload })
471     )
474 export const itemPinFailure = createAction(
475     'item::pin::failure',
476     withRequestFailure((error: unknown) =>
477         withNotification({
478             type: 'error',
479             text: c('Error').t`Failed to pin item`,
480             error,
481         })({ payload: {}, error })
482     )
485 export const itemUnpinIntent = createAction('item::unpin::intent', (payload: UniqueItem) =>
486     pipe(
487         withRequest({ status: 'start', id: itemUnpinRequest(payload.shareId, payload.itemId) }),
488         withNotification({
489             type: 'info',
490             text: c('Info').t`Unpinning item...`,
491             loading: true,
492             expiration: -1,
493         })
494     )({ payload })
497 export const itemUnpinSuccess = createAction(
498     'item::unpin::success',
499     withRequestSuccess((payload: UniqueItem) =>
500         pipe(
501             withCache,
502             withNotification({
503                 type: 'info',
504                 text: c('Info').t`Item successfully unpinned`,
505             })
506         )({ payload })
507     )
510 export const itemUnpinFailure = createAction(
511     'item::unpin::failure',
512     withRequestFailure((error: unknown) =>
513         withNotification({
514             type: 'error',
515             text: c('Error').t`Failed to unpin item`,
516             error,
517         })({ payload: {}, error })
518     )
521 export const itemHistoryIntent = createAction('item::history::intent', (payload: ItemRevisionsIntent) =>
522     withRequest({ status: 'start', id: itemRevisionsRequest(payload.shareId, payload.itemId) })({ payload })
525 export const itemHistorySuccess = createAction(
526     'item::history::success',
527     withRequestSuccess((payload: ItemRevisionsSuccess) => ({ payload }), { data: true })
530 export const itemHistoryFailure = createAction(
531     'item::history::failure',
532     withRequestFailure((error: unknown) =>
533         withNotification({
534             type: 'error',
535             text: c('Error').t`Failed to load item history`,
536             error,
537         })({ payload: {}, error })
538     )
541 export const secureLinksGet = requestActionsFactory<void, SecureLink[]>('secure-link::get')({
542     requestId: secureLinksGetRequest,
543     success: { config: { maxAge: UNIX_MINUTE } },
546 export const secureLinkCreate = requestActionsFactory<SecureLinkCreationDTO, SecureLink>('secure-link::create')({
547     requestId: ({ shareId, itemId }) => secureLinkCreateRequest(shareId, itemId),
548     success: { config: { data: true } },
549     failure: {
550         prepare: (error) =>
551             withNotification({
552                 type: 'error',
553                 text: c('Error').t`Secure link could not be created.`,
554                 error,
555             })({ payload: { error: getErrorMessage(error) } }),
556     },
559 export const secureLinkOpen = requestActionsFactory<SecureLinkQuery, SecureLinkItem>('secure-link::open')({
560     requestId: ({ token }) => secureLinkOpenRequest(token),
561     success: { config: { data: true } },
562     failure: {
563         config: { data: true },
564         prepare: (error) =>
565             withNotification({
566                 type: 'error',
567                 text: c('Error').t`Secure link could not be opened.`,
568                 error,
569             })({ payload: { error: getErrorMessage(error) } }),
570     },
573 export const secureLinkRemove = requestActionsFactory<SecureLinkDeleteDTO, SecureLinkDeleteDTO>('secure-link::remove')({
574     requestId: ({ shareId, itemId }) => secureLinkRemoveRequest(shareId, itemId),
575     intent: {
576         prepare: (payload) =>
577             withNotification({
578                 type: 'info',
579                 loading: true,
580                 text: c('Info').t`Removing secure link...`,
581             })({ payload }),
582     },
583     success: {
584         prepare: (payload) =>
585             withNotification({
586                 type: 'info',
587                 text: c('Info').t`The secure link has been removed`,
588             })({ payload }),
589     },
590     failure: {
591         prepare: (error) =>
592             withNotification({
593                 type: 'error',
594                 text: c('Error')
595                     .t`There was an error while removing the secure link. Please try again in a few minutes.`,
596                 error,
597             })({ payload: null }),
598     },
601 export const secureLinksRemoveInactive = requestActionsFactory<void, SecureLink[]>('secure-links::remove::inactive')({
602     requestId: secureLinksRemoveInactiveRequest,
603     intent: {
604         prepare: (payload) =>
605             withNotification({
606                 type: 'info',
607                 loading: true,
608                 text: c('Info').t`Removing all inactive secure links...`,
609             })({ payload }),
610     },
611     success: {
612         prepare: (payload) =>
613             withNotification({
614                 type: 'info',
615                 text: c('Info').t`All inactive secure links were removed`,
616             })({ payload }),
617     },
618     failure: {
619         prepare: (error) =>
620             withNotification({
621                 type: 'error',
622                 text: c('Error').t`Inactive secure links could not be removed.`,
623                 error,
624             })({ payload: null }),
625     },