Files
medreport_mrb2b/packages/features/accounts/src/components/personal-account-dropdown.tsx

269 lines
7.2 KiB
TypeScript

'use client';
import { useMemo } from 'react';
import Link from 'next/link';
import type { User } from '@supabase/supabase-js';
import {
ChevronsUpDown,
Home,
LogOut,
UserCircle,
Shield,
} from 'lucide-react';
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuSeparator,
DropdownMenuTrigger,
} from '@kit/ui/dropdown-menu';
import { If } from '@kit/ui/if';
import { SubMenuModeToggle } from '@kit/ui/mode-toggle';
import { ProfileAvatar } from '@kit/ui/profile-avatar';
import { Trans } from '@kit/ui/trans';
import { cn } from '@kit/ui/utils';
import { usePersonalAccountData } from '../hooks/use-personal-account-data';
import { Avatar, AvatarFallback, AvatarImage } from '@kit/ui/avatar';
import { toTitleCase } from '~/lib/utils';
const PERSONAL_ACCOUNT_SLUG = 'personal';
export function PersonalAccountDropdown({
className,
user,
signOutRequested,
showProfileName = true,
paths,
features,
account,
accounts = []
}: {
user: User;
account?: {
id: string | null;
name: string | null;
picture_url: string | null;
};
accounts: {
label: string | null;
value: string | null;
image?: string | null;
}[];
signOutRequested: () => unknown;
paths: {
home: string;
};
features: {
enableThemeToggle: boolean;
};
showProfileName?: boolean;
className?: string;
}) {
const { data: personalAccountData } = usePersonalAccountData(
user.id,
account,
);
const signedInAsLabel = useMemo(() => {
const email = user?.email ?? undefined;
const phone = user?.phone ?? undefined;
return email ?? phone;
}, [user]);
const displayName =
personalAccountData?.name ?? account?.name ?? user?.email ?? '';
const isSuperAdmin = useMemo(() => {
const factors = user?.factors ?? [];
const hasAdminRole = user?.app_metadata.role === 'super-admin';
const hasTotpFactor = factors.some(
(factor) => factor.factor_type === 'totp' && factor.status === 'verified',
);
return hasAdminRole && hasTotpFactor;
}, [user]);
return (
<DropdownMenu>
<DropdownMenuTrigger
aria-label="Open your profile menu"
data-test={'account-dropdown-trigger'}
className={cn(
'animate-in fade-in focus:outline-primary flex cursor-pointer items-center duration-500 group-data-[minimized=true]:px-0',
className ?? '',
{
['active:bg-secondary/50 items-center gap-4 rounded-md' +
' hover:bg-secondary m-0 transition-colors border-1 rounded-md px-4 py-1 h-10']: showProfileName,
},
)}
>
<ProfileAvatar
className={'rounded-md'}
fallbackClassName={'rounded-md border'}
displayName={displayName ?? user?.email ?? ''}
pictureUrl={personalAccountData?.picture_url}
/>
<If condition={showProfileName}>
<div
className={
'fade-in animate-in flex w-full flex-col truncate text-left group-data-[minimized=true]:hidden'
}
>
<span
data-test={'account-dropdown-display-name'}
className={'truncate text-sm'}
>
{toTitleCase(displayName)}
</span>
</div>
<ChevronsUpDown
className={
'text-muted-foreground mr-1 h-8 group-data-[minimized=true]:hidden'
}
/>
</If>
</DropdownMenuTrigger>
<DropdownMenuContent className={'xl:min-w-[15rem]!'}>
<DropdownMenuItem className={'h-10! rounded-none'}>
<div
className={'flex flex-col justify-start truncate text-left text-xs'}
>
<div className={'text-muted-foreground'}>
<Trans i18nKey={'common:signedInAs'} />
</div>
<div>
<span className={'block truncate'}>{signedInAsLabel}</span>
</div>
</div>
</DropdownMenuItem>
<DropdownMenuSeparator />
<DropdownMenuItem asChild>
<Link
className={'s-full flex cursor-pointer items-center space-x-2'}
href={paths.home}
>
<Home className={'h-5'} />
<span>
<Trans i18nKey={'common:routes.home'} />
</span>
</Link>
</DropdownMenuItem>
<DropdownMenuSeparator />
<If condition={accounts.length > 0}>
<span className='px-2 text-muted-foreground text-xs'>
<Trans
i18nKey={'teams:yourTeams'}
values={{ teamsCount: accounts.length }}
/>
</span>
{accounts.map((account) => (
<DropdownMenuItem key={account.value} asChild>
<Link
className={'s-full flex cursor-pointer items-center space-x-2'}
href={`/home/${account.value}`}
>
<div className={'flex items-center'}>
<Avatar className={'rounded-xs h-5 w-5 ' + account.image}>
<AvatarImage {...(account.image && { src: account.image })} />
<AvatarFallback
className={cn('rounded-md', {
['bg-background']: PERSONAL_ACCOUNT_SLUG === account.value,
['group-hover:bg-background']:
PERSONAL_ACCOUNT_SLUG !== account.value,
})}
>
{account.label ? account.label[0] : ''}
</AvatarFallback>
</Avatar>
<span className={'pl-3'}>
{account.label}
</span>
</div>
</Link>
</DropdownMenuItem>
))}
</If>
<DropdownMenuSeparator />
<DropdownMenuItem asChild>
<Link
className={'s-full flex cursor-pointer items-center space-x-2'}
href={'/home/settings'}
>
<UserCircle className={'h-5'} />
<span>
<Trans i18nKey={'common:routes.profile'} />
</span>
</Link>
</DropdownMenuItem>
<If condition={isSuperAdmin}>
<DropdownMenuSeparator />
<DropdownMenuItem asChild>
<Link
className={
's-full flex cursor-pointer items-center space-x-2 text-yellow-700 dark:text-yellow-500'
}
href={'/admin'}
>
<Shield className={'h-5'} />
<span>Super Admin</span>
</Link>
</DropdownMenuItem>
</If>
<DropdownMenuSeparator />
<If condition={features.enableThemeToggle}>
<SubMenuModeToggle />
</If>
<DropdownMenuSeparator />
<DropdownMenuItem
data-test={'account-dropdown-sign-out'}
role={'button'}
className={'cursor-pointer'}
onClick={signOutRequested}
>
<span className={'flex w-full items-center space-x-2'}>
<LogOut className={'h-5'} />
<span>
<Trans i18nKey={'auth:signOut'} />
</span>
</span>
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
);
}