From 3e7d15728c3bf566e5cbfc82f13ee303de3a7059 Mon Sep 17 00:00:00 2001 From: Xavier Abad Date: Tue, 20 Jan 2026 13:12:31 +0100 Subject: [PATCH 1/8] refactor(share): extract logic to custom hooks --- .../components/ShareDialog/ShareDialog.tsx | 438 ++++-------------- .../ShareDialog/ShareDialogWrapper.tsx | 12 + .../ShareDialog/components/Header.tsx | 21 +- .../context/ShareDialogContext.actions.ts | 121 +++++ .../ShareDialog/context/ShareDialogContext.ts | 81 ++++ .../context/ShareDialogContextProvider.tsx | 145 ++++++ .../components/ShareDialog/context/index.ts | 4 + .../ShareDialog/hooks/useShareItemActions.ts | 172 +++++++ .../hooks/useShareItemInvitations.ts | 77 +++ .../hooks/useShareItemUserRoles.ts | 90 ++++ .../components/ShareDialog/utils/index.ts | 45 ++ src/app/store/slices/storage/storage.model.ts | 11 +- src/app/store/slices/storage/types.ts | 10 + .../DriveExplorer/DriveExplorer.tsx | 4 +- src/views/Shared/SharedView.tsx | 4 +- 15 files changed, 874 insertions(+), 361 deletions(-) create mode 100644 src/app/drive/components/ShareDialog/ShareDialogWrapper.tsx create mode 100644 src/app/drive/components/ShareDialog/context/ShareDialogContext.actions.ts create mode 100644 src/app/drive/components/ShareDialog/context/ShareDialogContext.ts create mode 100644 src/app/drive/components/ShareDialog/context/ShareDialogContextProvider.tsx create mode 100644 src/app/drive/components/ShareDialog/context/index.ts create mode 100644 src/app/drive/components/ShareDialog/hooks/useShareItemActions.ts create mode 100644 src/app/drive/components/ShareDialog/hooks/useShareItemInvitations.ts create mode 100644 src/app/drive/components/ShareDialog/hooks/useShareItemUserRoles.ts create mode 100644 src/app/drive/components/ShareDialog/utils/index.ts diff --git a/src/app/drive/components/ShareDialog/ShareDialog.tsx b/src/app/drive/components/ShareDialog/ShareDialog.tsx index 408fcc3783..16fcf7359e 100644 --- a/src/app/drive/components/ShareDialog/ShareDialog.tsx +++ b/src/app/drive/components/ShareDialog/ShareDialog.tsx @@ -1,38 +1,22 @@ import { Popover } from '@headlessui/react'; -import { SharingMeta } from '@internxt/sdk/dist/drive/share/types'; import { UserSettings } from '@internxt/sdk/dist/shared/types/userSettings'; import { CaretDown, CheckCircle, UserPlus } from '@phosphor-icons/react'; import { useTranslationContext } from 'app/i18n/provider/TranslationProvider'; -import { MAX_SHARED_NAME_LENGTH } from '../../../../views/Shared/SharedView'; import { Avatar, 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 localStorageService from 'services/local-storage.service'; -import notificationsService, { ToastType } from 'app/notifications/services/notifications.service'; -import shareService, { copyTextToClipboard, getSharingRoles } from 'app/share/services/share.service'; +import shareService, { getSharingRoles } from 'app/share/services/share.service'; import { AdvancedSharedItem } from 'app/share/types'; import { isUserItemOwner } from 'views/Shared/utils/sharedViewUtils'; 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 './ShareDialog.scss'; -import envService from 'services/env.service'; -import { - AccessMode, - InvitedUserProps, - REQUEST_STATUS, - RequestProps, - RequestStatus, - 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'; @@ -43,52 +27,41 @@ import { ProtectWithPassword } from './components/GeneralView/ProtectWithPasswor import { UserRoleSelection } from './components/GeneralView/UserRoleSelection'; import { InvitedUsersList } from './components/GeneralView/InvitedUsersList'; import { Header } from './components/Header'; - -const isRequestPending = (status: RequestStatus): boolean => - status !== REQUEST_STATUS.DENIED && status !== REQUEST_STATUS.ACCEPTED; - -const cropSharedName = (name: string) => { - if (name.length > MAX_SHARED_NAME_LENGTH) { - return name.substring(0, 32).concat('...'); - } else { - return name; - } -}; - -type ShareDialogProps = { +import { useShareDialogContext } from './context'; +import { + resetDialogData, + setAccessMode, + setAccessRequests, + setCurrentUserFolderRole, + setInviteDialogRoles, + setIsLoading, + setIsPasswordProtected, + setIsRestrictedPasswordDialogOpen, + setIsRestrictedSharingDialogOpen, + setOpenPasswordDisableDialog, + setOpenPasswordInput, + setRoles, + setSelectedUserListIndex, + setSharingMeta, + setShowStopSharingConfirmation, + setUserOptionsEmail, + setUserOptionsY, + setView, +} from './context/ShareDialogContext.actions'; +import { filterEditorAndReader, isAdvancedShareItem, isRequestPending } from './utils'; +import { useShareItemActions } from './hooks/useShareItemActions'; +import { useShareItemInvitations } from './hooks/useShareItemInvitations'; +import { useShareItemUserRoles } from './hooks/useShareItemUserRoles'; + +export interface ShareDialogProps { user: UserSettings; isDriveItem?: boolean; onShareItem?: () => void; onStopSharingItem?: () => void; onCloseDialog?: () => void; -}; - -const isAdvancedShareItem = (item: DriveItemData | AdvancedSharedItem): item is AdvancedSharedItem => { - return item['encryptionKey']; -}; - -const getLocalUserData = () => { - const user = localStorageService.getUser() as UserSettings; - const ownerData = { - name: user.name, - lastname: user.lastname, - email: user.email, - sharingId: '', - avatar: user.avatar, - uuid: user.uuid, - role: { - id: 'NONE', - name: 'OWNER', - createdAt: '', - updatedAt: '', - }, - }; - return ownerData; -}; +} -const filterEditorAndReader = (users: Role[]): Role[] => { - return users.filter((user) => user.name === 'EDITOR' || user.name === 'READER'); -}; +const OWNER_ROLE = { id: 'NONE', name: 'owner' }; const ShareDialog = (props: ShareDialogProps): JSX.Element => { const { onCloseDialog } = props; @@ -102,26 +75,28 @@ 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 [accessRequests, setAccessRequests] = useState([]); - const [userOptionsEmail, setUserOptionsEmail] = useState(); - const [userOptionsY, setUserOptionsY] = useState(0); - const [view, setView] = useState('general'); + const { state, dispatch: actionDispatch } = useShareDialogContext(); + + const { + accessMode, + accessRequests, + inviteDialogRoles, + invitedUsers, + isLoading, + isPasswordProtected, + isRestrictedSharingDialogOpen, + isRestrictedPasswordDialogOpen, + selectedUserListIndex, + showStopSharingConfirmation, + userOptionsEmail, + userOptionsY, + view, + openPasswordDisableDialog, + openPasswordInput, + roles, + currentUserFolderRole, + } = state; + const userList = useRef(null); const userOptions = useRef(null); @@ -131,34 +106,46 @@ const ShareDialog = (props: ShareDialogProps): JSX.Element => { userEmail: props?.user?.email, }); const isProtectWithPasswordOptionAvailable = accessMode === 'public' && !isLoading && isUserOwner; - const closeSelectedUserPopover = () => setSelectedUserListIndex(null); - - const resetDialogData = () => { - setSelectedUserListIndex(null); - setAccessMode('public'); - setShowStopSharingConfirmation(false); - setIsLoading(false); - setInvitedUsers([]); - setAccessRequests([]); - setUserOptionsEmail(undefined); - setUserOptionsY(0); - setView('general'); - setIsPasswordProtected(false); - setSharingMeta(null); - onCloseDialog?.(); - }; + 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 { getAndUpdateInvitedUsers, handleDenyRequest, onAcceptRequest, 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(() => { @@ -169,7 +156,8 @@ const ShareDialog = (props: ShareDialogProps): JSX.Element => { useEffect(() => { const removeDeniedRequests = () => { - setAccessRequests((prevRequests) => prevRequests.filter((request) => isRequestPending(request.status))); + const filteredRequests = accessRequests.filter((request) => isRequestPending(request.status)); + actionDispatch(setAccessRequests(filteredRequests)); }; let timer; @@ -180,38 +168,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'; @@ -230,7 +193,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); } @@ -239,203 +202,22 @@ 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)); } }; - 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)); }; - const getPrivateShareLink = async () => { - 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 { - notificationsService.show({ - text: translate('modals.shareModal.errors.copy-to-clipboard'), - type: ToastType.Error, - }); - } - }; - - 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', @@ -447,30 +229,6 @@ 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; @@ -544,7 +302,7 @@ const ShareDialog = (props: ShareDialogProps): JSX.Element => { setOpenPasswordInput(true)} + onChangePassword={() => actionDispatch(setOpenPasswordInput(true))} onPasswordCheckboxChange={onPasswordCheckboxChange} /> )} @@ -562,7 +320,7 @@ const ShareDialog = (props: ShareDialogProps): JSX.Element => { /> setOpenPasswordInput(false)} + onClose={() => actionDispatch(setOpenPasswordInput(false))} isOpen={openPasswordInput} onSavePassword={onSavePublicSharePassword} isAlreadyProtected={isPasswordProtected} @@ -602,7 +360,7 @@ const ShareDialog = (props: ShareDialogProps): JSX.Element => { ), invite: ( { + onClose={() => { setView('general'); }} onInviteUser={onInviteUser} @@ -718,7 +476,7 @@ const ShareDialog = (props: ShareDialogProps): JSX.Element => { return (
-
+
diff --git a/src/app/drive/components/ShareDialog/ShareDialogWrapper.tsx b/src/app/drive/components/ShareDialog/ShareDialogWrapper.tsx new file mode 100644 index 0000000000..7e32b8e9d6 --- /dev/null +++ b/src/app/drive/components/ShareDialog/ShareDialogWrapper.tsx @@ -0,0 +1,12 @@ +import { ShareDialogProvider } from './context'; +import ShareDialog, { ShareDialogProps } from './ShareDialog'; + +const ShareDialogWrapper = (props: Omit) => { + return ( + + + + ); +}; + +export default ShareDialogWrapper; diff --git a/src/app/drive/components/ShareDialog/components/Header.tsx b/src/app/drive/components/ShareDialog/components/Header.tsx index aa7dc581f6..aafd3b6996 100644 --- a/src/app/drive/components/ShareDialog/components/Header.tsx +++ b/src/app/drive/components/ShareDialog/components/Header.tsx @@ -1,17 +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; - isLoading: boolean; - headerView: Views; - setView: (view: Views) => void; + itemToShare: ItemToShare | null; 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: ( @@ -29,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')} @@ -37,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')} @@ -45,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/ShareDialogContext.actions.ts b/src/app/drive/components/ShareDialog/context/ShareDialogContext.actions.ts new file mode 100644 index 0000000000..b5ddf34c7f --- /dev/null +++ b/src/app/drive/components/ShareDialog/context/ShareDialogContext.actions.ts @@ -0,0 +1,121 @@ +import { SharingMeta } from '@internxt/sdk/dist/drive/share/types'; +import { Role } from 'app/store/slices/sharedLinks/types'; +import { AccessMode, InvitedUserProps, RequestProps, Views } from '../types'; +import { ShareDialogAction, ActionTypes } from './ShareDialogContext'; + +// View actions +export const setView = (payload: Views): ShareDialogAction => ({ + type: ActionTypes.SET_VIEW, + payload, +}); + +export const setIsLoading = (payload: boolean): ShareDialogAction => ({ + type: ActionTypes.SET_IS_LOADING, + payload, +}); + +// Roles actions +export const setRoles = (payload: Role[]): ShareDialogAction => ({ + type: ActionTypes.SET_ROLES, + payload, +}); + +export const setInviteDialogRoles = (payload: Role[]): ShareDialogAction => ({ + type: ActionTypes.SET_INVITE_DIALOG_ROLES, + payload, +}); + +// Access & sharing actions +export const setAccessMode = (payload: AccessMode): ShareDialogAction => ({ + type: ActionTypes.SET_ACCESS_MODE, + payload, +}); + +export const setIsPasswordProtected = (payload: boolean): ShareDialogAction => ({ + type: ActionTypes.SET_IS_PASSWORD_PROTECTED, + payload, +}); + +export const setSharingMeta = (payload: SharingMeta | null): ShareDialogAction => ({ + type: ActionTypes.SET_SHARING_META, + payload, +}); + +// Invited users actions +export const setInvitedUsers = (payload: InvitedUserProps[]): ShareDialogAction => ({ + type: ActionTypes.SET_INVITED_USERS, + payload, +}); + +export const setCurrentUserFolderRole = (payload?: string): ShareDialogAction => ({ + type: ActionTypes.SET_CURRENT_USER_FOLDER_ROLE, + payload, +}); + +export const updateUserRole = (payload: { email: string; roleId: string; roleName: string }): ShareDialogAction => ({ + type: ActionTypes.UPDATE_USER_ROLE, + payload, +}); + +export const removeUser = (payload: string): ShareDialogAction => ({ + type: ActionTypes.REMOVE_USER, + payload, +}); + +// Access requests actions +export const setAccessRequests = (payload: RequestProps[]): ShareDialogAction => ({ + type: ActionTypes.SET_ACCESS_REQUESTS, + payload, +}); + +export const updateRequestStatus = (payload: { email: string; status: RequestProps['status'] }): ShareDialogAction => ({ + type: ActionTypes.UPDATE_REQUEST_STATUS, + payload, +}); + +// User options popover actions +export const setSelectedUserListIndex = (payload: number | null): ShareDialogAction => ({ + type: ActionTypes.SET_SELECTED_USER_LIST_INDEX, + payload, +}); + +export const setUserOptionsEmail = (payload?: InvitedUserProps): ShareDialogAction => ({ + type: ActionTypes.SET_USER_OPTIONS_EMAIL, + payload, +}); + +export const setUserOptionsY = (payload: number): ShareDialogAction => ({ + type: ActionTypes.SET_USER_OPTIONS_Y, + payload, +}); + +// Dialog states actions +export const setShowStopSharingConfirmation = (payload: boolean): ShareDialogAction => ({ + type: ActionTypes.SET_SHOW_STOP_SHARING_CONFIRMATION, + payload, +}); + +export const setOpenPasswordInput = (payload: boolean): ShareDialogAction => ({ + type: ActionTypes.SET_OPEN_PASSWORD_INPUT, + payload, +}); + +export const setOpenPasswordDisableDialog = (payload: boolean): ShareDialogAction => ({ + type: ActionTypes.SET_OPEN_PASSWORD_DISABLE_DIALOG, + payload, +}); + +export const setIsRestrictedSharingDialogOpen = (payload: boolean): ShareDialogAction => ({ + type: ActionTypes.SET_IS_RESTRICTED_SHARING_DIALOG_OPEN, + payload, +}); + +export const setIsRestrictedPasswordDialogOpen = (payload: boolean): ShareDialogAction => ({ + type: ActionTypes.SET_IS_RESTRICTED_PASSWORD_DIALOG_OPEN, + payload, +}); + +// Batch actions +export const resetDialogData = (): ShareDialogAction => ({ + type: ActionTypes.RESET_DIALOG_DATA, +}); diff --git a/src/app/drive/components/ShareDialog/context/ShareDialogContext.ts b/src/app/drive/components/ShareDialog/context/ShareDialogContext.ts new file mode 100644 index 0000000000..f77b913a28 --- /dev/null +++ b/src/app/drive/components/ShareDialog/context/ShareDialogContext.ts @@ -0,0 +1,81 @@ +import { createContext, Dispatch } from 'react'; +import { SharingMeta } from '@internxt/sdk/dist/drive/share/types'; +import { Role } from 'app/store/slices/sharedLinks/types'; +import { AccessMode, InvitedUserProps, RequestProps, Views } from '../types'; + +export interface ShareDialogState { + view: Views; + isLoading: boolean; + roles: Role[]; + inviteDialogRoles: Role[]; + accessMode: AccessMode; + isPasswordProtected: boolean; + sharingMeta: SharingMeta | null; + invitedUsers: InvitedUserProps[]; + currentUserFolderRole?: string; + accessRequests: RequestProps[]; + selectedUserListIndex: number | null; + userOptionsEmail?: InvitedUserProps; + userOptionsY: number; + showStopSharingConfirmation: boolean; + openPasswordInput: boolean; + openPasswordDisableDialog: boolean; + isRestrictedSharingDialogOpen: boolean; + isRestrictedPasswordDialogOpen: boolean; +} + +export type ShareDialogAction = + | { type: 'SET_VIEW'; payload: Views } + | { type: 'SET_IS_LOADING'; payload: boolean } + | { type: 'SET_ROLES'; payload: Role[] } + | { type: 'SET_INVITE_DIALOG_ROLES'; payload: Role[] } + | { type: 'SET_ACCESS_MODE'; payload: AccessMode } + | { type: 'SET_IS_PASSWORD_PROTECTED'; payload: boolean } + | { type: 'SET_SHARING_META'; payload: SharingMeta | null } + | { type: 'SET_INVITED_USERS'; payload: InvitedUserProps[] } + | { type: 'SET_CURRENT_USER_FOLDER_ROLE'; payload?: string } + | { type: 'UPDATE_USER_ROLE'; payload: { email: string; roleId: string; roleName: string } } + | { type: 'REMOVE_USER'; payload: string } + | { type: 'SET_ACCESS_REQUESTS'; payload: RequestProps[] } + | { type: 'UPDATE_REQUEST_STATUS'; payload: { email: string; status: RequestProps['status'] } } + | { type: 'SET_SELECTED_USER_LIST_INDEX'; payload: number | null } + | { type: 'SET_USER_OPTIONS_EMAIL'; payload?: InvitedUserProps } + | { type: 'SET_USER_OPTIONS_Y'; payload: number } + | { type: 'SET_SHOW_STOP_SHARING_CONFIRMATION'; payload: boolean } + | { type: 'SET_OPEN_PASSWORD_INPUT'; payload: boolean } + | { type: 'SET_OPEN_PASSWORD_DISABLE_DIALOG'; payload: boolean } + | { type: 'SET_IS_RESTRICTED_SHARING_DIALOG_OPEN'; payload: boolean } + | { type: 'SET_IS_RESTRICTED_PASSWORD_DIALOG_OPEN'; payload: boolean } + | { type: 'RESET_DIALOG_DATA' }; + +export const ActionTypes = { + SET_VIEW: 'SET_VIEW', + SET_IS_LOADING: 'SET_IS_LOADING', + SET_ROLES: 'SET_ROLES', + SET_INVITE_DIALOG_ROLES: 'SET_INVITE_DIALOG_ROLES', + SET_ACCESS_MODE: 'SET_ACCESS_MODE', + SET_IS_PASSWORD_PROTECTED: 'SET_IS_PASSWORD_PROTECTED', + SET_SHARING_META: 'SET_SHARING_META', + SET_INVITED_USERS: 'SET_INVITED_USERS', + SET_CURRENT_USER_FOLDER_ROLE: 'SET_CURRENT_USER_FOLDER_ROLE', + UPDATE_USER_ROLE: 'UPDATE_USER_ROLE', + REMOVE_USER: 'REMOVE_USER', + SET_ACCESS_REQUESTS: 'SET_ACCESS_REQUESTS', + UPDATE_REQUEST_STATUS: 'UPDATE_REQUEST_STATUS', + SET_SELECTED_USER_LIST_INDEX: 'SET_SELECTED_USER_LIST_INDEX', + SET_USER_OPTIONS_EMAIL: 'SET_USER_OPTIONS_EMAIL', + SET_USER_OPTIONS_Y: 'SET_USER_OPTIONS_Y', + SET_SHOW_STOP_SHARING_CONFIRMATION: 'SET_SHOW_STOP_SHARING_CONFIRMATION', + SET_OPEN_PASSWORD_INPUT: 'SET_OPEN_PASSWORD_INPUT', + SET_OPEN_PASSWORD_DISABLE_DIALOG: 'SET_OPEN_PASSWORD_DISABLE_DIALOG', + SET_IS_RESTRICTED_SHARING_DIALOG_OPEN: 'SET_IS_RESTRICTED_SHARING_DIALOG_OPEN', + SET_IS_RESTRICTED_PASSWORD_DIALOG_OPEN: 'SET_IS_RESTRICTED_PASSWORD_DIALOG_OPEN', + RESET_DIALOG_DATA: 'RESET_DIALOG_DATA', +} as const; + +export interface ShareDialogContextProps { + state: ShareDialogState; + dispatch: Dispatch; +} + +export const ShareDialogContext = createContext(undefined); diff --git a/src/app/drive/components/ShareDialog/context/ShareDialogContextProvider.tsx b/src/app/drive/components/ShareDialog/context/ShareDialogContextProvider.tsx new file mode 100644 index 0000000000..60246057fe --- /dev/null +++ b/src/app/drive/components/ShareDialog/context/ShareDialogContextProvider.tsx @@ -0,0 +1,145 @@ +import React, { useContext, useReducer, ReactNode, useMemo } from 'react'; +import { + ShareDialogAction, + ShareDialogState, + ShareDialogContext, + ShareDialogContextProps, + ActionTypes, +} from './ShareDialogContext'; +import { UserRole } from '../types'; + +const initialState: ShareDialogState = { + view: 'general', + isLoading: false, + roles: [], + inviteDialogRoles: [], + accessMode: 'restricted', + isPasswordProtected: false, + sharingMeta: null, + invitedUsers: [], + currentUserFolderRole: undefined, + accessRequests: [], + selectedUserListIndex: null, + userOptionsEmail: undefined, + userOptionsY: 0, + showStopSharingConfirmation: false, + openPasswordInput: false, + openPasswordDisableDialog: false, + isRestrictedSharingDialogOpen: false, + isRestrictedPasswordDialogOpen: false, +}; + +const reducer = (state: ShareDialogState, action: ShareDialogAction): ShareDialogState => { + switch (action.type) { + case ActionTypes.SET_VIEW: + return { ...state, view: action.payload }; + + case ActionTypes.SET_IS_LOADING: + return { ...state, isLoading: action.payload }; + + case ActionTypes.SET_ROLES: + return { ...state, roles: action.payload }; + + case ActionTypes.SET_INVITE_DIALOG_ROLES: + return { ...state, inviteDialogRoles: action.payload }; + + case ActionTypes.SET_ACCESS_MODE: + return { ...state, accessMode: action.payload }; + + case ActionTypes.SET_IS_PASSWORD_PROTECTED: + return { ...state, isPasswordProtected: action.payload }; + + case ActionTypes.SET_SHARING_META: + return { ...state, sharingMeta: action.payload }; + + case ActionTypes.SET_INVITED_USERS: + return { ...state, invitedUsers: action.payload }; + + case ActionTypes.SET_CURRENT_USER_FOLDER_ROLE: + return { ...state, currentUserFolderRole: action.payload }; + + case ActionTypes.UPDATE_USER_ROLE: + return { + ...state, + invitedUsers: state.invitedUsers.map((user) => + user.email === action.payload.email + ? { ...user, roleId: action.payload.roleId, roleName: action.payload.roleName as UserRole } + : user, + ), + }; + + case ActionTypes.REMOVE_USER: + return { + ...state, + invitedUsers: state.invitedUsers.filter((user) => user.uuid !== action.payload), + }; + + case ActionTypes.SET_ACCESS_REQUESTS: + return { ...state, accessRequests: action.payload }; + + case ActionTypes.UPDATE_REQUEST_STATUS: + return { + ...state, + accessRequests: state.accessRequests.map((request) => + request.email === action.payload.email ? { ...request, status: action.payload.status } : request, + ), + }; + + case ActionTypes.SET_SELECTED_USER_LIST_INDEX: + return { ...state, selectedUserListIndex: action.payload }; + + case ActionTypes.SET_USER_OPTIONS_EMAIL: + return { ...state, userOptionsEmail: action.payload }; + + case ActionTypes.SET_USER_OPTIONS_Y: + return { ...state, userOptionsY: action.payload }; + + case ActionTypes.SET_SHOW_STOP_SHARING_CONFIRMATION: + return { ...state, showStopSharingConfirmation: action.payload }; + + case ActionTypes.SET_OPEN_PASSWORD_INPUT: + return { ...state, openPasswordInput: action.payload }; + + case ActionTypes.SET_OPEN_PASSWORD_DISABLE_DIALOG: + return { ...state, openPasswordDisableDialog: action.payload }; + + case ActionTypes.SET_IS_RESTRICTED_SHARING_DIALOG_OPEN: + return { ...state, isRestrictedSharingDialogOpen: action.payload }; + + case ActionTypes.SET_IS_RESTRICTED_PASSWORD_DIALOG_OPEN: + return { ...state, isRestrictedPasswordDialogOpen: action.payload }; + + case ActionTypes.RESET_DIALOG_DATA: + return { + ...initialState, + roles: state.roles, + inviteDialogRoles: state.inviteDialogRoles, + }; + + default: + return state; + } +}; + +const ShareDialogProvider: React.FC<{ children: ReactNode }> = ({ children }) => { + const [state, dispatch] = useReducer(reducer, initialState); + + const contextValue: ShareDialogContextProps = useMemo(() => { + return { + state, + dispatch, + }; + }, [state, dispatch]); + + return {children}; +}; + +const useShareDialogContext = (): ShareDialogContextProps => { + const context = useContext(ShareDialogContext); + if (!context) { + throw new Error('useShareDialogContext must be used within a ShareDialogProvider'); + } + return context; +}; + +export { ShareDialogProvider, useShareDialogContext }; 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 0000000000..6841cd3041 --- /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 new file mode 100644 index 0000000000..c9dcb89d1f --- /dev/null +++ b/src/app/drive/components/ShareDialog/hooks/useShareItemActions.ts @@ -0,0 +1,172 @@ +import { useCallback } from 'react'; +import { useShareDialogContext } from '../context'; +import { + removeUser, + setIsLoading, + setIsPasswordProtected, + setIsRestrictedPasswordDialogOpen, + setOpenPasswordDisableDialog, + setOpenPasswordInput, + setSelectedUserListIndex, + setSharingMeta, + setShowStopSharingConfirmation, +} from '../context/ShareDialogContext.actions'; +import { cropSharedName, isAdvancedShareItem } from '../utils'; +import { sharedThunks } from 'app/store/slices/sharedLinks'; +import { ItemToShare } from 'app/store/slices/storage/types'; +import shareService, { copyTextToClipboard } from 'app/share/services/share.service'; +import notificationsService, { ToastType } from 'app/notifications/services/notifications.service'; +import { envService, errorService } from 'services'; +import { useTranslationContext } from 'app/i18n/provider/TranslationProvider'; +import { InvitedUserProps } from '../types'; + +interface ShareItemActionsProps { + itemToShare: ItemToShare | null; + isPasswordSharingAvailable: boolean; + dispatch: any; + onClose: () => void; + onShareItem?: () => void; + onStopSharingItem?: () => void; +} + +export const useShareItemActions = ({ + itemToShare, + isPasswordSharingAvailable, + dispatch, + ...props +}: ShareItemActionsProps) => { + const { translate } = useTranslationContext(); + const { state, dispatch: actionDispatch } = useShareDialogContext(); + + const { accessMode, sharingMeta, isPasswordProtected } = state; + + const getPrivateShareLink = async () => { + 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 { + notificationsService.show({ + text: translate('modals.shareModal.errors.copy-to-clipboard'), + type: ToastType.Error, + }); + } + }; + + const onCopyLink = async (): Promise => { + if (accessMode === 'restricted') { + await getPrivateShareLink(); + actionDispatch(setSelectedUserListIndex(null)); + 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) { + actionDispatch(setSharingMeta(sharingInfo)); + } + props.onShareItem?.(); + actionDispatch(setSelectedUserListIndex(null)); + } + }; + + const onPasswordCheckboxChange = useCallback(() => { + if (!isPasswordSharingAvailable) { + actionDispatch(setIsRestrictedPasswordDialogOpen(true)); + return; + } + + if (!isPasswordProtected) { + actionDispatch(setOpenPasswordInput(true)); + } else { + actionDispatch(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); + actionDispatch(setSharingMeta(sharingInfo)); + } else { + await shareService.saveSharingPassword(sharingInfo.id, plainPassword, sharingInfo.encryptedCode); + } + + actionDispatch(setIsPasswordProtected(true)); + props.onShareItem?.(); + } catch (error) { + errorService.castError(error); + } finally { + actionDispatch(setOpenPasswordInput(false)); + } + }, + [sharingMeta, itemToShare], + ); + + const onDisablePassword = useCallback(async () => { + try { + if (sharingMeta) { + await shareService.removeSharingPassword(sharingMeta.id); + actionDispatch(setIsPasswordProtected(false)); + } + } catch (error) { + errorService.castError(error); + } finally { + actionDispatch(setOpenPasswordDisableDialog(false)); + } + }, [sharingMeta]); + + const onStopSharing = async () => { + actionDispatch(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?.(); + actionDispatch(setShowStopSharingConfirmation(false)); + props.onClose(); + setIsLoading(false); + }; + + 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) { + actionDispatch(removeUser(user.uuid)); + } + } + props.onClose(); + }; + + return { + onPasswordCheckboxChange, + onSavePublicSharePassword, + onDisablePassword, + onCopyLink, + onStopSharing, + getPrivateShareLink, + onRemoveUser, + }; +}; diff --git a/src/app/drive/components/ShareDialog/hooks/useShareItemInvitations.ts b/src/app/drive/components/ShareDialog/hooks/useShareItemInvitations.ts new file mode 100644 index 0000000000..99ad85c9e2 --- /dev/null +++ b/src/app/drive/components/ShareDialog/hooks/useShareItemInvitations.ts @@ -0,0 +1,77 @@ +import shareService from 'app/share/services/share.service'; +import { useCallback } from 'react'; +import { + setAccessRequests, + setInvitedUsers, + setSelectedUserListIndex, + setView, +} from '../context/ShareDialogContext.actions'; +import { getLocalUserData } from '../utils'; +import { useShareDialogContext } from '../context'; +import { REQUEST_STATUS, UserRole } from '../types'; + +export const useShareItemInvitations = ({ itemToShare, isUserOwner }) => { + const { state, dispatch: actionDispatch } = useShareDialogContext(); + + const { roles, accessRequests } = state; + + 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(), + })); + + actionDispatch(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(); + actionDispatch(setInvitedUsers([{ ...ownerData, roleName: 'owner' }])); + } + } + }, [itemToShare, roles, isUserOwner, actionDispatch]); + + const onAcceptRequest = (email: string, roleName: UserRole) => { + // TODO -> Accept user access request + const modifiedAccessRequests = accessRequests.map((request) => { + if (request.email === email) { + return { ...request, status: REQUEST_STATUS.ACCEPTED }; + } + return request; + }); + + actionDispatch(setAccessRequests(modifiedAccessRequests)); + }; + + const handleDenyRequest = (email: string) => { + const deniedRequests = accessRequests.map((request) => { + if (request.email === email) { + return { ...request, status: REQUEST_STATUS.DENIED }; + } + return request; + }); + + actionDispatch(setAccessRequests(deniedRequests)); + }; + + const onInviteUser = () => { + actionDispatch(setView('invite')); + actionDispatch(setSelectedUserListIndex(null)); + }; + + return { + getAndUpdateInvitedUsers, + onAcceptRequest, + handleDenyRequest, + onInviteUser, + }; +}; diff --git a/src/app/drive/components/ShareDialog/hooks/useShareItemUserRoles.ts b/src/app/drive/components/ShareDialog/hooks/useShareItemUserRoles.ts new file mode 100644 index 0000000000..0e4952647a --- /dev/null +++ b/src/app/drive/components/ShareDialog/hooks/useShareItemUserRoles.ts @@ -0,0 +1,90 @@ +import shareService from 'app/share/services/share.service'; +import { + setAccessMode, + setInvitedUsers, + setIsLoading, + setIsPasswordProtected, + setIsRestrictedSharingDialogOpen, + setSelectedUserListIndex, + setSharingMeta, +} from '../context/ShareDialogContext.actions'; +import { AccessMode, UserRole } from '../types'; +import notificationsService, { ToastType } from 'app/notifications/services/notifications.service'; +import { errorService } from 'services'; +import { useTranslationContext } from 'app/i18n/provider/TranslationProvider'; +import { useShareDialogContext } from '../context'; +import { ItemToShare } from 'app/store/slices/storage/types'; + +interface ShareItemUserRolesProps { + isRestrictedSharingAvailable: boolean; + itemToShare: ItemToShare | null; +} + +export const useShareItemUserRoles = ({ isRestrictedSharingAvailable, itemToShare }: ShareItemUserRolesProps) => { + const { translate } = useTranslationContext(); + const { state, dispatch: actionDispatch } = useShareDialogContext(); + + const { accessMode, roles, invitedUsers } = state; + + const changeAccess = async (mode: AccessMode) => { + actionDispatch(setSelectedUserListIndex(null)); + + if (!isRestrictedSharingAvailable) { + actionDispatch(setIsRestrictedSharingDialogOpen(true)); + return; + } + + if (mode != accessMode) { + actionDispatch(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); + actionDispatch(setSharingMeta(shareInfo)); + actionDispatch(setIsPasswordProtected(false)); + } + actionDispatch(setAccessMode(mode)); + } catch (error) { + errorService.reportError(error); + notificationsService.show({ + text: translate('modals.shareModal.errors.update-sharing-access'), + type: ToastType.Error, + }); + } + actionDispatch(setIsLoading(false)); + } + }; + + const handleUserRoleChange = async (email: string, roleName: string) => { + try { + actionDispatch(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; + }); + actionDispatch(setInvitedUsers(modifiedInvitedUsers)); + } + } catch (error) { + errorService.reportError(error); + notificationsService.show({ text: translate('modals.shareModal.errors.updatingRole'), type: ToastType.Error }); + } + }; + + return { + changeAccess, + handleUserRoleChange, + }; +}; diff --git a/src/app/drive/components/ShareDialog/utils/index.ts b/src/app/drive/components/ShareDialog/utils/index.ts new file mode 100644 index 0000000000..839923c13b --- /dev/null +++ b/src/app/drive/components/ShareDialog/utils/index.ts @@ -0,0 +1,45 @@ +import { DriveItemData } from 'app/drive/types'; +import { REQUEST_STATUS, RequestStatus } from '../types'; +import { AdvancedSharedItem } from 'app/share/types'; +import { UserSettings } from '@internxt/sdk/dist/shared/types/userSettings'; +import { localStorageService } from 'services'; +import { Role } from '@internxt/sdk/dist/drive/share/types'; +import { MAX_SHARED_NAME_LENGTH } from 'views/Shared/SharedView'; + +export const isRequestPending = (status: RequestStatus): boolean => + status !== REQUEST_STATUS.DENIED && status !== REQUEST_STATUS.ACCEPTED; + +export const cropSharedName = (name: string) => { + if (name.length > MAX_SHARED_NAME_LENGTH) { + return name.substring(0, 32).concat('...'); + } else { + return name; + } +}; + +export const isAdvancedShareItem = (item: DriveItemData | AdvancedSharedItem): item is AdvancedSharedItem => { + return item['encryptionKey']; +}; + +export const getLocalUserData = () => { + const user = localStorageService.getUser() as UserSettings; + const ownerData = { + name: user.name, + lastname: user.lastname, + email: user.email, + sharingId: '', + avatar: user.avatar, + uuid: user.uuid, + role: { + id: 'NONE', + name: 'OWNER', + createdAt: '', + updatedAt: '', + }, + }; + return ownerData; +}; + +export const filterEditorAndReader = (users: Role[]): Role[] => { + return users.filter((user) => user.name === 'EDITOR' || user.name === 'READER'); +}; diff --git a/src/app/store/slices/storage/storage.model.ts b/src/app/store/slices/storage/storage.model.ts index ad61d0efdf..08be63edab 100644 --- a/src/app/store/slices/storage/storage.model.ts +++ b/src/app/store/slices/storage/storage.model.ts @@ -1,8 +1,7 @@ -import { ShareLink } from '@internxt/sdk/dist/drive/share/types'; -import { AdvancedSharedItem, SharedNamePath } from 'app/share/types'; +import { SharedNamePath } from 'app/share/types'; import { OrderDirection, OrderSettings } from '../../../core/types'; import { DriveItemData, FileViewMode, FolderPath, FolderPathDialog } from 'app/drive/types'; -import { IRoot } from './types'; +import { IRoot, ItemToShare } from './types'; export interface StorageFilters { text: string; @@ -24,11 +23,7 @@ export interface StorageState { filters: StorageFilters; order: OrderSettings; selectedItems: DriveItemData[]; - itemToShare: { - share?: ShareLink; - sharings?: { type: string; id: string }[]; - item: DriveItemData | (AdvancedSharedItem & { user: { email: string } }); - } | null; + itemToShare: ItemToShare | null; itemsToDelete: DriveItemData[]; itemsToMove: DriveItemData[]; itemToRename: DriveItemData | null; diff --git a/src/app/store/slices/storage/types.ts b/src/app/store/slices/storage/types.ts index 9bb300c2f5..f812b4e594 100644 --- a/src/app/store/slices/storage/types.ts +++ b/src/app/store/slices/storage/types.ts @@ -1,3 +1,7 @@ +import { ShareLink } from '@internxt/sdk/dist/drive/storage/types'; +import { DriveItemData } from 'app/drive/types'; +import { AdvancedSharedItem } from 'app/share/types'; + export interface IRoot { name: string; folderId: string | null; @@ -5,3 +9,9 @@ export interface IRoot { childrenFolders: IRoot[]; fullPathEdited: string; } + +export interface ItemToShare { + share?: ShareLink; + sharings?: { type: string; id: string }[]; + item: DriveItemData | (AdvancedSharedItem & { user: { email: string } }); +} diff --git a/src/views/Drive/components/DriveExplorer/DriveExplorer.tsx b/src/views/Drive/components/DriveExplorer/DriveExplorer.tsx index 3a7c4b07cf..8c43de610e 100644 --- a/src/views/Drive/components/DriveExplorer/DriveExplorer.tsx +++ b/src/views/Drive/components/DriveExplorer/DriveExplorer.tsx @@ -56,12 +56,12 @@ import EditItemNameDialog from 'app/drive/components/EditItemNameDialog/EditItem import ItemDetailsDialog from 'app/drive/components/ItemDetailsDialog/ItemDetailsDialog'; import MoveItemsDialog from 'app/drive/components/MoveItemsDialog/MoveItemsDialog'; import NameCollisionContainer from 'app/drive/components/NameCollisionDialog/NameCollisionContainer'; -import ShareDialog from 'app/drive/components/ShareDialog/ShareDialog'; import StopSharingAndMoveToTrashDialogWrapper from 'views/Trash/components/StopSharingAndMoveToTrashDialogWrapper'; import UploadItemsFailsDialog from 'app/drive/components/UploadItemsFailsDialog/UploadItemsFailsDialog'; import WarningMessageWrapper from 'views/Home/components/WarningMessageWrapper'; import './DriveExplorer.scss'; import { DriveTopBarItems } from './DriveTopBarItems'; +import ShareDialogWrapper from 'app/drive/components/ShareDialog/ShareDialogWrapper'; const MenuItemToGetSize = ({ isTrash, @@ -532,7 +532,7 @@ const DriveExplorer = (props: DriveExplorerProps): JSX.Element => { > - + {isShareDialogOpen && ( - actionDispatch(setSelectedItems([]))} /> From 7622c3cfb952c91d067ffcb4cba4130ae17608c3 Mon Sep 17 00:00:00 2001 From: Xavier Abad Date: Tue, 20 Jan 2026 13:21:48 +0100 Subject: [PATCH 2/8] fix: remove useless comments --- .../ShareDialog/context/ShareDialogContext.actions.ts | 8 -------- 1 file changed, 8 deletions(-) diff --git a/src/app/drive/components/ShareDialog/context/ShareDialogContext.actions.ts b/src/app/drive/components/ShareDialog/context/ShareDialogContext.actions.ts index b5ddf34c7f..e5e837237d 100644 --- a/src/app/drive/components/ShareDialog/context/ShareDialogContext.actions.ts +++ b/src/app/drive/components/ShareDialog/context/ShareDialogContext.actions.ts @@ -3,7 +3,6 @@ import { Role } from 'app/store/slices/sharedLinks/types'; import { AccessMode, InvitedUserProps, RequestProps, Views } from '../types'; import { ShareDialogAction, ActionTypes } from './ShareDialogContext'; -// View actions export const setView = (payload: Views): ShareDialogAction => ({ type: ActionTypes.SET_VIEW, payload, @@ -14,7 +13,6 @@ export const setIsLoading = (payload: boolean): ShareDialogAction => ({ payload, }); -// Roles actions export const setRoles = (payload: Role[]): ShareDialogAction => ({ type: ActionTypes.SET_ROLES, payload, @@ -25,7 +23,6 @@ export const setInviteDialogRoles = (payload: Role[]): ShareDialogAction => ({ payload, }); -// Access & sharing actions export const setAccessMode = (payload: AccessMode): ShareDialogAction => ({ type: ActionTypes.SET_ACCESS_MODE, payload, @@ -41,7 +38,6 @@ export const setSharingMeta = (payload: SharingMeta | null): ShareDialogAction = payload, }); -// Invited users actions export const setInvitedUsers = (payload: InvitedUserProps[]): ShareDialogAction => ({ type: ActionTypes.SET_INVITED_USERS, payload, @@ -62,7 +58,6 @@ export const removeUser = (payload: string): ShareDialogAction => ({ payload, }); -// Access requests actions export const setAccessRequests = (payload: RequestProps[]): ShareDialogAction => ({ type: ActionTypes.SET_ACCESS_REQUESTS, payload, @@ -73,7 +68,6 @@ export const updateRequestStatus = (payload: { email: string; status: RequestPro payload, }); -// User options popover actions export const setSelectedUserListIndex = (payload: number | null): ShareDialogAction => ({ type: ActionTypes.SET_SELECTED_USER_LIST_INDEX, payload, @@ -89,7 +83,6 @@ export const setUserOptionsY = (payload: number): ShareDialogAction => ({ payload, }); -// Dialog states actions export const setShowStopSharingConfirmation = (payload: boolean): ShareDialogAction => ({ type: ActionTypes.SET_SHOW_STOP_SHARING_CONFIRMATION, payload, @@ -115,7 +108,6 @@ export const setIsRestrictedPasswordDialogOpen = (payload: boolean): ShareDialog payload, }); -// Batch actions export const resetDialogData = (): ShareDialogAction => ({ type: ActionTypes.RESET_DIALOG_DATA, }); From 20b763ca8de0650c054f65b5e1de60ce2e0c42ee Mon Sep 17 00:00:00 2001 From: Xavier Abad Date: Wed, 21 Jan 2026 11:38:46 +0100 Subject: [PATCH 3/8] fix: remove dead code --- .../components/ShareDialog/ShareDialog.tsx | 3 +- .../ShareDialog/context/ShareDialogContext.ts | 3 -- .../context/ShareDialogContextProvider.tsx | 12 ------ .../hooks/useShareItemInvitations.ts | 37 ++----------------- 4 files changed, 4 insertions(+), 51 deletions(-) diff --git a/src/app/drive/components/ShareDialog/ShareDialog.tsx b/src/app/drive/components/ShareDialog/ShareDialog.tsx index 783e11f3f8..9fa30880ca 100644 --- a/src/app/drive/components/ShareDialog/ShareDialog.tsx +++ b/src/app/drive/components/ShareDialog/ShareDialog.tsx @@ -77,7 +77,6 @@ const ShareDialog = (props: ShareDialogProps): JSX.Element => { const { accessMode, - accessRequests, inviteDialogRoles, invitedUsers, isLoading, @@ -121,7 +120,7 @@ const ShareDialog = (props: ShareDialogProps): JSX.Element => { onStopSharingItem: props.onStopSharingItem, }); - const { getAndUpdateInvitedUsers, handleDenyRequest, onAcceptRequest, onInviteUser } = useShareItemInvitations({ + const { getAndUpdateInvitedUsers, onInviteUser } = useShareItemInvitations({ isUserOwner, itemToShare, }); diff --git a/src/app/drive/components/ShareDialog/context/ShareDialogContext.ts b/src/app/drive/components/ShareDialog/context/ShareDialogContext.ts index f77b913a28..442e2b4623 100644 --- a/src/app/drive/components/ShareDialog/context/ShareDialogContext.ts +++ b/src/app/drive/components/ShareDialog/context/ShareDialogContext.ts @@ -13,7 +13,6 @@ export interface ShareDialogState { sharingMeta: SharingMeta | null; invitedUsers: InvitedUserProps[]; currentUserFolderRole?: string; - accessRequests: RequestProps[]; selectedUserListIndex: number | null; userOptionsEmail?: InvitedUserProps; userOptionsY: number; @@ -36,7 +35,6 @@ export type ShareDialogAction = | { type: 'SET_CURRENT_USER_FOLDER_ROLE'; payload?: string } | { type: 'UPDATE_USER_ROLE'; payload: { email: string; roleId: string; roleName: string } } | { type: 'REMOVE_USER'; payload: string } - | { type: 'SET_ACCESS_REQUESTS'; payload: RequestProps[] } | { type: 'UPDATE_REQUEST_STATUS'; payload: { email: string; status: RequestProps['status'] } } | { type: 'SET_SELECTED_USER_LIST_INDEX'; payload: number | null } | { type: 'SET_USER_OPTIONS_EMAIL'; payload?: InvitedUserProps } @@ -60,7 +58,6 @@ export const ActionTypes = { SET_CURRENT_USER_FOLDER_ROLE: 'SET_CURRENT_USER_FOLDER_ROLE', UPDATE_USER_ROLE: 'UPDATE_USER_ROLE', REMOVE_USER: 'REMOVE_USER', - SET_ACCESS_REQUESTS: 'SET_ACCESS_REQUESTS', UPDATE_REQUEST_STATUS: 'UPDATE_REQUEST_STATUS', SET_SELECTED_USER_LIST_INDEX: 'SET_SELECTED_USER_LIST_INDEX', SET_USER_OPTIONS_EMAIL: 'SET_USER_OPTIONS_EMAIL', diff --git a/src/app/drive/components/ShareDialog/context/ShareDialogContextProvider.tsx b/src/app/drive/components/ShareDialog/context/ShareDialogContextProvider.tsx index 60246057fe..812d556875 100644 --- a/src/app/drive/components/ShareDialog/context/ShareDialogContextProvider.tsx +++ b/src/app/drive/components/ShareDialog/context/ShareDialogContextProvider.tsx @@ -18,7 +18,6 @@ const initialState: ShareDialogState = { sharingMeta: null, invitedUsers: [], currentUserFolderRole: undefined, - accessRequests: [], selectedUserListIndex: null, userOptionsEmail: undefined, userOptionsY: 0, @@ -74,17 +73,6 @@ const reducer = (state: ShareDialogState, action: ShareDialogAction): ShareDialo invitedUsers: state.invitedUsers.filter((user) => user.uuid !== action.payload), }; - case ActionTypes.SET_ACCESS_REQUESTS: - return { ...state, accessRequests: action.payload }; - - case ActionTypes.UPDATE_REQUEST_STATUS: - return { - ...state, - accessRequests: state.accessRequests.map((request) => - request.email === action.payload.email ? { ...request, status: action.payload.status } : request, - ), - }; - case ActionTypes.SET_SELECTED_USER_LIST_INDEX: return { ...state, selectedUserListIndex: action.payload }; diff --git a/src/app/drive/components/ShareDialog/hooks/useShareItemInvitations.ts b/src/app/drive/components/ShareDialog/hooks/useShareItemInvitations.ts index 99ad85c9e2..e1c2b1c76f 100644 --- a/src/app/drive/components/ShareDialog/hooks/useShareItemInvitations.ts +++ b/src/app/drive/components/ShareDialog/hooks/useShareItemInvitations.ts @@ -1,19 +1,13 @@ import shareService from 'app/share/services/share.service'; import { useCallback } from 'react'; -import { - setAccessRequests, - setInvitedUsers, - setSelectedUserListIndex, - setView, -} from '../context/ShareDialogContext.actions'; +import { setInvitedUsers, setSelectedUserListIndex, setView } from '../context/ShareDialogContext.actions'; import { getLocalUserData } from '../utils'; import { useShareDialogContext } from '../context'; -import { REQUEST_STATUS, UserRole } from '../types'; export const useShareItemInvitations = ({ itemToShare, isUserOwner }) => { const { state, dispatch: actionDispatch } = useShareDialogContext(); - const { roles, accessRequests } = state; + const { roles } = state; const getAndUpdateInvitedUsers = useCallback(async () => { if (!itemToShare?.item) return; @@ -40,38 +34,13 @@ export const useShareItemInvitations = ({ itemToShare, isUserOwner }) => { } }, [itemToShare, roles, isUserOwner, actionDispatch]); - const onAcceptRequest = (email: string, roleName: UserRole) => { - // TODO -> Accept user access request - const modifiedAccessRequests = accessRequests.map((request) => { - if (request.email === email) { - return { ...request, status: REQUEST_STATUS.ACCEPTED }; - } - return request; - }); - - actionDispatch(setAccessRequests(modifiedAccessRequests)); - }; - - const handleDenyRequest = (email: string) => { - const deniedRequests = accessRequests.map((request) => { - if (request.email === email) { - return { ...request, status: REQUEST_STATUS.DENIED }; - } - return request; - }); - - actionDispatch(setAccessRequests(deniedRequests)); - }; - const onInviteUser = () => { actionDispatch(setView('invite')); actionDispatch(setSelectedUserListIndex(null)); }; return { - getAndUpdateInvitedUsers, - onAcceptRequest, - handleDenyRequest, onInviteUser, + getAndUpdateInvitedUsers, }; }; From 6f1b8aea872d6fe02677649ef2a260376447eda2 Mon Sep 17 00:00:00 2001 From: Xavier Abad Date: Thu, 22 Jan 2026 10:25:00 +0100 Subject: [PATCH 4/8] fix: remove useless action --- .../ShareDialog/context/ShareDialogContext.actions.ts | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/app/drive/components/ShareDialog/context/ShareDialogContext.actions.ts b/src/app/drive/components/ShareDialog/context/ShareDialogContext.actions.ts index e5e837237d..4ad0c28243 100644 --- a/src/app/drive/components/ShareDialog/context/ShareDialogContext.actions.ts +++ b/src/app/drive/components/ShareDialog/context/ShareDialogContext.actions.ts @@ -58,11 +58,6 @@ export const removeUser = (payload: string): ShareDialogAction => ({ payload, }); -export const setAccessRequests = (payload: RequestProps[]): ShareDialogAction => ({ - type: ActionTypes.SET_ACCESS_REQUESTS, - payload, -}); - export const updateRequestStatus = (payload: { email: string; status: RequestProps['status'] }): ShareDialogAction => ({ type: ActionTypes.UPDATE_REQUEST_STATUS, payload, From 2848e558ece6287f7bec9eb249da2e3787228f54 Mon Sep 17 00:00:00 2001 From: Xavier Abad Date: Thu, 22 Jan 2026 13:18:28 +0100 Subject: [PATCH 5/8] fix: Share Dialog --- src/app/drive/components/ShareDialog/ShareDialog.tsx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/app/drive/components/ShareDialog/ShareDialog.tsx b/src/app/drive/components/ShareDialog/ShareDialog.tsx index 7bca0ad3ef..9fa30880ca 100644 --- a/src/app/drive/components/ShareDialog/ShareDialog.tsx +++ b/src/app/drive/components/ShareDialog/ShareDialog.tsx @@ -59,6 +59,8 @@ export interface ShareDialogProps { onCloseDialog?: () => void; } +const OWNER_ROLE = { id: 'NONE', name: 'owner' }; + const ShareDialog = (props: ShareDialogProps): JSX.Element => { const { onCloseDialog } = props; From bc42b121621f61e117f8fe9f602928d79c04b2b3 Mon Sep 17 00:00:00 2001 From: Xavier Abad Date: Fri, 23 Jan 2026 10:11:12 +0100 Subject: [PATCH 6/8] fix: imports --- .../drive/components/ShareDialog/hooks/useShareItemActions.ts | 2 +- .../components/ShareDialog/hooks/useShareItemInvitations.ts | 2 +- .../drive/components/ShareDialog/hooks/useShareItemUserRoles.ts | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/app/drive/components/ShareDialog/hooks/useShareItemActions.ts b/src/app/drive/components/ShareDialog/hooks/useShareItemActions.ts index 0d686524d2..d7874a6d1c 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 719b166073..eed8efc5a9 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 8580840621..7589fd36e3 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'; From 4546fac49e31fb44600e96f8eccea6b609e2d61b Mon Sep 17 00:00:00 2001 From: Xavier Abad Date: Fri, 23 Jan 2026 11:17:52 +0100 Subject: [PATCH 7/8] tests: update playwright tests --- test/e2e/tests/pages/loginPage.ts | 2 +- test/e2e/tests/pages/signUpPage.ts | 2 +- test/e2e/tests/specs/internxt-login.spec.ts | 6 +++--- test/e2e/tests/specs/internxt-signup.spec.ts | 4 ++-- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/test/e2e/tests/pages/loginPage.ts b/test/e2e/tests/pages/loginPage.ts index 758891a6bd..714b5eaf6e 100644 --- a/test/e2e/tests/pages/loginPage.ts +++ b/test/e2e/tests/pages/loginPage.ts @@ -37,7 +37,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 de69e63feb..a07bf7c44b 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) { diff --git a/test/e2e/tests/specs/internxt-login.spec.ts b/test/e2e/tests/specs/internxt-login.spec.ts index 9a081fab9e..aa26d122e8 100644 --- a/test/e2e/tests/specs/internxt-login.spec.ts +++ b/test/e2e/tests/specs/internxt-login.spec.ts @@ -1,4 +1,4 @@ -import { expect, test } from '@playwright/test'; +import { expect, Request, Route, test } from '@playwright/test'; import { staticData } from '../helper/staticData'; import { LoginPage } from '../pages/loginPage'; import { getLoggedUser, getUserCredentials } from '../helper/getUser'; @@ -8,7 +8,7 @@ const credentialsFile = getUserCredentials(); const user = getLoggedUser(); const invalidEmail = 'invalid@internxt.com'; -const mockLoginCall = async (route, request) => { +const mockLoginCall = async (route: Route, request: Request) => { await route.fulfill({ status: 200, contentType: 'application/json', @@ -22,7 +22,7 @@ const mockLoginCall = async (route, request) => { }); }; -const mockAccessCall = async (route, request) => { +const mockAccessCall = async (route: Route, request: Request) => { const { email } = request.postDataJSON(); if (invalidEmail === email) { diff --git a/test/e2e/tests/specs/internxt-signup.spec.ts b/test/e2e/tests/specs/internxt-signup.spec.ts index 1cb10be93f..8a17fca331 100644 --- a/test/e2e/tests/specs/internxt-signup.spec.ts +++ b/test/e2e/tests/specs/internxt-signup.spec.ts @@ -1,5 +1,5 @@ import { faker } from '@faker-js/faker'; -import { expect, test } from '@playwright/test'; +import { expect, Request, Route, test } from '@playwright/test'; import { staticData } from '../helper/staticData'; import { SignUpPage } from '../pages/signUpPage'; import { getUser, getUserCredentials } from '../helper/getUser'; @@ -9,7 +9,7 @@ const credentialsFile = getUserCredentials(); const user = getUser(); const invalidEmail = 'invalid@internxt.com'; -const mockedCall = async (route, request) => { +const mockedCall = async (route: Route, request: Request) => { const { email } = request.postDataJSON(); if (invalidEmail === email) { From 56aae9454c6f8edce711bbf544b668d74b0742dc Mon Sep 17 00:00:00 2001 From: Xavier Abad Date: Wed, 11 Feb 2026 09:47:10 +0100 Subject: [PATCH 8/8] fix: use action dispatch for some state actions --- src/app/drive/components/ShareDialog/ShareDialog.tsx | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/app/drive/components/ShareDialog/ShareDialog.tsx b/src/app/drive/components/ShareDialog/ShareDialog.tsx index e9d8bb7392..a1e279c007 100644 --- a/src/app/drive/components/ShareDialog/ShareDialog.tsx +++ b/src/app/drive/components/ShareDialog/ShareDialog.tsx @@ -218,12 +218,12 @@ const ShareDialog = (props: ShareDialogProps): JSX.Element => { 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(); @@ -335,7 +335,7 @@ const ShareDialog = (props: ShareDialogProps): JSX.Element => { invite: ( { - setView('general'); + actionDispatch(setView('general')); }} onInviteUser={onInviteUser} itemToShare={itemToShare?.item}