You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 

207 lines
6.8 KiB

import LoginDialog from '@/components/LoginDialog'
import LogoutDialog from '@/components/LogoutDialog'
import { KeyboardShortcutsHelpButton } from '@/components/KeyboardShortcutsHelp'
import KeyboardShortcutsHelpSidebarButton from '@/components/Sidebar/KeyboardShortcutsHelpSidebarButton'
import SidebarItem from '@/components/Sidebar/SidebarItem'
import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar'
import { Button } from '@/components/ui/button'
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuSeparator,
DropdownMenuTrigger
} from '@/components/ui/dropdown-menu'
import { Skeleton } from '@/components/ui/skeleton'
import { formatPubkey, formatNpub, generateImageByPubkey, pubkeyToNpub } from '@/lib/pubkey'
import { cn } from '@/lib/utils'
import { usePrimaryPage } from '@/contexts/primary-page-context'
import { useNostr } from '@/providers/NostrProvider'
import { ArrowDownUp, LogIn, LogOut, Settings, User, UserRound } from 'lucide-react'
import { useMemo, useState, type ReactNode } from 'react'
import { useTranslation } from 'react-i18next'
export type HelpAndAccountMenuVariant = 'sidebar' | 'titlebar'
function AccountDropdownItems({
onSwitchAccount,
onLogoutClick
}: {
onSwitchAccount: () => void
onLogoutClick: () => void
}) {
const { t } = useTranslation()
const { navigate } = usePrimaryPage()
return (
<>
<DropdownMenuItem onClick={() => navigate('profile')}>
<User className="size-4" />
{t('Profile')}
</DropdownMenuItem>
<DropdownMenuItem onClick={() => navigate('settings')}>
<Settings className="size-4" />
{t('Settings')}
</DropdownMenuItem>
<DropdownMenuSeparator />
<DropdownMenuItem onClick={onSwitchAccount}>
<ArrowDownUp className="size-4" />
{t('Switch account')}
</DropdownMenuItem>
<DropdownMenuItem className="text-destructive focus:text-destructive" onClick={onLogoutClick}>
<LogOut className="size-4" />
{t('Logout')}
</DropdownMenuItem>
</>
)
}
function SidebarAccountMenu({
onSwitchAccount,
onLogoutClick
}: {
onSwitchAccount: () => void
onLogoutClick: () => void
}) {
const { t } = useTranslation()
const { account, profile } = useNostr()
const { current, display } = usePrimaryPage()
const pubkey = account?.pubkey
const active = useMemo(() => current === 'profile' && display, [display, current])
if (!pubkey) return null
const defaultAvatar = generateImageByPubkey(pubkey)
const npub = pubkeyToNpub(pubkey)
const fallbackUsername = npub ? formatNpub(npub) : formatPubkey(pubkey)
const { username, avatar } = profile || { username: fallbackUsername, avatar: defaultAvatar }
return (
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button
type="button"
variant="ghost"
title={t('Account menu')}
aria-label={t('Account menu')}
className={cn(
'clickable h-12 min-w-0 justify-start gap-2 rounded-lg bg-transparent p-2 text-lg font-semibold text-foreground shadow-none hover:text-accent-foreground',
'w-12 xl:w-full xl:px-2 xl:py-2',
active && 'bg-accent/50'
)}
>
<Avatar className="size-8 shrink-0">
<AvatarImage src={avatar} />
<AvatarFallback>
<img src={defaultAvatar} alt="" />
</AvatarFallback>
</Avatar>
<span className="truncate max-xl:hidden">{username}</span>
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent side="top" align="end" className="z-[220]">
<AccountDropdownItems onSwitchAccount={onSwitchAccount} onLogoutClick={onLogoutClick} />
</DropdownMenuContent>
</DropdownMenu>
)
}
function TitlebarAccountMenu({
onSwitchAccount,
onLogoutClick
}: {
onSwitchAccount: () => void
onLogoutClick: () => void
}) {
const { t } = useTranslation()
const { profile } = useNostr()
const { current, display } = usePrimaryPage()
const defaultAvatar = useMemo(
() => (profile?.pubkey ? generateImageByPubkey(profile.pubkey) : ''),
[profile]
)
const active = useMemo(() => current === 'profile' && display, [display, current])
return (
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button
variant="ghost"
size="titlebar-icon"
className={cn(active ? 'bg-accent/50' : '')}
title={t('Account menu')}
aria-label={t('Account menu')}
>
{profile ? (
<Avatar className={cn('w-6 h-6', active ? 'ring-primary ring-1' : '')}>
<AvatarImage src={profile.avatar} className="object-cover object-center" />
<AvatarFallback>
<img src={defaultAvatar} alt="" />
</AvatarFallback>
</Avatar>
) : (
<Skeleton className={cn('w-6 h-6 rounded-full', active ? 'ring-primary ring-1' : '')} />
)}
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end" side="bottom" className="z-[220]">
<AccountDropdownItems onSwitchAccount={onSwitchAccount} onLogoutClick={onLogoutClick} />
</DropdownMenuContent>
</DropdownMenu>
)
}
/**
* Help (?) + account avatar with the same dropdown on sidebar (desktop) and titlebar (mobile).
*/
export default function HelpAndAccountMenu({ variant }: { variant: HelpAndAccountMenuVariant }) {
const { t } = useTranslation()
const { pubkey, checkLogin } = useNostr()
const [loginDialogOpen, setLoginDialogOpen] = useState(false)
const [logoutDialogOpen, setLogoutDialogOpen] = useState(false)
const help =
variant === 'sidebar' ? <KeyboardShortcutsHelpSidebarButton /> : <KeyboardShortcutsHelpButton />
let account: ReactNode
if (pubkey) {
account =
variant === 'sidebar' ? (
<SidebarAccountMenu
onSwitchAccount={() => setLoginDialogOpen(true)}
onLogoutClick={() => setLogoutDialogOpen(true)}
/>
) : (
<TitlebarAccountMenu
onSwitchAccount={() => setLoginDialogOpen(true)}
onLogoutClick={() => setLogoutDialogOpen(true)}
/>
)
} else if (variant === 'sidebar') {
account = (
<SidebarItem onClick={() => checkLogin()} title="Login">
<LogIn strokeWidth={3} />
</SidebarItem>
)
} else {
account = (
<Button variant="ghost" size="titlebar-icon" onClick={() => checkLogin()} title={t('Login')}>
<UserRound />
</Button>
)
}
const wrapClass =
variant === 'titlebar' ? 'flex shrink-0 items-center gap-1' : 'flex flex-col space-y-2'
return (
<>
<div className={wrapClass}>
{help}
{account}
</div>
<LoginDialog open={loginDialogOpen} setOpen={setLoginDialogOpen} />
<LogoutDialog open={logoutDialogOpen} setOpen={setLogoutDialogOpen} />
</>
)
}