From 6f7121083de6980fa15d75d6170bfb565ebe5bd5 Mon Sep 17 00:00:00 2001 From: antoncoding Date: Thu, 4 Dec 2025 14:34:53 +0800 Subject: [PATCH 1/9] feat: vault context and update AddressIdentity --- .../stats/components/TransactionTableBody.tsx | 9 +- .../components/VaultInitializationModal.tsx | 8 +- .../components/settings/AgentListItem.tsx | 4 +- .../components/settings/AgentsTab.tsx | 15 +- .../[chainId]/[vaultAddress]/content.tsx | 9 +- app/layout.tsx | 11 +- .../[marketid]/components/BorrowsTable.tsx | 16 +- .../components/LiquidationsTable.tsx | 16 +- .../[marketid]/components/SuppliesTable.tsx | 16 +- app/positions/components/PositionsContent.tsx | 4 +- app/rewards/components/RewardContent.tsx | 4 +- docs/Styling.md | 73 +++++ src/components/Account/AccountWithAvatar.tsx | 20 -- src/components/Account/AccountWithENS.tsx | 26 -- src/components/common/AccountIdentity.tsx | 301 ++++++++++++++++++ src/components/common/AddressDisplay.tsx | 164 ---------- src/components/common/AddressIdentity.tsx | 51 --- src/components/common/AllocatorCard.tsx | 4 +- src/components/common/index.ts | 2 +- .../layout/header/AccountDropdown.tsx | 7 +- src/contexts/VaultRegistryContext.tsx | 50 +++ src/hooks/useAddressLabel.ts | 33 ++ 22 files changed, 518 insertions(+), 325 deletions(-) delete mode 100644 src/components/Account/AccountWithAvatar.tsx delete mode 100644 src/components/Account/AccountWithENS.tsx create mode 100644 src/components/common/AccountIdentity.tsx delete mode 100644 src/components/common/AddressDisplay.tsx delete mode 100644 src/components/common/AddressIdentity.tsx create mode 100644 src/contexts/VaultRegistryContext.tsx create mode 100644 src/hooks/useAddressLabel.ts diff --git a/app/admin/stats/components/TransactionTableBody.tsx b/app/admin/stats/components/TransactionTableBody.tsx index f465d0d3..c450cfd0 100644 --- a/app/admin/stats/components/TransactionTableBody.tsx +++ b/app/admin/stats/components/TransactionTableBody.tsx @@ -1,7 +1,7 @@ import React from 'react'; import Link from 'next/link'; import { formatUnits } from 'viem'; -import { AddressIdentity } from '@/components/common/AddressIdentity'; +import { AccountIdentity } from '@/components/common/AccountIdentity'; import { TransactionIdentity } from '@/components/common/TransactionIdentity'; import { MarketIdBadge } from '@/components/MarketIdBadge'; import { MarketIdentity, MarketIdentityFocus, MarketIdentityMode } from '@/components/MarketIdentity'; @@ -86,7 +86,12 @@ export function TransactionTableBody({ {/* User Address */} - + {/* Loan Asset */} diff --git a/app/autovault/[chainId]/[vaultAddress]/components/VaultInitializationModal.tsx b/app/autovault/[chainId]/[vaultAddress]/components/VaultInitializationModal.tsx index fdd076c5..05f9a390 100644 --- a/app/autovault/[chainId]/[vaultAddress]/components/VaultInitializationModal.tsx +++ b/app/autovault/[chainId]/[vaultAddress]/components/VaultInitializationModal.tsx @@ -4,7 +4,7 @@ import { useCallback, useEffect, useMemo, useState } from 'react'; import { FiZap } from 'react-icons/fi'; import { Address, zeroAddress } from 'viem'; import { Button } from '@/components/common'; -import { AddressDisplay } from '@/components/common/AddressDisplay'; +import { AccountIdentity } from '@/components/common/AccountIdentity'; import { AllocatorCard } from '@/components/common/AllocatorCard'; import { Modal, ModalHeader, ModalBody, ModalFooter } from '@/components/common/Modal'; import { Spinner } from '@/components/common/Spinner'; @@ -94,7 +94,7 @@ function AdapterCapStep({
Adapter address - +
Adapter cap (%) @@ -141,14 +141,14 @@ function FinalizeSetupStep({
Adapter {adapterIsReady ? ( - + ) : ( Adapter not detected yet. )}
Morpho registry - +
  • Only Morpho-approved adapters can be enabled after this step.
  • diff --git a/app/autovault/[chainId]/[vaultAddress]/components/settings/AgentListItem.tsx b/app/autovault/[chainId]/[vaultAddress]/components/settings/AgentListItem.tsx index 0afd1ab0..862c2e8a 100644 --- a/app/autovault/[chainId]/[vaultAddress]/components/settings/AgentListItem.tsx +++ b/app/autovault/[chainId]/[vaultAddress]/components/settings/AgentListItem.tsx @@ -1,6 +1,6 @@ import { Address } from 'viem'; import { AgentIcon } from '@/components/AgentIcon'; -import { AddressDisplay } from '@/components/common/AddressDisplay'; +import { AccountIdentity } from '@/components/common/AccountIdentity'; import { findAgent } from '@/utils/monarch-agent'; type AgentListItemProps = { @@ -14,7 +14,7 @@ export function AgentListItem({ address }: AgentListItemProps) {
    {agent && {agent.name}} - +
    ); } diff --git a/app/autovault/[chainId]/[vaultAddress]/components/settings/AgentsTab.tsx b/app/autovault/[chainId]/[vaultAddress]/components/settings/AgentsTab.tsx index 31e87a7b..84d4b778 100644 --- a/app/autovault/[chainId]/[vaultAddress]/components/settings/AgentsTab.tsx +++ b/app/autovault/[chainId]/[vaultAddress]/components/settings/AgentsTab.tsx @@ -1,6 +1,6 @@ import { useCallback, useState } from 'react'; import { Address } from 'viem'; -import { AddressDisplay } from '@/components/common/AddressDisplay'; +import { AccountIdentity } from '@/components/common/AccountIdentity'; import { Button } from '@/components/common/Button'; import { Spinner } from '@/components/common/Spinner'; import { useMarketNetwork } from '@/hooks/useMarketNetwork'; @@ -75,12 +75,7 @@ export function AgentsTab({

    {description}

{normalized ? ( - + ) : ( Not assigned )} @@ -235,11 +230,11 @@ export function AgentsTab({ ) : (
{sentinels.map((address) => ( - ))} diff --git a/app/autovault/[chainId]/[vaultAddress]/content.tsx b/app/autovault/[chainId]/[vaultAddress]/content.tsx index 8b8c26bd..429dd1e1 100644 --- a/app/autovault/[chainId]/[vaultAddress]/content.tsx +++ b/app/autovault/[chainId]/[vaultAddress]/content.tsx @@ -9,7 +9,7 @@ import { IoRefreshOutline } from 'react-icons/io5'; import { Address } from 'viem'; import { useAccount } from 'wagmi'; import { Button } from '@/components/common'; -import { AddressDisplay } from '@/components/common/AddressDisplay'; +import { AccountIdentity } from '@/components/common/AccountIdentity'; import Header from '@/components/layout/header/Header'; import { useVaultPage } from '@/hooks/useVaultPage'; import { getSlicedAddress } from '@/utils/address'; @@ -146,7 +146,12 @@ export default function VaultContent() { )}
- +
- +
- +
diff --git a/docs/Styling.md b/docs/Styling.md index 2f907eee..9dec2ff0 100644 --- a/docs/Styling.md +++ b/docs/Styling.md @@ -437,6 +437,79 @@ Add an action link (like explorer) in the top-right corner: - Render token avatars with `TokenIcon` (`@/components/TokenIcon`) so chain-specific fallbacks, glyph sizing, and tooltips stay consistent. - Display oracle provenance data with `OracleVendorBadge` (`@/components/OracleVendorBadge`) instead of plain text to benefit from vendor icons, warnings, and tooltips. +### Account Identity Component + +**AccountIdentity** (`@/components/common/AccountIdentity`) +- Unified component for displaying addresses, vault names, and ENS names +- Three variants: `badge`, `compact`, `full` +- All avatars are round by default + +**Variant Behaviors:** + +**Badge** - Minimal inline (no avatar) +- Shows: Vault name → ENS name → Shortened address + +**Compact** - Avatar (16px) wrapped in badge +- Avatar + (Vault name → ENS name → Shortened address) +- Single badge wraps both avatar and text + +**Full** - Horizontal layout with all info +- Avatar (36px) + Address badge + Extra badges (all on one line, centered) +- **Address badge**: Always shows shortened address (e.g., 0x1234...5678), click to copy +- **Extra badges** (shown based on conditions): + - Connected badge (if wallet is connected) + - ENS badge (if `showAddress=true` and no vault name) + - Vault badge (if address is a known vault) + +**Styling Rules:** +- Use `rounded-sm` for badges (not `rounded`) +- Background: `bg-hovered` (or `bg-green-500/10` for connected) +- Text: `font-monospace` with `text-secondary` or `text-primary` +- No underscores in variable names +- All avatars are round +- Full variant: all elements centered vertically + +```tsx +import { AccountIdentity } from '@/components/common/AccountIdentity'; + +// Badge variant - minimal inline (no avatar) + + +// Compact variant - avatar (16px) wrapped in badge background + + +// Full variant - avatar + address + extra info badges + + +// Full variant for vault address + +``` + +**Props:** +- `variant`: `'badge'` | `'compact'` | `'full'` +- `linkTo`: `'explorer'` | `'profile'` | `'none'` +- `showCopy`: Show copy icon at end of badge +- `copyable`: Make entire component clickable to copy +- `showAddress`: Show ENS badge (full variant only) + ### Market Display Components Use the right component for displaying market information: diff --git a/src/components/Account/AccountWithAvatar.tsx b/src/components/Account/AccountWithAvatar.tsx deleted file mode 100644 index e14e5b71..00000000 --- a/src/components/Account/AccountWithAvatar.tsx +++ /dev/null @@ -1,20 +0,0 @@ -import { Address } from 'viem'; -import { Avatar } from '@/components/Avatar/Avatar'; -import { getSlicedAddress } from '@/utils/address'; - -type AccountWithAvatarProps = { - address: Address; -}; - -function AccountWithSmallAvatar({ address }: AccountWithAvatarProps) { - return ( -
- - - {getSlicedAddress(address as `0x${string}`)} - -
- ); -} - -export default AccountWithSmallAvatar; diff --git a/src/components/Account/AccountWithENS.tsx b/src/components/Account/AccountWithENS.tsx deleted file mode 100644 index 77ca856e..00000000 --- a/src/components/Account/AccountWithENS.tsx +++ /dev/null @@ -1,26 +0,0 @@ -import { Address } from 'viem'; -import { Avatar } from '@/components/Avatar/Avatar'; -import { getSlicedAddress } from '@/utils/address'; -import { Name } from '../common/Name'; - -type AccountWithENSProps = { - address: Address; -}; - -function AccountWithENS({ address }: AccountWithENSProps) { - return ( -
- -
-
- -
- - {getSlicedAddress(address)} - -
-
- ); -} - -export default AccountWithENS; diff --git a/src/components/common/AccountIdentity.tsx b/src/components/common/AccountIdentity.tsx new file mode 100644 index 00000000..5bc80247 --- /dev/null +++ b/src/components/common/AccountIdentity.tsx @@ -0,0 +1,301 @@ +'use client'; + +import { useMemo, useState, useEffect, useCallback, type HTMLAttributes } from 'react'; +import clsx from 'clsx'; +import { FaCircle } from 'react-icons/fa'; +import { LuExternalLink, LuCopy } from 'react-icons/lu'; +import Link from 'next/link'; +import type { Address } from 'viem'; +import { useAccount } from 'wagmi'; +import { Avatar } from '@/components/Avatar/Avatar'; +import { Name } from '@/components/common/Name'; +import { useAddressLabel } from '@/hooks/useAddressLabel'; +import { useStyledToast } from '@/hooks/useStyledToast'; +import { getExplorerURL } from '@/utils/external'; +import type { SupportedNetworks } from '@/utils/networks'; + +type AccountIdentityProps = { + address: Address; + chainId?: number; + variant?: 'badge' | 'compact' | 'full'; + linkTo?: 'explorer' | 'profile' | 'none'; + copyable?: boolean; + showCopy?: boolean; + showAddress?: boolean; + className?: string; +}; + +/** + * Unified component for displaying account identities across the app. + * + * Badge & Compact: Show vault name → ENS name → shortened address + * Full: Always show address badge + optional extra badges (connected, ENS, vault) + * + * Variants: + * - badge: Minimal inline badge (no avatar) + * - compact: Avatar (16px) wrapped in badge background + * - full: Avatar (36px) + address badge + extra badges (all centered on one line) + */ +export function AccountIdentity({ + address, + chainId, + variant = 'badge', + linkTo = 'none', + copyable = false, + showCopy = false, + showAddress = false, + className, +}: AccountIdentityProps) { + const { address: connectedAddress, isConnected } = useAccount(); + const [mounted, setMounted] = useState(false); + const toast = useStyledToast(); + const { vaultName, shortAddress } = useAddressLabel(address, chainId); + + useEffect(() => { + setMounted(true); + }, []); + + const isOwner = useMemo(() => { + return mounted && isConnected && address === connectedAddress; + }, [address, connectedAddress, isConnected, mounted]); + + const href = useMemo(() => { + if (linkTo === 'none') return null; + if (linkTo === 'explorer') { + const numericChainId = Number(chainId ?? 1); + if (!Number.isFinite(numericChainId)) return null; + return getExplorerURL(address as `0x${string}`, numericChainId as SupportedNetworks); + } + if (linkTo === 'profile') { + return `/positions/${address}`; + } + return null; + }, [linkTo, address, chainId]); + + const handleCopy = useCallback(async () => { + try { + await navigator.clipboard.writeText(address); + toast.success('Address copied', `${address.slice(0, 6)}...${address.slice(-4)}`); + } catch (error) { + console.error('Failed to copy address', error); + } + }, [address, toast]); + + const handleKeyDown = useCallback['onKeyDown']>>( + (event) => { + if (!copyable) return; + if (event.key === 'Enter' || event.key === ' ') { + event.preventDefault(); + void handleCopy(); + } + }, + [copyable, handleCopy], + ); + + const interactiveProps: HTMLAttributes = copyable + ? { + role: 'button', + tabIndex: 0, + onClick: () => void handleCopy(), + onKeyDown: handleKeyDown, + } + : {}; + + // Badge variant - minimal inline badge (no avatar) + if (variant === 'badge') { + const content = ( + <> + {vaultName ?? } + {linkTo === 'explorer' && } + {showCopy && ( + { + e.preventDefault(); + e.stopPropagation(); + void handleCopy(); + }} + /> + )} + + ); + + const badgeClasses = clsx( + 'inline-flex items-center gap-1 rounded-sm bg-hovered px-2 py-1 font-zen text-xs text-secondary', + copyable && 'cursor-pointer transition-colors hover:brightness-110', + href && + 'no-underline transition-colors hover:bg-gray-300 hover:text-primary hover:no-underline dark:hover:bg-gray-700', + className, + ); + + if (href) { + return ( + { + if (copyable) { + e.preventDefault(); + void handleCopy(); + } else if (linkTo === 'explorer') { + e.stopPropagation(); + } + }} + > + {content} + + ); + } + + return ( +
+ {content} +
+ ); + } + + // Compact variant - avatar (16px) wrapped in badge background + if (variant === 'compact') { + const badgeContent = ( + <> + + + {vaultName ?? } + + {linkTo === 'explorer' && } + {showCopy && ( + { + e.preventDefault(); + e.stopPropagation(); + void handleCopy(); + }} + /> + )} + + ); + + const compactClasses = clsx( + 'inline-flex items-center gap-1.5 rounded-sm px-1.5 py-1 font-zen text-xs', + mounted && isOwner ? 'bg-green-500/10 text-green-500' : 'bg-hovered text-secondary', + copyable && 'cursor-pointer transition-colors hover:brightness-110', + href && + 'no-underline transition-colors hover:bg-gray-300 hover:text-primary hover:no-underline dark:hover:bg-gray-700', + className, + ); + + if (href) { + return ( + { + if (copyable) { + e.preventDefault(); + void handleCopy(); + } else if (linkTo === 'explorer') { + e.stopPropagation(); + } + }} + > + {badgeContent} + + ); + } + + return ( +
+ {badgeContent} +
+ ); + } + + // Full variant - avatar + address badge + extra info badges (all on one line, centered) + const fullContent = ( + <> + + + {/* Address badge - always shows shortened address, click to copy */} + { + e.preventDefault(); + e.stopPropagation(); + void handleCopy(); + }} + role="button" + tabIndex={0} + onKeyDown={(e) => { + if (e.key === 'Enter' || e.key === ' ') { + e.preventDefault(); + void handleCopy(); + } + }} + > + {shortAddress} + + + + {/* Connected indicator badge */} + {mounted && isOwner && ( + + + Connected + + )} + + {/* ENS badge (if showAddress is enabled, shows ENS or address) */} + {showAddress && !vaultName && ( + + + + )} + + {/* Vault name badge (if it's a vault) */} + {vaultName && ( + + {vaultName} + + )} + + {/* Explorer link */} + {linkTo === 'explorer' && href && ( + e.stopPropagation()} + > + + + )} + + ); + + const fullClasses = clsx( + 'flex items-center gap-2', + copyable && 'cursor-pointer transition-colors hover:brightness-110', + className, + ); + + if (href && linkTo === 'profile') { + return ( + + {fullContent} + + ); + } + + return ( +
+ {fullContent} +
+ ); +} diff --git a/src/components/common/AddressDisplay.tsx b/src/components/common/AddressDisplay.tsx deleted file mode 100644 index 6b25beec..00000000 --- a/src/components/common/AddressDisplay.tsx +++ /dev/null @@ -1,164 +0,0 @@ -'use client'; - -import { useMemo, useState, useEffect, useCallback, HTMLAttributes } from 'react'; -import clsx from 'clsx'; -import { FaCircle } from 'react-icons/fa'; -import { LuExternalLink } from 'react-icons/lu'; -import { Address } from 'viem'; -import { useAccount } from 'wagmi'; -import { Avatar } from '@/components/Avatar/Avatar'; -import { Name } from '@/components/common/Name'; -import { useStyledToast } from '@/hooks/useStyledToast'; -import { getExplorerURL } from '@/utils/external'; -import { SupportedNetworks } from '@/utils/networks'; - -type AddressDisplayProps = { - address: Address; - chainId?: SupportedNetworks | number; - size?: 'md' | 'sm'; - showExplorerLink?: boolean; - className?: string; - copyable?: boolean; -}; - -export function AddressDisplay({ - address, - chainId, - size = 'md', - showExplorerLink = false, - className, - copyable = false, -}: AddressDisplayProps) { - const { address: connectedAddress, isConnected } = useAccount(); - const [mounted, setMounted] = useState(false); - const { success: toastSuccess } = useStyledToast(); - - useEffect(() => { - setMounted(true); - }, []); - - const isOwner = useMemo(() => { - return address === connectedAddress; - }, [address, connectedAddress]); - - const explorerHref = useMemo(() => { - if (!showExplorerLink) return null; - const numericChainId = Number(chainId ?? 1); - if (!Number.isFinite(numericChainId)) return null; - return getExplorerURL(address as `0x${string}`, numericChainId as SupportedNetworks); - }, [address, chainId, showExplorerLink]); - - const handleCopy = useCallback(async () => { - if (!copyable) return; - - try { - await navigator.clipboard.writeText(address); - toastSuccess('Address copied', `${address.slice(0, 6)}...${address.slice(-4)}`); - } catch (error) { - console.error('Failed to copy address', error); - } - }, [address, copyable, toastSuccess]); - - const handleKeyDown = useCallback['onKeyDown']>>( - (event) => { - if (!copyable) return; - if (event.key === 'Enter' || event.key === ' ') { - event.preventDefault(); - void handleCopy(); - } - }, - [copyable, handleCopy], - ); - - // Only add interactive props when copyable=true to satisfy a11y lint rules - const interactiveProps: HTMLAttributes = copyable - ? { - role: 'button', - tabIndex: 0, - onClick: () => void handleCopy(), - onKeyDown: handleKeyDown, - } - : {}; - - if (size === 'sm') { - return ( -
- - {explorerHref && ( - event.stopPropagation()} - > - - - )} -
- ); - } - - return ( -
-
- - {mounted && isOwner && isConnected && ( -
-
- -
-
- )} -
-
-
- - {explorerHref && ( - event.stopPropagation()} - > - - - )} -
-
-
- ); -} diff --git a/src/components/common/AddressIdentity.tsx b/src/components/common/AddressIdentity.tsx deleted file mode 100644 index 88e8ceda..00000000 --- a/src/components/common/AddressIdentity.tsx +++ /dev/null @@ -1,51 +0,0 @@ -'use client'; - -import { useMemo } from 'react'; -import { ExternalLinkIcon } from '@radix-ui/react-icons'; -import Link from 'next/link'; -import { Address } from 'viem'; -import { Name } from '@/components/common/Name'; -import { getExplorerURL } from '@/utils/external'; -import { SupportedNetworks } from '@/utils/networks'; - -type AddressIdentityProps = { - address: Address; - chainId: SupportedNetworks; - showExplorerLink?: boolean; - className?: string; -}; - -export function AddressIdentity({ - address, - chainId, - showExplorerLink = true, - className = '', -}: AddressIdentityProps) { - const explorerHref = useMemo(() => { - return getExplorerURL(address as `0x${string}`, chainId); - }, [address, chainId]); - - if (!showExplorerLink) { - return ( -
- -
- ); - } - - return ( - e.stopPropagation()} - > - - - - ); -} diff --git a/src/components/common/AllocatorCard.tsx b/src/components/common/AllocatorCard.tsx index 54c05bd4..49977bbf 100644 --- a/src/components/common/AllocatorCard.tsx +++ b/src/components/common/AllocatorCard.tsx @@ -1,6 +1,6 @@ import React from 'react'; import { Address } from 'viem'; -import { AddressDisplay } from './AddressDisplay'; +import { AccountIdentity } from './AccountIdentity'; type AllocatorCardProps = { name: string; @@ -50,7 +50,7 @@ export function AllocatorCard({ )}
- +

{description}

diff --git a/src/components/common/index.ts b/src/components/common/index.ts index b61f8abb..0d1a8b23 100644 --- a/src/components/common/index.ts +++ b/src/components/common/index.ts @@ -1,3 +1,3 @@ export * from './Button'; export * from './TransactionIdentity'; -export * from './AddressIdentity'; +export * from './AccountIdentity'; diff --git a/src/components/layout/header/AccountDropdown.tsx b/src/components/layout/header/AccountDropdown.tsx index 82acfca3..5b43f720 100644 --- a/src/components/layout/header/AccountDropdown.tsx +++ b/src/components/layout/header/AccountDropdown.tsx @@ -5,7 +5,7 @@ import { Dropdown, DropdownTrigger, DropdownMenu, DropdownItem } from '@heroui/r import { ExitIcon, ExternalLinkIcon, CopyIcon } from '@radix-ui/react-icons'; import { clsx } from 'clsx'; import { useAccount, useDisconnect } from 'wagmi'; -import AccountWithENS from '@/components/Account/AccountWithENS'; +import { AccountIdentity } from '@/components/common/AccountIdentity'; import { Avatar } from '@/components/Avatar/Avatar'; import { useStyledToast } from '@/hooks/useStyledToast'; import { getExplorerURL } from '@/utils/external'; @@ -59,8 +59,9 @@ export function AccountDropdown() { isReadOnly showDivider={false} > -
- +
+ +
diff --git a/src/contexts/VaultRegistryContext.tsx b/src/contexts/VaultRegistryContext.tsx new file mode 100644 index 00000000..e85d1689 --- /dev/null +++ b/src/contexts/VaultRegistryContext.tsx @@ -0,0 +1,50 @@ +'use client'; + +import { createContext, useContext, useMemo, type ReactNode } from 'react'; +import { useAllMorphoVaults } from '@/hooks/useAllMorphoVaults'; +import type { MorphoVault } from '@/data-sources/morpho-api/vaults'; +import type { Address } from 'viem'; + +type VaultRegistryContextType = { + vaults: MorphoVault[]; + loading: boolean; + error: Error | null; + getVaultByAddress: (address: Address, chainId?: number) => MorphoVault | undefined; +}; + +const VaultRegistryContext = createContext(undefined); + +export function VaultRegistryProvider({ children }: { children: ReactNode }) { + const { vaults, loading, error } = useAllMorphoVaults(); + + const getVaultByAddress = useMemo( + () => (address: Address, chainId?: number) => { + const normalizedAddress = address.toLowerCase(); + return vaults.find( + (v) => + v.address.toLowerCase() === normalizedAddress && (!chainId || v.chainId === chainId), + ); + }, + [vaults], + ); + + const value = useMemo( + () => ({ + vaults, + loading, + error, + getVaultByAddress, + }), + [vaults, loading, error, getVaultByAddress], + ); + + return {children}; +} + +export function useVaultRegistry() { + const context = useContext(VaultRegistryContext); + if (context === undefined) { + throw new Error('useVaultRegistry must be used within a VaultRegistryProvider'); + } + return context; +} diff --git a/src/hooks/useAddressLabel.ts b/src/hooks/useAddressLabel.ts new file mode 100644 index 00000000..4b03612e --- /dev/null +++ b/src/hooks/useAddressLabel.ts @@ -0,0 +1,33 @@ +import { useMemo } from 'react'; +import type { Address } from 'viem'; +import { useVaultRegistry } from '@/contexts/VaultRegistryContext'; +import { getSlicedAddress } from '@/utils/address'; + +type UseAddressLabelReturn = { + vaultName: string | undefined; + shortAddress: string; +}; + +/** + * Hook to resolve address labels in priority order: + * 1. Vault name (if address is a known vault) + * 2. ENS name (handled by Name component) + * 3. Shortened address (0x1234...5678) + */ +export function useAddressLabel(address: Address, chainId?: number): UseAddressLabelReturn { + const { getVaultByAddress } = useVaultRegistry(); + + const vaultName = useMemo(() => { + const vault = getVaultByAddress(address, chainId); + return vault?.name; + }, [address, chainId, getVaultByAddress]); + + const shortAddress = useMemo(() => { + return getSlicedAddress(address as `0x${string}`); + }, [address]); + + return { + vaultName, + shortAddress, + }; +} From 046f587da65e6c3b725da3ae2eaf480ba0925e09 Mon Sep 17 00:00:00 2001 From: antoncoding Date: Thu, 4 Dec 2025 14:35:36 +0800 Subject: [PATCH 2/9] docs: styling guide --- docs/Styling.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/Styling.md b/docs/Styling.md index 9dec2ff0..912b5dc8 100644 --- a/docs/Styling.md +++ b/docs/Styling.md @@ -464,7 +464,7 @@ Add an action link (like explorer) in the top-right corner: **Styling Rules:** - Use `rounded-sm` for badges (not `rounded`) - Background: `bg-hovered` (or `bg-green-500/10` for connected) -- Text: `font-monospace` with `text-secondary` or `text-primary` +- Text: `font-zen` with `text-secondary` or `text-primary` - No underscores in variable names - All avatars are round - Full variant: all elements centered vertically From 95b2c8a6c7ac32d030ea58af8fbfb9674e849eb3 Mon Sep 17 00:00:00 2001 From: antoncoding Date: Thu, 4 Dec 2025 14:43:14 +0800 Subject: [PATCH 3/9] feat: default tooltips --- docs/Styling.md | 18 +++ .../common/AccountActionsPopover.tsx | 99 ++++++++++++++ src/components/common/AccountIdentity.tsx | 124 +++++++++++------- .../layout/header/AccountDropdown.tsx | 6 +- 4 files changed, 193 insertions(+), 54 deletions(-) create mode 100644 src/components/common/AccountActionsPopover.tsx diff --git a/docs/Styling.md b/docs/Styling.md index 912b5dc8..d4fdc14d 100644 --- a/docs/Styling.md +++ b/docs/Styling.md @@ -509,6 +509,24 @@ import { AccountIdentity } from '@/components/common/AccountIdentity'; - `showCopy`: Show copy icon at end of badge - `copyable`: Make entire component clickable to copy - `showAddress`: Show ENS badge (full variant only) +- `showActions`: Show actions popover on click (default: `true`) + +**Actions Popover (Default Behavior):** + +By default, clicking any AccountIdentity shows a minimal popover with: +1. **Copy Address** - Copies address to clipboard +2. **View Account** - Navigate to positions page +3. **View on Explorer** - Opens Etherscan in new tab + +To disable: `showActions={false}` + +```tsx +// Default - shows actions popover on click + + +// Disable actions (e.g., in dropdown menus) + +``` ### Market Display Components diff --git a/src/components/common/AccountActionsPopover.tsx b/src/components/common/AccountActionsPopover.tsx new file mode 100644 index 00000000..aac38bcb --- /dev/null +++ b/src/components/common/AccountActionsPopover.tsx @@ -0,0 +1,99 @@ +'use client'; + +import { useState, useCallback, type ReactNode } from 'react'; +import { Popover, PopoverTrigger, PopoverContent } from '@heroui/react'; +import { LuCopy, LuUser, LuExternalLink } from 'react-icons/lu'; +import { SiEthereum } from 'react-icons/si'; +import type { Address } from 'viem'; +import { useStyledToast } from '@/hooks/useStyledToast'; +import { getExplorerURL } from '@/utils/external'; +import type { SupportedNetworks } from '@/utils/networks'; + +type AccountActionsPopoverProps = { + address: Address; + chainId?: number; + children: ReactNode; +}; + +/** + * Minimal popover showing account actions: + * - Copy address + * - View account (positions page) + * - View on Etherscan + */ +export function AccountActionsPopover({ + address, + chainId = 1, + children, +}: AccountActionsPopoverProps) { + const [isOpen, setIsOpen] = useState(false); + const toast = useStyledToast(); + + const handleCopy = useCallback(async () => { + try { + await navigator.clipboard.writeText(address); + toast.success('Address copied', `${address.slice(0, 6)}...${address.slice(-4)}`); + setIsOpen(false); + } catch (error) { + console.error('Failed to copy address', error); + } + }, [address, toast]); + + const handleViewAccount = useCallback(() => { + window.location.href = `/positions/${address}`; + setIsOpen(false); + }, [address]); + + const handleViewExplorer = useCallback(() => { + const explorerUrl = getExplorerURL(address, chainId as SupportedNetworks); + window.open(explorerUrl, '_blank', 'noopener,noreferrer'); + setIsOpen(false); + }, [address, chainId]); + + return ( + + +
{children}
+
+ +
+ {/* Copy Address */} + + + {/* View Account */} + + + {/* View on Explorer */} + +
+
+
+ ); +} diff --git a/src/components/common/AccountIdentity.tsx b/src/components/common/AccountIdentity.tsx index 5bc80247..f97167a2 100644 --- a/src/components/common/AccountIdentity.tsx +++ b/src/components/common/AccountIdentity.tsx @@ -9,6 +9,7 @@ import type { Address } from 'viem'; import { useAccount } from 'wagmi'; import { Avatar } from '@/components/Avatar/Avatar'; import { Name } from '@/components/common/Name'; +import { AccountActionsPopover } from '@/components/common/AccountActionsPopover'; import { useAddressLabel } from '@/hooks/useAddressLabel'; import { useStyledToast } from '@/hooks/useStyledToast'; import { getExplorerURL } from '@/utils/external'; @@ -22,6 +23,7 @@ type AccountIdentityProps = { copyable?: boolean; showCopy?: boolean; showAddress?: boolean; + showActions?: boolean; className?: string; }; @@ -44,6 +46,7 @@ export function AccountIdentity({ copyable = false, showCopy = false, showAddress = false, + showActions = true, className, }: AccountIdentityProps) { const { address: connectedAddress, isConnected } = useAccount(); @@ -128,32 +131,38 @@ export function AccountIdentity({ className, ); - if (href) { - return ( - { - if (copyable) { - e.preventDefault(); - void handleCopy(); - } else if (linkTo === 'explorer') { - e.stopPropagation(); - } - }} - > - {content} - - ); - } - - return ( + const badgeElement = href ? ( + { + if (copyable) { + e.preventDefault(); + void handleCopy(); + } else if (linkTo === 'explorer') { + e.stopPropagation(); + } + }} + > + {content} + + ) : (
{content}
); + + if (showActions) { + return ( + + {badgeElement} + + ); + } + + return badgeElement; } // Compact variant - avatar (16px) wrapped in badge background @@ -187,32 +196,38 @@ export function AccountIdentity({ className, ); - if (href) { - return ( - { - if (copyable) { - e.preventDefault(); - void handleCopy(); - } else if (linkTo === 'explorer') { - e.stopPropagation(); - } - }} - > - {badgeContent} - - ); - } - - return ( + const compactElement = href ? ( + { + if (copyable) { + e.preventDefault(); + void handleCopy(); + } else if (linkTo === 'explorer') { + e.stopPropagation(); + } + }} + > + {badgeContent} + + ) : (
{badgeContent}
); + + if (showActions) { + return ( + + {compactElement} + + ); + } + + return compactElement; } // Full variant - avatar + address badge + extra info badges (all on one line, centered) @@ -285,17 +300,24 @@ export function AccountIdentity({ className, ); - if (href && linkTo === 'profile') { - return ( + const fullElement = + href && linkTo === 'profile' ? ( {fullContent} + ) : ( +
+ {fullContent} +
+ ); + + if (showActions) { + return ( + + {fullElement} + ); } - return ( -
- {fullContent} -
- ); + return fullElement; } diff --git a/src/components/layout/header/AccountDropdown.tsx b/src/components/layout/header/AccountDropdown.tsx index 5b43f720..369ec75f 100644 --- a/src/components/layout/header/AccountDropdown.tsx +++ b/src/components/layout/header/AccountDropdown.tsx @@ -59,9 +59,9 @@ export function AccountDropdown() { isReadOnly showDivider={false} > -
- - +
+ +
From 7fd3820b09e2f6ba047e7fed13e661cedb578ffc Mon Sep 17 00:00:00 2001 From: antoncoding Date: Thu, 4 Dec 2025 14:50:15 +0800 Subject: [PATCH 4/9] chore: clickable menu --- app/layout.tsx | 2 +- .../[marketid]/components/BorrowsTable.tsx | 2 +- .../components/LiquidationsTable.tsx | 2 +- .../[marketid]/components/SuppliesTable.tsx | 2 +- docs/Styling.md | 1 + .../common/AccountActionsPopover.tsx | 74 +++++++---- src/components/common/AccountIdentity.tsx | 123 ++++++++++++------ src/components/common/index.ts | 1 + .../layout/header/AccountDropdown.tsx | 2 +- src/contexts/VaultRegistryContext.tsx | 2 +- src/hooks/useAddressLabel.ts | 2 +- 11 files changed, 137 insertions(+), 76 deletions(-) diff --git a/app/layout.tsx b/app/layout.tsx index 99307fb2..53de578d 100644 --- a/app/layout.tsx +++ b/app/layout.tsx @@ -4,8 +4,8 @@ import GoogleAnalytics from '@/components/GoogleAnalytics/GoogleAnalytics'; import { ClientProviders } from '@/components/providers/ClientProviders'; import { QueryProvider } from '@/components/providers/QueryProvider'; import RiskNotificationModal from '@/components/RiskNotificationModal'; -import OnchainProviders from '@/OnchainProviders'; import { VaultRegistryProvider } from '@/contexts/VaultRegistryContext'; +import OnchainProviders from '@/OnchainProviders'; import { initAnalytics } from '@/utils/analytics'; import { ThemeProviders } from '../src/components/providers/ThemeProvider'; diff --git a/app/market/[chainId]/[marketid]/components/BorrowsTable.tsx b/app/market/[chainId]/[marketid]/components/BorrowsTable.tsx index f508cae6..9b573b4f 100644 --- a/app/market/[chainId]/[marketid]/components/BorrowsTable.tsx +++ b/app/market/[chainId]/[marketid]/components/BorrowsTable.tsx @@ -17,7 +17,7 @@ import { AccountIdentity } from '@/components/common/AccountIdentity'; import { Badge } from '@/components/common/Badge'; import { TokenIcon } from '@/components/TokenIcon'; import { useMarketBorrows } from '@/hooks/useMarketBorrows'; -import { getExplorerURL, getExplorerTxURL } from '@/utils/external'; +import { getExplorerTxURL } from '@/utils/external'; import { Market } from '@/utils/types'; // Helper functions to format data diff --git a/app/market/[chainId]/[marketid]/components/LiquidationsTable.tsx b/app/market/[chainId]/[marketid]/components/LiquidationsTable.tsx index 78dd4fd4..40db379a 100644 --- a/app/market/[chainId]/[marketid]/components/LiquidationsTable.tsx +++ b/app/market/[chainId]/[marketid]/components/LiquidationsTable.tsx @@ -15,7 +15,7 @@ import { Address, formatUnits } from 'viem'; import { AccountIdentity } from '@/components/common/AccountIdentity'; import { TokenIcon } from '@/components/TokenIcon'; import { useMarketLiquidations } from '@/hooks/useMarketLiquidations'; -import { getExplorerTxURL, getExplorerURL } from '@/utils/external'; +import { getExplorerTxURL } from '@/utils/external'; import { Market, MarketLiquidationTransaction } from '@/utils/types'; // Helper functions to format data diff --git a/app/market/[chainId]/[marketid]/components/SuppliesTable.tsx b/app/market/[chainId]/[marketid]/components/SuppliesTable.tsx index 2d615e0c..a5b5f292 100644 --- a/app/market/[chainId]/[marketid]/components/SuppliesTable.tsx +++ b/app/market/[chainId]/[marketid]/components/SuppliesTable.tsx @@ -17,7 +17,7 @@ import { AccountIdentity } from '@/components/common/AccountIdentity'; import { Badge } from '@/components/common/Badge'; import { TokenIcon } from '@/components/TokenIcon'; import useMarketSupplies from '@/hooks/useMarketSupplies'; -import { getExplorerURL, getExplorerTxURL } from '@/utils/external'; +import { getExplorerTxURL } from '@/utils/external'; import { Market } from '@/utils/types'; // Helper functions to format data diff --git a/docs/Styling.md b/docs/Styling.md index d4fdc14d..5b007b21 100644 --- a/docs/Styling.md +++ b/docs/Styling.md @@ -468,6 +468,7 @@ Add an action link (like explorer) in the top-right corner: - No underscores in variable names - All avatars are round - Full variant: all elements centered vertically +- Smooth Framer Motion animations on all interactions ```tsx import { AccountIdentity } from '@/components/common/AccountIdentity'; diff --git a/src/components/common/AccountActionsPopover.tsx b/src/components/common/AccountActionsPopover.tsx index aac38bcb..f968ba37 100644 --- a/src/components/common/AccountActionsPopover.tsx +++ b/src/components/common/AccountActionsPopover.tsx @@ -2,7 +2,8 @@ import { useState, useCallback, type ReactNode } from 'react'; import { Popover, PopoverTrigger, PopoverContent } from '@heroui/react'; -import { LuCopy, LuUser, LuExternalLink } from 'react-icons/lu'; +import { motion, AnimatePresence } from 'framer-motion'; +import { LuCopy, LuUser } from 'react-icons/lu'; import { SiEthereum } from 'react-icons/si'; import type { Address } from 'viem'; import { useStyledToast } from '@/hooks/useStyledToast'; @@ -65,34 +66,53 @@ export function AccountActionsPopover({
{children}
-
- {/* Copy Address */} - + + {isOpen && ( + + {/* Copy Address */} + void handleCopy()} + className="flex items-center gap-3 px-4 py-2.5 text-sm text-secondary transition-colors hover:bg-hovered hover:text-primary" + whileHover={{ x: 2 }} + whileTap={{ scale: 0.98 }} + > + + Copy Address + - {/* View Account */} - + {/* View Account */} + + + View Account + - {/* View on Explorer */} - -
+ {/* View on Explorer */} + + + View on Explorer + + + )} +
); diff --git a/src/components/common/AccountIdentity.tsx b/src/components/common/AccountIdentity.tsx index f97167a2..552ba0d3 100644 --- a/src/components/common/AccountIdentity.tsx +++ b/src/components/common/AccountIdentity.tsx @@ -2,14 +2,15 @@ import { useMemo, useState, useEffect, useCallback, type HTMLAttributes } from 'react'; import clsx from 'clsx'; +import { motion } from 'framer-motion'; +import Link from 'next/link'; import { FaCircle } from 'react-icons/fa'; import { LuExternalLink, LuCopy } from 'react-icons/lu'; -import Link from 'next/link'; -import type { Address } from 'viem'; import { useAccount } from 'wagmi'; +import type { Address } from 'viem'; import { Avatar } from '@/components/Avatar/Avatar'; -import { Name } from '@/components/common/Name'; import { AccountActionsPopover } from '@/components/common/AccountActionsPopover'; +import { Name } from '@/components/common/Name'; import { useAddressLabel } from '@/hooks/useAddressLabel'; import { useStyledToast } from '@/hooks/useStyledToast'; import { getExplorerURL } from '@/utils/external'; @@ -132,26 +133,39 @@ export function AccountIdentity({ ); const badgeElement = href ? ( - { - if (copyable) { - e.preventDefault(); - void handleCopy(); - } else if (linkTo === 'explorer') { - e.stopPropagation(); - } - }} + - {content} - + { + if (copyable) { + e.preventDefault(); + void handleCopy(); + } else if (linkTo === 'explorer') { + e.stopPropagation(); + } + }} + > + {content} + + ) : ( -
+ void handleCopy() : undefined} + style={{ cursor: copyable ? 'pointer' : 'default' }} + whileHover={{ scale: 1.02 }} + whileTap={{ scale: 0.98 }} + transition={{ type: 'spring', stiffness: 400, damping: 25 }} + > {content} -
+ ); if (showActions) { @@ -197,26 +211,39 @@ export function AccountIdentity({ ); const compactElement = href ? ( - { - if (copyable) { - e.preventDefault(); - void handleCopy(); - } else if (linkTo === 'explorer') { - e.stopPropagation(); - } - }} + - {badgeContent} - + { + if (copyable) { + e.preventDefault(); + void handleCopy(); + } else if (linkTo === 'explorer') { + e.stopPropagation(); + } + }} + > + {badgeContent} + + ) : ( -
+ void handleCopy() : undefined} + style={{ cursor: copyable ? 'pointer' : 'default' }} + whileHover={{ scale: 1.02 }} + whileTap={{ scale: 0.98 }} + transition={{ type: 'spring', stiffness: 400, damping: 25 }} + > {badgeContent} -
+ ); if (showActions) { @@ -302,13 +329,25 @@ export function AccountIdentity({ const fullElement = href && linkTo === 'profile' ? ( - - {fullContent} - + + + {fullContent} + + ) : ( -
+ {fullContent} -
+ ); if (showActions) { diff --git a/src/components/common/index.ts b/src/components/common/index.ts index 0d1a8b23..8d412f78 100644 --- a/src/components/common/index.ts +++ b/src/components/common/index.ts @@ -1,3 +1,4 @@ export * from './Button'; export * from './TransactionIdentity'; export * from './AccountIdentity'; +export * from './AccountActionsPopover'; diff --git a/src/components/layout/header/AccountDropdown.tsx b/src/components/layout/header/AccountDropdown.tsx index 369ec75f..7b4440c6 100644 --- a/src/components/layout/header/AccountDropdown.tsx +++ b/src/components/layout/header/AccountDropdown.tsx @@ -5,8 +5,8 @@ import { Dropdown, DropdownTrigger, DropdownMenu, DropdownItem } from '@heroui/r import { ExitIcon, ExternalLinkIcon, CopyIcon } from '@radix-ui/react-icons'; import { clsx } from 'clsx'; import { useAccount, useDisconnect } from 'wagmi'; -import { AccountIdentity } from '@/components/common/AccountIdentity'; import { Avatar } from '@/components/Avatar/Avatar'; +import { AccountIdentity } from '@/components/common/AccountIdentity'; import { useStyledToast } from '@/hooks/useStyledToast'; import { getExplorerURL } from '@/utils/external'; diff --git a/src/contexts/VaultRegistryContext.tsx b/src/contexts/VaultRegistryContext.tsx index e85d1689..6a42bfcc 100644 --- a/src/contexts/VaultRegistryContext.tsx +++ b/src/contexts/VaultRegistryContext.tsx @@ -1,8 +1,8 @@ 'use client'; import { createContext, useContext, useMemo, type ReactNode } from 'react'; -import { useAllMorphoVaults } from '@/hooks/useAllMorphoVaults'; import type { MorphoVault } from '@/data-sources/morpho-api/vaults'; +import { useAllMorphoVaults } from '@/hooks/useAllMorphoVaults'; import type { Address } from 'viem'; type VaultRegistryContextType = { diff --git a/src/hooks/useAddressLabel.ts b/src/hooks/useAddressLabel.ts index 4b03612e..6064cdb5 100644 --- a/src/hooks/useAddressLabel.ts +++ b/src/hooks/useAddressLabel.ts @@ -1,7 +1,7 @@ import { useMemo } from 'react'; -import type { Address } from 'viem'; import { useVaultRegistry } from '@/contexts/VaultRegistryContext'; import { getSlicedAddress } from '@/utils/address'; +import type { Address } from 'viem'; type UseAddressLabelReturn = { vaultName: string | undefined; From b7c297eaa43a9bf3091f97ee673249f64d855189 Mon Sep 17 00:00:00 2001 From: antoncoding Date: Thu, 4 Dec 2025 16:23:54 +0800 Subject: [PATCH 5/9] feat: animation --- src/components/common/AccountIdentity.tsx | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/src/components/common/AccountIdentity.tsx b/src/components/common/AccountIdentity.tsx index 552ba0d3..2c33f8ed 100644 --- a/src/components/common/AccountIdentity.tsx +++ b/src/components/common/AccountIdentity.tsx @@ -6,7 +6,7 @@ import { motion } from 'framer-motion'; import Link from 'next/link'; import { FaCircle } from 'react-icons/fa'; import { LuExternalLink, LuCopy } from 'react-icons/lu'; -import { useAccount } from 'wagmi'; +import { useAccount, useEnsName } from 'wagmi'; import type { Address } from 'viem'; import { Avatar } from '@/components/Avatar/Avatar'; import { AccountActionsPopover } from '@/components/common/AccountActionsPopover'; @@ -54,6 +54,10 @@ export function AccountIdentity({ const [mounted, setMounted] = useState(false); const toast = useStyledToast(); const { vaultName, shortAddress } = useAddressLabel(address, chainId); + const { data: ensName } = useEnsName({ + address: address as `0x${string}`, + chainId: 1, + }); useEffect(() => { setMounted(true); @@ -64,6 +68,9 @@ export function AccountIdentity({ }, [address, connectedAddress, isConnected, mounted]); const href = useMemo(() => { + // When showActions is enabled, don't use linkTo - popover handles navigation + if (showActions) return null; + if (linkTo === 'none') return null; if (linkTo === 'explorer') { const numericChainId = Number(chainId ?? 1); @@ -74,7 +81,7 @@ export function AccountIdentity({ return `/positions/${address}`; } return null; - }, [linkTo, address, chainId]); + }, [linkTo, address, chainId, showActions]); const handleCopy = useCallback(async () => { try { @@ -291,10 +298,10 @@ export function AccountIdentity({ )} - {/* ENS badge (if showAddress is enabled, shows ENS or address) */} - {showAddress && !vaultName && ( + {/* ENS badge (only show if there's an actual ENS name) */} + {showAddress && !vaultName && ensName && ( - + {ensName} )} @@ -341,7 +348,8 @@ export function AccountIdentity({ ) : ( void handleCopy() : undefined} + style={{ cursor: copyable ? 'pointer' : 'default' }} whileHover={{ scale: 1.01 }} whileTap={{ scale: 0.99 }} transition={{ type: 'spring', stiffness: 400, damping: 25 }} From 0c179e9e35d52bebc9b40d95b01c68cdbeb90029 Mon Sep 17 00:00:00 2001 From: antoncoding Date: Thu, 4 Dec 2025 16:29:58 +0800 Subject: [PATCH 6/9] chore: lint --- .../common/AccountActionsPopover.tsx | 2 +- src/components/common/AccountIdentity.tsx | 24 ++----------------- 2 files changed, 3 insertions(+), 23 deletions(-) diff --git a/src/components/common/AccountActionsPopover.tsx b/src/components/common/AccountActionsPopover.tsx index f968ba37..d4fd12ed 100644 --- a/src/components/common/AccountActionsPopover.tsx +++ b/src/components/common/AccountActionsPopover.tsx @@ -5,10 +5,10 @@ import { Popover, PopoverTrigger, PopoverContent } from '@heroui/react'; import { motion, AnimatePresence } from 'framer-motion'; import { LuCopy, LuUser } from 'react-icons/lu'; import { SiEthereum } from 'react-icons/si'; -import type { Address } from 'viem'; import { useStyledToast } from '@/hooks/useStyledToast'; import { getExplorerURL } from '@/utils/external'; import type { SupportedNetworks } from '@/utils/networks'; +import type { Address } from 'viem'; type AccountActionsPopoverProps = { address: Address; diff --git a/src/components/common/AccountIdentity.tsx b/src/components/common/AccountIdentity.tsx index 2c33f8ed..ed6d4258 100644 --- a/src/components/common/AccountIdentity.tsx +++ b/src/components/common/AccountIdentity.tsx @@ -1,13 +1,12 @@ 'use client'; -import { useMemo, useState, useEffect, useCallback, type HTMLAttributes } from 'react'; +import { useMemo, useState, useEffect, useCallback } from 'react'; import clsx from 'clsx'; import { motion } from 'framer-motion'; import Link from 'next/link'; import { FaCircle } from 'react-icons/fa'; import { LuExternalLink, LuCopy } from 'react-icons/lu'; import { useAccount, useEnsName } from 'wagmi'; -import type { Address } from 'viem'; import { Avatar } from '@/components/Avatar/Avatar'; import { AccountActionsPopover } from '@/components/common/AccountActionsPopover'; import { Name } from '@/components/common/Name'; @@ -15,6 +14,7 @@ import { useAddressLabel } from '@/hooks/useAddressLabel'; import { useStyledToast } from '@/hooks/useStyledToast'; import { getExplorerURL } from '@/utils/external'; import type { SupportedNetworks } from '@/utils/networks'; +import type { Address } from 'viem'; type AccountIdentityProps = { address: Address; @@ -92,26 +92,6 @@ export function AccountIdentity({ } }, [address, toast]); - const handleKeyDown = useCallback['onKeyDown']>>( - (event) => { - if (!copyable) return; - if (event.key === 'Enter' || event.key === ' ') { - event.preventDefault(); - void handleCopy(); - } - }, - [copyable, handleCopy], - ); - - const interactiveProps: HTMLAttributes = copyable - ? { - role: 'button', - tabIndex: 0, - onClick: () => void handleCopy(), - onKeyDown: handleKeyDown, - } - : {}; - // Badge variant - minimal inline badge (no avatar) if (variant === 'badge') { const content = ( From aaae1f54c333b05aa51fb2710f1dc4cc5be3b1e1 Mon Sep 17 00:00:00 2001 From: antoncoding Date: Thu, 4 Dec 2025 16:39:36 +0800 Subject: [PATCH 7/9] feat: remove chainId --- .../common/AccountActionsPopover.tsx | 8 +++----- src/components/common/AccountIdentity.tsx | 18 +++++++----------- 2 files changed, 10 insertions(+), 16 deletions(-) diff --git a/src/components/common/AccountActionsPopover.tsx b/src/components/common/AccountActionsPopover.tsx index d4fd12ed..f18c1403 100644 --- a/src/components/common/AccountActionsPopover.tsx +++ b/src/components/common/AccountActionsPopover.tsx @@ -7,12 +7,11 @@ import { LuCopy, LuUser } from 'react-icons/lu'; import { SiEthereum } from 'react-icons/si'; import { useStyledToast } from '@/hooks/useStyledToast'; import { getExplorerURL } from '@/utils/external'; -import type { SupportedNetworks } from '@/utils/networks'; +import { SupportedNetworks } from '@/utils/networks'; import type { Address } from 'viem'; type AccountActionsPopoverProps = { address: Address; - chainId?: number; children: ReactNode; }; @@ -24,7 +23,6 @@ type AccountActionsPopoverProps = { */ export function AccountActionsPopover({ address, - chainId = 1, children, }: AccountActionsPopoverProps) { const [isOpen, setIsOpen] = useState(false); @@ -46,10 +44,10 @@ export function AccountActionsPopover({ }, [address]); const handleViewExplorer = useCallback(() => { - const explorerUrl = getExplorerURL(address, chainId as SupportedNetworks); + const explorerUrl = getExplorerURL(address, SupportedNetworks.Mainnet); window.open(explorerUrl, '_blank', 'noopener,noreferrer'); setIsOpen(false); - }, [address, chainId]); + }, [address]); return ( { try { @@ -157,7 +153,7 @@ export function AccountIdentity({ if (showActions) { return ( - + {badgeElement} ); @@ -235,7 +231,7 @@ export function AccountIdentity({ if (showActions) { return ( - + {compactElement} ); @@ -340,7 +336,7 @@ export function AccountIdentity({ if (showActions) { return ( - + {fullElement} ); From c234c02637a8ee25a0546e0d914213fb2691b8d8 Mon Sep 17 00:00:00 2001 From: antoncoding Date: Thu, 4 Dec 2025 16:44:12 +0800 Subject: [PATCH 8/9] chore: lint --- app/admin/stats/components/TransactionTableBody.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/app/admin/stats/components/TransactionTableBody.tsx b/app/admin/stats/components/TransactionTableBody.tsx index c450cfd0..bfce381c 100644 --- a/app/admin/stats/components/TransactionTableBody.tsx +++ b/app/admin/stats/components/TransactionTableBody.tsx @@ -88,7 +88,6 @@ export function TransactionTableBody({ From 21fcdcc42a7382145b7630c813f41aa6342eaafb Mon Sep 17 00:00:00 2001 From: antoncoding Date: Thu, 4 Dec 2025 16:50:01 +0800 Subject: [PATCH 9/9] chore: lint --- app/autovault/[chainId]/[vaultAddress]/content.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/app/autovault/[chainId]/[vaultAddress]/content.tsx b/app/autovault/[chainId]/[vaultAddress]/content.tsx index 429dd1e1..9e21aa74 100644 --- a/app/autovault/[chainId]/[vaultAddress]/content.tsx +++ b/app/autovault/[chainId]/[vaultAddress]/content.tsx @@ -148,7 +148,6 @@ export default function VaultContent() {