Merge branch 'feat/inda-383-daily-stat' into 'main'
[ProtonMail-WebClient.git] / applications / drive / src / app / components / modals / ShareLinkModal / ShareLinkModal.tsx
blob25ca188d2f8e62792729fc6a1277c5699283b6eb
1 /* eslint react-hooks/rules-of-hooks: 0 */
2 import type { MouseEvent } from 'react';
3 import { useMemo, useState } from 'react';
5 import { c } from 'ttag';
7 import { Button } from '@proton/atoms';
8 import type { ModalStateProps } from '@proton/components';
9 import {
10     ContactEmailsProvider,
11     Icon,
12     ModalTwo,
13     ModalTwoContent,
14     ModalTwoFooter,
15     ModalTwoHeader,
16     Tooltip,
17     useModalTwoStatic,
18     useToggle,
19 } from '@proton/components';
20 import { SHARE_MEMBER_PERMISSIONS } from '@proton/shared/lib/drive/permissions';
21 import useFlag from '@proton/unleash/useFlag';
23 import type { ShareMember } from '../../../store';
24 import {
25     useDrivePublicSharingFlags,
26     useDriveSharingFlags,
27     useShareMemberView,
28     useShareMemberViewZustand,
29     useShareURLView,
30 } from '../../../store';
31 import { useIsPaid } from '../../../store/_user';
32 import ModalContentLoader from '../ModalContentLoader';
33 import { DirectSharingAutocomplete, DirectSharingListing, useShareInvitees } from './DirectSharing';
34 import { DirectSharingInviteMessage } from './DirectSharing/DirectSharingInviteMessage';
35 import ErrorState from './ErrorState';
36 import { PublicSharing } from './PublicSharing';
37 import { useLinkSharingSettingsModal } from './ShareLinkSettingsModal';
38 import { ShareLinkModalLEGACY } from './_legacy/ShareLinkModalLEGACY';
40 interface Props {
41     modalTitleID?: string;
42     shareId: string;
43     linkId: string;
46 export function SharingModal({ shareId: rootShareId, linkId, onClose, ...modalProps }: Props & ModalStateProps) {
47     const {
48         customPassword,
49         initialExpiration,
50         name,
51         deleteLink,
52         stopSharing,
53         sharedLink,
54         permissions: sharedLinkPermissions,
55         updatePermissions: updateSharedLinkPermissions,
56         hasSharedLink,
57         errorMessage,
58         loadingMessage,
59         confirmationMessage,
60         hasGeneratedPasswordIncluded,
61         createSharedLink,
62         saveSharedLink,
63         isSaving,
64         isDeleting,
65         isCreating,
66         isShareUrlLoading,
67         isShareUrlEnabled,
68     } = useShareURLView(rootShareId, linkId);
70     const isZustandShareMemberListEnabled = useFlag('DriveWebZustandShareMemberList');
71     const shareMemberList = isZustandShareMemberListEnabled
72         ? useShareMemberViewZustand(rootShareId, linkId)
73         : useShareMemberView(rootShareId, linkId);
75     const {
76         volumeId,
77         members,
78         invitations,
79         externalInvitations,
80         existingEmails,
81         isLoading,
82         isAdding,
83         isShared,
84         addNewMembers,
85         removeMember,
86         updateMemberPermissions,
87         removeInvitation,
88         resendInvitation,
89         resendExternalInvitation,
90         updateInvitePermissions,
91         removeExternalInvitation,
92         updateExternalInvitePermissions,
93         deleteShareIfEmpty,
94     } = shareMemberList;
96     const { isDirectSharingDisabled } = useDriveSharingFlags();
97     const { isPublicEditModeEnabled } = useDrivePublicSharingFlags();
98     const isPaid = useIsPaid();
100     const [settingsModal, showSettingsModal] = useLinkSharingSettingsModal();
102     const [selectedPermissions, setPermissions] = useState<SHARE_MEMBER_PERMISSIONS>(SHARE_MEMBER_PERMISSIONS.EDITOR);
103     const [inviteMessage, setInviteMessage] = useState('');
104     const {
105         state: includeInviteMessage,
106         toggle: toggleIncludeInviteMessage,
107         set: setIncludeInviteMessage,
108     } = useToggle(true);
110     const isClosedButtonDisabled = isSaving || isDeleting || isCreating || isAdding;
111     // It's important in this order. As if it's hasSharedLink is true, isShared is true as well (even if cache not updated)
112     const isSharedAvailable = hasSharedLink || isShared;
113     const isSettingsDisabled = isShareUrlLoading || isSaving || isDeleting || isCreating || !isSharedAvailable;
114     const { invitees, add: addInvitee, remove: removeInvitee, clean: cleanInvitees } = useShareInvitees(existingEmails);
116     const isInvitationWorkflow = !!invitees.length;
117     const isShareWithAnyoneLoading = isShareUrlLoading || isDeleting || isCreating;
118     const isDirectSharingAutocompleteDisabled = isAdding || isLoading || isDirectSharingDisabled;
120     const cleanFields = () => {
121         setInviteMessage('');
122         setIncludeInviteMessage(true);
123         cleanInvitees();
124     };
126     const handleSubmit = async (e: MouseEvent) => {
127         e.preventDefault();
128         await addNewMembers({
129             invitees,
130             permissions: selectedPermissions,
131             emailDetails: includeInviteMessage
132                 ? {
133                       message: inviteMessage,
134                       itemName: name,
135                   }
136                 : undefined,
137         });
138         cleanFields();
139     };
141     // Here we check if the email address is already in invited members
142     const isSubmitDisabled = useMemo(
143         () => !invitees.length || !!invitees.find((invitee) => invitee.isLoading || invitee.error),
144         [invitees]
145     );
147     const handleCancel = () => {
148         cleanFields();
149     };
151     const handlePermissionsChange = async (member: ShareMember, permissions: SHARE_MEMBER_PERMISSIONS) => {
152         await updateMemberPermissions({ ...member, permissions });
153     };
155     const handleDeleteLink = async () => {
156         await deleteLink(deleteShareIfEmpty);
157     };
159     const renderModalState = () => {
160         if (errorMessage) {
161             return <ErrorState onClose={onClose}>{errorMessage}</ErrorState>;
162         }
164         if (loadingMessage && !isShareUrlLoading) {
165             return <ModalContentLoader>{loadingMessage}</ModalContentLoader>;
166         }
168         return (
169             <ContactEmailsProvider>
170                 <ModalTwoHeader
171                     title={c('Title').t`Share ${name}`}
172                     closeButtonProps={{ disabled: isClosedButtonDisabled }}
173                     actions={
174                         !isInvitationWorkflow
175                             ? [
176                                   <Tooltip disabled={isSettingsDisabled} title={c('Info').t`Share via link settings`}>
177                                       <Button
178                                           icon
179                                           shape="ghost"
180                                           onClick={() =>
181                                               showSettingsModal({
182                                                   customPassword,
183                                                   initialExpiration,
184                                                   onSaveLinkClick: saveSharedLink,
185                                                   isDeleting,
186                                                   stopSharing: async () => {
187                                                       await stopSharing();
188                                                       onClose();
189                                                   },
190                                                   havePublicSharedLink: !!sharedLink,
191                                                   confirmationMessage,
192                                                   modificationDisabled: !hasGeneratedPasswordIncluded,
193                                                   isShareUrlEnabled,
194                                               })
195                                           }
196                                           data-testid="share-modal-settings"
197                                       >
198                                           <Icon name="cog-wheel" />
199                                       </Button>
200                                   </Tooltip>,
201                               ]
202                             : undefined
203                     }
204                     additionalContent={
205                         !isInvitationWorkflow ? (
206                             <>
207                                 <DirectSharingAutocomplete
208                                     disabled={isDirectSharingAutocompleteDisabled}
209                                     existingEmails={existingEmails}
210                                     invitees={invitees}
211                                     onAdd={addInvitee}
212                                     onRemove={removeInvitee}
213                                     onChangePermissions={setPermissions}
214                                     selectedPermissions={selectedPermissions}
215                                 />
216                                 <h2 className="text-lg text-semibold">{c('Info').t`People with access`}</h2>
217                             </>
218                         ) : undefined
219                     }
220                 />
221                 {!isInvitationWorkflow ? (
222                     <>
223                         <ModalTwoContent className="mb-5">
224                             <DirectSharingListing
225                                 viewOnly={isDirectSharingDisabled}
226                                 volumeId={volumeId}
227                                 linkId={linkId}
228                                 isLoading={isLoading}
229                                 members={members}
230                                 invitations={invitations}
231                                 externalInvitations={externalInvitations}
232                                 onPermissionsChange={handlePermissionsChange}
233                                 onMemberRemove={removeMember}
234                                 onInvitationRemove={removeInvitation}
235                                 onInvitationPermissionsChange={updateInvitePermissions}
236                                 onExternalInvitationRemove={removeExternalInvitation}
237                                 onExternalInvitationPermissionsChange={updateExternalInvitePermissions}
238                                 onResendInvitationEmail={resendInvitation}
239                                 onResendExternalInvitationEmail={resendExternalInvitation}
240                             />
241                         </ModalTwoContent>
242                         {isShareUrlEnabled ? (
243                             <>
244                                 <hr className="mb-0.5 min-h-px" />
245                                 <ModalTwoFooter>
246                                     <PublicSharing
247                                         // TODO: Implement Upgrade to drive plus modal in case user is free user
248                                         viewOnly={!isPublicEditModeEnabled || !isPaid}
249                                         createSharedLink={createSharedLink}
250                                         isLoading={isShareWithAnyoneLoading}
251                                         publicSharedLink={sharedLink}
252                                         publicSharedLinkPermissions={sharedLinkPermissions}
253                                         onChangePermissions={updateSharedLinkPermissions}
254                                         deleteSharedLink={handleDeleteLink}
255                                     />
256                                 </ModalTwoFooter>
257                             </>
258                         ) : null}
259                     </>
260                 ) : (
261                     <>
262                         <ModalTwoContent className="mb-5">
263                             <DirectSharingAutocomplete
264                                 disabled={isDirectSharingAutocompleteDisabled}
265                                 existingEmails={existingEmails}
266                                 invitees={invitees}
267                                 onAdd={addInvitee}
268                                 onRemove={removeInvitee}
269                                 onChangePermissions={setPermissions}
270                                 selectedPermissions={selectedPermissions}
271                             />
272                             <DirectSharingInviteMessage
273                                 isAdding={isAdding}
274                                 inviteMessage={inviteMessage}
275                                 includeInviteMessage={includeInviteMessage}
276                                 onChangeInviteMessage={setInviteMessage}
277                                 onToggleIncludeInviteMessage={toggleIncludeInviteMessage}
278                             />
279                         </ModalTwoContent>
280                         <ModalTwoFooter>
281                             <div className="w-full flex justify-space-between mb-4">
282                                 <Button disabled={isAdding} onClick={handleCancel}>{c('Action').t`Cancel`}</Button>
283                                 <Button
284                                     type="submit"
285                                     color="norm"
286                                     disabled={isSubmitDisabled}
287                                     loading={isAdding}
288                                     onClick={handleSubmit}
289                                 >
290                                     {c('Action').t`Share`}
291                                 </Button>
292                             </div>
293                         </ModalTwoFooter>
294                     </>
295                 )}
296             </ContactEmailsProvider>
297         );
298     };
300     return (
301         <>
302             <ModalTwo
303                 as="form"
304                 onClose={onClose}
305                 onReset={(e: any) => {
306                     e.preventDefault();
307                     onClose();
308                 }}
309                 disableCloseOnEscape={isSaving || isDeleting}
310                 size="large"
311                 fullscreenOnMobile
312                 {...modalProps}
313             >
314                 {renderModalState()}
315             </ModalTwo>
316             {settingsModal}
317         </>
318     );
321 export const useLinkSharingModal = () => {
322     const { isSharingInviteAvailable } = useDriveSharingFlags();
323     return useModalTwoStatic(isSharingInviteAvailable ? SharingModal : ShareLinkModalLEGACY);