import LoginDialog from '@/components/LoginDialog'
import LogoutDialog from '@/components/LogoutDialog'
import SidebarItem from '@/components/Sidebar/SidebarItem'
import { Avatar, AvatarFallback, AvatarIdenticon, 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 {
accountPubkeyToHex,
formatPubkey,
formatNpub,
generateImageByPubkey,
hexPubkeysEqual,
pubkeyToNpub
} from '@/lib/pubkey'
import { isVideo } from '@/lib/url'
import { cn } from '@/lib/utils'
import { openBrowseCacheFromRegistry } from '@/contexts/cache-browser-context'
import { toCacheSettings } from '@/lib/link'
import { usePrimaryPage } from '@/contexts/primary-page-context'
import { useSmartSettingsNavigation } from '@/PageManager'
import { useFetchProfile } from '@/hooks/useFetchProfile'
import { useNostr } from '@/providers/NostrProvider'
import { AccountQuickSwitchMenuItems } from '@/components/AccountQuickSwitchMenuItems'
import { ReadOnlySessionIndicator } from '@/components/ReadOnlySessionIndicator'
import { ArrowDownUp, Database, LogIn, LogOut, Settings, User, UserRound } from 'lucide-react'
import { useCallback, useMemo, useState, type ReactNode } from 'react'
import { useTranslation } from 'react-i18next'
import type { TProfile } from '@/types'
/** Profile for the badge only when it belongs to the active session pubkey (avoids stale name/avatar). */
function profileForActivePubkey(
pubkey: string | undefined,
nostrProfile: TProfile | null,
fetchedProfile: TProfile | null
): TProfile | null {
const pk = pubkey ? accountPubkeyToHex(pubkey) : null
if (!pk) return null
if (fetchedProfile && hexPubkeysEqual(fetchedProfile.pubkey, pk)) return fetchedProfile
if (nostrProfile && hexPubkeysEqual(nostrProfile.pubkey, pk)) return nostrProfile
return null
}
const titlebarAccountMenuContentClassName =
'z-[220] w-[min(18rem,calc(100vw-1.5rem))] overflow-y-auto overscroll-contain'
export type HelpAndAccountMenuVariant = 'sidebar' | 'titlebar'
function AccountDropdownItems({
onSwitchAccount,
onLogoutClick,
onBrowseCache,
onCloseMenu
}: {
onSwitchAccount: () => void
onLogoutClick: () => void
onBrowseCache: () => void
onCloseMenu?: () => void
}) {
const { t } = useTranslation()
const { navigate } = usePrimaryPage()
return (
<>
navigate('profile')}>
{t('Profile')}
navigate('settings')}>
{t('Settings')}
{t('Browse Cache')}
{t('Switch account')}
{t('Logout')}
>
)
}
function SidebarAccountMenu({
onSwitchAccount,
onLogoutClick,
onBrowseCache
}: {
onSwitchAccount: () => void
onLogoutClick: () => void
onBrowseCache: () => void
}) {
const { t } = useTranslation()
const { account, profile } = useNostr()
const { current, display } = usePrimaryPage()
const [menuOpen, setMenuOpen] = useState(false)
const pubkey = account?.pubkey
const { profile: fetchedProfile } = useFetchProfile(pubkey)
const resolvedProfile = useMemo(
() => profileForActivePubkey(pubkey, profile, fetchedProfile),
[pubkey, profile, fetchedProfile]
)
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 } = resolvedProfile
? { username: resolvedProfile.username, avatar: resolvedProfile.avatar ?? defaultAvatar }
: { username: fallbackUsername, avatar: defaultAvatar }
return (
setMenuOpen(false)}
/>
)
}
function TitlebarAccountMenu({
onSwitchAccount,
onLogoutClick,
onBrowseCache
}: {
onSwitchAccount: () => void
onLogoutClick: () => void
onBrowseCache: () => void
}) {
const { t } = useTranslation()
const { account, profile } = useNostr()
const pubkey = account?.pubkey
const { profile: fetchedProfile } = useFetchProfile(pubkey)
const resolvedProfile = useMemo(
() => profileForActivePubkey(pubkey, profile, fetchedProfile),
[pubkey, profile, fetchedProfile]
)
const { current, display } = usePrimaryPage()
const [menuOpen, setMenuOpen] = useState(false)
const defaultAvatar = useMemo(
() => (resolvedProfile?.pubkey ? generateImageByPubkey(resolvedProfile.pubkey) : ''),
[resolvedProfile]
)
const active = useMemo(() => current === 'profile' && display, [display, current])
return (
setMenuOpen(false)}
/>
)
}
function LoggedOutTitlebarMenu({ onLogin }: { onLogin: () => void }) {
const { t } = useTranslation()
return (
)
}
/** Sidebar: account / login stack. Titlebar (mobile): compact account or login control. */
export default function HelpAndAccountMenu({ variant }: { variant: HelpAndAccountMenuVariant }) {
const { pubkey, checkLogin, isNip07LoginInFlight } = useNostr()
const { navigateToSettings } = useSmartSettingsNavigation()
const onBrowseCache = useCallback(() => {
if (!openBrowseCacheFromRegistry()) {
navigateToSettings(toCacheSettings())
}
}, [navigateToSettings])
const [loginDialogOpen, setLoginDialogOpen] = useState(false)
const [logoutDialogOpen, setLogoutDialogOpen] = useState(false)
let account: ReactNode
if (pubkey) {
account =
variant === 'sidebar' ? (
setLoginDialogOpen(true)}
onLogoutClick={() => setLogoutDialogOpen(true)}
onBrowseCache={onBrowseCache}
/>
) : (
setLoginDialogOpen(true)}
onLogoutClick={() => setLogoutDialogOpen(true)}
onBrowseCache={onBrowseCache}
/>
)
} else if (variant === 'titlebar') {
account = checkLogin()} />
} else {
account = (
checkLogin()} title="Login">
)
}
const wrapClass =
variant === 'titlebar' ? 'flex shrink-0 items-center gap-1' : 'flex flex-col space-y-2'
return (
<>
{account}
>
)
}