diff --git a/src/app/drive/components/ShareDialog/ShareDialog.tsx b/src/app/drive/components/ShareDialog/ShareDialog.tsx index 92d99cb4df..0f411c548a 100644 --- a/src/app/drive/components/ShareDialog/ShareDialog.tsx +++ b/src/app/drive/components/ShareDialog/ShareDialog.tsx @@ -1,31 +1,15 @@ -import { Popover } from '@headlessui/react'; import { SharingMeta } from '@internxt/sdk/dist/drive/share/types'; import { UserSettings } from '@internxt/sdk/dist/shared/types/userSettings'; -import { - ArrowLeft, - CaretDown, - Check, - CheckCircle, - Globe, - Link, - Question, - UserPlus, - Users, - X, -} from '@phosphor-icons/react'; +import { UserPlus } from '@phosphor-icons/react'; import { useTranslationContext } from 'app/i18n/provider/TranslationProvider'; -import { SharePasswordDisableDialog } from 'app/share/components/SharePasswordDisableDialog/SharePasswordDisableDialog'; -import { SharePasswordInputDialog } from 'app/share/components/SharePasswordInputDialog/SharePasswordInputDialog'; import { MAX_SHARED_NAME_LENGTH } from '../../../../views/Shared/SharedView'; -import { Avatar, Button, Checkbox, Loader, Modal } from '@internxt/ui'; -import { DELAY_SHOW_MS } from 'components/TooltipElement'; +import { Button, Modal } from '@internxt/ui'; import { RootState } from 'app/store'; import { useAppDispatch, useAppSelector } from 'app/store/hooks'; import { Role } from 'app/store/slices/sharedLinks/types'; import { uiActions } from 'app/store/slices/ui'; import { MouseEvent, useCallback, useEffect, useRef, useState } from 'react'; import { connect } from 'react-redux'; -import { Tooltip } from 'react-tooltip'; import errorService from 'services/error.service'; import localStorageService from 'services/local-storage.service'; import notificationsService, { ToastType } from 'app/notifications/services/notifications.service'; @@ -36,27 +20,19 @@ import { sharedThunks } from 'app/store/slices/sharedLinks'; import workspacesSelectors from 'app/store/slices/workspaces/workspaces.selectors'; import { DriveItemData } from 'app/drive/types'; import ShareInviteDialog from '../ShareInviteDialog/ShareInviteDialog'; -import StopSharingItemDialog from '../StopSharingItemDialog/StopSharingItemDialog'; import './ShareDialog.scss'; import envService from 'services/env.service'; -import { User } from './components/User'; -import { InvitedUsersSkeletonLoader } from './components/InvitedUsersSkeletonLoader'; -import { - AccessMode, - InvitedUserProps, - REQUEST_STATUS, - RequestProps, - RequestStatus, - UserRole, - ViewProps, - Views, -} from './types'; +import { AccessMode, InvitedUserProps, UserRole, ViewProps, Views } from './types'; import navigationService from 'services/navigation.service'; import { Service } from '@internxt/sdk/dist/drive/payments/types/tiers'; +import { SharePasswordInputDialog } from 'app/share/components/SharePasswordInputDialog/SharePasswordInputDialog'; import { UpgradeDialog } from '../UpgradeDialog/UpgradeDialog'; - -const isRequestPending = (status: RequestStatus): boolean => - status !== REQUEST_STATUS.DENIED && status !== REQUEST_STATUS.ACCEPTED; +import { SharePasswordDisableDialog } from 'app/share/components/SharePasswordDisableDialog/SharePasswordDisableDialog'; +import StopSharingItemDialog from '../StopSharingItemDialog/StopSharingItemDialog'; +import { ProtectWithPassword } from './components/GeneralView/ProtectWithPassword'; +import { UserRoleSelection } from './components/GeneralView/UserRoleSelection'; +import { InvitedUsersList } from './components/GeneralView/InvitedUsersList'; +import { Header } from './components/Header'; const cropSharedName = (name: string) => { if (name.length > MAX_SHARED_NAME_LENGTH) { @@ -97,7 +73,6 @@ const getLocalUserData = () => { return ownerData; }; -// TODO: THIS IS TEMPORARY, REMOVE WHEN NEED TO SHOW OTHER ROLES const filterEditorAndReader = (users: Role[]): Role[] => { return users.filter((user) => user.name === 'EDITOR' || user.name === 'READER'); }; @@ -130,7 +105,6 @@ const ShareDialog = (props: ShareDialogProps): JSX.Element => { const [isRestrictedSharingDialogOpen, setIsRestrictedSharingDialogOpen] = useState(false); const [isRestrictedPasswordDialogOpen, setIsRestrictedPasswordDialogOpen] = useState(false); - const [accessRequests, setAccessRequests] = useState([]); const [userOptionsEmail, setUserOptionsEmail] = useState(); const [userOptionsY, setUserOptionsY] = useState(0); const [view, setView] = useState('general'); @@ -142,6 +116,7 @@ const ShareDialog = (props: ShareDialogProps): JSX.Element => { item: itemToShare?.item as AdvancedSharedItem, userEmail: props?.user?.email, }); + const isProtectWithPasswordOptionAvailable = accessMode === 'public' && !isLoading && isUserOwner; const closeSelectedUserPopover = () => setSelectedUserListIndex(null); const resetDialogData = () => { @@ -150,7 +125,6 @@ const ShareDialog = (props: ShareDialogProps): JSX.Element => { setShowStopSharingConfirmation(false); setIsLoading(false); setInvitedUsers([]); - setAccessRequests([]); setUserOptionsEmail(undefined); setUserOptionsY(0); setView('general'); @@ -178,17 +152,6 @@ const ShareDialog = (props: ShareDialogProps): JSX.Element => { if (roles.length > 0 && isOpen) loadShareInfo(); }, [roles, isOpen]); - useEffect(() => { - const removeDeniedRequests = () => { - setAccessRequests((prevRequests) => prevRequests.filter((request) => isRequestPending(request.status))); - }; - - let timer; - if (accessRequests.some((req) => !isRequestPending(req.status))) timer = setTimeout(removeDeniedRequests, 500); - - return () => clearTimeout(timer); - }, [accessRequests]); - useEffect(() => { const currentInvitedUser = invitedUsers.find((user) => user.email === props.user.email); setCurrentUserFolderRole(currentInvitedUser?.roleName); @@ -209,7 +172,7 @@ const ShareDialog = (props: ShareDialogProps): JSX.Element => { })); setInvitedUsers(invitedUsersListParsed); - } catch (error) { + } catch { // the server throws an error when there are no users with shared item, // that means that the local user is the owner as there is nobody else with this shared file. if (isUserOwner) { @@ -262,29 +225,6 @@ const ShareDialog = (props: ShareDialogProps): JSX.Element => { } }; - const onAcceptRequest = (email: string, roleName: UserRole) => { - // TODO -> Accept user access request - setAccessRequests((prevRequests) => - prevRequests.map((request) => { - if (request.email === email) { - return { ...request, status: REQUEST_STATUS.ACCEPTED }; - } - return request; - }), - ); - }; - - const handleDenyRequest = (email: string) => { - setAccessRequests((prevRequests) => - prevRequests.map((request) => { - if (request.email === email) { - return { ...request, status: REQUEST_STATUS.DENIED }; - } - return request; - }), - ); - }; - const onClose = (): void => { dispatch(uiActions.setIsShareDialogOpen(false)); }; @@ -293,7 +233,7 @@ const ShareDialog = (props: ShareDialogProps): JSX.Element => { try { await copyTextToClipboard(`${envService.getVariable('hostname')}/shared/?folderuuid=${itemToShare?.item.uuid}`); notificationsService.show({ text: translate('shared-links.toast.copy-to-clipboard'), type: ToastType.Success }); - } catch (error) { + } catch { notificationsService.show({ text: translate('modals.shareModal.errors.copy-to-clipboard'), type: ToastType.Error, @@ -328,7 +268,7 @@ const ShareDialog = (props: ShareDialogProps): JSX.Element => { closeSelectedUserPopover(); }; - const onRemoveUser = async (user) => { + const onRemoveUser = async (user: InvitedUserProps) => { if (user) { const hasBeenRemoved = await dispatch( sharedThunks.removeUserFromSharedFolder({ @@ -498,42 +438,6 @@ const ShareDialog = (props: ShareDialogProps): JSX.Element => { } }; - const Header = (headerProps: ViewProps): JSX.Element => { - const headers = { - general: ( - <> - - {translate('modals.shareModal.title', { name: itemToShare?.item.name })} - -
- (isLoading ? null : onClose())} size={22} /> -
- - ), - invite: ( -
- setView('general')} size={24} /> - - {translate('modals.shareModal.invite.title')} - -
- ), - requests: ( -
- setView('general')} size={24} /> - - {translate('modals.shareModal.requests.title')} - -
- ), - }; - - return headers[headerProps.view]; - }; - const View = (viewProps: ViewProps): JSX.Element => { const view = { general: ( @@ -547,17 +451,6 @@ const ShareDialog = (props: ShareDialogProps): JSX.Element => { {translate('modals.shareModal.list.peopleWithAccess')}
- {accessRequests.length > 0 && ( - - )} {currentUserFolderRole !== 'reader' && !isWorkspace ? (
+ {/* List of users invited to the shared item */} -
- {invitedUsers.length === 0 && isLoading ? ( - <> - {Array.from({ length: 4 }, (_, i) => ( - - ))} - - ) : ( - invitedUsers - .sort((a, b) => { - if (a.email === props.user.email && b.email !== props.user.email) return -1; - return 0; - }) - .map((user, index) => ( - - )) - )} -
+
- {accessMode === 'public' && !isLoading && isUserOwner && ( -
-
-
- -

- {translate('modals.shareModal.protectSharingModal.protect')} -

- {isPasswordSharingAvailable ? ( - <> - - -

- {translate('modals.shareModal.protectSharingModal.protectTooltipText')} -

-
- - ) : ( -
-

{translate('actions.locked')}

-
- )} -
-
- {isPasswordProtected && ( - - )} -
+ {isProtectWithPasswordOptionAvailable && ( + setOpenPasswordInput(true)} + onPasswordCheckboxChange={onPasswordCheckboxChange} + /> )} -
-
-

{translate('modals.shareModal.general.generalAccess')}

- - - {({ open }) => ( - <> - - - - - - {({ close }) => ( - <> - {/* Public */} - - {/* Restricted */} - {!isWorkspace && ( - - )} - {/* Stop sharing */} - {(currentUserFolderRole === 'owner' || isUserOwner || props?.isDriveItem) && ( - - )} - - )} - - - )} - -
- -
+ setOpenPasswordInput(false)} @@ -798,7 +531,6 @@ const ShareDialog = (props: ShareDialogProps): JSX.Element => { onConfirmHandler={onDisablePassword} /> - {/* Stop sharing confirmation dialog */} setShowStopSharingConfirmation(false)} @@ -818,106 +550,6 @@ const ShareDialog = (props: ShareDialogProps): JSX.Element => { roles={inviteDialogRoles} /> ), - requests: ( -
- {accessRequests.length > 0 ? ( - accessRequests.map((request, index) => ( -
0 && isRequestPending(request.status) && 'border-t border-gray-5 pt-3' - } ${!isRequestPending(request.status) && 'hide-request'}`} - key={request.email + index} - > -
- - -
-

- {request.name} {request.lastname} -

-

- {request.email} -

-
- -
- - {({ open }) => ( - <> - - - - - - {({ close }) => ( - <> - {/* Reader */} - - - {/* Editor */} - - - )} - - - )} - - -
-
- - {request.message ? ( -
- {request.message.split('\n').map((line) => ( -

{line}

- ))} -
- ) : ( - <> - )} -
- )) - ) : ( -
-
- - {translate('modals.shareModal.requests.empty')} -
-
- )} -
- ), }; return view[viewProps.view]; @@ -926,7 +558,7 @@ const ShareDialog = (props: ShareDialogProps): JSX.Element => { return (
-
+
diff --git a/src/app/drive/components/ShareDialog/components/GeneralView/InvitedUsersList.tsx b/src/app/drive/components/ShareDialog/components/GeneralView/InvitedUsersList.tsx new file mode 100644 index 0000000000..4bdc26b15c --- /dev/null +++ b/src/app/drive/components/ShareDialog/components/GeneralView/InvitedUsersList.tsx @@ -0,0 +1,78 @@ +import { RefObject, MouseEvent } from 'react'; +import { useTranslationContext } from 'app/i18n/provider/TranslationProvider'; +import { User } from '../User'; +import { InvitedUsersSkeletonLoader } from '../InvitedUsersSkeletonLoader'; +import { InvitedUserProps } from '../../types'; +import { UserSettings } from '@internxt/sdk/dist/shared/types/userSettings'; + +interface InvitedUsersListProps { + userList: RefObject; + invitedUsers: InvitedUserProps[]; + areInvitedUsersLoading: boolean; + user: UserSettings; + openUserOptions: ( + e: MouseEvent, + user: InvitedUserProps, + selectedIndex: number | null, + ) => void; + userOptionsY: number; + selectedUserListIndex: number | null; + onRemoveUser: (user: InvitedUserProps) => void; + currentUserFolderRole?: string; + userOptionsEmail?: InvitedUserProps; + handleUserRoleChange: (email: string, role: string) => Promise; +} + +export const InvitedUsersList = ({ + currentUserFolderRole, + userList, + invitedUsers, + areInvitedUsersLoading, + user, + openUserOptions, + selectedUserListIndex, + userOptionsY, + onRemoveUser, + userOptionsEmail, + handleUserRoleChange, +}: InvitedUsersListProps) => { + const { translate } = useTranslationContext(); + const invitedUsersSorted = invitedUsers.toSorted((a, b) => { + if (a.email === user.email && b.email !== user.email) return -1; + return 0; + }); + const isListLoading = invitedUsers.length === 0 && areInvitedUsersLoading; + + return ( +
+ {isListLoading ? ( + <> + {Array.from({ length: 4 }, (_, i) => ( + + ))} + + ) : ( + invitedUsersSorted.map((invitedUser, index) => ( + + )) + )} +
+ ); +}; diff --git a/src/app/drive/components/ShareDialog/components/GeneralView/ProtectWithPassword.tsx b/src/app/drive/components/ShareDialog/components/GeneralView/ProtectWithPassword.tsx new file mode 100644 index 0000000000..d9464d596d --- /dev/null +++ b/src/app/drive/components/ShareDialog/components/GeneralView/ProtectWithPassword.tsx @@ -0,0 +1,58 @@ +import { Button, Checkbox } from '@internxt/ui'; +import { Question } from '@phosphor-icons/react'; +import { useTranslationContext } from 'app/i18n/provider/TranslationProvider'; +import { DELAY_SHOW_MS } from 'components'; +import { Tooltip } from 'react-tooltip'; + +interface ProtectWithPasswordProps { + isPasswordProtected: boolean; + onPasswordCheckboxChange: () => void; + isPasswordSharingAvailable: boolean; + onChangePassword: () => void; +} + +export const ProtectWithPassword = ({ + isPasswordProtected, + onPasswordCheckboxChange, + isPasswordSharingAvailable, + onChangePassword, +}: ProtectWithPasswordProps) => { + const { translate } = useTranslationContext(); + + return ( +
+
+
+ +

+ {translate('modals.shareModal.protectSharingModal.protect')} +

+ {isPasswordSharingAvailable ? ( + <> + + +

+ {translate('modals.shareModal.protectSharingModal.protectTooltipText')} +

+
+ + ) : ( +
+

{translate('actions.locked')}

+
+ )} +
+
+ {isPasswordProtected && ( + + )} +
+ ); +}; diff --git a/src/app/drive/components/ShareDialog/components/GeneralView/UserRoleSelection.tsx b/src/app/drive/components/ShareDialog/components/GeneralView/UserRoleSelection.tsx new file mode 100644 index 0000000000..432a327b9e --- /dev/null +++ b/src/app/drive/components/ShareDialog/components/GeneralView/UserRoleSelection.tsx @@ -0,0 +1,151 @@ +import { Popover } from '@headlessui/react'; +import { Button, Loader } from '@internxt/ui'; +import { CaretDown, Check, Globe, Link, Users } from '@phosphor-icons/react'; +import { useTranslationContext } from 'app/i18n/provider/TranslationProvider'; +import { AccessMode } from '../../types'; + +const LoadingState = ({ isLoading }: { isLoading: boolean }) => { + return ( +
+ {isLoading ? : } +
+ ); +}; + +interface UserRoleSelectionProps { + accessMode: AccessMode; + isLoading: boolean; + isUserOwner: boolean; + isWorkspace: boolean; + isRestrictedSharingAvailable: boolean; + isStopSharingAvailable: boolean; + onCopyLink: () => void; + changeAccess: (accessMode: AccessMode) => void; + setShowStopSharingConfirmation: (show: boolean) => void; +} + +export const UserRoleSelection = ({ + accessMode, + changeAccess, + isLoading, + isUserOwner, + isWorkspace, + isRestrictedSharingAvailable, + isStopSharingAvailable, + onCopyLink, + setShowStopSharingConfirmation, +}: UserRoleSelectionProps) => { + const { translate } = useTranslationContext(); + + return ( +
+
+

{translate('modals.shareModal.general.generalAccess')}

+ + + {({ open }) => ( + <> + + + + + + {({ close }) => ( + <> + {/* Public */} + + {/* Restricted */} + {!isWorkspace && ( + + )} + {/* Stop sharing */} + {isStopSharingAvailable && ( + + )} + + )} + + + )} + +
+ + +
+ ); +}; diff --git a/src/app/drive/components/ShareDialog/components/Header.tsx b/src/app/drive/components/ShareDialog/components/Header.tsx new file mode 100644 index 0000000000..5387a9410f --- /dev/null +++ b/src/app/drive/components/ShareDialog/components/Header.tsx @@ -0,0 +1,50 @@ +import { useTranslationContext } from 'app/i18n/provider/TranslationProvider'; +import { ArrowLeft, X } from '@phosphor-icons/react'; +import { Views } from '../types'; +import { ItemToShare } from 'app/store/slices/storage/types'; + +interface HeaderProps { + itemToShare: ItemToShare | null; + isLoading: boolean; + headerView: Views; + setView: (view: Views) => void; + onClose: () => void; +} + +export const Header = ({ headerView, isLoading, itemToShare, onClose, setView }: HeaderProps): JSX.Element => { + const { translate } = useTranslationContext(); + + const headers = { + general: ( + <> + + {translate('modals.shareModal.title', { name: itemToShare?.item.name })} + +
+ (isLoading ? null : onClose())} size={22} /> +
+ + ), + invite: ( +
+ setView('general')} size={24} /> + + {translate('modals.shareModal.invite.title')} + +
+ ), + requests: ( +
+ setView('general')} size={24} /> + + {translate('modals.shareModal.requests.title')} + +
+ ), + }; + + return headers[headerView]; +}; diff --git a/src/app/drive/components/ShareDialog/components/User.tsx b/src/app/drive/components/ShareDialog/components/User.tsx index 795e683b5a..5009c7ab20 100644 --- a/src/app/drive/components/ShareDialog/components/User.tsx +++ b/src/app/drive/components/ShareDialog/components/User.tsx @@ -4,6 +4,24 @@ import { MouseEvent } from 'react'; import { UserOptions } from './UserOptions'; import { InvitedUserProps } from '../types'; +interface UserProps { + user: InvitedUserProps; + listPosition: number | null; + translate: (key: string, props?: Record) => string; + openUserOptions: ( + event: MouseEvent, + user: InvitedUserProps, + listPosition: number | null, + ) => void; + selectedUserListIndex: number | null; + userOptionsY: number; + onRemoveUser: (user: InvitedUserProps) => void; + userOptionsEmail?: InvitedUserProps; + onChangeRole: (email: string, roleName: string) => void; + disableUserOptionsPanel: boolean; + disableRoleChange: boolean; +} + export const User = ({ user, listPosition, @@ -16,23 +34,7 @@ export const User = ({ onChangeRole, disableUserOptionsPanel, disableRoleChange, -}: { - user: InvitedUserProps; - listPosition: number | null; - translate: (key: string, props?: Record) => string; - openUserOptions: ( - event: MouseEvent, - user: InvitedUserProps, - listPosition: number | null, - ) => void; - selectedUserListIndex; - userOptionsY; - onRemoveUser; - userOptionsEmail; - onChangeRole: (email: string, roleName: string) => void; - disableUserOptionsPanel: boolean; - disableRoleChange: boolean; -}) => ( +}: UserProps) => (