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';
10 ContactEmailsProvider,
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';
25 useDrivePublicSharingFlags,
28 useShareMemberViewZustand,
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';
41 modalTitleID?: string;
46 export function SharingModal({ shareId: rootShareId, linkId, onClose, ...modalProps }: Props & ModalStateProps) {
54 permissions: sharedLinkPermissions,
55 updatePermissions: updateSharedLinkPermissions,
60 hasGeneratedPasswordIncluded,
68 } = useShareURLView(rootShareId, linkId);
70 const isZustandShareMemberListEnabled = useFlag('DriveWebZustandShareMemberList');
71 const shareMemberList = isZustandShareMemberListEnabled
72 ? useShareMemberViewZustand(rootShareId, linkId)
73 : useShareMemberView(rootShareId, linkId);
86 updateMemberPermissions,
89 resendExternalInvitation,
90 updateInvitePermissions,
91 removeExternalInvitation,
92 updateExternalInvitePermissions,
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('');
105 state: includeInviteMessage,
106 toggle: toggleIncludeInviteMessage,
107 set: setIncludeInviteMessage,
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);
126 const handleSubmit = async (e: MouseEvent) => {
128 await addNewMembers({
130 permissions: selectedPermissions,
131 emailDetails: includeInviteMessage
133 message: inviteMessage,
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),
147 const handleCancel = () => {
151 const handlePermissionsChange = async (member: ShareMember, permissions: SHARE_MEMBER_PERMISSIONS) => {
152 await updateMemberPermissions({ ...member, permissions });
155 const handleDeleteLink = async () => {
156 await deleteLink(deleteShareIfEmpty);
159 const renderModalState = () => {
161 return <ErrorState onClose={onClose}>{errorMessage}</ErrorState>;
164 if (loadingMessage && !isShareUrlLoading) {
165 return <ModalContentLoader>{loadingMessage}</ModalContentLoader>;
169 <ContactEmailsProvider>
171 title={c('Title').t`Share ${name}`}
172 closeButtonProps={{ disabled: isClosedButtonDisabled }}
174 !isInvitationWorkflow
176 <Tooltip disabled={isSettingsDisabled} title={c('Info').t`Share via link settings`}>
184 onSaveLinkClick: saveSharedLink,
186 stopSharing: async () => {
190 havePublicSharedLink: !!sharedLink,
192 modificationDisabled: !hasGeneratedPasswordIncluded,
196 data-testid="share-modal-settings"
198 <Icon name="cog-wheel" />
205 !isInvitationWorkflow ? (
207 <DirectSharingAutocomplete
208 disabled={isDirectSharingAutocompleteDisabled}
209 existingEmails={existingEmails}
212 onRemove={removeInvitee}
213 onChangePermissions={setPermissions}
214 selectedPermissions={selectedPermissions}
216 <h2 className="text-lg text-semibold">{c('Info').t`People with access`}</h2>
221 {!isInvitationWorkflow ? (
223 <ModalTwoContent className="mb-5">
224 <DirectSharingListing
225 viewOnly={isDirectSharingDisabled}
228 isLoading={isLoading}
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}
242 {isShareUrlEnabled ? (
244 <hr className="mb-0.5 min-h-px" />
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}
262 <ModalTwoContent className="mb-5">
263 <DirectSharingAutocomplete
264 disabled={isDirectSharingAutocompleteDisabled}
265 existingEmails={existingEmails}
268 onRemove={removeInvitee}
269 onChangePermissions={setPermissions}
270 selectedPermissions={selectedPermissions}
272 <DirectSharingInviteMessage
274 inviteMessage={inviteMessage}
275 includeInviteMessage={includeInviteMessage}
276 onChangeInviteMessage={setInviteMessage}
277 onToggleIncludeInviteMessage={toggleIncludeInviteMessage}
281 <div className="w-full flex justify-space-between mb-4">
282 <Button disabled={isAdding} onClick={handleCancel}>{c('Action').t`Cancel`}</Button>
286 disabled={isSubmitDisabled}
288 onClick={handleSubmit}
290 {c('Action').t`Share`}
296 </ContactEmailsProvider>
305 onReset={(e: any) => {
309 disableCloseOnEscape={isSaving || isDeleting}
321 export const useLinkSharingModal = () => {
322 const { isSharingInviteAvailable } = useDriveSharingFlags();
323 return useModalTwoStatic(isSharingInviteAvailable ? SharingModal : ShareLinkModalLEGACY);