From 3909c18d63135df6f6ed566865f9ab9d17fd6142 Mon Sep 17 00:00:00 2001 From: Awopetu Feyisayo <32976424+feyibosslady@users.noreply.github.com> Date: Mon, 31 Aug 2026 10:33:09 +0000 Subject: [PATCH] feat: implement Stellar asset balance card grid with token conversion summary - Create types for AssetBalance and WalletSummary - Add mock data with XLM, USDC, EURC, ASTRO tokens - Build AssetCard component showing ticker, balance, and USD equivalent - Build AssetBalanceGrid with responsive card grid layout - Handle loading and empty wallet states gracefully - Use design tokens from tokens.css with responsive grid - Export barrel for wallet feature Closes #61 --- .../wallet/components/AssetBalanceGrid.tsx | 72 +++++++++++++++++++ src/features/wallet/components/AssetCard.tsx | 56 +++++++++++++++ src/features/wallet/index.ts | 4 ++ src/features/wallet/mock-data.ts | 47 ++++++++++++ src/features/wallet/types.ts | 18 +++++ 5 files changed, 197 insertions(+) create mode 100644 src/features/wallet/components/AssetBalanceGrid.tsx create mode 100644 src/features/wallet/components/AssetCard.tsx create mode 100644 src/features/wallet/index.ts create mode 100644 src/features/wallet/mock-data.ts create mode 100644 src/features/wallet/types.ts diff --git a/src/features/wallet/components/AssetBalanceGrid.tsx b/src/features/wallet/components/AssetBalanceGrid.tsx new file mode 100644 index 0000000..e9f6556 --- /dev/null +++ b/src/features/wallet/components/AssetBalanceGrid.tsx @@ -0,0 +1,72 @@ +'use client'; + +import { Card, CardHeader, CardTitle, CardDescription, CardContent } from '@/components/ui/card'; +import { Wallet } from 'lucide-react'; +import { formatCurrency, formatRelativeTime } from '@/lib/format'; +import { AssetCard } from './AssetCard'; +import type { WalletSummary } from '../types'; + +interface AssetBalanceGridProps { + summary: WalletSummary | null; + isLoading?: boolean; +} + +export function AssetBalanceGrid({ summary, isLoading }: AssetBalanceGridProps) { + if (isLoading) { + return ( + +
+
+
+
+
+ + ); + } + + if (!summary || summary.assets.length === 0) { + return ( + +
+ +

No assets found

+

+ Your wallet does not hold any Stellar assets yet. Once you receive tokens, they will appear here. +

+
+
+ ); + } + + return ( +
+ + +
+
+ Asset Balances + + Multi-asset overview across your Stellar wallet + +
+
+

+ {formatCurrency(summary.totalUsdValue, 'USD')} +

+

+ Total Value · {formatRelativeTime(summary.lastUpdated)} +

+
+
+
+ +
+ {summary.assets.map((asset) => ( + + ))} +
+
+
+
+ ); +} diff --git a/src/features/wallet/components/AssetCard.tsx b/src/features/wallet/components/AssetCard.tsx new file mode 100644 index 0000000..8404ebf --- /dev/null +++ b/src/features/wallet/components/AssetCard.tsx @@ -0,0 +1,56 @@ +'use client'; + +import { Card, CardContent } from '@/components/ui/card'; +import { Badge } from '@/components/ui/badge'; +import { formatCurrency } from '@/lib/format'; +import type { AssetBalance } from '../types'; + +interface AssetCardProps { + asset: AssetBalance; +} + +const ASSET_COLORS: Record = { + XLM: 'bg-info-soft text-info', + USDC: 'bg-success-soft text-success', + EURC: 'bg-gold-soft text-gold-strong', + ASTRO: 'bg-warning-soft text-warning', +}; + +export function AssetCard({ asset }: AssetCardProps) { + const usdValue = asset.balance * asset.usdPrice; + const colorClass = ASSET_COLORS[asset.code] ?? 'bg-surface-secondary text-foreground-secondary'; + + return ( + + +
+
+
+ {asset.code} +
+
+

{asset.name}

+

{asset.code}

+
+
+ + ${asset.usdPrice.toFixed(2)} + +
+ +
+
+ + {formatCurrency(asset.balance, asset.code)} + +
+

+ {formatCurrency(usdValue, 'USD')} equivalent +

+
+
+
+ ); +} diff --git a/src/features/wallet/index.ts b/src/features/wallet/index.ts new file mode 100644 index 0000000..5236035 --- /dev/null +++ b/src/features/wallet/index.ts @@ -0,0 +1,4 @@ +export { AssetBalanceGrid } from './components/AssetBalanceGrid'; +export { AssetCard } from './components/AssetCard'; +export type { AssetBalance, AssetCode, WalletSummary } from './types'; +export { MOCK_WALLET_SUMMARY, MOCK_ASSET_BALANCES } from './mock-data'; diff --git a/src/features/wallet/mock-data.ts b/src/features/wallet/mock-data.ts new file mode 100644 index 0000000..cacf2c4 --- /dev/null +++ b/src/features/wallet/mock-data.ts @@ -0,0 +1,47 @@ +import type { AssetBalance, WalletSummary } from './types'; + +export const MOCK_ASSET_BALANCES: AssetBalance[] = [ + { + id: 'ast-xlm', + code: 'XLM', + name: 'Stellar Lumens', + balance: 12450.75, + usdPrice: 0.12, + decimals: 7, + }, + { + id: 'ast-usdc', + code: 'USDC', + name: 'USD Coin', + balance: 8420.5, + usdPrice: 1.0, + issuer: 'GA5ZSEJ6B3AE8RGX3C古城L4PBRPSNY7HO6AZGEBTXOZMOI57DPMGBGI', + decimals: 7, + }, + { + id: 'ast-eurc', + code: 'EURC', + name: 'Euro Coin', + balance: 3200.0, + usdPrice: 1.09, + issuer: 'GAXLKZUT5RRPOOMN6HEB3TTSX5IREHBWMFT76Y2G4Q377E3VZE5BN2EQ', + decimals: 7, + }, + { + id: 'ast-astro', + code: 'ASTRO', + name: 'Astroid Token', + balance: 45000.0, + usdPrice: 0.05, + decimals: 7, + }, +]; + +export const MOCK_WALLET_SUMMARY: WalletSummary = { + totalUsdValue: MOCK_ASSET_BALANCES.reduce( + (sum, asset) => sum + asset.balance * asset.usdPrice, + 0, + ), + assets: MOCK_ASSET_BALANCES, + lastUpdated: new Date().toISOString(), +}; diff --git a/src/features/wallet/types.ts b/src/features/wallet/types.ts new file mode 100644 index 0000000..dbc20f3 --- /dev/null +++ b/src/features/wallet/types.ts @@ -0,0 +1,18 @@ +export type AssetCode = 'XLM' | 'USDC' | 'EURC' | 'ASTRO'; + +export interface AssetBalance { + id: string; + code: AssetCode; + name: string; + balance: number; + usdPrice: number; + iconUrl?: string; + issuer?: string; + decimals: number; +} + +export interface WalletSummary { + totalUsdValue: number; + assets: AssetBalance[]; + lastUpdated: string; +}