From 6bc286afc77a52571fbd714ad042662176a2e01c Mon Sep 17 00:00:00 2001 From: Xavier Abad Date: Thu, 22 Jan 2026 10:41:40 +0100 Subject: [PATCH] feat(share): add context for Share Dialog view --- .../ShareDialogContext.actions.test.ts | 117 +++++++++++++++ .../context/ShareDialogContext.actions.ts | 108 ++++++++++++++ .../ShareDialog/context/ShareDialogContext.ts | 78 ++++++++++ .../context/ShareDialogContextProvider.tsx | 133 ++++++++++++++++++ 4 files changed, 436 insertions(+) create mode 100644 src/app/drive/components/ShareDialog/context/ShareDialogContext.actions.test.ts 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 diff --git a/src/app/drive/components/ShareDialog/context/ShareDialogContext.actions.test.ts b/src/app/drive/components/ShareDialog/context/ShareDialogContext.actions.test.ts new file mode 100644 index 000000000..f15800844 --- /dev/null +++ b/src/app/drive/components/ShareDialog/context/ShareDialogContext.actions.test.ts @@ -0,0 +1,117 @@ +/** + * @jest-environment jsdom + */ + +import { describe, expect, test } from 'vitest'; +import { ActionTypes } from './ShareDialogContext'; +import * as actions from './ShareDialogContext.actions'; + +describe('Actions for Share Dialog', () => { + test('When the action is saved, then the state is updated correctly', () => { + expect(actions.setView('general')).toEqual({ type: ActionTypes.SET_VIEW, payload: 'general' }); + expect(actions.setIsLoading(true)).toEqual({ type: ActionTypes.SET_IS_LOADING, payload: true }); + expect(actions.setRoles([{ id: '1', name: 'Admin' }])).toEqual({ + type: ActionTypes.SET_ROLES, + payload: [{ id: '1', name: 'Admin' }], + }); + expect(actions.setInviteDialogRoles([{ id: '2', name: 'Editor' }])).toEqual({ + type: ActionTypes.SET_INVITE_DIALOG_ROLES, + payload: [{ id: '2', name: 'Editor' }], + }); + expect(actions.setAccessMode('public')).toEqual({ type: ActionTypes.SET_ACCESS_MODE, payload: 'public' }); + expect(actions.setIsPasswordProtected(true)).toEqual({ + type: ActionTypes.SET_IS_PASSWORD_PROTECTED, + payload: true, + }); + expect(actions.setInvitedUsers([])).toEqual({ + type: ActionTypes.SET_INVITED_USERS, + payload: [], + }); + expect( + actions.setInvitedUsers([ + { + avatar: null, + name: 'John', + lastname: 'Doe', + email: 'john@example.com', + roleName: 'reader', + uuid: 'user-123', + sharingId: 'share-456', + }, + ]), + ).toEqual({ + type: ActionTypes.SET_INVITED_USERS, + payload: [ + { + avatar: null, + name: 'John', + lastname: 'Doe', + email: 'john@example.com', + roleName: 'reader', + uuid: 'user-123', + sharingId: 'share-456', + }, + ], + }); + expect(actions.setCurrentUserFolderRole('owner')).toEqual({ + type: ActionTypes.SET_CURRENT_USER_FOLDER_ROLE, + payload: 'owner', + }); + expect(actions.updateUserRole({ email: 'user@example.com', roleId: '1', roleName: 'editor' })).toEqual({ + type: ActionTypes.UPDATE_USER_ROLE, + payload: { email: 'user@example.com', roleId: '1', roleName: 'editor' }, + }); + expect(actions.removeUser('user@example.com')).toEqual({ + type: ActionTypes.REMOVE_USER, + payload: 'user@example.com', + }); + expect(actions.updateRequestStatus({ email: 'user@example.com', status: 'accepted' })).toEqual({ + type: ActionTypes.UPDATE_REQUEST_STATUS, + payload: { email: 'user@example.com', status: 'accepted' }, + }); + expect(actions.setSelectedUserListIndex(2)).toEqual({ + type: ActionTypes.SET_SELECTED_USER_LIST_INDEX, + payload: 2, + }); + expect( + actions.setUserOptionsEmail({ + avatar: null, + name: 'Jane', + lastname: 'Smith', + email: 'jane@example.com', + roleName: 'editor', + uuid: 'user-789', + sharingId: 'share-101', + }), + ).toEqual({ + type: ActionTypes.SET_USER_OPTIONS_EMAIL, + payload: { + avatar: null, + name: 'Jane', + lastname: 'Smith', + email: 'jane@example.com', + roleName: 'editor', + uuid: 'user-789', + sharingId: 'share-101', + }, + }); + expect(actions.setUserOptionsY(150)).toEqual({ type: ActionTypes.SET_USER_OPTIONS_Y, payload: 150 }); + expect(actions.setShowStopSharingConfirmation(true)).toEqual({ + type: ActionTypes.SET_SHOW_STOP_SHARING_CONFIRMATION, + payload: true, + }); + expect(actions.setOpenPasswordInput(true)).toEqual({ type: ActionTypes.SET_OPEN_PASSWORD_INPUT, payload: true }); + expect(actions.setOpenPasswordDisableDialog(false)).toEqual({ + type: ActionTypes.SET_OPEN_PASSWORD_DISABLE_DIALOG, + payload: false, + }); + expect(actions.setIsRestrictedSharingDialogOpen(true)).toEqual({ + type: ActionTypes.SET_IS_RESTRICTED_SHARING_DIALOG_OPEN, + payload: true, + }); + expect(actions.setIsRestrictedPasswordDialogOpen(false)).toEqual({ + type: ActionTypes.SET_IS_RESTRICTED_PASSWORD_DIALOG_OPEN, + payload: false, + }); + }); +}); 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 000000000..4ad0c2824 --- /dev/null +++ b/src/app/drive/components/ShareDialog/context/ShareDialogContext.actions.ts @@ -0,0 +1,108 @@ +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'; + +export const setView = (payload: Views): ShareDialogAction => ({ + type: ActionTypes.SET_VIEW, + payload, +}); + +export const setIsLoading = (payload: boolean): ShareDialogAction => ({ + type: ActionTypes.SET_IS_LOADING, + payload, +}); + +export const setRoles = (payload: Role[]): ShareDialogAction => ({ + type: ActionTypes.SET_ROLES, + payload, +}); + +export const setInviteDialogRoles = (payload: Role[]): ShareDialogAction => ({ + type: ActionTypes.SET_INVITE_DIALOG_ROLES, + payload, +}); + +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, +}); + +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, +}); + +export const updateRequestStatus = (payload: { email: string; status: RequestProps['status'] }): ShareDialogAction => ({ + type: ActionTypes.UPDATE_REQUEST_STATUS, + payload, +}); + +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, +}); + +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, +}); + +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 000000000..442e2b462 --- /dev/null +++ b/src/app/drive/components/ShareDialog/context/ShareDialogContext.ts @@ -0,0 +1,78 @@ +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; + 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: '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', + 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 000000000..812d55687 --- /dev/null +++ b/src/app/drive/components/ShareDialog/context/ShareDialogContextProvider.tsx @@ -0,0 +1,133 @@ +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, + 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_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 };