/** * Enhanced Address page with balance flow visualization and UTXO filtering. */ import { useState, useMemo } from 'react'; import { useParams, Link } from 'react-router-dom'; import { Wallet, Coins, Box, ArrowDownLeft, ArrowUpRight, Filter, TrendingUp, Gift, } from 'lucide-react'; import { useAddress, useAddressUtxos } from '../hooks/useApi'; import CopyButton from '../components/CopyButton'; import { formatSynor, truncateHash, cn } from '../lib/utils'; type UtxoFilter = 'all' | 'coinbase' | 'regular'; export default function Address() { const { address } = useParams<{ address: string }>(); const { data: info, isLoading: infoLoading, error: infoError } = useAddress(address || ''); const { data: utxos, isLoading: utxosLoading } = useAddressUtxos(address || ''); const [utxoFilter, setUtxoFilter] = useState('all'); // Filter UTXOs based on selection const filteredUtxos = useMemo(() => { if (!utxos) return []; switch (utxoFilter) { case 'coinbase': return utxos.filter((u) => u.utxoEntry.isCoinbase); case 'regular': return utxos.filter((u) => !u.utxoEntry.isCoinbase); default: return utxos; } }, [utxos, utxoFilter]); // Calculate coinbase count for filter badge const coinbaseCount = useMemo(() => { if (!utxos) return 0; return utxos.filter((u) => u.utxoEntry.isCoinbase).length; }, [utxos]); if (!address) { return
Address is required
; } if (infoLoading) { return ; } if (infoError) { return (
Error loading address: {infoError.message}
); } if (!info) { return
Address not found
; } // Calculate percentages for balance flow const totalFlow = info.totalReceived + info.totalSent; const receivedPercent = totalFlow > 0 ? (info.totalReceived / totalFlow) * 100 : 50; return (
{/* Modern Header */}
{/* Background glow */}

Address

{info.address}
{/* Balance Overview Card */}
{/* Current Balance */}
Balance

{info.balanceHuman}

{/* Total Received */}
Total Received

{formatSynor(info.totalReceived)}

{/* Total Sent */}
Total Sent

{formatSynor(info.totalSent)}

{/* Transaction Count */}
Transactions

{info.transactionCount.toLocaleString()}

{/* Balance Flow Visualization */}
Balance Flow {info.utxoCount} UTXO{info.utxoCount !== 1 ? 's' : ''}
In: {((info.totalReceived / totalFlow) * 100).toFixed(1)}% Out: {((info.totalSent / totalFlow) * 100).toFixed(1)}%
{/* UTXOs with Filtering */}

UTXOs {utxos && `(${filteredUtxos.length}${utxoFilter !== 'all' ? ` of ${utxos.length}` : ''})`}

{/* Filter Buttons */}
setUtxoFilter('all')} label="All" icon={} /> setUtxoFilter('coinbase')} label="Coinbase" icon={} badge={coinbaseCount > 0 ? coinbaseCount : undefined} /> setUtxoFilter('regular')} label="Regular" icon={} />
{utxosLoading ? (
Loading UTXOs...
) : filteredUtxos.length > 0 ? (
{filteredUtxos.map((utxo, i) => ( ))}
Transaction Index Amount Type
{truncateHash(utxo.outpoint.transactionId)} {utxo.outpoint.index} {formatSynor(utxo.utxoEntry.amount, 4)}
{utxo.utxoEntry.isCoinbase ? ( Coinbase ) : ( Regular )} v{utxo.utxoEntry.scriptPublicKey.version}
) : (

{utxoFilter === 'all' ? 'No UTXOs found for this address' : `No ${utxoFilter} UTXOs found`}

{utxoFilter !== 'all' && utxos && utxos.length > 0 && ( )}
)}
); } function FilterButton({ active, onClick, label, icon, badge, }: { active: boolean; onClick: () => void; label: string; icon: React.ReactNode; badge?: number; }) { return ( ); } function AddressSkeleton() { return (
{Array.from({ length: 4 }).map((_, i) => (
))}
{Array.from({ length: 3 }).map((_, i) => (
))}
); }