wallet: remove duplicate balance hero, single CashuWalletCard as source of truth

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
This commit is contained in:
shakespeare.diy 2026-07-23 19:29:52 +07:00
parent 1876d14b0f
commit f0eadf307b

View file

@ -7,7 +7,6 @@ import { useWallet } from '@/hooks/useWallet';
import { useNWC } from '@/hooks/useNWCContext';
import { useCurrentUser } from '@/hooks/useCurrentUser';
import { useCashu } from '@/hooks/wallet/useCashu';
import { useSatsToUsd } from '@/hooks/useExchangeRate';
import { LoginArea } from '@/components/auth/LoginArea';
import { CashuWalletCard } from '@/components/wallet/CashuWalletCard';
import { cn } from '@/lib/utils';
@ -24,115 +23,43 @@ import {
ChevronDown,
ChevronUp,
Wallet,
Bitcoin,
} from 'lucide-react';
// ─── Balance Hero ─────────────────────────────────────────────────────────────
// ─── Wallet status bar ────────────────────────────────────────────────────────
function BalanceHero() {
const { totalBalance: cashuBalance, isInitialized: hasCashu } = useCashu();
const cashuUsd = useSatsToUsd(cashuBalance);
function WalletStatusBar() {
const { webln } = useWallet();
const { connections } = useNWC();
const { isInitialized: hasCashu } = useCashu();
const hasNWC = connections.length > 0 && connections.some((c) => c.isConnected);
const isReady = webln || hasNWC || hasCashu;
return (
<div className="relative overflow-hidden rounded-2xl bg-gradient-to-br from-orange-950/60 via-black to-yellow-950/30 border border-orange-500/20 p-6">
{/* Background glows */}
<div className="absolute -top-10 -right-10 w-40 h-40 bg-orange-500/10 rounded-full blur-3xl pointer-events-none" />
<div className="absolute -bottom-8 -left-8 w-32 h-32 bg-yellow-500/8 rounded-full blur-2xl pointer-events-none" />
<div className="relative space-y-5">
{/* Balance */}
<div className="text-center">
<p className="text-[11px] font-black uppercase tracking-widest text-orange-400/70 mb-1">Cashu Balance</p>
<div className="flex items-baseline justify-center gap-2">
<span className="text-5xl font-black tabular-nums leading-none text-white">
{hasCashu ? cashuBalance.toLocaleString() : '0'}
</span>
<span className="text-lg font-bold text-orange-400/80">sats</span>
</div>
{cashuUsd !== null && cashuBalance > 0 ? (
<p className="text-sm text-muted-foreground mt-1">
≈ ${cashuUsd.toLocaleString('en-US', { minimumFractionDigits: 2, maximumFractionDigits: 2 })}
</p>
) : (
<p className="text-xs text-muted-foreground/50 mt-1">no balance yet</p>
)}
</div>
{/* Wallet status — compact icon row */}
<div className="flex items-center justify-center gap-2">
<WalletStatusPill
icon={Nut}
label="Cashu"
active={hasCashu}
color="text-orange-400"
activeBg="bg-orange-500/20 border-orange-500/30"
/>
<WalletStatusPill
icon={Globe}
label="WebLN"
active={!!webln}
color="text-blue-400"
activeBg="bg-blue-500/20 border-blue-500/30"
/>
<WalletStatusPill
icon={WalletMinimal}
label="NWC"
active={hasNWC}
color="text-purple-400"
activeBg="bg-purple-500/20 border-purple-500/30"
/>
</div>
{/* Primary action buttons — scroll down to Cashu card to act */}
<div className="grid grid-cols-2 gap-3">
<button
onClick={() => document.getElementById('cashu-card')?.scrollIntoView({ behavior: 'smooth' })}
className="w-full h-12 rounded-xl bg-orange-500 hover:bg-orange-400 active:scale-[0.98] transition-all font-black text-sm uppercase tracking-wide text-white flex items-center justify-center gap-2"
>
<Bitcoin className="h-4 w-4" />
Receive
</button>
<button
onClick={() => document.getElementById('cashu-card')?.scrollIntoView({ behavior: 'smooth' })}
className="w-full h-12 rounded-xl bg-white/10 hover:bg-white/15 active:scale-[0.98] transition-all font-black text-sm uppercase tracking-wide text-white flex items-center justify-center gap-2 border border-white/10"
>
<Zap className="h-4 w-4 text-yellow-400" />
Send
</button>
</div>
{/* Manage wallets */}
<WalletModal>
<button className="w-full flex items-center justify-between px-4 py-3 rounded-xl bg-white/5 hover:bg-white/8 active:scale-[0.99] border border-white/8 transition-all">
<div className="flex items-center gap-2">
<Settings className="h-4 w-4 text-orange-400" />
<span className="text-sm font-bold text-white">Manage Wallets</span>
</div>
<div className="flex items-center gap-2">
{isReady ? (
<Badge className="bg-green-500/20 text-green-400 border-green-500/30 text-[10px] px-1.5 py-0">Connected</Badge>
) : (
<Badge variant="secondary" className="text-[10px] px-1.5 py-0">Not connected</Badge>
)}
<ChevronRight className="h-4 w-4 text-muted-foreground" />
</div>
</button>
</WalletModal>
<div className="flex items-center justify-between gap-3">
{/* Status pills */}
<div className="flex items-center gap-1.5">
<StatusPill icon={Nut} label="Cashu" active={hasCashu} color="text-orange-400" activeBg="bg-orange-500/20 border-orange-500/30" />
<StatusPill icon={Globe} label="WebLN" active={!!webln} color="text-blue-400" activeBg="bg-blue-500/20 border-blue-500/30" />
<StatusPill icon={WalletMinimal} label="NWC" active={hasNWC} color="text-purple-400" activeBg="bg-purple-500/20 border-purple-500/30" />
</div>
{/* Manage wallets */}
<WalletModal>
<button className="flex items-center gap-1.5 px-3 py-1.5 rounded-xl bg-white/5 hover:bg-white/10 border border-white/8 active:scale-95 transition-all flex-shrink-0">
<Settings className="h-3.5 w-3.5 text-orange-400" />
<span className="text-xs font-bold text-white">Manage</span>
{isReady
? <Badge className="bg-green-500/20 text-green-400 border-green-500/30 text-[9px] px-1 py-0 leading-none">ON</Badge>
: <ChevronRight className="h-3 w-3 text-muted-foreground" />
}
</button>
</WalletModal>
</div>
);
}
function WalletStatusPill({
icon: Icon,
label,
active,
color,
activeBg,
function StatusPill({
icon: Icon, label, active, color, activeBg,
}: {
icon: React.ComponentType<{ className?: string }>;
label: string;
@ -142,22 +69,22 @@ function WalletStatusPill({
}) {
return (
<div className={cn(
'flex items-center gap-1.5 px-2.5 py-1.5 rounded-full border text-[11px] font-bold transition-all',
active ? `${activeBg} ${color}` : 'bg-white/5 border-white/10 text-gray-600'
'flex items-center gap-1 px-2 py-1 rounded-full border text-[10px] font-bold transition-all',
active ? `${activeBg} ${color}` : 'bg-white/3 border-white/8 text-gray-600'
)}>
<Icon className="h-3 w-3" />
<Icon className="h-2.5 w-2.5" />
{label}
{active
? <CheckCircle className="h-3 w-3 text-green-500" />
: <XCircle className="h-3 w-3 text-gray-700" />
? <CheckCircle className="h-2.5 w-2.5 text-green-500" />
: <XCircle className="h-2.5 w-2.5 text-gray-700" />
}
</div>
);
}
// ─── Recommended Wallets (collapsible) ───────────────────────────────────────
// ─── Get a wallet (collapsible) ───────────────────────────────────────────────
function RecommendedWallets() {
function GetAWallet() {
const [open, setOpen] = useState(false);
const wallets = [
@ -177,7 +104,10 @@ function RecommendedWallets() {
<Zap className="h-4 w-4 text-yellow-400" />
<span className="text-sm font-black uppercase tracking-wide text-white">Get a Wallet</span>
</div>
{open ? <ChevronUp className="h-4 w-4 text-muted-foreground" /> : <ChevronDown className="h-4 w-4 text-muted-foreground" />}
{open
? <ChevronUp className="h-4 w-4 text-muted-foreground" />
: <ChevronDown className="h-4 w-4 text-muted-foreground" />
}
</button>
{open && (
@ -188,7 +118,7 @@ function RecommendedWallets() {
href={w.url}
target="_blank"
rel="noopener noreferrer"
className="flex items-center justify-between px-4 py-3 hover:bg-white/5 active:scale-[0.99] transition-all group"
className="flex items-center justify-between px-4 py-3 hover:bg-white/5 active:scale-[0.99] transition-all"
>
<div className="min-w-0">
<p className={cn('font-bold text-sm', w.color)}>{w.name}</p>
@ -214,7 +144,7 @@ function RecommendedWallets() {
);
}
// ─── Zaps explainer (collapsible, hidden once wallet connected) ───────────────
// ─── What are zaps (collapsible, hidden once wallet connected) ────────────────
function WhatAreZaps({ walletConnected }: { walletConnected: boolean }) {
const [open, setOpen] = useState(!walletConnected);
@ -223,7 +153,7 @@ function WhatAreZaps({ walletConnected }: { walletConnected: boolean }) {
return (
<button
onClick={() => setOpen(true)}
className="text-xs text-muted-foreground/50 hover:text-muted-foreground transition-colors flex items-center gap-1 mx-auto"
className="text-xs text-muted-foreground/40 hover:text-muted-foreground transition-colors flex items-center gap-1 mx-auto"
>
<ChevronDown className="h-3 w-3" />
What are zaps?
@ -241,13 +171,16 @@ function WhatAreZaps({ walletConnected }: { walletConnected: boolean }) {
<Zap className="h-4 w-4 text-amber-500" />
<span className="text-sm font-black uppercase tracking-wide text-white">What are Zaps?</span>
</div>
{open ? <ChevronUp className="h-4 w-4 text-muted-foreground" /> : <ChevronDown className="h-4 w-4 text-muted-foreground" />}
{open
? <ChevronUp className="h-4 w-4 text-muted-foreground" />
: <ChevronDown className="h-4 w-4 text-muted-foreground" />
}
</button>
{open && (
<div className="px-4 pb-4 pt-3 space-y-3">
<p className="text-sm text-muted-foreground leading-relaxed">
Zaps are instant Bitcoin Lightning payments. Send sats to any skater, spot creator, or coach — no middleman, no platform cut.
Instant Bitcoin Lightning payments. Send sats to any skater, spot creator, or coach — no middleman, no platform cut.
</p>
<div className="grid grid-cols-2 gap-2">
{[
@ -308,7 +241,7 @@ export default function WalletPage() {
<div className="container px-4 py-5 max-w-lg mx-auto">
{/* Header */}
<div className="mb-5">
<div className="mb-4">
<h1 className="text-3xl font-black uppercase leading-none mb-0.5">
<span className="neon-orange">WALLET</span>
</h1>
@ -317,18 +250,16 @@ export default function WalletPage() {
{user ? (
<div className="space-y-4">
{/* 1. Balance hero — always first */}
<BalanceHero />
{/* Status bar + manage — compact single row */}
<WalletStatusBar />
{/* 2. Cashu wallet card — the main interactive section */}
<div id="cashu-card">
<CashuWalletCard />
</div>
{/* Cashu wallet — the one and only balance + send/receive UI */}
<CashuWalletCard />
{/* 3. Get a wallet — collapsed by default */}
<RecommendedWallets />
{/* Get a wallet — collapsed by default */}
<GetAWallet />
{/* 4. Zaps explainer — auto-collapsed once wallet is connected */}
{/* What are zaps — auto-collapses once wallet connected */}
<WhatAreZaps walletConnected={walletConnected} />
</div>
) : (