Skip to content
Merged
462 changes: 47 additions & 415 deletions src/app/drive/components/ShareDialog/ShareDialog.tsx

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
import { RefObject, MouseEvent } from 'react';
import { useTranslationContext } from 'app/i18n/provider/TranslationProvider';
import { User } from '../User';
import { InvitedUsersSkeletonLoader } from '../InvitedUsersSkeletonLoader';
import { InvitedUserProps } from '../../types';
import { UserSettings } from '@internxt/sdk/dist/shared/types/userSettings';

interface InvitedUsersListProps {
userList: RefObject<HTMLDivElement>;
invitedUsers: InvitedUserProps[];
areInvitedUsersLoading: boolean;
user: UserSettings;
openUserOptions: (
e: MouseEvent<HTMLDivElement, globalThis.MouseEvent>,
user: InvitedUserProps,
selectedIndex: number | null,
) => void;
userOptionsY: number;
selectedUserListIndex: number | null;
onRemoveUser: (user: InvitedUserProps) => void;
currentUserFolderRole?: string;
userOptionsEmail?: InvitedUserProps;
handleUserRoleChange: (email: string, role: string) => Promise<void>;
}

export const InvitedUsersList = ({
currentUserFolderRole,
userList,
invitedUsers,
areInvitedUsersLoading,
user,
openUserOptions,
selectedUserListIndex,
userOptionsY,
onRemoveUser,
userOptionsEmail,
handleUserRoleChange,
}: InvitedUsersListProps) => {
const { translate } = useTranslationContext();
const invitedUsersSorted = invitedUsers.toSorted((a, b) => {
if (a.email === user.email && b.email !== user.email) return -1;
return 0;
});
const isListLoading = invitedUsers.length === 0 && areInvitedUsersLoading;

return (
<div
ref={userList}
className="mt-1.5 flex flex-col overflow-y-auto"
style={{ minHeight: '224px', maxHeight: '336px' }}
>
{isListLoading ? (
<>
{Array.from({ length: 4 }, (_, i) => (
<InvitedUsersSkeletonLoader key={`loader-${i}`} />
))}
</>
) : (
invitedUsersSorted.map((invitedUser, index) => (
<User
user={invitedUser}
key={invitedUser.email}
listPosition={index}
translate={translate}
openUserOptions={openUserOptions}
selectedUserListIndex={selectedUserListIndex}
userOptionsY={userOptionsY}
onRemoveUser={onRemoveUser}
userOptionsEmail={userOptionsEmail}
onChangeRole={handleUserRoleChange}
disableUserOptionsPanel={currentUserFolderRole !== 'owner' && invitedUser.email !== user.email}
disableRoleChange={currentUserFolderRole !== 'owner'}
/>
))
)}
</div>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import { Button, Checkbox } from '@internxt/ui';
import { Question } from '@phosphor-icons/react';
import { useTranslationContext } from 'app/i18n/provider/TranslationProvider';
import { DELAY_SHOW_MS } from 'components';
import { Tooltip } from 'react-tooltip';

interface ProtectWithPasswordProps {
isPasswordProtected: boolean;
onPasswordCheckboxChange: () => void;
isPasswordSharingAvailable: boolean;
onChangePassword: () => void;
}

export const ProtectWithPassword = ({
isPasswordProtected,
onPasswordCheckboxChange,
isPasswordSharingAvailable,
onChangePassword,
}: ProtectWithPasswordProps) => {
const { translate } = useTranslationContext();

return (
<div className="flex items-center justify-between">
<div className="flex flex-col space-y-2.5">
<div className="flex items-center">
<Checkbox checked={isPasswordProtected} onClick={onPasswordCheckboxChange} />
<p className={`ml-2 select-none text-base font-medium ${isPasswordSharingAvailable ? '' : 'text-gray-50'}`}>
{translate('modals.shareModal.protectSharingModal.protect')}
</p>
{isPasswordSharingAvailable ? (
<>
<Question
size={20}
className="ml-2 flex items-center justify-center font-medium text-gray-50"
data-tooltip-id="uploadFolder-tooltip"
data-tooltip-place="top"
/>
<Tooltip id="uploadFolder-tooltip" delayShow={DELAY_SHOW_MS} className="z-40 rounded-md">
<p className="break-word w-60 text-center text-white">
{translate('modals.shareModal.protectSharingModal.protectTooltipText')}
</p>
</Tooltip>
</>
) : (
<div className="py-1 px-2 ml-2 rounded-md bg-gray-5">
<p className="text-xs font-semibold">{translate('actions.locked')}</p>
</div>
)}
</div>
</div>
{isPasswordProtected && (
<Button variant="secondary" onClick={onChangePassword}>
<span>{translate('modals.shareModal.protectSharingModal.buttons.changePassword')}</span>
</Button>
)}
</div>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
import { Popover } from '@headlessui/react';
import { Button, Loader } from '@internxt/ui';
import { CaretDown, Check, Globe, Link, Users } from '@phosphor-icons/react';
import { useTranslationContext } from 'app/i18n/provider/TranslationProvider';
import { AccessMode } from '../../types';

const LoadingState = ({ isLoading }: { isLoading: boolean }) => {
return (
<div className="flex h-full w-5 items-center justify-center">
{isLoading ? <Loader classNameLoader="h-5 w-5" /> : <Check size={20} />}
</div>
);
};

interface UserRoleSelectionProps {
accessMode: AccessMode;
isLoading: boolean;
isUserOwner: boolean;
isWorkspace: boolean;
isRestrictedSharingAvailable: boolean;
isStopSharingAvailable: boolean;
onCopyLink: () => void;
changeAccess: (accessMode: AccessMode) => void;
setShowStopSharingConfirmation: (show: boolean) => void;
}

export const UserRoleSelection = ({
accessMode,
changeAccess,
isLoading,
isUserOwner,
isWorkspace,
isRestrictedSharingAvailable,
isStopSharingAvailable,
onCopyLink,
setShowStopSharingConfirmation,
}: UserRoleSelectionProps) => {
const { translate } = useTranslationContext();

return (
<div className="flex items-end justify-between">
<div className="flex flex-col space-y-2.5">
<p className="font-medium">{translate('modals.shareModal.general.generalAccess')}</p>

<Popover className="relative z-10">
{({ open }) => (
<>
<Popover.Button as="div" className="z-1 outline-none">
<Button variant="secondary" disabled={isLoading || !isUserOwner}>
{accessMode === 'public' ? <Globe size={24} /> : <Users size={24} />}
<span>
{accessMode === 'public'
? translate('modals.shareModal.general.accessOptions.public.title')
: translate('modals.shareModal.general.accessOptions.restricted.title')}
</span>
{isLoading ? (
<div className="flex h-6 w-6 items-center justify-center">
<Loader classNameLoader="h-5 w-5" />
</div>
) : (
<CaretDown size={24} />
)}
</Button>
</Popover.Button>

<Popover.Panel
className={`absolute bottom-full z-0 mb-1 w-80 origin-bottom-left rounded-lg border border-gray-10 bg-surface p-1 shadow-subtle transition-all duration-50 ease-out ${
open ? 'scale-100 opacity-100' : 'pointer-events-none scale-95 opacity-0'
}`}
static
>
{({ close }) => (
<>
{/* Public */}
<button
className="flex h-16 w-full cursor-pointer items-center justify-start space-x-3 rounded-lg px-3 hover:bg-gray-5"
onClick={() => changeAccess('public')}
>
<Globe size={32} weight="light" />
<div className="flex flex-1 flex-col items-start">
<p className="text-base font-medium leading-none">
{translate('modals.shareModal.general.accessOptions.public.title')}
</p>
<p className="text-left text-sm leading-tight text-gray-60">
{translate('modals.shareModal.general.accessOptions.public.subtitle')}
</p>
</div>
<div className="flex h-full w-5 items-center justify-center">
{accessMode === 'public' && <LoadingState isLoading={isLoading} />}
</div>
</button>
{/* Restricted */}
{!isWorkspace && (
<button
className="flex h-16 w-full cursor-pointer items-center justify-start space-x-3 rounded-lg px-3 hover:bg-gray-5"
onClick={() => changeAccess('restricted')}
>
<Users size={32} weight="light" />
<div className="flex flex-1 flex-col items-start">
<div className="flex flex-row gap-2 items-center">
<p
className={`text-base font-medium leading-none ${isRestrictedSharingAvailable ? '' : 'text-gray-70'}`}
>
{translate('modals.shareModal.general.accessOptions.restricted.title')}
</p>
{!isRestrictedSharingAvailable && (
<div className="py-1 px-2 ml-2 rounded-md bg-gray-5">
<p className="text-xs font-semibold">{translate('actions.locked')}</p>
</div>
)}
</div>
<p
className={`text-left text-sm leading-tight ${isRestrictedSharingAvailable ? 'text-gray-60' : 'text-gray-70'}`}
>
{translate('modals.shareModal.general.accessOptions.restricted.subtitle')}
</p>
</div>
<div className="flex h-full w-5 items-center justify-center">
{accessMode === 'restricted' && <LoadingState isLoading={isLoading} />}
</div>
</button>
)}
{/* Stop sharing */}
{isStopSharingAvailable && (
<button
className="flex h-11 w-full cursor-pointer items-center justify-start rounded-lg pl-14 pr-3 hover:bg-gray-5"
onClick={() => {
setShowStopSharingConfirmation(true);
close();
}}
>
<p className="text-base font-medium">
{translate('modals.shareModal.general.accessOptions.stopSharing')}
</p>
</button>
)}
</>
)}
</Popover.Panel>
</>
)}
</Popover>
</div>

<Button variant="primary" onClick={onCopyLink}>
<Link size={24} />
<span>{translate('modals.shareModal.general.copyLink')}</span>
</Button>
</div>
);
};
50 changes: 50 additions & 0 deletions src/app/drive/components/ShareDialog/components/Header.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import { useTranslationContext } from 'app/i18n/provider/TranslationProvider';
import { ArrowLeft, X } from '@phosphor-icons/react';
import { Views } from '../types';
import { ItemToShare } from 'app/store/slices/storage/types';

interface HeaderProps {
itemToShare: ItemToShare | null;
isLoading: boolean;
headerView: Views;
setView: (view: Views) => void;
onClose: () => void;
}

export const Header = ({ headerView, isLoading, itemToShare, onClose, setView }: HeaderProps): JSX.Element => {
const { translate } = useTranslationContext();

const headers = {
general: (
<>
<span
className="max-w-full overflow-hidden text-ellipsis whitespace-nowrap text-xl font-medium"
title={translate('modals.shareModal.title', { name: itemToShare?.item.name })}
>
{translate('modals.shareModal.title', { name: itemToShare?.item.name })}
</span>
<div className="flex h-9 w-9 cursor-pointer items-center justify-center rounded-md bg-black/0 transition-all duration-200 ease-in-out hover:bg-black/4 active:bg-black/8">
<X onClick={() => (isLoading ? null : onClose())} size={22} />
</div>
</>
),
invite: (
<div className="flex items-center space-x-4">
<ArrowLeft className="cursor-pointer" onClick={() => setView('general')} size={24} />
<span className="max-w-full overflow-hidden text-ellipsis whitespace-nowrap text-xl font-medium">
{translate('modals.shareModal.invite.title')}
</span>
</div>
),
requests: (
<div className="flex items-center space-x-4">
<ArrowLeft className="cursor-pointer" onClick={() => setView('general')} size={24} />
<span className="max-w-full overflow-hidden text-ellipsis whitespace-nowrap text-xl font-medium">
{translate('modals.shareModal.requests.title')}
</span>
</div>
),
};

return headers[headerView];
};
36 changes: 19 additions & 17 deletions src/app/drive/components/ShareDialog/components/User.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,24 @@ import { MouseEvent } from 'react';
import { UserOptions } from './UserOptions';
import { InvitedUserProps } from '../types';

interface UserProps {
user: InvitedUserProps;
listPosition: number | null;
translate: (key: string, props?: Record<string, unknown>) => string;
openUserOptions: (
event: MouseEvent<HTMLDivElement, globalThis.MouseEvent>,
user: InvitedUserProps,
listPosition: number | null,
) => void;
selectedUserListIndex: number | null;
userOptionsY: number;
onRemoveUser: (user: InvitedUserProps) => void;
userOptionsEmail?: InvitedUserProps;
onChangeRole: (email: string, roleName: string) => void;
disableUserOptionsPanel: boolean;
disableRoleChange: boolean;
}

export const User = ({
user,
listPosition,
Expand All @@ -16,23 +34,7 @@ export const User = ({
onChangeRole,
disableUserOptionsPanel,
disableRoleChange,
}: {
user: InvitedUserProps;
listPosition: number | null;
translate: (key: string, props?: Record<string, unknown>) => string;
openUserOptions: (
event: MouseEvent<HTMLDivElement, globalThis.MouseEvent>,
user: InvitedUserProps,
listPosition: number | null,
) => void;
selectedUserListIndex;
userOptionsY;
onRemoveUser;
userOptionsEmail;
onChangeRole: (email: string, roleName: string) => void;
disableUserOptionsPanel: boolean;
disableRoleChange: boolean;
}) => (
}: UserProps) => (
<div
className={`group flex h-14 shrink-0 items-center space-x-2.5 border-t ${
user.roleName === 'owner' ? 'border-transparent' : 'border-gray-5'
Expand Down
Loading
Loading