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}
) }