Merge branch 'feat/inda-383-daily-stat' into 'main'
[ProtonMail-WebClient.git] / applications / drive / src / app / store / _views / useLegacyShareURLView.tsx
blobb69b78954dd5f46e670b2cee6b53aa1642bbf204
1 import { useEffect, useState } from 'react';
3 import { c } from 'ttag';
5 import { useNotifications } from '@proton/components';
6 import { useLoading } from '@proton/hooks';
7 import { SHARE_GENERATED_PASSWORD_LENGTH } from '@proton/shared/lib/drive/constants';
8 import type { SharedURLSessionKeyPayload } from '@proton/shared/lib/interfaces/drive/sharing';
10 import { sendErrorReport } from '../../utils/errorHandling';
11 import type { DecryptedLink } from '../_links';
12 import { useLink } from '../_links';
13 import type { ShareURLLEGACY } from '../_shares';
14 import { getSharedLink, splitGeneratedAndCustomPassword } from '../_shares';
15 import useLegacyShareUrl from '../_shares/useLegacyShareUrl';
17 const getLoadingMessage = (isLinkLoading: boolean, haveShareUrl: boolean, isFile: boolean) => {
18     if (isLinkLoading) {
19         return c('Info').t`Loading link`;
20     }
21     if (haveShareUrl) {
22         return isFile ? c('Info').t`Preparing link to file` : c('Info').t`Preparing link to folder`;
23     }
25     return isFile ? c('Info').t`Creating link to file` : c('Info').t`Creating link to folder`;
28 const getConfirmationMessage = (isFile: boolean) => {
29     return isFile
30         ? c('Info')
31               .t`This link will be permanently disabled. No one with this link will be able to access your file. To reshare the file, you will need a new link.`
32         : c('Info')
33               .t`This link will be permanently disabled. No one with this link will be able to access your folder. To reshare the folder, you will need a new link.`;
36 const getErrorMessage = (isCreationError: boolean, error: string) => {
37     if (isCreationError) {
38         return error
39             ? c('Info').t`Failed to generate a secure link. The reason is: ${error}`
40             : c('Info').t`Failed to generate a secure link. Try again later`;
41     }
42     return c('Info').t`Failed to open a secure link. The reason is: ${error}`;
45 const getSharingInfoMessage = (isFile: boolean) => {
46     return isFile
47         ? c('Info').t`Anyone with this link can access your file.`
48         : c('Info').t`Anyone with this link can access your folder.`;
51 const getPasswordProtectedSharingInfoMessage = (isFile: boolean) => {
52     return isFile
53         ? c('Info').t`Only the people with the link and the password can access this file.`
54         : c('Info').t`Only the people with the link and the password can access this folder.`;
57 /**
58  * useLinkView loads link if not cached yet.
59  */
60 export default function useLegacyShareURLView(shareId: string, linkId: string) {
61     const { getLink } = useLink();
62     const [shareUrlInfo, setShareUrlInfo] = useState<{
63         shareUrl: ShareURLLEGACY;
64         keyInfo: SharedURLSessionKeyPayload;
65     }>();
66     const { loadOrCreateShareUrl, updateShareUrl, deleteShareUrl } = useLegacyShareUrl();
68     const [sharedLink, setSharedLink] = useState('');
69     const [password, setPassword] = useState('');
70     const [initialExpiration, setInitialExpiration] = useState<number | null>(null);
71     const [error, setError] = useState('');
73     const [link, setLink] = useState<DecryptedLink>();
74     const [isLinkLoading, withLinkLoading] = useLoading(true);
75     const [isShareUrlLoading, withShareUrlLoading] = useLoading(true);
76     const [isSaving, withSaving] = useLoading();
77     const [isDeleting, withDeleting] = useLoading();
78     const { createNotification } = useNotifications();
80     const shareUrl = shareUrlInfo?.shareUrl;
82     const [, customPassword] = splitGeneratedAndCustomPassword(password, shareUrl);
84     useEffect(() => {
85         const abortController = new AbortController();
86         void withLinkLoading(
87             getLink(abortController.signal, shareId, linkId)
88                 .then((link) => {
89                     setLink(link);
90                 })
91                 .catch((err) => {
92                     setError(err);
93                     sendErrorReport(err);
94                 })
95         );
96         return () => {
97             abortController.abort();
98         };
99     }, [shareId, linkId]);
101     const ShareID = shareUrl?.shareId;
102     useEffect(() => {
103         const abortController = new AbortController();
104         void withShareUrlLoading(() => {
105             if (ShareID) {
106                 return Promise.resolve();
107             }
108             return loadOrCreateShareUrl(abortController.signal, shareId, linkId)
109                 .then((shareUrlInfo) => {
110                     setShareUrlInfo(shareUrlInfo);
111                     setPassword(shareUrlInfo.shareUrl.password);
112                     setInitialExpiration(shareUrlInfo.shareUrl.expirationTime);
113                     const sharedLink = getSharedLink(shareUrlInfo.shareUrl);
114                     if (sharedLink) {
115                         setSharedLink(sharedLink);
116                     }
117                 })
118                 .catch((err) => {
119                     setError(err);
120                 });
121         });
123         return () => {
124             abortController.abort();
125         };
126     }, [shareId, linkId, ShareID]);
128     const saveSharedLink = async (newCustomPassword?: string, newDuration?: number | null) => {
129         if (!shareUrl) {
130             return;
131         }
133         // Empty string as a newCustomPassword will remove it from the link.
134         // `undefined` is to leave the password as it is.
135         let newPassword = newCustomPassword;
136         if (newCustomPassword !== undefined && shareUrl.hasGeneratedPasswordIncluded) {
137             newPassword = password.substring(0, SHARE_GENERATED_PASSWORD_LENGTH) + newCustomPassword;
138         }
140         const update = () => {
141             const abortController = new AbortController();
142             return updateShareUrl(
143                 abortController.signal,
144                 {
145                     shareId: shareUrl.shareId,
146                     shareUrlId: shareUrl.shareUrlId,
147                     flags: shareUrl.flags,
148                     keyInfo: shareUrlInfo.keyInfo,
149                 },
150                 newDuration,
151                 newPassword
152             );
153         };
154         const updatedFields = await withSaving(update()).catch((error) => {
155             createNotification({
156                 type: 'error',
157                 text: c('Notification').t`Your settings failed to be saved`,
158             });
159             throw error;
160         });
161         createNotification({
162             text: c('Notification').t`Your settings have been changed successfully`,
163         });
164         setShareUrlInfo({
165             shareUrl: {
166                 ...shareUrl,
167                 ...updatedFields,
168             },
169             keyInfo: shareUrlInfo.keyInfo,
170         });
172         if (updatedFields && updatedFields.password !== undefined) {
173             setPassword(updatedFields.password);
174         }
175         if (updatedFields && updatedFields.expirationTime !== undefined) {
176             setInitialExpiration(updatedFields.expirationTime);
177         }
179         return updatedFields;
180     };
182     const deleteLink = async () => {
183         if (!link || !shareUrl) {
184             return;
185         }
187         return withDeleting(
188             deleteShareUrl(shareUrl.shareId, shareUrl.shareUrlId)
189                 .then(() => {
190                     createNotification({
191                         text: c('Notification').t`The link to your item was deleted`,
192                     });
193                 })
194                 .catch(() => {
195                     createNotification({
196                         type: 'error',
197                         text: c('Notification').t`The link to your item failed to be deleted`,
198                     });
199                 })
200         );
201     };
203     const loadingMessage =
204         isLinkLoading || isShareUrlLoading
205             ? getLoadingMessage(isLinkLoading, !!link?.shareUrl, !!link?.isFile)
206             : undefined;
207     const confirmationMessage = getConfirmationMessage(!!link?.isFile);
208     const haveError = error || (!isLinkLoading && !link) || (!isShareUrlLoading && !shareUrlInfo);
209     const errorMessage = haveError ? getErrorMessage(!link?.shareUrl, error) : undefined;
210     // Show message "protected by password" only when password is saved.
211     const sharedInfoMessage = customPassword
212         ? getPasswordProtectedSharingInfoMessage(!!link?.isFile)
213         : getSharingInfoMessage(!!link?.isFile);
215     return {
216         isDeleting,
217         isSaving,
218         name: link?.name || '', // If the link is not loaded we will return an error message anyway
219         initialExpiration,
220         customPassword,
221         sharedLink,
222         loadingMessage,
223         confirmationMessage,
224         errorMessage,
225         sharedInfoMessage,
226         hasCustomPassword: !!shareUrl?.hasCustomPassword,
227         hasGeneratedPasswordIncluded: !!shareUrl?.hasGeneratedPasswordIncluded,
228         hasExpirationTime: !!shareUrl?.expirationTime,
229         saveSharedLink,
230         deleteLink,
231     };