Translations are memory-only; on logout/account switch we now clear the cache, reject pending requests and terminate the worker (the model itself is public and stays in the browser cache).
117 lines
No EOL
4.9 KiB
TypeScript
117 lines
No EOL
4.9 KiB
TypeScript
// NOTE: This file is stable and usually should not be modified.
|
|
// It is important that all functionality in this file is preserved, and should only be modified if explicitly requested.
|
|
|
|
import { ChevronDown, LogOut, UserIcon, UserPlus, Wallet } from 'lucide-react';
|
|
import {
|
|
DropdownMenu,
|
|
DropdownMenuContent,
|
|
DropdownMenuItem,
|
|
DropdownMenuSeparator,
|
|
DropdownMenuTrigger,
|
|
} from '@/components/ui/dropdown-menu.tsx';
|
|
import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar.tsx';
|
|
import { WalletModal } from '@/components/WalletModal';
|
|
import { clearDmDecryptCache } from '@/lib/dmDecryptCache';
|
|
import { clearTranslationMemory } from '@/lib/translation';
|
|
import { useLoggedInAccounts, type Account } from '@/hooks/useLoggedInAccounts';
|
|
import { genUserName } from '@/lib/genUserName';
|
|
import { useTranslation } from '@/contexts/language';
|
|
|
|
interface AccountSwitcherProps {
|
|
onAddAccountClick: () => void;
|
|
}
|
|
|
|
export function AccountSwitcher({ onAddAccountClick }: AccountSwitcherProps) {
|
|
const { currentUser, otherUsers, setLogin, removeLogin } = useLoggedInAccounts();
|
|
const { t } = useTranslation();
|
|
|
|
if (!currentUser) return null;
|
|
|
|
const getDisplayName = (account: Account): string => {
|
|
return account.metadata?.name ?? genUserName(account.pubkey);
|
|
}
|
|
|
|
const getNip05 = (account: Account): string | undefined => {
|
|
return account.metadata?.nip05;
|
|
}
|
|
|
|
return (
|
|
<DropdownMenu modal={false}>
|
|
<DropdownMenuTrigger asChild>
|
|
<button className='flex items-center gap-3 p-3 rounded-full hover:bg-accent transition-all w-full text-foreground'>
|
|
<div className="relative">
|
|
<Avatar className='w-10 h-10'>
|
|
<AvatarImage src={currentUser.metadata?.picture} alt={getDisplayName(currentUser)} />
|
|
<AvatarFallback>{getDisplayName(currentUser).charAt(0)}</AvatarFallback>
|
|
</Avatar>
|
|
{/* Green online indicator dot */}
|
|
<span className="absolute -bottom-0.5 -right-0.5 w-3.5 h-3.5 bg-green-500 border-2 border-background rounded-full" />
|
|
</div>
|
|
<div className='flex-1 text-left hidden md:block truncate'>
|
|
<p className='font-medium text-sm truncate'>{getDisplayName(currentUser)}</p>
|
|
{getNip05(currentUser) && (
|
|
<p className='text-xs text-muted-foreground truncate'>
|
|
{getNip05(currentUser)}
|
|
</p>
|
|
)}
|
|
</div>
|
|
<ChevronDown className='w-4 h-4 text-muted-foreground' />
|
|
</button>
|
|
</DropdownMenuTrigger>
|
|
<DropdownMenuContent className='w-56 p-2 animate-scale-in'>
|
|
<div className='font-medium text-sm px-2 py-1.5'>{t('settingsUi.accountSwitcher.switchAccount')}</div>
|
|
{otherUsers.map((user) => (
|
|
<DropdownMenuItem
|
|
key={user.id}
|
|
onClick={() => setLogin(user.id)}
|
|
className='flex items-center gap-2 cursor-pointer p-2 rounded-md'
|
|
>
|
|
<Avatar className='w-8 h-8'>
|
|
<AvatarImage src={user.metadata?.picture} alt={getDisplayName(user)} />
|
|
<AvatarFallback>{getDisplayName(user)?.charAt(0) || <UserIcon />}</AvatarFallback>
|
|
</Avatar>
|
|
<div className='flex-1 truncate'>
|
|
<p className='text-sm font-medium'>{getDisplayName(user)}</p>
|
|
{getNip05(user) && (
|
|
<p className='text-xs text-muted-foreground truncate'>
|
|
{getNip05(user)}
|
|
</p>
|
|
)}
|
|
</div>
|
|
{user.id === currentUser.id && <div className='w-2 h-2 rounded-full bg-primary'></div>}
|
|
</DropdownMenuItem>
|
|
))}
|
|
<DropdownMenuSeparator />
|
|
<WalletModal>
|
|
<DropdownMenuItem
|
|
className='flex items-center gap-2 cursor-pointer p-2 rounded-md'
|
|
onSelect={(e) => e.preventDefault()}
|
|
>
|
|
<Wallet className='w-4 h-4' />
|
|
<span>{t('settingsUi.accountSwitcher.walletSettings')}</span>
|
|
</DropdownMenuItem>
|
|
</WalletModal>
|
|
<DropdownMenuItem
|
|
onClick={onAddAccountClick}
|
|
className='flex items-center gap-2 cursor-pointer p-2 rounded-md'
|
|
>
|
|
<UserPlus className='w-4 h-4' />
|
|
<span>{t('settingsUi.accountSwitcher.addAccount')}</span>
|
|
</DropdownMenuItem>
|
|
<DropdownMenuItem
|
|
onClick={() => {
|
|
// Drop this account's plaintext DM cache (device-local) on logout so
|
|
// another account on the same device can't read it from IndexedDB.
|
|
void clearDmDecryptCache(currentUser.id).catch(() => {});
|
|
clearTranslationMemory();
|
|
removeLogin(currentUser.id);
|
|
}}
|
|
className='flex items-center gap-2 cursor-pointer p-2 rounded-md text-red-500'
|
|
>
|
|
<LogOut className='w-4 h-4' />
|
|
<span>{t('settingsUi.accountSwitcher.logout')}</span>
|
|
</DropdownMenuItem>
|
|
</DropdownMenuContent>
|
|
</DropdownMenu>
|
|
);
|
|
} |