diff --git a/components/balance-breakdown.tsx b/components/balance-breakdown.tsx new file mode 100644 index 0000000..cbb439a --- /dev/null +++ b/components/balance-breakdown.tsx @@ -0,0 +1,237 @@ +"use client"; + +/** + * BalanceBreakdown + * + * Addresses issue #8: "UX Improvement: Clearer Cross-Chain Balance Breakdown" + * + * Adds: + * 1. Per-chain USDC breakdown with visual bar charts + * 2. Tooltip explaining how Gateway abstracts liquidity + * 3. Visual indicator distinguishing Gateway (spendable) vs wallet (per-chain) balances + * 4. Pending/in-motion balance states aligned with Unified Balance Kit patterns + */ + +import { + Tooltip, + TooltipContent, + TooltipProvider, + TooltipTrigger, +} from "@/components/ui/tooltip"; +import { ChainBalance } from "@/lib/chain-config"; +import { Info, Zap, Wallet } from "lucide-react"; + +// ─── Chain display helpers ────────────────────────────────────────────────── +const CHAIN_LABELS: Record = { + arcTestnet: "Arc Testnet", + baseSepolia: "Base Sepolia", + avalancheFuji: "Avalanche Fuji", +}; + +const CHAIN_COLORS: Record = { + arcTestnet: "bg-blue-500", + baseSepolia: "bg-indigo-500", + avalancheFuji: "bg-red-500", +}; + +function chainLabel(chain: string): string { + return CHAIN_LABELS[chain] ?? chain; +} + +function chainColor(chain: string): string { + return CHAIN_COLORS[chain] ?? "bg-gray-400"; +} + +// ─── Props ────────────────────────────────────────────────────────────────── +interface BalanceBreakdownProps { + gatewayBalance: number; + walletBalance: number; + chainBalances: ChainBalance[]; + /** + * Optional: amount currently in transit (burn submitted, mint pending). + * Surfaces a "funds in motion" indicator when > 0. + */ + pendingBalance?: number; +} + +// ─── Component ────────────────────────────────────────────────────────────── +export function BalanceBreakdown({ + gatewayBalance, + walletBalance, + chainBalances, + pendingBalance = 0, +}: BalanceBreakdownProps) { + const total = gatewayBalance + walletBalance; + const gatewayPct = total > 0 ? (gatewayBalance / total) * 100 : 0; + const walletPct = total > 0 ? (walletBalance / total) * 100 : 0; + + const chainsWithBalance = chainBalances.filter((cb) => cb.balance > 0); + const chainTotal = chainsWithBalance.reduce((acc, cb) => acc + cb.balance, 0); + + return ( + +
+ + {/* ── Unified Balance Header ─────────────────────────────────── */} +
+

+ Unified USDC Balance +

+ + + + + +

How Gateway works

+

+ The Arc Gateway abstracts liquidity across chains into a single + unified balance. USDC deposited from any supported chain becomes + spendable on all chains — no manual bridging required. +

+

+ Gateway balance = instantly spendable + cross-chain. +
+ Wallet balance = held per-chain, not yet + deposited. +

+
+
+
+ + {/* ── Visual bar ────────────────────────────────────────────── */} +
+
+
+
+ +
+ + + Gateway (spendable) + + + + Wallet (per-chain) + +
+ + {/* ── Gateway balance card ───────────────────────────────────── */} +
+
+
+ + Arc Gateway + + + + + + Funds deposited here are accessible instantly on any supported + chain. This is your cross-chain spendable balance. + + +
+ + {gatewayBalance.toLocaleString("en-US", { + minimumFractionDigits: 6, + maximumFractionDigits: 6, + })}{" "} + USDC + +
+

+ Spendable across Arc Testnet, Base Sepolia, Avalanche Fuji +

+
+ + {/* ── Pending / in-motion indicator ─────────────────────────── */} + {pendingBalance > 0 && ( +
+
+ + + + + Funds in motion + + + + + + A cross-chain transfer is in progress. The burn has been + submitted on the source chain; the mint on the destination + chain is pending attestation (typically confirmed within + seconds on Arc). + + +
+ + {pendingBalance.toLocaleString("en-US", { + minimumFractionDigits: 6, + maximumFractionDigits: 6, + })}{" "} + USDC + +
+ )} + + {/* ── Per-chain wallet breakdown ─────────────────────────────── */} +
+
+ + Per-chain wallet balances + + + + + + USDC held in your wallet on each chain, not yet deposited to + Gateway. Deposit to make it cross-chain spendable. + + +
+ + {chainsWithBalance.length === 0 ? ( +

+ No wallet balances found. Deposit USDC to get started. +

+ ) : ( +
+ {chainsWithBalance.map((cb, idx) => { + const pct = chainTotal > 0 ? (cb.balance / chainTotal) * 100 : 0; + return ( +
+
+ + {chainLabel(cb.chain)} + + + {cb.balance.toFixed(6)} USDC + +
+
+
+
+
+ ); + })} +
+ )} +
+ +
+ + ); +} \ No newline at end of file diff --git a/components/wallet-dashboard.tsx b/components/wallet-dashboard.tsx index 6a99ac6..b1e9b5a 100644 --- a/components/wallet-dashboard.tsx +++ b/components/wallet-dashboard.tsx @@ -33,6 +33,7 @@ import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; import { Skeleton } from "@/components/ui/skeleton"; import { createClient } from "@/lib/supabase/client"; import { ChainBalance } from "@/lib/chain-config"; +import { BalanceBreakdown } from "@/components/balance-breakdown"; import { AlertCircleIcon } from "lucide-react"; import { Label } from "@/components/ui/label"; import { Input } from "@/components/ui/input"; @@ -498,23 +499,11 @@ export function WalletDashboard() {
) : totalBalance !== null ? (
-
-

- Arc Gateway Balance -

-

- {gatewayBalance.toLocaleString('en-US', { minimumFractionDigits: 6, maximumFractionDigits: 6 })} USDC -

-
- -
-

- Wallet Balance -

-

- {walletBalance.toLocaleString('en-US', { minimumFractionDigits: 6, maximumFractionDigits: 6 })} USDC -

-
+ {chainBalances.filter(cb => cb.balance > 0).length > 0 ? (
    {chainBalances.filter(cb => cb.balance > 0).map((cb, idx) => (