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