fix(mobile): redesign wallet page UI for mobile + global mobile polish

Wallet page (/wallet):
- Add BalanceHero banner at top showing Cashu balance + wallet status pills
- Cashu/Lightning tabs with colored active states (orange/yellow)
- Remove nested balance display in CashuWalletCard (now shown in hero)
- Compact balance row in CashuWalletCard
- Stacked full-width inputs on mobile (no side-by-side on small screens)
- h-11 touch targets on all inputs and buttons (44px min)
- inputMode="numeric" on number fields (prevents iOS zoom)
- Receive/Send tabs with green/red active colors for clear context
- History items now 52px min height with colored amount text
- WalletComparisonGuide stays collapsed by default
- Fix: add Nut icon import to CashuWalletCard (was crashing)
- All modals use w-[calc(100%-1rem)] for proper mobile sizing

Settings page (/settings):
- 7-tab bar now scrollable horizontally on mobile (no overflow/wrapping)
- -mx-3 bleed trick so scrollable area goes edge-to-edge
- Wallet tab buttons full-width on mobile
- Container max-w-3xl for better desktop centering

FAQ page (/faq):
- Compact hero on mobile (smaller icon, tighter padding)
- py-6 on mobile instead of py-12

CSS utilities:
- Add xs: breakpoint (375px) variants: flex, flex-row, px-4, -mx-4, gap-2, text-xs/sm, hidden

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
This commit is contained in:
shakespeare.diy 2026-07-23 16:35:49 +07:00
parent 9da1796774
commit 340f6fd89f
6 changed files with 489 additions and 429 deletions

View file

@ -1,10 +1,36 @@
v1.0.38 - Amber login fix (Capacitor native) v1.1.0 - Map as Homepage, Camera Fix, Sasquatch AI upgrades, UI/UX Polish
- Fixed login with Amber for ZapStore/Capacitor app users WHAT'S NEW
- Now uses the native nostr-signer-capacitor-plugin instead of browser
intent URLs (which don't work inside a WebView) Map is now the Homepage
- Amber opens natively as a system intent, user approves, done - Opens directly to the interactive map on launch — no extra tap needed
- All event signing (posts, zaps, etc.) also goes through Amber natively - HOME removed from nav, SPOTS is now the first tab
- Fixed Android 11+ package visibility so Amber is detected correctly - Map zoom capped: no more grey bands when zooming out, world stays visible
- Fixed Capacitor packages missing from package.json (prevented clean builds)
- Added SNT4EVER as community partner and friend Camera Fixed for Native App
- Tapping the camera button now opens your phone's native OS camera directly
- No more browser permission errors inside the app
- Upload from Gallery fallback added if camera is denied
Settings → My Stash
- Quick link to your private gear inventory added to Settings > Wallet tab
Sasquatch AI Guide Upgrades
- FAQ page is now powered by The Sasquatch — ask anything, get live answers
- Quick question chips: tap a topic to instantly ask The Sasquatch
- About page: Ask The Sasquatch button added
- Sasquatch can now be triggered from anywhere in the app
UI/UX Polish
- Wallet page fully reworked for mobile — better spacing, touch targets, readable on small screens
- Focus rings are now on-brand red (accessibility compliant)
- Card hover micro-interactions throughout the app
- Consistent button/link transitions (150ms)
- Reduced motion support for accessibility
- Map min zoom capped — no more empty grey space when zooming out
- Safe area insets improved for notched phones
BUG FIXES
- Map no longer duplicates tiles when zoomed out too far
- Camera no longer shows browser permission error in native app
- Sasquatch panel now responds to external triggers from FAQ/About pages

View file

@ -34,6 +34,7 @@ import {
Shield, Shield,
ScanLine, ScanLine,
Zap, Zap,
Nut,
} from 'lucide-react'; } from 'lucide-react';
import { useToast } from '@/hooks/useToast'; import { useToast } from '@/hooks/useToast';
import { QRScannerModal } from '@/components/wallet/QRScannerModal'; import { QRScannerModal } from '@/components/wallet/QRScannerModal';
@ -228,38 +229,42 @@ export function CashuWalletCard() {
return ( return (
<> <>
<div className="grid gap-4 sm:gap-6 lg:grid-cols-2"> <div className="space-y-4">
{/* Main Wallet Card */} {/* Main Wallet Card */}
<Card className="lg:col-span-2 border-orange-500/30 bg-gradient-to-br from-orange-500/5 to-yellow-500/5"> <Card className="border-orange-500/30 bg-gradient-to-br from-orange-500/5 to-yellow-500/5">
<CardHeader className="p-4 sm:p-6"> <CardHeader className="p-4">
<CardTitle className="text-lg sm:text-xl font-bold flex items-center gap-2"> <CardTitle className="text-base font-bold flex items-center gap-2">
<Wallet className="h-5 w-5 text-orange-500" /> <Wallet className="h-5 w-5 text-orange-500" />
Cashu Wallet Cashu Wallet
</CardTitle> </CardTitle>
<CardDescription className="text-xs sm:text-sm"> <CardDescription className="text-xs">
Built-in ecash wallet - no external setup required Built-in ecash wallet — no external setup required
</CardDescription> </CardDescription>
</CardHeader> </CardHeader>
<CardContent className="space-y-4 sm:space-y-6 p-4 sm:p-6 pt-0 sm:pt-0"> <CardContent className="space-y-4 p-4 pt-0">
{/* Balance Display */} {/* Balance Display — compact on mobile since BalanceHero already shows it */}
<div className="text-center py-4 sm:py-6 space-y-1 sm:space-y-2"> <div className="flex items-center justify-between py-3 px-4 rounded-xl bg-orange-500/10 border border-orange-500/20">
<div className="text-xs sm:text-sm text-muted-foreground uppercase tracking-wide">Total Balance</div> <div>
<div className="text-3xl sm:text-4xl font-black"> <div className="text-[10px] text-orange-400/80 uppercase tracking-widest font-bold mb-0.5">Balance</div>
{totalBalance.toLocaleString()} <span className="text-xl sm:text-2xl text-muted-foreground">sats</span> <div className="text-2xl font-black tabular-nums leading-none">
{totalBalance.toLocaleString()}
<span className="text-sm font-semibold text-muted-foreground ml-1.5">sats</span>
</div> </div>
{usdValue !== null && ( {usdValue !== null && (
<div className="text-base sm:text-lg text-muted-foreground"> <div className="text-xs text-muted-foreground mt-0.5">
≈ ${usdValue.toLocaleString('en-US', { minimumFractionDigits: 2, maximumFractionDigits: 2 })} ≈ ${usdValue.toLocaleString('en-US', { minimumFractionDigits: 2, maximumFractionDigits: 2 })}
</div> </div>
)} )}
</div> </div>
<Nut className="h-8 w-8 text-orange-500/30 flex-shrink-0" />
</div>
{/* Active Mint Selector */} {/* Active Mint Selector */}
{mints.length > 0 && ( {mints.length > 0 && (
<div className="space-y-2"> <div className="space-y-1.5">
<Label className="text-sm font-medium">Active Mint</Label> <Label className="text-xs font-semibold text-muted-foreground uppercase tracking-wide">Active Mint</Label>
<Select value={activeMintUrl || undefined} onValueChange={setActiveMintUrl}> <Select value={activeMintUrl || undefined} onValueChange={setActiveMintUrl}>
<SelectTrigger> <SelectTrigger className="h-11">
<SelectValue placeholder="Select a mint" /> <SelectValue placeholder="Select a mint" />
</SelectTrigger> </SelectTrigger>
<SelectContent> <SelectContent>
@ -280,12 +285,12 @@ export function CashuWalletCard() {
{/* Main Actions */} {/* Main Actions */}
<Tabs defaultValue="receive" className="w-full"> <Tabs defaultValue="receive" className="w-full">
<TabsList className="grid w-full grid-cols-2"> <TabsList className="grid w-full grid-cols-2 h-11 rounded-xl">
<TabsTrigger value="receive" className="gap-2"> <TabsTrigger value="receive" className="gap-2 rounded-lg h-full font-bold text-sm data-[state=active]:bg-green-600 data-[state=active]:text-white">
<ArrowDownCircle className="h-4 w-4" /> <ArrowDownCircle className="h-4 w-4" />
Receive Receive
</TabsTrigger> </TabsTrigger>
<TabsTrigger value="send" className="gap-2"> <TabsTrigger value="send" className="gap-2 rounded-lg h-full font-bold text-sm data-[state=active]:bg-red-600 data-[state=active]:text-white">
<ArrowUpCircle className="h-4 w-4" /> <ArrowUpCircle className="h-4 w-4" />
Send Send
</TabsTrigger> </TabsTrigger>
@ -295,19 +300,20 @@ export function CashuWalletCard() {
<TabsContent value="receive" className="space-y-4 mt-4"> <TabsContent value="receive" className="space-y-4 mt-4">
{/* Lightning Invoice */} {/* Lightning Invoice */}
<div className="space-y-3"> <div className="space-y-3">
<Label className="text-sm font-medium">Receive Lightning Payment</Label> <Label className="text-sm font-semibold">⚡ Receive via Lightning</Label>
<div className="flex flex-col xs:flex-row gap-2"> <div className="flex flex-col gap-2">
<Input <Input
type="number" type="number"
inputMode="numeric"
placeholder="Amount in sats" placeholder="Amount in sats"
value={quoteAmount} value={quoteAmount}
onChange={(e) => setQuoteAmount(e.target.value)} onChange={(e) => setQuoteAmount(e.target.value)}
className="min-w-0" className="h-11 min-w-0"
/> />
<Button <Button
onClick={handleLightningReceive} onClick={handleLightningReceive}
disabled={!activeMintUrl || !quoteAmount} disabled={!activeMintUrl || !quoteAmount}
className="bg-orange-500 hover:bg-orange-600 whitespace-nowrap xs:flex-shrink-0" className="h-11 bg-orange-500 hover:bg-orange-600 font-bold"
> >
Generate Invoice Generate Invoice
</Button> </Button>
@ -326,15 +332,15 @@ export function CashuWalletCard() {
{/* Cashu Token */} {/* Cashu Token */}
<div className="space-y-3"> <div className="space-y-3">
<div className="flex items-center justify-between"> <div className="flex items-center justify-between">
<Label className="text-sm font-medium">Receive Cashu Token</Label> <Label className="text-sm font-semibold">🥜 Receive Cashu Token</Label>
<Button <Button
variant="ghost" variant="outline"
size="sm" size="sm"
className="h-8 gap-1" className="h-9 gap-1.5 px-3"
onClick={() => setShowQRScanner(true)} onClick={() => setShowQRScanner(true)}
> >
<ScanLine className="h-4 w-4" /> <ScanLine className="h-4 w-4" />
Scan Scan QR
</Button> </Button>
</div> </div>
<div className="space-y-2"> <div className="space-y-2">
@ -342,12 +348,12 @@ export function CashuWalletCard() {
placeholder="Paste Cashu token (cashu...)" placeholder="Paste Cashu token (cashu...)"
value={tokenInput} value={tokenInput}
onChange={(e) => setTokenInput(e.target.value)} onChange={(e) => setTokenInput(e.target.value)}
className="font-mono text-xs" className="font-mono text-xs h-11"
/> />
<Button <Button
onClick={handleTokenReceive} onClick={handleTokenReceive}
disabled={!tokenInput} disabled={!tokenInput}
className="w-full bg-green-600 hover:bg-green-700" className="w-full h-11 bg-green-600 hover:bg-green-700 font-bold"
> >
Receive Token Receive Token
</Button> </Button>
@ -359,19 +365,20 @@ export function CashuWalletCard() {
<TabsContent value="send" className="space-y-4 mt-4"> <TabsContent value="send" className="space-y-4 mt-4">
{/* Send Token */} {/* Send Token */}
<div className="space-y-3"> <div className="space-y-3">
<Label className="text-sm font-medium">Create Cashu Token</Label> <Label className="text-sm font-semibold">🥜 Create Cashu Token</Label>
<div className="flex flex-col xs:flex-row gap-2"> <div className="flex flex-col gap-2">
<Input <Input
type="number" type="number"
inputMode="numeric"
placeholder="Amount in sats" placeholder="Amount in sats"
value={sendAmount} value={sendAmount}
onChange={(e) => setSendAmount(e.target.value)} onChange={(e) => setSendAmount(e.target.value)}
className="min-w-0" className="min-w-0 h-11"
/> />
<Button <Button
onClick={handleTokenSend} onClick={handleTokenSend}
disabled={!activeMintUrl || !sendAmount} disabled={!activeMintUrl || !sendAmount}
className="bg-orange-500 hover:bg-orange-600 whitespace-nowrap xs:flex-shrink-0" className="h-11 bg-orange-500 hover:bg-orange-600 font-bold"
> >
Create Token Create Token
</Button> </Button>
@ -390,15 +397,15 @@ export function CashuWalletCard() {
{/* Pay Lightning Invoice */} {/* Pay Lightning Invoice */}
<div className="space-y-3"> <div className="space-y-3">
<div className="flex items-center justify-between"> <div className="flex items-center justify-between">
<Label className="text-sm font-medium">Pay Lightning Invoice</Label> <Label className="text-sm font-semibold">⚡ Pay Lightning Invoice</Label>
<Button <Button
variant="ghost" variant="outline"
size="sm" size="sm"
className="h-8 gap-1" className="h-9 gap-1.5 px-3"
onClick={() => setShowQRScanner(true)} onClick={() => setShowQRScanner(true)}
> >
<ScanLine className="h-4 w-4" /> <ScanLine className="h-4 w-4" />
Scan Scan QR
</Button> </Button>
</div> </div>
<div className="space-y-2"> <div className="space-y-2">
@ -406,12 +413,12 @@ export function CashuWalletCard() {
placeholder="Paste Lightning invoice (lnbc...)" placeholder="Paste Lightning invoice (lnbc...)"
value={lightningInvoice} value={lightningInvoice}
onChange={(e) => setLightningInvoice(e.target.value)} onChange={(e) => setLightningInvoice(e.target.value)}
className="font-mono text-xs" className="font-mono text-xs h-11"
/> />
<Button <Button
onClick={handleLightningSend} onClick={handleLightningSend}
disabled={!lightningInvoice || isProcessingPayment} disabled={!lightningInvoice || isProcessingPayment}
className="w-full bg-blue-600 hover:bg-blue-700" className="w-full h-11 bg-blue-600 hover:bg-blue-700 font-bold"
> >
<Zap className="h-4 w-4 mr-2" /> <Zap className="h-4 w-4 mr-2" />
{isProcessingPayment ? 'Processing...' : 'Pay Invoice'} {isProcessingPayment ? 'Processing...' : 'Pay Invoice'}
@ -430,78 +437,71 @@ export function CashuWalletCard() {
{/* Pay Lightning Address */} {/* Pay Lightning Address */}
<div className="space-y-3"> <div className="space-y-3">
<Label className="text-sm font-medium">Pay Lightning Address</Label> <Label className="text-sm font-semibold">📧 Pay Lightning Address</Label>
<div className="space-y-2"> <div className="space-y-2">
<Input <Input
placeholder="user@domain.com" placeholder="user@domain.com"
value={lightningAddress} value={lightningAddress}
onChange={(e) => setLightningAddress(e.target.value)} onChange={(e) => setLightningAddress(e.target.value)}
className="h-11"
/> />
<div className="flex gap-2">
<Input <Input
type="number" type="number"
inputMode="numeric"
placeholder="Amount in sats" placeholder="Amount in sats"
value={sendAmount} value={sendAmount}
onChange={(e) => setSendAmount(e.target.value)} onChange={(e) => setSendAmount(e.target.value)}
className="flex-1" className="h-11"
/> />
<Button <Button
onClick={handlePayLightningAddress} onClick={handlePayLightningAddress}
disabled={!lightningAddress || !sendAmount || isResolvingAddress || isProcessingPayment} disabled={!lightningAddress || !sendAmount || isResolvingAddress || isProcessingPayment}
className="bg-purple-600 hover:bg-purple-700" className="w-full h-11 bg-purple-600 hover:bg-purple-700 font-bold"
> >
{isResolvingAddress ? 'Resolving...' : 'Send'} {isResolvingAddress ? 'Resolving...' : 'Send to Address'}
</Button> </Button>
</div> </div>
</div> </div>
</div>
</TabsContent> </TabsContent>
</Tabs> </Tabs>
{/* History */} {/* History */}
{history.length > 0 && ( {history.length > 0 && (
<div className="space-y-3"> <div className="space-y-3">
<div className="flex items-center justify-between"> <Label className="text-xs font-semibold text-muted-foreground uppercase tracking-wide flex items-center gap-2">
<Label className="text-sm font-medium flex items-center gap-2"> <History className="h-3.5 w-3.5" />
<History className="h-4 w-4" />
Recent Activity Recent Activity
</Label> </Label>
</div> <div className="space-y-2 max-h-52 overflow-y-auto scroll-container">
<div className="space-y-2 max-h-48 overflow-y-auto">
{history.slice(0, 5).map((entry) => ( {history.slice(0, 5).map((entry) => (
<div <div
key={entry.id} key={entry.id}
className="flex items-center justify-between p-2 rounded-lg bg-muted/50 border" className="flex items-center justify-between p-3 rounded-xl bg-muted/50 border min-h-[52px]"
> >
<div className="flex items-center gap-2"> <div className="flex items-center gap-2.5 min-w-0">
{entry.type === 'mint' || entry.type === 'receive' ? ( {entry.type === 'mint' || entry.type === 'receive' ? (
<ArrowDownCircle className="h-4 w-4 text-green-500" /> <ArrowDownCircle className="h-5 w-5 text-green-500 flex-shrink-0" />
) : ( ) : (
<ArrowUpCircle className="h-4 w-4 text-red-500" /> <ArrowUpCircle className="h-5 w-5 text-red-500 flex-shrink-0" />
)} )}
<div> <div className="min-w-0">
<div className="text-sm font-medium"> <div className="text-sm font-semibold leading-tight">
{entry.type === 'mint' && 'Received Lightning'} {entry.type === 'mint' && 'Received ⚡ Lightning'}
{entry.type === 'melt' && 'Sent Lightning'} {entry.type === 'melt' && 'Sent ⚡ Lightning'}
{entry.type === 'send' && 'Sent Token'} {entry.type === 'send' && 'Sent 🥜 Token'}
{entry.type === 'receive' && 'Received Token'} {entry.type === 'receive' && 'Received 🥜 Token'}
</div> </div>
<div className="text-xs text-muted-foreground"> <div className="text-xs text-muted-foreground">
{new Date(entry.timestamp).toLocaleDateString()} {new Date(entry.timestamp).toLocaleDateString()}
</div> </div>
</div> </div>
</div> </div>
<div className="text-right"> <div className="text-right flex-shrink-0 ml-2">
<div className="text-sm font-mono"> <div className={`text-sm font-mono font-bold ${entry.type === 'mint' || entry.type === 'receive' ? 'text-green-400' : 'text-red-400'}`}>
{entry.type === 'mint' || entry.type === 'receive' ? '+' : '-'} {entry.type === 'mint' || entry.type === 'receive' ? '+' : '-'}
{entry.amount.toLocaleString()} sats {entry.amount.toLocaleString()}
</div> </div>
<Badge <div className="text-xs text-muted-foreground">sats</div>
variant={entry.status === 'completed' ? 'default' : entry.status === 'pending' ? 'secondary' : 'destructive'}
className="text-[10px]"
>
{entry.status}
</Badge>
</div> </div>
</div> </div>
))} ))}
@ -512,34 +512,29 @@ export function CashuWalletCard() {
</Card> </Card>
{/* What is Cashu Info Card */} {/* What is Cashu Info Card */}
<Card> <Card className="border-gray-700">
<CardHeader> <CardHeader className="px-4 pt-4 pb-3">
<CardTitle className="flex items-center gap-2"> <CardTitle className="flex items-center gap-2 text-base">
<Zap className="h-5 w-5 text-amber-500" /> <Zap className="h-5 w-5 text-amber-500" />
What is Cashu? What is Cashu?
</CardTitle> </CardTitle>
</CardHeader> </CardHeader>
<CardContent className="space-y-3"> <CardContent className="px-4 pb-4 space-y-3">
<p className="text-muted-foreground text-sm"> <p className="text-muted-foreground text-sm leading-relaxed">
Cashu is digital cash backed by Bitcoin Lightning. Think of it as Bitcoin tokens you hold in your browser. Cashu is digital cash backed by Bitcoin Lightning. Think of it as Bitcoin tokens you hold in your browser.
</p> </p>
<div className="grid gap-2 text-sm"> <div className="grid grid-cols-1 sm:grid-cols-2 gap-2 text-sm">
<div className="flex items-start gap-2"> {[
<span className="text-green-500">✅</span> 'Works offline at skate spots',
<span className="text-muted-foreground">Works offline at skate spots</span> 'Instant peer-to-peer payments',
</div> "Privacy-focused (mints can't track you)",
<div className="flex items-start gap-2"> 'Always redeemable for Lightning sats',
<span className="text-green-500">✅</span> ].map((item) => (
<span className="text-muted-foreground">Instant peer-to-peer payments</span> <div key={item} className="flex items-start gap-2 p-2.5 bg-muted/40 rounded-lg">
</div> <span className="text-green-500 flex-shrink-0 mt-0.5">✅</span>
<div className="flex items-start gap-2"> <span className="text-muted-foreground text-xs leading-snug">{item}</span>
<span className="text-green-500">✅</span>
<span className="text-muted-foreground">Privacy-focused (mints don't track you)</span>
</div>
<div className="flex items-start gap-2">
<span className="text-green-500">✅</span>
<span className="text-muted-foreground">Always redeemable for Lightning sats</span>
</div> </div>
))}
</div> </div>
<a <a
href="https://cashumints.space/" href="https://cashumints.space/"
@ -567,28 +562,28 @@ export function CashuWalletCard() {
{/* Backup Warning */} {/* Backup Warning */}
{mnemonic && ( {mnemonic && (
<Card className="mt-6 border-amber-500/50 bg-amber-500/5"> <Card className="border-amber-500/50 bg-amber-500/5">
<CardHeader> <CardHeader className="px-4 pt-4 pb-3">
<CardTitle className="flex items-center gap-2 text-amber-600"> <CardTitle className="flex items-center gap-2 text-amber-600 text-base">
<Shield className="h-5 w-5" /> <Shield className="h-5 w-5" />
Backup Your Wallet Backup Your Wallet
</CardTitle> </CardTitle>
</CardHeader> </CardHeader>
<CardContent className="space-y-3"> <CardContent className="px-4 pb-4 space-y-3">
<p className="text-muted-foreground text-sm"> <p className="text-muted-foreground text-sm leading-relaxed">
Your wallet is secured with a 12-word recovery phrase. Save it safely - you'll need it to restore your balance if you clear browser data. Your wallet is secured with a 12-word recovery phrase. Save it safely — you'll need it to restore your balance if you clear browser data.
</p> </p>
<Button <Button
onClick={() => { onClick={() => {
alert(`Your backup phrase:\n\n${mnemonic}\n\nWrite these words down and store them safely!`); alert(`Your backup phrase:\n\n${mnemonic}\n\nWrite these words down and store them safely!`);
}} }}
variant="outline" variant="outline"
className="w-full border-amber-500/50 text-amber-600 hover:bg-amber-500/10" className="w-full h-11 border-amber-500/50 text-amber-600 hover:bg-amber-500/10 font-bold"
> >
View Recovery Phrase View Recovery Phrase
</Button> </Button>
<p className="text-xs text-muted-foreground italic"> <p className="text-xs text-muted-foreground italic">
This is like your Nostr nsec - keep it secret and backed up! This is like your Nostr nsec — keep it secret and backed up!
</p> </p>
</CardContent> </CardContent>
</Card> </Card>

View file

@ -413,11 +413,17 @@ html {
display: none; /* Chrome, Safari and Opera */ display: none; /* Chrome, Safari and Opera */
} }
/* Extra small breakpoint for very small phones */ /* Extra small breakpoint for very small phones (375px+) */
@media (min-width: 375px) { @media (min-width: 375px) {
.xs\:inline { .xs\:inline { display: inline; }
display: inline; .xs\:flex { display: flex; }
} .xs\:hidden { display: none; }
.xs\:flex-row { flex-direction: row; }
.xs\:px-4 { padding-left: 1rem; padding-right: 1rem; }
.xs\:-mx-4 { margin-left: -1rem; margin-right: -1rem; }
.xs\:text-xs { font-size: 0.75rem; line-height: 1rem; }
.xs\:text-sm { font-size: 0.875rem; line-height: 1.25rem; }
.xs\:gap-2 { gap: 0.5rem; }
} }
/* ─── Global UI/UX Polish ────────────────────────────────────────────────── */ /* ─── Global UI/UX Polish ────────────────────────────────────────────────── */

View file

@ -57,17 +57,17 @@ export default function FAQPage() {
return ( return (
<Layout> <Layout>
<div className="container px-4 py-12 max-w-3xl"> <div className="container px-3 xs:px-4 py-6 sm:py-10 max-w-3xl mx-auto">
{/* Hero */} {/* Hero */}
<div className="text-center mb-10"> <div className="text-center mb-7">
<div className="inline-flex items-center justify-center w-24 h-24 rounded-3xl bg-gradient-to-br from-[#ff0040] via-[#ff6600] to-[#ccff00] mb-5 shadow-2xl shadow-[#ff0040]/40"> <div className="inline-flex items-center justify-center w-16 h-16 sm:w-20 sm:h-20 rounded-2xl sm:rounded-3xl bg-gradient-to-br from-[#ff0040] via-[#ff6600] to-[#ccff00] mb-4 shadow-2xl shadow-[#ff0040]/40">
<span className="text-5xl">🦧</span> <span className="text-3xl sm:text-4xl">🦧</span>
</div> </div>
<h1 className="text-4xl md:text-5xl font-black uppercase mb-3 tracking-tight"> <h1 className="text-3xl sm:text-4xl md:text-5xl font-black uppercase mb-2 tracking-tight">
<span className="text-white">Got Questions?</span> <span className="text-white">Got Questions?</span>
</h1> </h1>
<p className="text-xl text-gray-400 max-w-lg mx-auto leading-relaxed"> <p className="text-base sm:text-xl text-gray-400 max-w-lg mx-auto leading-relaxed">
Ask The Sasquatch — he knows everything about SkateSpots, Nostr, Bitcoin, and skateboarding. Ask The Sasquatch — he knows everything about SkateSpots, Nostr, Bitcoin, and skateboarding.
</p> </p>
</div> </div>

View file

@ -503,8 +503,8 @@ function WalletTab() {
</CardDescription> </CardDescription>
</CardHeader> </CardHeader>
<CardContent className="space-y-4"> <CardContent className="space-y-4">
<a href="/wallet"> <a href="/wallet" className="block sm:inline-block">
<Button className="gap-2 bg-orange-500 hover:bg-orange-600 w-full sm:w-auto"> <Button className="gap-2 bg-orange-500 hover:bg-orange-600 w-full sm:w-auto h-11 font-bold">
<Wallet className="h-4 w-4" /> <Wallet className="h-4 w-4" />
Go to Wallet Go to Wallet
</Button> </Button>
@ -545,7 +545,7 @@ function WalletTab() {
</CardHeader> </CardHeader>
<CardContent className="space-y-4"> <CardContent className="space-y-4">
<WalletModal> <WalletModal>
<Button variant="outline" className="gap-2 border-purple-500/50 text-purple-400 hover:bg-purple-500/10 w-full sm:w-auto"> <Button variant="outline" className="gap-2 border-purple-500/50 text-purple-400 hover:bg-purple-500/10 w-full h-11 font-bold">
<ExternalLink className="h-4 w-4" /> <ExternalLink className="h-4 w-4" />
Connect with NWC Connect with NWC
</Button> </Button>
@ -624,49 +624,51 @@ export default function SettingsPage() {
return ( return (
<Layout> <Layout>
<div className="container px-4 sm:px-6 py-6 sm:py-8"> <div className="container px-3 xs:px-4 sm:px-6 py-4 sm:py-8 max-w-3xl mx-auto">
<div className="mb-8"> <div className="mb-5">
<h1 className="text-3xl font-bold flex items-center gap-3"> <h1 className="text-2xl sm:text-3xl font-bold flex items-center gap-3">
<Settings className="h-8 w-8" /> <Settings className="h-6 w-6 sm:h-8 sm:w-8" />
Settings Settings
</h1> </h1>
<p className="text-muted-foreground mt-1"> <p className="text-muted-foreground text-sm mt-1">
Manage your account and app preferences Manage your account and app preferences
</p> </p>
</div> </div>
<Tabs defaultValue={initialTab} className="space-y-6"> <Tabs defaultValue={initialTab} className="space-y-5">
{/* Grid tabs for mobile, horizontal for larger screens */} {/* Horizontally scrollable tab bar on mobile — no wrapping */}
<TabsList className="grid grid-cols-4 sm:grid-cols-7 gap-1 p-1 h-auto"> <div className="overflow-x-auto scrollbar-hide -mx-3 xs:-mx-4 sm:mx-0 px-3 xs:px-4 sm:px-0">
<TabsTrigger value="profile" className="flex-col sm:flex-row gap-1 sm:gap-1.5 px-2 py-2 sm:py-1.5 h-auto"> <TabsList className="inline-flex w-auto min-w-full sm:w-full sm:grid sm:grid-cols-7 gap-0.5 p-1 h-auto rounded-xl">
<User className="h-4 w-4" /> <TabsTrigger value="profile" className="flex-col gap-1 px-3 py-2.5 h-auto min-w-[64px] sm:min-w-0 rounded-lg data-[state=active]:bg-background">
<span className="text-[10px] sm:text-sm">Profile</span> <User className="h-4 w-4 flex-shrink-0" />
<span className="text-[10px] font-semibold whitespace-nowrap">Profile</span>
</TabsTrigger> </TabsTrigger>
<TabsTrigger value="mydata" className="flex-col sm:flex-row gap-1 sm:gap-1.5 px-2 py-2 sm:py-1.5 h-auto"> <TabsTrigger value="mydata" className="flex-col gap-1 px-3 py-2.5 h-auto min-w-[64px] sm:min-w-0 rounded-lg data-[state=active]:bg-background">
<Database className="h-4 w-4" /> <Database className="h-4 w-4 flex-shrink-0" />
<span className="text-[10px] sm:text-sm">My Data</span> <span className="text-[10px] font-semibold whitespace-nowrap">My Data</span>
</TabsTrigger> </TabsTrigger>
<TabsTrigger value="security" className="flex-col sm:flex-row gap-1 sm:gap-1.5 px-2 py-2 sm:py-1.5 h-auto"> <TabsTrigger value="security" className="flex-col gap-1 px-3 py-2.5 h-auto min-w-[64px] sm:min-w-0 rounded-lg data-[state=active]:bg-background">
<Shield className="h-4 w-4" /> <Shield className="h-4 w-4 flex-shrink-0" />
<span className="text-[10px] sm:text-sm">Security</span> <span className="text-[10px] font-semibold whitespace-nowrap">Security</span>
</TabsTrigger> </TabsTrigger>
<TabsTrigger value="appearance" className="flex-col sm:flex-row gap-1 sm:gap-1.5 px-2 py-2 sm:py-1.5 h-auto"> <TabsTrigger value="appearance" className="flex-col gap-1 px-3 py-2.5 h-auto min-w-[64px] sm:min-w-0 rounded-lg data-[state=active]:bg-background">
<Palette className="h-4 w-4" /> <Palette className="h-4 w-4 flex-shrink-0" />
<span className="text-[10px] sm:text-sm">Theme</span> <span className="text-[10px] font-semibold whitespace-nowrap">Theme</span>
</TabsTrigger> </TabsTrigger>
<TabsTrigger value="relays" className="flex-col sm:flex-row gap-1 sm:gap-1.5 px-2 py-2 sm:py-1.5 h-auto"> <TabsTrigger value="relays" className="flex-col gap-1 px-3 py-2.5 h-auto min-w-[64px] sm:min-w-0 rounded-lg data-[state=active]:bg-background">
<Radio className="h-4 w-4" /> <Radio className="h-4 w-4 flex-shrink-0" />
<span className="text-[10px] sm:text-sm">Relays</span> <span className="text-[10px] font-semibold whitespace-nowrap">Relays</span>
</TabsTrigger> </TabsTrigger>
<TabsTrigger value="wallet" className="flex-col sm:flex-row gap-1 sm:gap-1.5 px-2 py-2 sm:py-1.5 h-auto"> <TabsTrigger value="wallet" className="flex-col gap-1 px-3 py-2.5 h-auto min-w-[64px] sm:min-w-0 rounded-lg data-[state=active]:bg-background">
<Wallet className="h-4 w-4" /> <Wallet className="h-4 w-4 flex-shrink-0" />
<span className="text-[10px] sm:text-sm">Wallet</span> <span className="text-[10px] font-semibold whitespace-nowrap">Wallet</span>
</TabsTrigger> </TabsTrigger>
<TabsTrigger value="advanced" className="flex-col sm:flex-row gap-1 sm:gap-1.5 px-2 py-2 sm:py-1.5 h-auto"> <TabsTrigger value="advanced" className="flex-col gap-1 px-3 py-2.5 h-auto min-w-[64px] sm:min-w-0 rounded-lg data-[state=active]:bg-background">
<Wrench className="h-4 w-4" /> <Wrench className="h-4 w-4 flex-shrink-0" />
<span className="text-[10px] sm:text-sm">Advanced</span> <span className="text-[10px] font-semibold whitespace-nowrap">Advanced</span>
</TabsTrigger> </TabsTrigger>
</TabsList> </TabsList>
</div>
<TabsContent value="profile"> <TabsContent value="profile">
<ProfileTab /> <ProfileTab />

View file

@ -25,8 +25,12 @@ import {
WalletMinimal, WalletMinimal,
Info, Info,
Nut, Nut,
ChevronRight,
Bitcoin,
} from 'lucide-react'; } from 'lucide-react';
// ─── Wallet Status Card ───────────────────────────────────────────────────────
function WalletStatusCard() { function WalletStatusCard() {
const { webln } = useWallet(); const { webln } = useWallet();
const { connections } = useNWC(); const { connections } = useNWC();
@ -36,82 +40,85 @@ function WalletStatusCard() {
const isReady = webln || hasNWC || hasCashu; const isReady = webln || hasNWC || hasCashu;
return ( return (
<Card className={isReady ? 'border-primary/30' : ''}> <Card className={isReady ? 'border-green-500/30 bg-green-950/10' : 'border-gray-700'}>
<CardHeader className="pb-3"> <CardHeader className="pb-3 px-4 pt-4">
<div className="flex items-center justify-between"> <div className="flex items-center justify-between">
<CardTitle className="flex items-center gap-2"> <CardTitle className="flex items-center gap-2 text-base">
<Wallet className="h-5 w-5 text-primary" /> <Wallet className="h-5 w-5 text-primary flex-shrink-0" />
Wallet Status Wallet Status
</CardTitle> </CardTitle>
<Badge variant={isReady ? 'default' : 'secondary'}> <Badge
{isReady ? 'Ready' : 'Not Connected'} 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> </Badge>
</div> </div>
<CardDescription> <CardDescription className="text-xs mt-1">
{isReady {isReady
? 'Your wallet is connected and ready to send zaps!' ? 'Your wallet is connected and ready to send zaps!'
: 'Connect a wallet to enable Lightning zaps'} : 'Connect a wallet to enable Lightning zaps'}
</CardDescription> </CardDescription>
</CardHeader> </CardHeader>
<CardContent className="space-y-4"> <CardContent className="px-4 pb-4 space-y-2">
{/* Cashu Wallet Status */} {/* Cashu */}
<div className="flex items-center justify-between p-3 bg-muted/50 rounded-lg"> <div className="flex items-center justify-between p-3 bg-muted/40 rounded-xl min-h-[56px]">
<div className="flex items-center gap-3"> <div className="flex items-center gap-3 min-w-0">
<Nut className="h-5 w-5 text-orange-500" /> <div className="w-9 h-9 rounded-lg bg-orange-500/15 flex items-center justify-center flex-shrink-0">
<div> <Nut className="h-4 w-4 text-orange-500" />
<p className="font-medium">Cashu Wallet</p> </div>
<p className="text-sm text-muted-foreground"> <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 {hasCashu && cashuBalance > 0
? `${cashuBalance.toLocaleString()} sats${cashuUsd ? ` (~$${cashuUsd.toFixed(2)})` : ''}` ? `${cashuBalance.toLocaleString()} sats${cashuUsd ? ` · $${cashuUsd.toFixed(2)}` : ''}`
: 'Built-in ecash wallet'} : 'Built-in ecash wallet'}
</p> </p>
</div> </div>
</div> </div>
{hasCashu ? ( {hasCashu
<CheckCircle className="h-5 w-5 text-green-500" /> ? <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" />}
<XCircle className="h-5 w-5 text-muted-foreground" />
)}
</div> </div>
{/* WebLN Status */} {/* WebLN */}
<div className="flex items-center justify-between p-3 bg-muted/50 rounded-lg"> <div className="flex items-center justify-between p-3 bg-muted/40 rounded-xl min-h-[56px]">
<div className="flex items-center gap-3"> <div className="flex items-center gap-3 min-w-0">
<Globe className="h-5 w-5 text-muted-foreground" /> <div className="w-9 h-9 rounded-lg bg-blue-500/15 flex items-center justify-center flex-shrink-0">
<div> <Globe className="h-4 w-4 text-blue-400" />
<p className="font-medium">WebLN</p> </div>
<p className="text-sm text-muted-foreground">Browser extension wallet</p> <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>
</div> </div>
{webln ? ( {webln
<CheckCircle className="h-5 w-5 text-green-500" /> ? <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" />}
<XCircle className="h-5 w-5 text-muted-foreground" />
)}
</div> </div>
{/* NWC Status */} {/* NWC */}
<div className="flex items-center justify-between p-3 bg-muted/50 rounded-lg"> <div className="flex items-center justify-between p-3 bg-muted/40 rounded-xl min-h-[56px]">
<div className="flex items-center gap-3"> <div className="flex items-center gap-3 min-w-0">
<WalletMinimal className="h-5 w-5 text-muted-foreground" /> <div className="w-9 h-9 rounded-lg bg-purple-500/15 flex items-center justify-center flex-shrink-0">
<div> <WalletMinimal className="h-4 w-4 text-purple-400" />
<p className="font-medium">Nostr Wallet Connect</p> </div>
<p className="text-sm text-muted-foreground"> <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 > 0
? `${connections.length} wallet${connections.length !== 1 ? 's' : ''} connected` ? `${connections.length} wallet${connections.length !== 1 ? 's' : ''} connected`
: 'Remote wallet connection'} : 'Remote wallet connection'}
</p> </p>
</div> </div>
</div> </div>
{hasNWC ? ( {hasNWC
<CheckCircle className="h-5 w-5 text-green-500" /> ? <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" />}
<XCircle className="h-5 w-5 text-muted-foreground" />
)}
</div> </div>
<WalletModal> <WalletModal>
<Button className="w-full gap-2"> <Button className="w-full gap-2 mt-1 h-12 rounded-xl font-bold">
<Settings className="h-4 w-4" /> <Settings className="h-4 w-4" />
Manage Wallets Manage Wallets
</Button> </Button>
@ -121,162 +128,112 @@ function WalletStatusCard() {
); );
} }
// ─── Zaps Info Card ───────────────────────────────────────────────────────────
function ZapsInfoCard() { 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 ( return (
<Card> <Card className="border-gray-700">
<CardHeader> <CardHeader className="px-4 pt-4 pb-3">
<CardTitle className="flex items-center gap-2"> <CardTitle className="flex items-center gap-2 text-base">
<Zap className="h-5 w-5 text-amber-500" /> <Zap className="h-5 w-5 text-amber-500" />
What are Zaps? What are Zaps?
</CardTitle> </CardTitle>
</CardHeader> </CardHeader>
<CardContent className="space-y-4"> <CardContent className="px-4 pb-4 space-y-2">
<p className="text-muted-foreground"> <p className="text-sm text-muted-foreground leading-relaxed">
Zaps are Lightning Network micropayments on Nostr. They allow you to send Zaps are Lightning micropayments — send sats instantly to any skater, spot creator, or coach. No middleman, no platform cut.
satoshis (sats) instantly to other users as a way of tipping, thanking, or
supporting their content.
</p> </p>
<div className="grid grid-cols-1 sm:grid-cols-2 gap-2 pt-1">
<div className="space-y-3"> {items.map((item) => (
<div className="flex items-start gap-3 p-3 bg-muted/50 rounded-lg"> <div key={item.title} className="flex items-start gap-3 p-3 bg-muted/40 rounded-xl">
<div className="w-8 h-8 rounded-full bg-primary/10 flex items-center justify-center flex-shrink-0"> <span className="text-xl flex-shrink-0 mt-0.5">{item.emoji}</span>
<span className="text-lg">⚡</span>
</div>
<div> <div>
<p className="font-medium">Instant</p> <p className="font-semibold text-sm">{item.title}</p>
<p className="text-sm text-muted-foreground"> <p className="text-xs text-muted-foreground leading-snug">{item.desc}</p>
Payments settle in seconds over the Lightning Network
</p>
</div>
</div>
<div className="flex items-start gap-3 p-3 bg-muted/50 rounded-lg">
<div className="w-8 h-8 rounded-full bg-primary/10 flex items-center justify-center flex-shrink-0">
<span className="text-lg">🔐</span>
</div>
<div>
<p className="font-medium">Flexible custody</p>
<p className="text-sm text-muted-foreground">
Use your own wallet (self-custodial) or a custodial service - your choice
</p>
</div>
</div>
<div className="flex items-start gap-3 p-3 bg-muted/50 rounded-lg">
<div className="w-8 h-8 rounded-full bg-orange-500/10 flex items-center justify-center flex-shrink-0">
<span className="text-lg">🥜</span>
</div>
<div>
<p className="font-medium">NutZaps for privacy</p>
<p className="text-sm text-muted-foreground">
Use Cashu ecash for private zaps - mints can't see who pays whom
</p>
</div>
</div>
<div className="flex items-start gap-3 p-3 bg-muted/50 rounded-lg">
<div className="w-8 h-8 rounded-full bg-primary/10 flex items-center justify-center flex-shrink-0">
<span className="text-lg">💜</span>
</div>
<div>
<p className="font-medium">Value for value</p>
<p className="text-sm text-muted-foreground">
Support creators directly for content you appreciate
</p>
</div> </div>
</div> </div>
))}
</div> </div>
</CardContent> </CardContent>
</Card> </Card>
); );
} }
// ─── Recommended Wallets ──────────────────────────────────────────────────────
function WalletResourcesCard() { function WalletResourcesCard() {
const walletResources = [ const wallets = [
{ { name: 'Alby', desc: 'Browser extension · WebLN + NWC', url: 'https://getalby.com', type: 'WebLN', color: 'text-yellow-400' },
name: 'Alby', { name: 'Minibits', desc: 'Mobile Cashu wallet · NWC', url: 'https://www.minibits.cash', type: 'NWC', color: 'text-orange-400' },
description: 'Browser extension wallet with WebLN + NWC support', { name: 'Zeus', desc: 'Self-custodial mobile Lightning', url: 'https://zeusln.com', type: 'NWC', color: 'text-blue-400' },
url: 'https://getalby.com', { name: 'Coinos', desc: 'Web-based · works everywhere', url: 'https://coinos.io', type: 'NWC', color: 'text-green-400' },
type: 'WebLN',
},
{
name: 'Minibits',
description: 'Mobile Cashu wallet with NWC support',
url: 'https://www.minibits.cash',
type: 'NWC',
},
{
name: 'Zeus',
description: 'Self-custodial mobile Lightning wallet',
url: 'https://zeusln.com',
type: 'NWC',
},
{
name: 'Coinos',
description: 'Web-based Lightning wallet, works on any device',
url: 'https://coinos.io',
type: 'NWC',
},
]; ];
return ( return (
<Card> <Card className="border-gray-700">
<CardHeader> <CardHeader className="px-4 pt-4 pb-3">
<CardTitle className="flex items-center gap-2"> <CardTitle className="flex items-center gap-2 text-base">
<Info className="h-5 w-5" /> <Info className="h-5 w-5" />
Recommended Wallets Recommended Wallets
</CardTitle> </CardTitle>
<CardDescription> <CardDescription className="text-xs">Get started with one of these Lightning wallets</CardDescription>
Get started with one of these Lightning wallets
</CardDescription>
</CardHeader> </CardHeader>
<CardContent className="space-y-4"> <CardContent className="px-4 pb-4 space-y-2">
<div className="grid gap-3"> {wallets.map((w) => (
{walletResources.map((wallet) => (
<a <a
key={wallet.name} key={w.name}
href={wallet.url} href={w.url}
target="_blank" target="_blank"
rel="noopener noreferrer" rel="noopener noreferrer"
className="flex items-center justify-between p-3 bg-muted/50 rounded-lg hover:bg-muted transition-colors group" 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> <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"> <div className="flex items-center gap-2">
<p className="font-medium group-hover:text-primary transition-colors"> <span className="font-semibold text-sm group-hover:text-primary transition-colors">{w.name}</span>
{wallet.name} <Badge variant="outline" className="text-[10px] px-1.5 py-0 h-4">{w.type}</Badge>
</p>
<Badge variant="outline" className="text-xs">
{wallet.type}
</Badge>
</div> </div>
<p className="text-sm text-muted-foreground">{wallet.description}</p> <p className="text-xs text-muted-foreground truncate">{w.desc}</p>
</div> </div>
<ExternalLink className="h-4 w-4 text-muted-foreground group-hover:text-primary transition-colors" /> </div>
<ChevronRight className="h-4 w-4 text-muted-foreground group-hover:text-primary flex-shrink-0 transition-colors" />
</a> </a>
))} ))}
</div>
<a <a
href="https://planb.academy/en/tutorials/wallet" href="https://planb.academy/en/tutorials/wallet"
target="_blank" target="_blank"
rel="noopener noreferrer" rel="noopener noreferrer"
className="flex items-center gap-2 text-sm text-orange-400 hover:text-orange-300 transition-colors" className="flex items-center gap-2 text-xs text-orange-400 hover:text-orange-300 transition-colors pt-1"
> >
<ExternalLink className="h-4 w-4" /> <ExternalLink className="h-3.5 w-3.5 flex-shrink-0" />
<span>Want to learn more? Check out Plan B Academy's wallet guides</span> Learn more at Plan B Academy
</a> </a>
</CardContent> </CardContent>
</Card> </Card>
); );
} }
// ─── Not Logged In ────────────────────────────────────────────────────────────
function NotLoggedInCard() { function NotLoggedInCard() {
return ( return (
<Card className="border-dashed"> <Card className="border-dashed border-gray-700">
<CardContent className="py-12 text-center"> <CardContent className="py-12 px-6 text-center">
<Wallet className="h-12 w-12 mx-auto mb-4 text-muted-foreground/50" /> <div className="w-16 h-16 rounded-2xl bg-[#ff0040]/10 flex items-center justify-center mx-auto mb-4">
<h3 className="text-lg font-medium mb-2">Login Required</h3> <Wallet className="h-8 w-8 text-[#ff0040]/60" />
<p className="text-muted-foreground max-w-md mx-auto mb-6"> </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. Log in with your Nostr account to connect your Lightning wallet and start zapping.
</p> </p>
<LoginArea className="justify-center" /> <LoginArea className="justify-center" />
@ -285,6 +242,75 @@ function NotLoggedInCard() {
); );
} }
// ─── Balance Hero Banner ──────────────────────────────────────────────────────
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);
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 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>
<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.toLocaleString('en-US', { minimumFractionDigits: 2, maximumFractionDigits: 2 })}
</div>
)}
{/* 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 */}
<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>
</WalletModal>
</div>
</div>
);
}
// ─── Page ─────────────────────────────────────────────────────────────────────
export default function WalletPage() { export default function WalletPage() {
useSeoMeta({ useSeoMeta({
title: 'Wallet - SkateSpots', title: 'Wallet - SkateSpots',
@ -295,59 +321,64 @@ export default function WalletPage() {
return ( return (
<Layout> <Layout>
<div className="container px-4 py-6 md:py-8"> <div className="container px-3 xs:px-4 py-4 md:py-8 max-w-2xl mx-auto">
{/* Header */} {/* Header */}
<div className="mb-6 md:mb-8"> <div className="mb-4">
<h1 className="text-2xl md:text-3xl font-bold">Wallet</h1> <h1 className="text-2xl font-black uppercase tracking-wide flex items-center gap-2">
<p className="text-muted-foreground mt-1 text-sm md:text-base"> <Wallet className="h-6 w-6 text-orange-400" />
Manage your Bitcoin and send zaps to skaters and creators Wallet
</h1>
<p className="text-muted-foreground text-sm mt-0.5">
Bitcoin Lightning payments &amp; zaps
</p> </p>
</div> </div>
{user ? ( {user ? (
<div className="space-y-4 md:space-y-6"> <div className="space-y-4">
{/* Wallet Comparison Guide */} {/* Balance hero — always at top for logged-in users */}
<BalanceHero />
{/* Quick Guide — collapsed by default */}
<WalletComparisonGuide /> <WalletComparisonGuide />
<Tabs defaultValue="cashu" className="w-full"> <Tabs defaultValue="cashu" className="w-full">
<TabsList className="grid w-full grid-cols-2 mb-4 md:mb-6 h-auto"> {/* Tab pills — full width, thumb-friendly height */}
<TabsTrigger value="cashu" className="gap-1.5 py-2.5 text-sm"> <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" /> <Nut className="h-4 w-4 flex-shrink-0" />
<span>Cashu Wallet</span> <span>Cashu</span>
</TabsTrigger> </TabsTrigger>
<TabsTrigger value="lightning" className="gap-1.5 py-2.5 text-sm"> <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" /> <Zap className="h-4 w-4 flex-shrink-0" />
<span>Lightning</span> <span>Lightning</span>
</TabsTrigger> </TabsTrigger>
</TabsList> </TabsList>
<TabsContent value="cashu" className="space-y-6"> {/* Cashu tab */}
<TabsContent value="cashu" className="space-y-4 mt-0">
<CashuWalletCard /> <CashuWalletCard />
</TabsContent> </TabsContent>
<TabsContent value="lightning" className="space-y-6"> {/* Lightning tab */}
{/* Privacy Tips at the top */} <TabsContent value="lightning" className="space-y-4 mt-0">
<LightningPrivacyTips /> <LightningPrivacyTips />
<div className="grid gap-6 lg:grid-cols-2">
<div className="space-y-6">
<WalletStatusCard /> <WalletStatusCard />
<WalletResourcesCard /> <WalletResourcesCard />
</div>
<div>
<ZapsInfoCard /> <ZapsInfoCard />
</div>
</div>
</TabsContent> </TabsContent>
</Tabs> </Tabs>
</div> </div>
) : ( ) : (
<div className="max-w-xl mx-auto"> <div className="space-y-4">
<NotLoggedInCard /> <NotLoggedInCard />
<div className="mt-6">
<ZapsInfoCard /> <ZapsInfoCard />
</div> </div>
</div>
)} )}
</div> </div>
</Layout> </Layout>