wallet: mobile-first redesign - balance hero, compact status pills, collapsible educational content
Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
This commit is contained in:
parent
df8bd39ce1
commit
1876d14b0f
1 changed files with 266 additions and 312 deletions
|
|
@ -1,10 +1,8 @@
|
|||
import { useState } from 'react';
|
||||
import { useSeoMeta } from '@unhead/react';
|
||||
import { Layout } from '@/components/Layout';
|
||||
import { WalletModal } from '@/components/WalletModal';
|
||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Badge } from '@/components/ui/badge';
|
||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs';
|
||||
import { useWallet } from '@/hooks/useWallet';
|
||||
import { useNWC } from '@/hooks/useNWCContext';
|
||||
import { useCurrentUser } from '@/hooks/useCurrentUser';
|
||||
|
|
@ -12,296 +10,116 @@ import { useCashu } from '@/hooks/wallet/useCashu';
|
|||
import { useSatsToUsd } from '@/hooks/useExchangeRate';
|
||||
import { LoginArea } from '@/components/auth/LoginArea';
|
||||
import { CashuWalletCard } from '@/components/wallet/CashuWalletCard';
|
||||
import { LightningPrivacyTips } from '@/components/wallet/LightningPrivacyTips';
|
||||
import { WalletComparisonGuide } from '@/components/wallet/WalletComparisonGuide';
|
||||
import { cn } from '@/lib/utils';
|
||||
import {
|
||||
Wallet,
|
||||
Zap,
|
||||
Settings,
|
||||
CheckCircle,
|
||||
XCircle,
|
||||
ExternalLink,
|
||||
Globe,
|
||||
WalletMinimal,
|
||||
Info,
|
||||
Nut,
|
||||
ChevronRight,
|
||||
ExternalLink,
|
||||
ChevronDown,
|
||||
ChevronUp,
|
||||
Wallet,
|
||||
Bitcoin,
|
||||
} from 'lucide-react';
|
||||
|
||||
// ─── Wallet Status Card ───────────────────────────────────────────────────────
|
||||
|
||||
function WalletStatusCard() {
|
||||
const { webln } = useWallet();
|
||||
const { connections } = useNWC();
|
||||
const { totalBalance: cashuBalance, isInitialized: hasCashu } = useCashu();
|
||||
const cashuUsd = useSatsToUsd(cashuBalance);
|
||||
const hasNWC = connections.length > 0 && connections.some(c => c.isConnected);
|
||||
const isReady = webln || hasNWC || hasCashu;
|
||||
|
||||
return (
|
||||
<Card className={isReady ? 'border-green-500/30 bg-green-950/10' : 'border-gray-700'}>
|
||||
<CardHeader className="pb-3 px-4 pt-4">
|
||||
<div className="flex items-center justify-between">
|
||||
<CardTitle className="flex items-center gap-2 text-base">
|
||||
<Wallet className="h-5 w-5 text-primary flex-shrink-0" />
|
||||
Wallet Status
|
||||
</CardTitle>
|
||||
<Badge
|
||||
variant={isReady ? 'default' : 'secondary'}
|
||||
className={isReady ? 'bg-green-500/20 text-green-400 border-green-500/30 text-xs' : 'text-xs'}
|
||||
>
|
||||
{isReady ? '✓ Ready' : 'Not Connected'}
|
||||
</Badge>
|
||||
</div>
|
||||
<CardDescription className="text-xs mt-1">
|
||||
{isReady
|
||||
? 'Your wallet is connected and ready to send zaps!'
|
||||
: 'Connect a wallet to enable Lightning zaps'}
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent className="px-4 pb-4 space-y-2">
|
||||
{/* Cashu */}
|
||||
<div className="flex items-center justify-between p-3 bg-muted/40 rounded-xl min-h-[56px]">
|
||||
<div className="flex items-center gap-3 min-w-0">
|
||||
<div className="w-9 h-9 rounded-lg bg-orange-500/15 flex items-center justify-center flex-shrink-0">
|
||||
<Nut className="h-4 w-4 text-orange-500" />
|
||||
</div>
|
||||
<div className="min-w-0">
|
||||
<p className="font-semibold text-sm">Cashu Wallet</p>
|
||||
<p className="text-xs text-muted-foreground truncate">
|
||||
{hasCashu && cashuBalance > 0
|
||||
? `${cashuBalance.toLocaleString()} sats${cashuUsd ? ` · $${cashuUsd.toFixed(2)}` : ''}`
|
||||
: 'Built-in ecash wallet'}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
{hasCashu
|
||||
? <CheckCircle className="h-5 w-5 text-green-500 flex-shrink-0" />
|
||||
: <XCircle className="h-5 w-5 text-muted-foreground/50 flex-shrink-0" />}
|
||||
</div>
|
||||
|
||||
{/* WebLN */}
|
||||
<div className="flex items-center justify-between p-3 bg-muted/40 rounded-xl min-h-[56px]">
|
||||
<div className="flex items-center gap-3 min-w-0">
|
||||
<div className="w-9 h-9 rounded-lg bg-blue-500/15 flex items-center justify-center flex-shrink-0">
|
||||
<Globe className="h-4 w-4 text-blue-400" />
|
||||
</div>
|
||||
<div className="min-w-0">
|
||||
<p className="font-semibold text-sm">WebLN</p>
|
||||
<p className="text-xs text-muted-foreground">Browser extension wallet</p>
|
||||
</div>
|
||||
</div>
|
||||
{webln
|
||||
? <CheckCircle className="h-5 w-5 text-green-500 flex-shrink-0" />
|
||||
: <XCircle className="h-5 w-5 text-muted-foreground/50 flex-shrink-0" />}
|
||||
</div>
|
||||
|
||||
{/* NWC */}
|
||||
<div className="flex items-center justify-between p-3 bg-muted/40 rounded-xl min-h-[56px]">
|
||||
<div className="flex items-center gap-3 min-w-0">
|
||||
<div className="w-9 h-9 rounded-lg bg-purple-500/15 flex items-center justify-center flex-shrink-0">
|
||||
<WalletMinimal className="h-4 w-4 text-purple-400" />
|
||||
</div>
|
||||
<div className="min-w-0">
|
||||
<p className="font-semibold text-sm">Nostr Wallet Connect</p>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
{connections.length > 0
|
||||
? `${connections.length} wallet${connections.length !== 1 ? 's' : ''} connected`
|
||||
: 'Remote wallet connection'}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
{hasNWC
|
||||
? <CheckCircle className="h-5 w-5 text-green-500 flex-shrink-0" />
|
||||
: <XCircle className="h-5 w-5 text-muted-foreground/50 flex-shrink-0" />}
|
||||
</div>
|
||||
|
||||
<WalletModal>
|
||||
<Button className="w-full gap-2 mt-1 h-12 rounded-xl font-bold">
|
||||
<Settings className="h-4 w-4" />
|
||||
Manage Wallets
|
||||
</Button>
|
||||
</WalletModal>
|
||||
</CardContent>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
|
||||
// ─── Zaps Info Card ───────────────────────────────────────────────────────────
|
||||
|
||||
function ZapsInfoCard() {
|
||||
const items = [
|
||||
{ emoji: '⚡', title: 'Instant', desc: 'Payments settle in seconds over Lightning' },
|
||||
{ emoji: '🔐', title: 'Flexible custody', desc: 'Use your own wallet or a custodial service' },
|
||||
{ emoji: '🥜', title: 'NutZaps for privacy', desc: "Cashu ecash — mints can't see who pays whom" },
|
||||
{ emoji: '💜', title: 'Value for value', desc: 'Support creators directly' },
|
||||
];
|
||||
|
||||
return (
|
||||
<Card className="border-gray-700">
|
||||
<CardHeader className="px-4 pt-4 pb-3">
|
||||
<CardTitle className="flex items-center gap-2 text-base">
|
||||
<Zap className="h-5 w-5 text-amber-500" />
|
||||
What are Zaps?
|
||||
</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent className="px-4 pb-4 space-y-2">
|
||||
<p className="text-sm text-muted-foreground leading-relaxed">
|
||||
Zaps are Lightning micropayments — send sats instantly to any skater, spot creator, or coach. No middleman, no platform cut.
|
||||
</p>
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 gap-2 pt-1">
|
||||
{items.map((item) => (
|
||||
<div key={item.title} className="flex items-start gap-3 p-3 bg-muted/40 rounded-xl">
|
||||
<span className="text-xl flex-shrink-0 mt-0.5">{item.emoji}</span>
|
||||
<div>
|
||||
<p className="font-semibold text-sm">{item.title}</p>
|
||||
<p className="text-xs text-muted-foreground leading-snug">{item.desc}</p>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
|
||||
// ─── Recommended Wallets ──────────────────────────────────────────────────────
|
||||
|
||||
function WalletResourcesCard() {
|
||||
const wallets = [
|
||||
{ name: 'Alby', desc: 'Browser extension · WebLN + NWC', url: 'https://getalby.com', type: 'WebLN', color: 'text-yellow-400' },
|
||||
{ name: 'Minibits', desc: 'Mobile Cashu wallet · NWC', url: 'https://www.minibits.cash', type: 'NWC', color: 'text-orange-400' },
|
||||
{ name: 'Zeus', desc: 'Self-custodial mobile Lightning', url: 'https://zeusln.com', type: 'NWC', color: 'text-blue-400' },
|
||||
{ name: 'Coinos', desc: 'Web-based · works everywhere', url: 'https://coinos.io', type: 'NWC', color: 'text-green-400' },
|
||||
];
|
||||
|
||||
return (
|
||||
<Card className="border-gray-700">
|
||||
<CardHeader className="px-4 pt-4 pb-3">
|
||||
<CardTitle className="flex items-center gap-2 text-base">
|
||||
<Info className="h-5 w-5" />
|
||||
Recommended Wallets
|
||||
</CardTitle>
|
||||
<CardDescription className="text-xs">Get started with one of these Lightning wallets</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent className="px-4 pb-4 space-y-2">
|
||||
{wallets.map((w) => (
|
||||
<a
|
||||
key={w.name}
|
||||
href={w.url}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="flex items-center justify-between p-3 bg-muted/40 rounded-xl hover:bg-muted active:scale-[0.98] transition-all group min-h-[56px]"
|
||||
>
|
||||
<div className="flex items-center gap-3 min-w-0">
|
||||
<div className="w-9 h-9 rounded-lg bg-white/5 flex items-center justify-center flex-shrink-0">
|
||||
<Wallet className={`h-4 w-4 ${w.color}`} />
|
||||
</div>
|
||||
<div className="min-w-0">
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="font-semibold text-sm group-hover:text-primary transition-colors">{w.name}</span>
|
||||
<Badge variant="outline" className="text-[10px] px-1.5 py-0 h-4">{w.type}</Badge>
|
||||
</div>
|
||||
<p className="text-xs text-muted-foreground truncate">{w.desc}</p>
|
||||
</div>
|
||||
</div>
|
||||
<ChevronRight className="h-4 w-4 text-muted-foreground group-hover:text-primary flex-shrink-0 transition-colors" />
|
||||
</a>
|
||||
))}
|
||||
<a
|
||||
href="https://planb.academy/en/tutorials/wallet"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="flex items-center gap-2 text-xs text-orange-400 hover:text-orange-300 transition-colors pt-1"
|
||||
>
|
||||
<ExternalLink className="h-3.5 w-3.5 flex-shrink-0" />
|
||||
Learn more at Plan B Academy
|
||||
</a>
|
||||
</CardContent>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
|
||||
// ─── Not Logged In ────────────────────────────────────────────────────────────
|
||||
|
||||
function NotLoggedInCard() {
|
||||
return (
|
||||
<Card className="border-dashed border-gray-700">
|
||||
<CardContent className="py-12 px-6 text-center">
|
||||
<div className="w-16 h-16 rounded-2xl bg-[#ff0040]/10 flex items-center justify-center mx-auto mb-4">
|
||||
<Wallet className="h-8 w-8 text-[#ff0040]/60" />
|
||||
</div>
|
||||
<h3 className="text-lg font-bold mb-2">Login Required</h3>
|
||||
<p className="text-muted-foreground text-sm max-w-xs mx-auto mb-6 leading-relaxed">
|
||||
Log in with your Nostr account to connect your Lightning wallet and start zapping.
|
||||
</p>
|
||||
<LoginArea className="justify-center" />
|
||||
</CardContent>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
|
||||
// ─── Balance Hero Banner ──────────────────────────────────────────────────────
|
||||
// ─── Balance Hero ─────────────────────────────────────────────────────────────
|
||||
|
||||
function BalanceHero() {
|
||||
const { totalBalance: cashuBalance, isInitialized: hasCashu } = useCashu();
|
||||
const cashuUsd = useSatsToUsd(cashuBalance);
|
||||
const { webln } = useWallet();
|
||||
const { connections } = useNWC();
|
||||
const hasNWC = connections.length > 0 && connections.some(c => c.isConnected);
|
||||
const hasNWC = connections.length > 0 && connections.some((c) => c.isConnected);
|
||||
const isReady = webln || hasNWC || hasCashu;
|
||||
|
||||
return (
|
||||
<div className="relative overflow-hidden rounded-2xl border border-orange-500/30 bg-gradient-to-br from-orange-950/40 via-black to-yellow-950/20 p-5">
|
||||
{/* Background glow */}
|
||||
<div className="absolute -top-8 -right-8 w-32 h-32 bg-orange-500/10 rounded-full blur-2xl pointer-events-none" />
|
||||
<div className="absolute -bottom-6 -left-6 w-24 h-24 bg-yellow-500/10 rounded-full blur-2xl pointer-events-none" />
|
||||
<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 flex items-center justify-between gap-4">
|
||||
<div>
|
||||
<div className="flex items-center gap-2 mb-1">
|
||||
<Bitcoin className="h-4 w-4 text-orange-400" />
|
||||
<span className="text-xs font-bold uppercase tracking-widest text-orange-400/80">Cashu Balance</span>
|
||||
<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>
|
||||
<div className="text-3xl font-black tabular-nums leading-none">
|
||||
{hasCashu ? cashuBalance.toLocaleString() : '—'}
|
||||
<span className="text-base font-semibold text-muted-foreground ml-2">sats</span>
|
||||
</div>
|
||||
{cashuUsd !== null && cashuBalance > 0 && (
|
||||
<div className="text-sm text-muted-foreground mt-1">
|
||||
{cashuUsd !== null && cashuBalance > 0 ? (
|
||||
<p className="text-sm text-muted-foreground mt-1">
|
||||
≈ ${cashuUsd.toLocaleString('en-US', { minimumFractionDigits: 2, maximumFractionDigits: 2 })}
|
||||
</div>
|
||||
</p>
|
||||
) : (
|
||||
<p className="text-xs text-muted-foreground/50 mt-1">no balance yet</p>
|
||||
)}
|
||||
{/* Wallet status pills */}
|
||||
<div className="flex flex-wrap gap-1.5 mt-3">
|
||||
{hasCashu && (
|
||||
<span className="inline-flex items-center gap-1 px-2 py-0.5 rounded-full bg-orange-500/20 border border-orange-500/30 text-[10px] font-bold text-orange-400">
|
||||
<Nut className="h-2.5 w-2.5" /> Cashu
|
||||
</span>
|
||||
)}
|
||||
{webln && (
|
||||
<span className="inline-flex items-center gap-1 px-2 py-0.5 rounded-full bg-blue-500/20 border border-blue-500/30 text-[10px] font-bold text-blue-400">
|
||||
<Globe className="h-2.5 w-2.5" /> WebLN
|
||||
</span>
|
||||
)}
|
||||
{hasNWC && (
|
||||
<span className="inline-flex items-center gap-1 px-2 py-0.5 rounded-full bg-purple-500/20 border border-purple-500/30 text-[10px] font-bold text-purple-400">
|
||||
<WalletMinimal className="h-2.5 w-2.5" /> NWC
|
||||
</span>
|
||||
)}
|
||||
{!hasCashu && !webln && !hasNWC && (
|
||||
<span className="inline-flex items-center gap-1 px-2 py-0.5 rounded-full bg-gray-700/50 border border-gray-600 text-[10px] font-semibold text-gray-400">
|
||||
No wallet connected
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Manage button */}
|
||||
{/* 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="flex-shrink-0 flex flex-col items-center gap-1.5 px-3 py-2.5 rounded-xl bg-orange-500/15 border border-orange-500/30 hover:bg-orange-500/25 active:scale-95 transition-all">
|
||||
<Settings className="h-5 w-5 text-orange-400" />
|
||||
<span className="text-[10px] font-bold text-orange-400 uppercase tracking-wide leading-none">Manage</span>
|
||||
<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>
|
||||
|
|
@ -309,6 +127,167 @@ function BalanceHero() {
|
|||
);
|
||||
}
|
||||
|
||||
function WalletStatusPill({
|
||||
icon: Icon,
|
||||
label,
|
||||
active,
|
||||
color,
|
||||
activeBg,
|
||||
}: {
|
||||
icon: React.ComponentType<{ className?: string }>;
|
||||
label: string;
|
||||
active: boolean;
|
||||
color: string;
|
||||
activeBg: string;
|
||||
}) {
|
||||
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'
|
||||
)}>
|
||||
<Icon className="h-3 w-3" />
|
||||
{label}
|
||||
{active
|
||||
? <CheckCircle className="h-3 w-3 text-green-500" />
|
||||
: <XCircle className="h-3 w-3 text-gray-700" />
|
||||
}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// ─── Recommended Wallets (collapsible) ───────────────────────────────────────
|
||||
|
||||
function RecommendedWallets() {
|
||||
const [open, setOpen] = useState(false);
|
||||
|
||||
const wallets = [
|
||||
{ name: 'Minibits', desc: 'Mobile Cashu · NWC', url: 'https://www.minibits.cash', color: 'text-orange-400' },
|
||||
{ name: 'Alby', desc: 'Browser extension · WebLN + NWC', url: 'https://getalby.com', color: 'text-yellow-400' },
|
||||
{ name: 'Zeus', desc: 'Self-custodial Lightning', url: 'https://zeusln.com', color: 'text-blue-400' },
|
||||
{ name: 'Coinos', desc: 'Web-based · works everywhere', url: 'https://coinos.io', color: 'text-green-400' },
|
||||
];
|
||||
|
||||
return (
|
||||
<div className="rounded-2xl border border-white/8 overflow-hidden">
|
||||
<button
|
||||
onClick={() => setOpen((v) => !v)}
|
||||
className="w-full flex items-center justify-between px-4 py-3.5 bg-white/3 hover:bg-white/5 transition-all"
|
||||
>
|
||||
<div className="flex items-center gap-2">
|
||||
<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" />}
|
||||
</button>
|
||||
|
||||
{open && (
|
||||
<div className="divide-y divide-white/5">
|
||||
{wallets.map((w) => (
|
||||
<a
|
||||
key={w.name}
|
||||
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"
|
||||
>
|
||||
<div className="min-w-0">
|
||||
<p className={cn('font-bold text-sm', w.color)}>{w.name}</p>
|
||||
<p className="text-xs text-muted-foreground">{w.desc}</p>
|
||||
</div>
|
||||
<ExternalLink className="h-3.5 w-3.5 text-muted-foreground flex-shrink-0" />
|
||||
</a>
|
||||
))}
|
||||
<div className="px-4 py-3">
|
||||
<a
|
||||
href="https://planb.academy/en/tutorials/wallet"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="text-xs text-orange-400 hover:text-orange-300 transition-colors flex items-center gap-1.5"
|
||||
>
|
||||
<ExternalLink className="h-3 w-3" />
|
||||
Learn more at Plan B Academy
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// ─── Zaps explainer (collapsible, hidden once wallet connected) ───────────────
|
||||
|
||||
function WhatAreZaps({ walletConnected }: { walletConnected: boolean }) {
|
||||
const [open, setOpen] = useState(!walletConnected);
|
||||
|
||||
if (walletConnected && !open) {
|
||||
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"
|
||||
>
|
||||
<ChevronDown className="h-3 w-3" />
|
||||
What are zaps?
|
||||
</button>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="rounded-2xl border border-white/8 overflow-hidden">
|
||||
<button
|
||||
onClick={() => setOpen((v) => !v)}
|
||||
className="w-full flex items-center justify-between px-4 py-3.5 bg-white/3 hover:bg-white/5 transition-all"
|
||||
>
|
||||
<div className="flex items-center gap-2">
|
||||
<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" />}
|
||||
</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.
|
||||
</p>
|
||||
<div className="grid grid-cols-2 gap-2">
|
||||
{[
|
||||
{ emoji: '⚡', title: 'Instant', desc: 'Settles in seconds' },
|
||||
{ emoji: '🔐', title: 'Flexible', desc: 'Your wallet, your keys' },
|
||||
{ emoji: '🥜', title: 'Private', desc: 'Cashu hides who pays' },
|
||||
{ emoji: '💜', title: 'Direct', desc: 'Value goes to creator' },
|
||||
].map((item) => (
|
||||
<div key={item.title} className="flex items-start gap-2 p-2.5 bg-white/3 rounded-xl">
|
||||
<span className="text-base flex-shrink-0">{item.emoji}</span>
|
||||
<div>
|
||||
<p className="font-bold text-xs text-white">{item.title}</p>
|
||||
<p className="text-[11px] text-muted-foreground">{item.desc}</p>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// ─── Not logged in ────────────────────────────────────────────────────────────
|
||||
|
||||
function NotLoggedIn() {
|
||||
return (
|
||||
<div className="flex flex-col items-center justify-center py-20 px-4 text-center">
|
||||
<div className="w-16 h-16 rounded-2xl bg-orange-500/10 flex items-center justify-center mb-4">
|
||||
<Wallet className="h-8 w-8 text-orange-500/60" />
|
||||
</div>
|
||||
<h3 className="text-lg font-black mb-2 text-white">Login Required</h3>
|
||||
<p className="text-muted-foreground text-sm max-w-xs mb-6 leading-relaxed">
|
||||
Log in with your Nostr account to connect your Lightning wallet and start zapping.
|
||||
</p>
|
||||
<LoginArea className="justify-center" />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// ─── Page ─────────────────────────────────────────────────────────────────────
|
||||
|
||||
export default function WalletPage() {
|
||||
|
|
@ -318,67 +297,42 @@ export default function WalletPage() {
|
|||
});
|
||||
|
||||
const { user } = useCurrentUser();
|
||||
const { webln } = useWallet();
|
||||
const { connections } = useNWC();
|
||||
const { isInitialized: hasCashu } = useCashu();
|
||||
const hasNWC = connections.length > 0 && connections.some((c) => c.isConnected);
|
||||
const walletConnected = !!(webln || hasNWC || hasCashu);
|
||||
|
||||
return (
|
||||
<Layout>
|
||||
<div className="container px-3 xs:px-4 py-4 md:py-8 max-w-2xl mx-auto">
|
||||
<div className="container px-4 py-5 max-w-lg mx-auto">
|
||||
|
||||
{/* Header */}
|
||||
<div className="mb-4">
|
||||
<h1 className="text-2xl font-black uppercase tracking-wide flex items-center gap-2">
|
||||
<Wallet className="h-6 w-6 text-orange-400" />
|
||||
Wallet
|
||||
<div className="mb-5">
|
||||
<h1 className="text-3xl font-black uppercase leading-none mb-0.5">
|
||||
<span className="neon-orange">WALLET</span>
|
||||
</h1>
|
||||
<p className="text-muted-foreground text-sm mt-0.5">
|
||||
Bitcoin Lightning payments & zaps
|
||||
</p>
|
||||
<p className="text-muted-foreground text-sm">Bitcoin Lightning + Cashu</p>
|
||||
</div>
|
||||
|
||||
{user ? (
|
||||
<div className="space-y-4">
|
||||
{/* Balance hero — always at top for logged-in users */}
|
||||
{/* 1. Balance hero — always first */}
|
||||
<BalanceHero />
|
||||
|
||||
{/* Quick Guide — collapsed by default */}
|
||||
<WalletComparisonGuide />
|
||||
{/* 2. Cashu wallet card — the main interactive section */}
|
||||
<div id="cashu-card">
|
||||
<CashuWalletCard />
|
||||
</div>
|
||||
|
||||
<Tabs defaultValue="cashu" className="w-full">
|
||||
{/* Tab pills — full width, thumb-friendly height */}
|
||||
<TabsList className="grid w-full grid-cols-2 h-12 rounded-xl p-1 mb-4 bg-muted/60">
|
||||
<TabsTrigger
|
||||
value="cashu"
|
||||
className="gap-2 rounded-lg text-sm font-bold h-full data-[state=active]:bg-orange-500 data-[state=active]:text-white"
|
||||
>
|
||||
<Nut className="h-4 w-4 flex-shrink-0" />
|
||||
<span>Cashu</span>
|
||||
</TabsTrigger>
|
||||
<TabsTrigger
|
||||
value="lightning"
|
||||
className="gap-2 rounded-lg text-sm font-bold h-full data-[state=active]:bg-yellow-500 data-[state=active]:text-black"
|
||||
>
|
||||
<Zap className="h-4 w-4 flex-shrink-0" />
|
||||
<span>Lightning</span>
|
||||
</TabsTrigger>
|
||||
</TabsList>
|
||||
{/* 3. Get a wallet — collapsed by default */}
|
||||
<RecommendedWallets />
|
||||
|
||||
{/* Cashu tab */}
|
||||
<TabsContent value="cashu" className="space-y-4 mt-0">
|
||||
<CashuWalletCard />
|
||||
</TabsContent>
|
||||
|
||||
{/* Lightning tab */}
|
||||
<TabsContent value="lightning" className="space-y-4 mt-0">
|
||||
<LightningPrivacyTips />
|
||||
<WalletStatusCard />
|
||||
<WalletResourcesCard />
|
||||
<ZapsInfoCard />
|
||||
</TabsContent>
|
||||
</Tabs>
|
||||
{/* 4. Zaps explainer — auto-collapsed once wallet is connected */}
|
||||
<WhatAreZaps walletConnected={walletConnected} />
|
||||
</div>
|
||||
) : (
|
||||
<div className="space-y-4">
|
||||
<NotLoggedInCard />
|
||||
<ZapsInfoCard />
|
||||
</div>
|
||||
<NotLoggedIn />
|
||||
)}
|
||||
</div>
|
||||
</Layout>
|
||||
|
|
|
|||
Loading…
Reference in a new issue