Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions bun.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@
"@radix-ui/react-tooltip": "^1.2.8",
"@tailwindcss/vite": "^4.1.18",
"@tanstack/react-query": "^5.90.20",
"boring-avatars": "^2.0.4",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
"cmdk": "^1.1.1",
Expand Down
58 changes: 58 additions & 0 deletions src/components/account-avatar.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import BoringAvatar from 'boring-avatars'
import { EyeIcon } from 'lucide-react'
import { cn } from '@/lib/utils'

const ACCOUNT_AVATAR_COLORS = [
'#1ADEF5',
'#03C1DB',
'#61F0FE',
'#0C131B',
'#152A38',
'#47CD89',
'#FABC3C',
]

const SIZE_STYLES = {
xs: { className: 'h-6 w-6', pixelSize: 24 },
sm: { className: 'h-8 w-8', pixelSize: 32 },
md: { className: 'h-10 w-10', pixelSize: 40 },
lg: { className: 'h-12 w-12', pixelSize: 48 },
} as const

type AccountAvatarProps = {
identity: string
name?: string
watchOnly?: boolean
size?: keyof typeof SIZE_STYLES
className?: string
}

const AccountAvatar = ({
identity,
name,
watchOnly = false,
size = 'md',
className,
}: AccountAvatarProps) => {
const { className: sizeClassName, pixelSize } = SIZE_STYLES[size]
const avatarName = name?.trim() ? `${name}-${identity}` : identity

return (
<div className={cn('relative shrink-0', sizeClassName, className)}>
<BoringAvatar
size={pixelSize}
name={avatarName}
variant="marble"
colors={ACCOUNT_AVATAR_COLORS}
className="h-full w-full rounded-full border border-border/60 bg-card"
/>
{watchOnly && (
<span className="absolute -right-0.5 -bottom-0.5 flex h-3.5 w-3.5 items-center justify-center rounded-full border border-background bg-card text-muted-foreground">
<EyeIcon className="h-2.5 w-2.5" />
</span>
)}
</div>
)
}

export default AccountAvatar
33 changes: 20 additions & 13 deletions src/components/app-header.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,5 @@
import {
CopyIcon,
EyeIcon,
XIcon,
PanelRightOpenIcon,
PlusIcon,
UsersIcon,
WalletIcon,
} from 'lucide-react'
import { CopyIcon, EyeIcon, XIcon, PanelRightOpenIcon, PlusIcon, UsersIcon } from 'lucide-react'
import AccountAvatar from '@/components/account-avatar'
import { Button } from '@/components/ui/button'
import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover'
import { truncateString } from '@/lib/utils'
Expand Down Expand Up @@ -59,6 +52,10 @@ const AppHeader = ({
Array<{ name: string; identity: string; watchOnly?: boolean }>
>([])
const [isMenuOpen, setIsMenuOpen] = useState(false)
const activeAccount = useMemo(
() => accounts.find((account) => account.identity === identity),
[accounts, identity],
)

const refreshAccounts = useCallback(() => {
const nextAccountName =
Expand Down Expand Up @@ -160,9 +157,12 @@ const AppHeader = ({
className="flex min-w-0 items-center gap-3 text-left"
aria-label={t('home.accounts.selectLabel')}
>
<div className="flex h-10 w-10 items-center justify-center rounded-md border border-border/60 bg-card">
<WalletIcon className="h-5 w-5 text-primary" />
</div>
<AccountAvatar
identity={identity}
name={accountName}
watchOnly={activeAccount?.watchOnly}
size="md"
/>
<div className="flex min-w-0 flex-col">
<span className="truncate text-sm font-semibold text-foreground">
{accountName}
Expand Down Expand Up @@ -212,10 +212,17 @@ const AppHeader = ({
key={account.identity}
type="button"
onClick={() => handleSelectAccount(account)}
className={`flex w-full items-center rounded-md px-2 py-2 text-left text-sm transition hover:bg-muted/30 ${
className={`flex w-full items-center gap-3 rounded-md px-2 py-2 text-left text-sm transition hover:bg-muted/30 ${
account.identity === identity ? 'bg-muted/20' : ''
}`}
>
<AccountAvatar
identity={account.identity}
name={account.name}
watchOnly={account.watchOnly}
size="sm"
className="mr-3"
/>
<div className="min-w-0 flex-1">
<div className="flex items-center gap-2">
<span className="truncate font-medium text-foreground">
Expand Down
50 changes: 35 additions & 15 deletions src/components/dapp/dapp-approval-drawer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { useCallback, useEffect, useMemo, useState } from 'react'
import { useTranslation } from 'react-i18next'
import { useLocation } from 'react-router-dom'
import { ChevronDownIcon, ChevronUpIcon, GlobeIcon, Link2OffIcon, LinkIcon } from 'lucide-react'
import AccountAvatar from '@/components/account-avatar'
import { Button } from '@/components/ui/button'
import {
Drawer,
Expand Down Expand Up @@ -282,12 +283,21 @@ const DappApprovalDrawer = () => {
<p className="text-[11px] uppercase tracking-wide text-muted-foreground">
{t('dapp.approval.sharedAccount')}
</p>
<p className="truncate text-sm font-medium text-foreground">
{connectSummary.accountName || t('dapp.approval.sharedAccountFallback')}
</p>
<p className="truncate font-mono text-xs text-muted-foreground">
{truncateString(connectSummary.accountIdentity)}
</p>
<div className="mt-2 flex items-center gap-3">
<AccountAvatar
identity={connectSummary.accountIdentity}
name={connectSummary.accountName}
size="md"
/>
<div className="min-w-0">
<p className="truncate text-sm font-medium text-foreground">
{connectSummary.accountName || t('dapp.approval.sharedAccountFallback')}
</p>
<p className="truncate font-mono text-xs text-muted-foreground">
{truncateString(connectSummary.accountIdentity)}
</p>
</div>
</div>
</div>
)}
{current.method === 'signMessage' && (
Expand Down Expand Up @@ -371,15 +381,25 @@ const DappApprovalDrawer = () => {
)}
{isWatchOnlySigningRequest && accountSummary && (
<div className="rounded-xl border border-amber-500/30 bg-amber-500/10 p-3">
<p className="text-sm font-medium text-foreground">
{t('dapp.approval.watchOnlyTitle')}
</p>
<p className="mt-1 text-sm text-muted-foreground">
{t('dapp.approval.watchOnlyDescription', {
accountName:
accountSummary.accountName || t('dapp.approval.sharedAccountFallback'),
})}
</p>
<div className="flex items-center gap-3">
<AccountAvatar
identity={accountSummary.accountIdentity}
name={accountSummary.accountName}
watchOnly={accountSummary.accountWatchOnly}
size="md"
/>
<div className="min-w-0">
<p className="text-sm font-medium text-foreground">
{t('dapp.approval.watchOnlyTitle')}
</p>
<p className="mt-1 text-sm text-muted-foreground">
{t('dapp.approval.watchOnlyDescription', {
accountName:
accountSummary.accountName || t('dapp.approval.sharedAccountFallback'),
})}
</p>
</div>
</div>
</div>
)}
{requiresPassphrase && !isWatchOnlySigningRequest && (
Expand Down
7 changes: 7 additions & 0 deletions src/components/pages/manage-accounts/account-list-item.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {
TrashIcon,
} from 'lucide-react'
import { useTranslation } from 'react-i18next'
import AccountAvatar from '@/components/account-avatar'
import { Button } from '@/components/ui/button'
import {
DropdownMenu,
Expand Down Expand Up @@ -76,6 +77,12 @@ const AccountListItem = ({
} ${isDragging ? 'opacity-60' : ''}`}
>
<div className="flex min-w-0 items-center gap-2">
<AccountAvatar
identity={account.identity}
name={account.name}
watchOnly={account.watchOnly}
size="sm"
/>
<div className="min-w-0">
<div className="flex items-center gap-2">
<span className="truncate text-sm font-semibold text-foreground">{account.name}</span>
Expand Down
Loading