Refine the default system settings structure and model pricing editor so pricing configuration is easier to scan and edit.
126 lines
4.1 KiB
TypeScript
Vendored
126 lines
4.1 KiB
TypeScript
Vendored
import { useMemo } from 'react'
|
|
import { useNavigate } from '@tanstack/react-router'
|
|
import { User, Wallet, LogOut, Settings } from 'lucide-react'
|
|
import { useTranslation } from 'react-i18next'
|
|
import { useAuthStore } from '@/stores/auth-store'
|
|
import { getUserAvatarFallback, getUserAvatarStyle } from '@/lib/avatar'
|
|
import { ROLE } from '@/lib/roles'
|
|
import useDialogState from '@/hooks/use-dialog'
|
|
import { useUserDisplay } from '@/hooks/use-user-display'
|
|
import { Avatar, AvatarFallback } from '@/components/ui/avatar'
|
|
import { Button } from '@/components/ui/button'
|
|
import {
|
|
DropdownMenu,
|
|
DropdownMenuContent,
|
|
DropdownMenuItem,
|
|
DropdownMenuSeparator,
|
|
DropdownMenuTrigger,
|
|
} from '@/components/ui/dropdown-menu'
|
|
import { SignOutDialog } from '@/components/sign-out-dialog'
|
|
|
|
const avatarFallbackClassName = 'font-semibold text-white'
|
|
|
|
export function ProfileDropdown() {
|
|
const { t } = useTranslation()
|
|
const navigate = useNavigate()
|
|
const [open, setOpen] = useDialogState()
|
|
const user = useAuthStore((state) => state.auth.user)
|
|
const { displayName, roleLabel } = useUserDisplay(user)
|
|
const isSuperAdmin = user?.role === ROLE.SUPER_ADMIN
|
|
const avatarName = user?.username || displayName
|
|
const avatarFallback = getUserAvatarFallback(avatarName)
|
|
const avatarFallbackStyle = useMemo(
|
|
() => getUserAvatarStyle(avatarName),
|
|
[avatarName]
|
|
)
|
|
|
|
return (
|
|
<>
|
|
<DropdownMenu modal={false}>
|
|
<DropdownMenuTrigger
|
|
render={
|
|
<Button
|
|
variant='ghost'
|
|
className='relative size-6 rounded-full p-0'
|
|
/>
|
|
}
|
|
>
|
|
<Avatar className='size-6'>
|
|
<AvatarFallback
|
|
className={`${avatarFallbackClassName} text-[11px]`}
|
|
style={avatarFallbackStyle}
|
|
>
|
|
{avatarFallback}
|
|
</AvatarFallback>
|
|
</Avatar>
|
|
</DropdownMenuTrigger>
|
|
<DropdownMenuContent align='end' sideOffset={8} className='w-56'>
|
|
<div className='flex items-center gap-2 px-1.5 py-1.5'>
|
|
<Avatar className='size-8'>
|
|
<AvatarFallback
|
|
className={`${avatarFallbackClassName} text-xs`}
|
|
style={avatarFallbackStyle}
|
|
>
|
|
{avatarFallback}
|
|
</AvatarFallback>
|
|
</Avatar>
|
|
<div className='flex flex-1 flex-col gap-0.5 overflow-hidden'>
|
|
<p className='text-foreground truncate text-sm font-medium'>
|
|
{displayName}
|
|
</p>
|
|
<div className='flex items-center gap-1.5'>
|
|
<span className='text-muted-foreground text-xs'>
|
|
{roleLabel}
|
|
</span>
|
|
{user?.group && (
|
|
<>
|
|
<span className='text-muted-foreground text-xs'>·</span>
|
|
<span className='text-muted-foreground truncate text-xs'>
|
|
{String(user.group)}
|
|
</span>
|
|
</>
|
|
)}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<DropdownMenuSeparator />
|
|
|
|
<DropdownMenuItem onClick={() => navigate({ to: '/profile' })}>
|
|
<User className='size-4' />
|
|
{t('Profile')}
|
|
</DropdownMenuItem>
|
|
|
|
<DropdownMenuItem onClick={() => navigate({ to: '/wallet' })}>
|
|
<Wallet className='size-4' />
|
|
{t('Wallet')}
|
|
</DropdownMenuItem>
|
|
|
|
{isSuperAdmin && (
|
|
<DropdownMenuItem
|
|
onClick={() =>
|
|
navigate({
|
|
to: '/system-settings/site/$section',
|
|
params: { section: 'system-info' },
|
|
})
|
|
}
|
|
>
|
|
<Settings className='size-4' />
|
|
{t('System Settings')}
|
|
</DropdownMenuItem>
|
|
)}
|
|
|
|
<DropdownMenuSeparator />
|
|
|
|
<DropdownMenuItem variant='destructive' onClick={() => setOpen(true)}>
|
|
<LogOut className='size-4' />
|
|
{t('Sign out')}
|
|
</DropdownMenuItem>
|
|
</DropdownMenuContent>
|
|
</DropdownMenu>
|
|
|
|
<SignOutDialog open={!!open} onOpenChange={setOpen} />
|
|
</>
|
|
)
|
|
}
|