diff --git a/src/app/drive/components/ShareDialog/ShareDialog.tsx b/src/app/drive/components/ShareDialog/ShareDialog.tsx index c85822653..a1e279c00 100644 --- a/src/app/drive/components/ShareDialog/ShareDialog.tsx +++ b/src/app/drive/components/ShareDialog/ShareDialog.tsx @@ -1,16 +1,13 @@ -import { SharingMeta } from '@internxt/sdk/dist/drive/share/types'; import { UserSettings } from '@internxt/sdk/dist/shared/types/userSettings'; import { UserPlus } from '@phosphor-icons/react'; import { useTranslationContext } from 'app/i18n/provider/TranslationProvider'; 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 { MouseEvent, useEffect, useRef } from 'react'; import { connect } from 'react-redux'; import errorService from 'services/error.service'; -import notificationsService, { ToastType } from 'app/notifications/services/notifications.service'; import shareService, { getSharingRoles } from 'app/share/services/share.service'; import { AdvancedSharedItem } from 'app/share/types'; import { isUserItemOwner } from 'views/Shared/utils/sharedViewUtils'; @@ -18,7 +15,7 @@ import { sharedThunks } from 'app/store/slices/sharedLinks'; import workspacesSelectors from 'app/store/slices/workspaces/workspaces.selectors'; import ShareInviteDialog from '../ShareInviteDialog/ShareInviteDialog'; import './ShareDialog.scss'; -import { AccessMode, InvitedUserProps, UserRole, ViewProps, Views } from './types'; +import { AccessMode, InvitedUserProps, ViewProps } 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'; @@ -29,8 +26,30 @@ import { ProtectWithPassword } from './components/GeneralView/ProtectWithPasswor import { UserRoleSelection } from './components/GeneralView/UserRoleSelection'; import { InvitedUsersList } from './components/GeneralView/InvitedUsersList'; import { Header } from './components/Header'; -import { cropSharedName, filterEditorAndReader, getLocalUserData, isAdvancedShareItem } from './utils'; +import { useShareDialogContext } from './context'; +import { + resetDialogData, + setAccessMode, + setCurrentUserFolderRole, + setInviteDialogRoles, + setIsLoading, + setIsPasswordProtected, + setIsRestrictedPasswordDialogOpen, + setIsRestrictedSharingDialogOpen, + setOpenPasswordDisableDialog, + setOpenPasswordInput, + setRoles, + setSelectedUserListIndex, + setSharingMeta, + setShowStopSharingConfirmation, + setUserOptionsEmail, + setUserOptionsY, + setView, +} from './context/ShareDialogContext.actions'; +import { filterEditorAndReader, isAdvancedShareItem } from './utils'; import { useShareItemActions } from './hooks/useShareItemActions'; +import { useShareItemInvitations } from './hooks/useShareItemInvitations'; +import { useShareItemUserRoles } from './hooks/useShareItemUserRoles'; export type ShareDialogProps = { user: UserSettings; @@ -40,6 +59,8 @@ export type ShareDialogProps = { onCloseDialog?: () => void; }; +const OWNER_ROLE = { id: 'NONE', name: 'owner' }; + const ShareDialog = (props: ShareDialogProps): JSX.Element => { const { onCloseDialog } = props; @@ -52,25 +73,27 @@ const ShareDialog = (props: ShareDialogProps): JSX.Element => { const isRestrictedSharingAvailable = userFeatures?.[Service.Drive].restrictedItemsSharing.enabled ?? false; const isPasswordSharingAvailable = userFeatures?.[Service.Drive].passwordProtectedSharing.enabled ?? false; - const [roles, setRoles] = useState([]); - const [inviteDialogRoles, setInviteDialogRoles] = useState([]); - - const [selectedUserListIndex, setSelectedUserListIndex] = useState(null); - const [accessMode, setAccessMode] = useState('restricted'); - const [showStopSharingConfirmation, setShowStopSharingConfirmation] = useState(false); - const [isLoading, setIsLoading] = useState(false); - const [invitedUsers, setInvitedUsers] = useState([]); - const [currentUserFolderRole, setCurrentUserFolderRole] = useState(''); - const [isPasswordProtected, setIsPasswordProtected] = useState(false); - const [openPasswordInput, setOpenPasswordInput] = useState(false); - const [openPasswordDisableDialog, setOpenPasswordDisableDialog] = useState(false); - const [sharingMeta, setSharingMeta] = useState(null); - const [isRestrictedSharingDialogOpen, setIsRestrictedSharingDialogOpen] = useState(false); - const [isRestrictedPasswordDialogOpen, setIsRestrictedPasswordDialogOpen] = useState(false); - - const [userOptionsEmail, setUserOptionsEmail] = useState(); - const [userOptionsY, setUserOptionsY] = useState(0); - const [view, setView] = useState('general'); + const { state, dispatch: actionDispatch } = useShareDialogContext(); + + const { + accessMode, + inviteDialogRoles, + invitedUsers, + isLoading, + isPasswordProtected, + isRestrictedSharingDialogOpen, + isRestrictedPasswordDialogOpen, + selectedUserListIndex, + showStopSharingConfirmation, + userOptionsEmail, + userOptionsY, + view, + openPasswordDisableDialog, + openPasswordInput, + roles, + currentUserFolderRole, + } = state; + const userList = useRef(null); const userOptions = useRef(null); @@ -80,41 +103,46 @@ const ShareDialog = (props: ShareDialogProps): JSX.Element => { userEmail: props?.user?.email, }); const isProtectWithPasswordOptionAvailable = accessMode === 'public' && !isLoading && isUserOwner; - const closeSelectedUserPopover = () => setSelectedUserListIndex(null); - const { getPrivateShareLink } = useShareItemActions({ - dispatch, - isPasswordSharingAvailable, + const closeSelectedUserPopover = () => actionDispatch(setSelectedUserListIndex(null)); + const { + onCopyLink, + onDisablePassword, + onPasswordCheckboxChange, + onSavePublicSharePassword, + onStopSharing, + onRemoveUser, + } = useShareItemActions({ itemToShare, + isPasswordSharingAvailable, + dispatch, onClose: () => dispatch(uiActions.setIsShareDialogOpen(false)), onShareItem: props.onShareItem, onStopSharingItem: props.onStopSharingItem, }); - const resetDialogData = () => { - setSelectedUserListIndex(null); - setAccessMode('public'); - setShowStopSharingConfirmation(false); - setIsLoading(false); - setInvitedUsers([]); - setUserOptionsEmail(undefined); - setUserOptionsY(0); - setView('general'); - setIsPasswordProtected(false); - setSharingMeta(null); - onCloseDialog?.(); - }; + const { getAndUpdateInvitedUsers, onInviteUser } = useShareItemInvitations({ + isUserOwner, + itemToShare, + }); + + const { changeAccess, handleUserRoleChange } = useShareItemUserRoles({ + isRestrictedSharingAvailable, + itemToShare, + }); useEffect(() => { - const OWNER_ROLE = { id: 'NONE', name: 'owner' }; if (isOpen) { getSharingRoles().then((roles) => { const parsedRoles = filterEditorAndReader(roles); - setRoles([...parsedRoles, OWNER_ROLE]); - setInviteDialogRoles(parsedRoles); + actionDispatch(setRoles([...parsedRoles, OWNER_ROLE])); + actionDispatch(setInviteDialogRoles(parsedRoles)); }); } - if (!isOpen) resetDialogData(); + if (!isOpen) { + actionDispatch(resetDialogData()); + onCloseDialog?.(); + } }, [isOpen]); useEffect(() => { @@ -125,38 +153,13 @@ const ShareDialog = (props: ShareDialogProps): JSX.Element => { useEffect(() => { const currentInvitedUser = invitedUsers.find((user) => user.email === props.user.email); - setCurrentUserFolderRole(currentInvitedUser?.roleName); + actionDispatch(setCurrentUserFolderRole(currentInvitedUser?.roleName)); }, [invitedUsers]); - const getAndUpdateInvitedUsers = useCallback(async () => { - if (!itemToShare?.item) return; - - try { - const invitedUsersList = await shareService.getUsersOfSharedFolder({ - itemType: itemToShare.item.isFolder ? 'folder' : 'file', - folderId: itemToShare.item.uuid, - }); - - const invitedUsersListParsed = invitedUsersList['users'].map((user) => ({ - ...user, - roleName: roles.find((role) => role.id === user.role.id)?.name.toLowerCase(), - })); - - setInvitedUsers(invitedUsersListParsed); - } 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) { - const ownerData = getLocalUserData(); - setInvitedUsers([{ ...ownerData, roleName: 'owner' }]); - } - } - }, [itemToShare, roles]); - const loadShareInfo = async () => { if (!itemToShare?.item) return; - setIsLoading(true); + actionDispatch(setIsLoading(true)); // Change object type of itemToShare to AdvancedSharedItem let shareAccessMode: AccessMode = 'public'; @@ -175,7 +178,7 @@ const ShareDialog = (props: ShareDialogProps): JSX.Element => { if (!isItemNotSharedYet) { try { const sharingData = await shareService.getSharingType(itemId, itemType); - setSharingMeta(sharingData); + actionDispatch(setSharingMeta(sharingData)); } catch (error) { errorService.reportError(error); } @@ -184,15 +187,15 @@ const ShareDialog = (props: ShareDialogProps): JSX.Element => { if (sharingType === 'private') { shareAccessMode = 'restricted'; } - setAccessMode(shareAccessMode); - setIsPasswordProtected(isAlreadyPasswordProtected); + actionDispatch(setAccessMode(shareAccessMode)); + actionDispatch(setIsPasswordProtected(isAlreadyPasswordProtected)); try { await getAndUpdateInvitedUsers(); } catch (error) { errorService.reportError(error); } finally { - setIsLoading(false); + actionDispatch(setIsLoading(false)); } }; @@ -200,152 +203,6 @@ const ShareDialog = (props: ShareDialogProps): JSX.Element => { dispatch(uiActions.setIsShareDialogOpen(false)); }; - const onCopyLink = async (): Promise => { - if (accessMode === 'restricted') { - await getPrivateShareLink(); - closeSelectedUserPopover(); - return; - } - - if (itemToShare?.item.uuid) { - const encryptionKey = isAdvancedShareItem(itemToShare.item) ? itemToShare?.item?.encryptionKey : undefined; - const sharingInfo = await shareService.getPublicShareLink( - itemToShare?.item.uuid, - itemToShare.item.isFolder ? 'folder' : 'file', - encryptionKey, - ); - if (sharingInfo) { - setSharingMeta(sharingInfo); - } - props.onShareItem?.(); - closeSelectedUserPopover(); - } - }; - - const onInviteUser = () => { - setView('invite'); - closeSelectedUserPopover(); - }; - - const onRemoveUser = async (user: InvitedUserProps) => { - if (user) { - const hasBeenRemoved = await dispatch( - sharedThunks.removeUserFromSharedFolder({ - itemType: itemToShare?.item.isFolder ? 'folder' : 'file', - itemId: itemToShare?.item.uuid as string, - userId: user.uuid, - userEmail: user.email, - }), - ); - - if (hasBeenRemoved.payload) { - setInvitedUsers((current) => current.filter((currentUser) => currentUser.uuid !== user.uuid)); - } - } - closeSelectedUserPopover(); - }; - - const onPasswordCheckboxChange = useCallback(() => { - if (!isPasswordSharingAvailable) { - setIsRestrictedPasswordDialogOpen(true); - return; - } - - if (!isPasswordProtected) { - setOpenPasswordInput(true); - } else { - setOpenPasswordDisableDialog(true); - } - }, [isPasswordProtected, isPasswordSharingAvailable]); - - const onSavePublicSharePassword = useCallback( - async (plainPassword: string) => { - try { - let sharingInfo = sharingMeta; - - if (!sharingInfo?.encryptedCode) { - const itemType = itemToShare?.item.isFolder ? 'folder' : 'file'; - const itemId = itemToShare?.item.uuid ?? ''; - sharingInfo = await shareService.createPublicShareFromOwnerUser(itemId, itemType, plainPassword); - setSharingMeta(sharingInfo); - } else { - await shareService.saveSharingPassword(sharingInfo.id, plainPassword, sharingInfo.encryptedCode); - } - - setIsPasswordProtected(true); - props.onShareItem?.(); - } catch (error) { - errorService.castError(error); - } finally { - setOpenPasswordInput(false); - } - }, - [sharingMeta, itemToShare], - ); - - const onDisablePassword = useCallback(async () => { - try { - if (sharingMeta) { - await shareService.removeSharingPassword(sharingMeta.id); - setIsPasswordProtected(false); - } - } catch (error) { - errorService.castError(error); - } finally { - setOpenPasswordDisableDialog(false); - } - }, [sharingMeta]); - - const changeAccess = async (mode: AccessMode) => { - closeSelectedUserPopover(); - - if (!isRestrictedSharingAvailable) { - setIsRestrictedSharingDialogOpen(true); - return; - } - - if (mode != accessMode) { - setIsLoading(true); - try { - const sharingType = mode === 'restricted' ? 'private' : 'public'; - const itemType = itemToShare?.item.isFolder ? 'folder' : 'file'; - const itemId = itemToShare?.item.uuid ?? ''; - - await shareService.updateSharingType(itemId, itemType, sharingType); - if (sharingType === 'public') { - const shareInfo = await shareService.createPublicShareFromOwnerUser(itemId, itemType); - setSharingMeta(shareInfo); - setIsPasswordProtected(false); - } - setAccessMode(mode); - } catch (error) { - errorService.reportError(error); - notificationsService.show({ - text: translate('modals.shareModal.errors.update-sharing-access'), - type: ToastType.Error, - }); - } - setIsLoading(false); - } - }; - - const onStopSharing = async () => { - setIsLoading(true); - const itemName = cropSharedName(itemToShare?.item.name as string); - await dispatch( - sharedThunks.stopSharingItem({ - itemType: itemToShare?.item.isFolder ? 'folder' : 'file', - itemId: itemToShare?.item.uuid as string, - itemName, - }), - ); - props.onShareItem?.(); - props.onStopSharingItem?.(); - setShowStopSharingConfirmation(false); - onClose(); - setIsLoading(false); - }; - const onUpgradePlan = () => { navigationService.openPreferencesDialog({ section: 'account', @@ -357,40 +214,16 @@ const ShareDialog = (props: ShareDialogProps): JSX.Element => { dispatch(uiActions.setIsPreferencesDialogOpen(true)); }; - const handleUserRoleChange = async (email: string, roleName: string) => { - try { - setSelectedUserListIndex(null); - const roleId = roles.find((role) => role.name.toLowerCase() === roleName.toLowerCase())?.id; - const sharingId = invitedUsers.find((invitedUser) => invitedUser.email === email)?.sharingId; - if (roleId && sharingId) { - await shareService.updateUserRoleOfSharedFolder({ - sharingId: sharingId, - newRoleId: roleId, - }); - const modifiedInvitedUsers = invitedUsers.map((invitedUser) => { - if (invitedUser.email === email) { - return { ...invitedUser, roleId, roleName: roleName as UserRole }; - } - return invitedUser; - }); - setInvitedUsers(modifiedInvitedUsers); - } - } catch (error) { - errorService.reportError(error); - notificationsService.show({ text: translate('modals.shareModal.errors.updatingRole'), type: ToastType.Error }); - } - }; - const openUserOptions = (e: any, user: InvitedUserProps, selectedIndex: number | null) => { const buttonY: number = ((e as MouseEvent).currentTarget as HTMLElement).getBoundingClientRect().top; const buttonHeight: number = ((e as MouseEvent).currentTarget as HTMLElement).offsetHeight; const userListY: number = userList.current ? userList.current.getBoundingClientRect().top : 0; - setUserOptionsY(buttonY + buttonHeight - userListY + 8); + actionDispatch(setUserOptionsY(buttonY + buttonHeight - userListY + 8)); if (selectedIndex === selectedUserListIndex) closeSelectedUserPopover(); - else setSelectedUserListIndex(selectedIndex); + else actionDispatch(setSelectedUserListIndex(selectedIndex)); - setUserOptionsEmail(user); + actionDispatch(setUserOptionsEmail(user)); if (userOptions.current) { userOptions.current.click(); @@ -443,7 +276,7 @@ const ShareDialog = (props: ShareDialogProps): JSX.Element => { setOpenPasswordInput(true)} + onChangePassword={() => actionDispatch(setOpenPasswordInput(true))} onPasswordCheckboxChange={onPasswordCheckboxChange} /> )} @@ -461,7 +294,7 @@ const ShareDialog = (props: ShareDialogProps): JSX.Element => { /> setOpenPasswordInput(false)} + onClose={() => actionDispatch(setOpenPasswordInput(false))} isOpen={openPasswordInput} onSavePassword={onSavePublicSharePassword} isAlreadyProtected={isPasswordProtected} @@ -501,8 +334,8 @@ const ShareDialog = (props: ShareDialogProps): JSX.Element => { ), invite: ( { - setView('general'); + onClose={() => { + actionDispatch(setView('general')); }} onInviteUser={onInviteUser} itemToShare={itemToShare?.item} @@ -517,7 +350,7 @@ const ShareDialog = (props: ShareDialogProps): JSX.Element => { return (
-
+
diff --git a/src/app/drive/components/ShareDialog/components/Header.tsx b/src/app/drive/components/ShareDialog/components/Header.tsx index 5387a9410..aafd3b699 100644 --- a/src/app/drive/components/ShareDialog/components/Header.tsx +++ b/src/app/drive/components/ShareDialog/components/Header.tsx @@ -1,18 +1,20 @@ import { useTranslationContext } from 'app/i18n/provider/TranslationProvider'; import { ArrowLeft, X } from '@phosphor-icons/react'; -import { Views } from '../types'; +import { useShareDialogContext } from '../context'; +import { setView } from '../context/ShareDialogContext.actions'; 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 => { +export const Header = ({ itemToShare, onClose }: HeaderProps): JSX.Element => { const { translate } = useTranslationContext(); + const { + state: { view, isLoading }, + dispatch: actionDispatch, + } = useShareDialogContext(); const headers = { general: ( @@ -30,7 +32,7 @@ export const Header = ({ headerView, isLoading, itemToShare, onClose, setView }: ), invite: (
- setView('general')} size={24} /> + actionDispatch(setView('general'))} size={24} /> {translate('modals.shareModal.invite.title')} @@ -38,7 +40,7 @@ export const Header = ({ headerView, isLoading, itemToShare, onClose, setView }: ), requests: (
- setView('general')} size={24} /> + actionDispatch(setView('general'))} size={24} /> {translate('modals.shareModal.requests.title')} @@ -46,5 +48,5 @@ export const Header = ({ headerView, isLoading, itemToShare, onClose, setView }: ), }; - return headers[headerView]; + return headers[view]; }; diff --git a/src/app/drive/components/ShareDialog/context/index.ts b/src/app/drive/components/ShareDialog/context/index.ts new file mode 100644 index 000000000..6841cd304 --- /dev/null +++ b/src/app/drive/components/ShareDialog/context/index.ts @@ -0,0 +1,4 @@ +export { ShareDialogProvider, useShareDialogContext } from './ShareDialogContextProvider'; +export { ShareDialogContext, ActionTypes } from './ShareDialogContext'; +export type { ShareDialogState, ShareDialogAction, ShareDialogContextProps } from './ShareDialogContext'; +export * as ShareDialogActions from './ShareDialogContext.actions'; diff --git a/src/app/drive/components/ShareDialog/hooks/useShareItemActions.ts b/src/app/drive/components/ShareDialog/hooks/useShareItemActions.ts index a051e2ae1..3d40aaed5 100644 --- a/src/app/drive/components/ShareDialog/hooks/useShareItemActions.ts +++ b/src/app/drive/components/ShareDialog/hooks/useShareItemActions.ts @@ -1,5 +1,5 @@ import { useCallback } from 'react'; -import { useShareDialogContext } from '../context/ShareDialogContextProvider'; +import { useShareDialogContext } from '../context'; import { removeUser, setIsLoading, diff --git a/src/app/drive/components/ShareDialog/hooks/useShareItemInvitations.ts b/src/app/drive/components/ShareDialog/hooks/useShareItemInvitations.ts index 719b16607..eed8efc5a 100644 --- a/src/app/drive/components/ShareDialog/hooks/useShareItemInvitations.ts +++ b/src/app/drive/components/ShareDialog/hooks/useShareItemInvitations.ts @@ -2,7 +2,7 @@ import { useCallback } from 'react'; import shareService from 'app/share/services/share.service'; import { setInvitedUsers, setSelectedUserListIndex, setView } from '../context/ShareDialogContext.actions'; import { getLocalUserData } from '../utils'; -import { useShareDialogContext } from '../context/ShareDialogContextProvider'; +import { useShareDialogContext } from '../context'; import { ItemToShare } from 'app/store/slices/storage/types'; interface ShareItemInvitationsProps { diff --git a/src/app/drive/components/ShareDialog/hooks/useShareItemUserRoles.ts b/src/app/drive/components/ShareDialog/hooks/useShareItemUserRoles.ts index 858084062..7589fd36e 100644 --- a/src/app/drive/components/ShareDialog/hooks/useShareItemUserRoles.ts +++ b/src/app/drive/components/ShareDialog/hooks/useShareItemUserRoles.ts @@ -11,7 +11,7 @@ import { import { AccessMode, UserRole } from '../types'; import notificationsService, { ToastType } from 'app/notifications/services/notifications.service'; import { useTranslationContext } from 'app/i18n/provider/TranslationProvider'; -import { useShareDialogContext } from '../context/ShareDialogContextProvider'; +import { useShareDialogContext } from '../context'; import { ItemToShare } from 'app/store/slices/storage/types'; import errorService from 'services/error.service'; diff --git a/test/e2e/tests/pages/loginPage.ts b/test/e2e/tests/pages/loginPage.ts index 7e6d05434..4d060e3c8 100644 --- a/test/e2e/tests/pages/loginPage.ts +++ b/test/e2e/tests/pages/loginPage.ts @@ -35,7 +35,7 @@ export class LoginPage { this.needHelp = this.page.getByRole('link', { name: 'Need help?' }); this.wrongCredentials = this.page.locator('[class="flex flex-row items-start pt-1"] span'); //drive - this.driveTitle = this.page.locator('span[class="max-w-sm flex-1 cursor-pointer truncate undefined"]'); + this.driveTitle = this.page.locator('span.max-w-sm.flex-1.truncate').first(); //account recovery this.accountRecoveryTitle = this.page.locator( 'h1[class="text-3xl font-medium text-gray-100"]:has-text("Account recovery")', diff --git a/test/e2e/tests/pages/signUpPage.ts b/test/e2e/tests/pages/signUpPage.ts index f45b018dc..e10b33841 100644 --- a/test/e2e/tests/pages/signUpPage.ts +++ b/test/e2e/tests/pages/signUpPage.ts @@ -41,7 +41,7 @@ export class SignUpPage { //LOGIN PAGE this.loginTitle = this.page.getByRole('heading', { level: 1 }); //DRIVE PAGE - this.driveTitle = this.page.locator('[class="max-w-sm flex-1 cursor-pointer truncate undefined"]'); + this.driveTitle = this.page.locator('span.max-w-sm.flex-1.truncate').first(); } async typeInEmail(email: string) {