Files
myeasycms-v2/packages/features/accounts/src/components/personal-account-dropdown.tsx
Giancarlo Buomprisco 7ebff31475 Next.js Supabase V3 (#463)
Version 3 of the kit:
- Radix UI replaced with Base UI (using the Shadcn UI patterns)
- next-intl replaces react-i18next
- enhanceAction deprecated; usage moved to next-safe-action
- main layout now wrapped with [locale] path segment
- Teams only mode
- Layout updates
- Zod v4
- Next.js 16.2
- Typescript 6
- All other dependencies updated
- Removed deprecated Edge CSRF
- Dynamic Github Action runner
2026-03-24 13:40:38 +08:00

230 lines
5.8 KiB
TypeScript

'use client';
import { useMemo } from 'react';
import Link from 'next/link';
import {
ChevronsUpDown,
Home,
LogOut,
MessageCircleQuestion,
Shield,
} from 'lucide-react';
import { JWTUserData } from '@kit/supabase/types';
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';
export function PersonalAccountDropdown({
className,
user,
signOutRequested,
showProfileName = true,
paths,
features,
account,
}: {
user: JWTUserData;
account?: {
id: string | null;
name: string | null;
picture_url: 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 hasAdminRole = user?.app_metadata.role === 'super-admin';
const isAal2 = user?.aal === 'aal2';
return hasAdminRole && isAal2;
}, [user]);
return (
<DropdownMenu>
<DropdownMenuTrigger
aria-label="Open your profile menu"
data-test={'account-dropdown-trigger'}
className={cn(
'group/trigger fade-in focus:outline-primary flex cursor-pointer items-center group-data-[collapsible=icon]:px-0',
className ?? '',
{
['active:bg-secondary/50 group-data-[collapsible=none]:hover:bg-secondary items-center gap-4 rounded-md border-dashed p-2 transition-colors group-data-[collapsible=none]:border']:
showProfileName,
},
)}
>
<ProfileAvatar
className={
'group-hover/trigger:border-background/50 border border-transparent transition-colors'
}
fallbackClassName={'border'}
displayName={displayName ?? user?.email ?? ''}
pictureUrl={personalAccountData?.picture_url}
/>
<If condition={showProfileName}>
<div
className={
'fade-in flex w-full flex-col truncate text-left group-data-[collapsible=icon]:hidden'
}
>
<span
data-test={'account-dropdown-display-name'}
className={'truncate text-sm'}
>
{displayName}
</span>
<span
data-test={'account-dropdown-email'}
className={'text-muted-foreground truncate text-xs'}
>
{signedInAsLabel}
</span>
</div>
<ChevronsUpDown
className={
'text-muted-foreground mr-1 h-8 group-data-[collapsible=icon]:hidden'
}
/>
</If>
</DropdownMenuTrigger>
<DropdownMenuContent className={'xl:min-w-[15rem]!'}>
<DropdownMenuItem
className={'group/item h-10! data-[highlighted]:bg-transparent'}
>
<div
className={'flex flex-col justify-start truncate text-left text-xs'}
>
<div
className={
'text-muted-foreground group-hover/item:text-muted-foreground!'
}
>
<Trans i18nKey={'common.signedInAs'} />
</div>
<div>
<span className={'block truncate'}>{signedInAsLabel}</span>
</div>
</div>
</DropdownMenuItem>
<DropdownMenuSeparator />
<DropdownMenuItem
render={
<Link className={'flex items-center gap-x-2'} href={paths.home} />
}
>
<Home className={'h-4 w-4'} />
<span>
<Trans i18nKey={'common.routes.home'} />
</span>
</DropdownMenuItem>
<DropdownMenuSeparator />
<DropdownMenuItem
render={
<Link className={'flex items-center gap-x-2'} href={'/docs'} />
}
>
<MessageCircleQuestion className={'h-4 w-4'} />
<span>
<Trans i18nKey={'common.documentation'} />
</span>
</DropdownMenuItem>
<If condition={isSuperAdmin}>
<DropdownMenuSeparator />
<DropdownMenuItem
render={
<Link
className={
'flex items-center gap-x-2 text-yellow-700 dark:text-yellow-500'
}
href={'/admin'}
/>
}
>
<Shield className={'h-4 w-4'} />
<span>Super Admin</span>
</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 gap-x-2'}>
<LogOut className={'h-4 w-4'} />
<span>
<Trans i18nKey={'auth.signOut'} />
</span>
</span>
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
);
}