Merge branch 'INDA-330-pii-update' into 'main'
[ProtonMail-WebClient.git] / applications / mail / src / app / hooks / composer / useCloseHandler.tsx
blob6b36e306775dd14f78df0a710be90ff580add589
1 import { useRef, useState } from 'react';
3 import { c } from 'ttag';
5 import type { Cancellable } from '@proton/components';
6 import { useHandler, useNotifications } from '@proton/components';
7 import useIsMounted from '@proton/hooks/useIsMounted';
8 import { wait } from '@proton/shared/lib/helpers/promise';
10 import type { SavingDraftNotificationAction } from '../../components/notifications/SavingDraftNotification';
11 import SavingDraftNotification from '../../components/notifications/SavingDraftNotification';
12 import { useOnCompose } from '../../containers/ComposeProvider';
13 import type { MessageState } from '../../store/messages/messagesTypes';
14 import type { PromiseHandlers } from '../usePromise';
15 import { ComposeTypes } from './useCompose';
17 export interface UseCloseHandlerParameters {
18     modelMessage: MessageState;
19     syncedMessage: MessageState;
20     lock: boolean;
21     ensureMessageContent: () => void;
22     uploadInProgress?: boolean;
23     promiseUpload?: Promise<void>;
24     pendingAutoSave: PromiseHandlers<void>;
25     autoSave: ((message: MessageState) => Promise<void>) & Cancellable;
26     saveNow: (message: MessageState) => Promise<void>;
27     onClose: () => void;
28     onDiscard: () => void;
29     onMessageAlreadySent: () => void;
30     hasNetworkError: boolean;
33 export const useCloseHandler = ({
34     modelMessage,
35     syncedMessage,
36     lock,
37     ensureMessageContent,
38     saveNow,
39     uploadInProgress,
40     pendingAutoSave,
41     promiseUpload,
42     onClose,
43     onDiscard,
44     onMessageAlreadySent,
45     hasNetworkError,
46 }: UseCloseHandlerParameters) => {
47     const { createNotification, hideNotification } = useNotifications();
48     const isMounted = useIsMounted();
49     const onCompose = useOnCompose();
51     // Indicates that the composer is saving a draft
52     const [saving, setSavingUnsafe] = useState(false);
54     // Manual save mostly used when closing, save state is not relevant then
55     const setSaving = (value: boolean) => {
56         if (isMounted()) {
57             setSavingUnsafe(value);
58         }
59     };
61     const notficationRef = useRef<SavingDraftNotificationAction>();
63     const handleManualSaveAfterUploads = useHandler(async (notificationID: number) => {
64         try {
65             await saveNow(modelMessage);
66             notficationRef.current?.saved();
67             await wait(3000);
68         } finally {
69             hideNotification(notificationID);
70             setSaving(false);
71         }
72     });
74     const handleManualSave = useHandler(async () => {
75         // Message already sent
76         if (syncedMessage.draftFlags?.isSentDraft) {
77             onMessageAlreadySent();
78             return;
79         }
81         const notificationID = createNotification({
82             text: (
83                 <SavingDraftNotification
84                     ref={notficationRef}
85                     onDiscard={() => {
86                         if (notificationID) {
87                             hideNotification(notificationID);
88                         }
89                         void onDiscard();
90                     }}
91                 />
92             ),
93             expiration: -1,
94             showCloseButton: false,
95         });
97         setSaving(true);
98         ensureMessageContent();
100         try {
101             await promiseUpload;
102         } catch (error: any) {
103             hideNotification(notificationID);
104             setSaving(false);
105             throw error;
106         }
108         // Split handlers to have the updated version of the message
109         await handleManualSaveAfterUploads(notificationID);
110     });
112     const handleClose = useHandler(async () => {
113         if (!lock) {
114             // Has to be done before the onClose call
115             // It needs to refer to composer components which will be disposed otherwise
116             ensureMessageContent();
117         }
119         // Closing the composer instantly, all the save process will be in background
120         onClose();
122         if (syncedMessage.draftFlags?.isSentDraft) {
123             createNotification({
124                 text: c('Error').t`This message has already been sent`,
125                 type: 'error',
126             });
127             return;
128         }
130         if (lock) {
131             // If the composer was locked, either it could have
132             // - failed at loading
133             // - still being created
134             // - still being loaded
135             // In all of those situation we don't need to save something, we can safely skip all the rest
136             return;
137         }
139         // Message requires to be saved in background
140         if (pendingAutoSave.isPending || uploadInProgress || hasNetworkError) {
141             try {
142                 await handleManualSave();
143             } catch {
144                 createNotification({
145                     text: c('Error').t`Draft could not be saved. Try again.`,
146                     type: 'error',
147                 });
148                 onCompose({ type: ComposeTypes.existingDraft, existingDraft: modelMessage, fromUndo: true });
149             }
150         }
151     });
153     return { handleClose, handleManualSave, saving };