SkateSpots/src/pages/OnboardingPage.tsx
SkateSpots 9189fc6584 fix(trust): tier 2 — moderation, labels & data integrity (H2–H17)
- H2 useAdminModeration now fetches the 'cleared' label too, so an admin Undo
  actually propagates (newest-verdict-wins) instead of only working on-device.
- H3 admin takedowns publish the NIP-32 'hidden' label (useAdminHide) instead of
  an author-only kind-5 relays ignore; the cross-app hide is real now.
- H5 the /stats outage banner works: queryCanonical reports relay failures via
  onError, so 'partial' can be true and zeros are never presented as truth.
- H6 useShops got its own cache namespace (was colliding with useSkateShops).
- H7 listing NIP-05 is verified against the user's key, not merely self-asserted.
- H8 NIP-22 spot comments/transfer now use uppercase root tags (A/K/P); readers
  accept legacy #a; NIP.md corrected.
- H9 Lab profile edits preserve foreign  spot refs (crew link = 35006 only).
- H10 collection edits preserve foreign NIP-51 tags.
- H11 LAB_KINDS = all 13 provider kinds (was 35001-35005).
- H16 skip-to-content is a button that focuses <main> (was href=#main, which
  HashRouter treated as a route → 404).
- H17 CustomizeNavSheet is translated; useLocalStorage re-reads on key change and
  keeps stateRef in sync cross-tab.
- Backup 'confirmed' flag is now per-account (backup-confirmed:<pubkey>).
2026-10-02 21:30:01 +07:00

642 lines
34 KiB
TypeScript

import { useState } from 'react';
import { useSeoMeta } from '@unhead/react';
import { Link } from 'react-router-dom';
import { Button } from '@/components/ui/button';
import { Dialog, DialogContent, DialogTitle } from '@/components/ui/dialog';
import { useCurrentUser } from '@/hooks/useCurrentUser';
import { useCashu } from '@/hooks/wallet/useCashu';
import { useLocalStorage } from '@/hooks/useLocalStorage';
import { backupConfirmedKey } from '@/lib/backup';
import { getCurrentPosition } from '@/lib/geolocation';
import { getLastLocation } from '@/lib/lastLocation';
import { LoginArea } from '@/components/auth/LoginArea';
import { WalletModal } from '@/components/WalletModal';
import { RelayEducation } from '@/components/onboarding/RelayEducation';
import { useToast } from '@/hooks/useToast';
import { cn } from '@/lib/utils';
import { CheckCircle2, Globe, Shield, Zap, MapPin, Calendar, GraduationCap, AlertCircle, ExternalLink, Wallet, ChevronDown, Nut, BookOpen, ShoppingBag, Package, Key, MessageCircle, Lock, Users, Download } from 'lucide-react';
import { FounderFollow } from '@/components/FounderFollow';
import { useTranslation } from '@/contexts/language';
// ─── Accordion FAQ ────────────────────────────────────────────────────────────
function FAQ({ q, a }: { q: string; a: React.ReactNode }) {
const [open, setOpen] = useState(false);
return (
<button
onClick={() => setOpen(o => !o)}
className="w-full text-left group"
>
<div className={cn(
'rounded-2xl border transition-all duration-200',
open
? 'bg-white/6 border-white/15'
: 'bg-white/3 border-white/8 hover:bg-white/5 hover:border-white/12'
)}>
<div className="flex items-center justify-between p-4 gap-3">
<span className="font-bold text-sm text-white leading-snug">{q}</span>
<ChevronDown className={cn(
'h-4 w-4 text-gray-500 flex-shrink-0 transition-transform duration-200',
open && 'rotate-180 text-[#ff0040]'
)} />
</div>
{open && (
<div className="px-4 pb-4 text-sm text-gray-400 leading-relaxed border-t border-white/6 pt-3">
{a}
</div>
)}
</div>
</button>
);
}
// ─── Collapsible section ──────────────────────────────────────────────────────
// Keeps heavier, optional content (Nostr explainer, FAQ) tucked away by default
// so the page feels light on arrival — tap to expand. Reduces first-glance
// cognitive load without removing anything.
function CollapsibleSection({
title,
icon,
defaultOpen = false,
children,
}: {
title: string;
icon?: React.ReactNode;
defaultOpen?: boolean;
children: React.ReactNode;
}) {
const [open, setOpen] = useState(defaultOpen);
return (
<div>
<button
onClick={() => setOpen(o => !o)}
aria-expanded={open}
className="w-full flex items-center gap-2 mb-3 group"
>
{icon}
<h2 className="text-xs font-black uppercase tracking-widest text-gray-500 group-hover:text-gray-300 transition-colors">
{title}
</h2>
<ChevronDown className={cn(
'h-3.5 w-3.5 text-gray-600 ml-auto transition-transform duration-200',
open && 'rotate-180 text-[#ff0040]'
)} />
</button>
{open && (
<div className="animate-in fade-in slide-in-from-top-1 duration-200 motion-reduce:animate-none">
{children}
</div>
)}
</div>
);
}
// ─── Feature pill ─────────────────────────────────────────────────────────────
function FeaturePill({ emoji, label }: { emoji: string; label: string }) {
return (
<div className="flex items-center gap-2 px-3 py-2 rounded-full bg-white/5 border border-white/8 text-sm text-gray-300">
<span>{emoji}</span>
<span className="font-medium">{label}</span>
</div>
);
}
export default function OnboardingPage() {
const { t } = useTranslation();
useSeoMeta({
title: t('settingsUi.onboarding.seoTitle'),
description: t('settingsUi.onboarding.seoDescription'),
});
const { user } = useCurrentUser();
const { isInitialized: hasCashu, totalBalance: cashuBalance } = useCashu();
const { toast } = useToast();
const [showRelayEducation, setShowRelayEducation] = useState(false);
const [, setHasSeenRelayEducation] = useLocalStorage('seen-relay-education', false);
// Shared with BackupReminder — the single source of truth for "has the user
// saved their login/key?". The banner below stays until this is true, so
// "remind me later" can never silently become "never" for this critical step.
const [backupConfirmed] = useLocalStorage(backupConfirmedKey(user?.pubkey), false);
// "Where do you skate?" — the single most useful first step. We reuse the
// same last-location cache MapPage reads on mount, so setting it here means
// the map opens on the skater's own area instead of the whole world, and no
// permission prompt surprises them at map load. `hasHome` just hides the
// card once a location exists.
const [locating, setLocating] = useState(false);
const [hasHome, setHasHome] = useState(() => !!getLastLocation());
const handleUseMyLocation = async () => {
setLocating(true);
try {
// getCurrentPosition() also persists the fix via saveLastLocation().
await getCurrentPosition();
setHasHome(true);
toast({ title: t('settingsUi.onboarding.homeSetTitle'), description: t('settingsUi.onboarding.homeSetDesc') });
} catch {
toast({
title: t('settingsUi.onboarding.locationFailedTitle'),
description: t('settingsUi.onboarding.locationFailedDesc'),
variant: 'destructive',
});
} finally {
setLocating(false);
}
};
const handleRelayEducationComplete = () => {
setShowRelayEducation(false);
setHasSeenRelayEducation(true);
toast({
title: t('settingsUi.onboarding.niceTitle'),
description: t('settingsUi.onboarding.niceDesc'),
});
};
const copyNpub = () => {
navigator.clipboard.writeText('npub1yrnuj56rnen08zp2h9h7p74ghgjx6ma39spmpj6w9hzxywutevsst7k5cx');
toast({
title: t('common.copied'),
description: t('settingsUi.onboarding.npubCopied'),
});
};
const bugReportUrl = `/messages/npub1yrnuj56rnen08zp2h9h7p74ghgjx6ma39spmpj6w9hzxywutevsst7k5cx`;
return (
<>
<div className="container px-4 py-10 max-w-3xl">
{/* ── Save your login (critical) — pinned ABOVE the hero right after
signup so a new skater sees it immediately, not below the fold.
Stays until the login is actually saved (backup-confirmed). ── */}
{user && !backupConfirmed && (
<Link to="/settings?section=security" className="block mb-6 group">
<div className="flex items-center gap-4 p-4 rounded-2xl bg-amber-500/15 border-2 border-amber-500/50 hover:bg-amber-500/20 hover:border-amber-500/70 transition-all shadow-[0_0_24px_rgba(245,158,11,0.15)]">
<div className="relative flex-shrink-0">
<div className="w-10 h-10 rounded-full bg-amber-500/25 flex items-center justify-center">
<Key className="h-5 w-5 text-amber-400" />
</div>
<span className="absolute -top-0.5 -right-0.5 w-3 h-3 rounded-full bg-amber-500 border-2 border-black animate-pulse motion-reduce:animate-none" />
</div>
<div className="flex-1 min-w-0">
<p className="font-black text-amber-300 text-sm uppercase tracking-wide">{t('settingsUi.onboarding.saveLoginNow')}</p>
<p className="text-xs text-amber-100/80 leading-snug mt-0.5">
{t('settingsUi.onboarding.saveLoginNowDesc')}
</p>
</div>
<div className="flex-shrink-0 flex items-center gap-1.5 rounded-full bg-amber-500 px-3 py-1.5 text-[11px] font-black uppercase tracking-wide text-black group-hover:bg-amber-400 transition-colors">
<Download className="h-3.5 w-3.5" />
{t('common.save')}
</div>
</div>
</Link>
)}
{/* ── Hero ── */}
<div className="text-center mb-12 relative">
<div className="absolute inset-0 -z-10 blur-[120px] opacity-20 pointer-events-none"
style={{ background: 'radial-gradient(ellipse at 50% 0%, #ff0040 0%, transparent 70%)' }} />
<div className="inline-flex items-center gap-2 px-3 py-1 rounded-full bg-[#ff0040]/10 border border-[#ff0040]/30 text-[#ff0040] text-xs font-black uppercase tracking-widest mb-5">
<span className="w-1.5 h-1.5 rounded-full bg-[#ff0040] animate-pulse" />
{t('settingsUi.onboarding.betaBadge')}
</div>
<h1 className="text-5xl md:text-7xl font-black uppercase leading-none mb-4">
<span style={{ color: '#ff0040', textShadow: '0 0 40px rgba(255,0,64,0.4)' }}>{t('settingsUi.onboarding.headline1')}</span>
<br />
<span style={{ color: '#ccff00', textShadow: '0 0 40px rgba(204,255,0,0.3)' }}>{t('settingsUi.onboarding.headline2')}</span>
</h1>
<p className="text-gray-400 text-lg max-w-xl mx-auto leading-relaxed mb-8">
{t('settingsUi.onboarding.heroBefore')} <strong className="text-white">{t('settingsUi.onboarding.decentralized')}</strong> {t('settingsUi.onboarding.heroAfter')}
</p>
{/* Feature pills */}
<div className="flex flex-wrap gap-2 justify-center mb-8">
<FeaturePill emoji="🗺️" label={t('settingsUi.onboarding.pillMap')} />
<FeaturePill emoji="🎓" label={t('settingsUi.onboarding.pillCoaches')} />
<FeaturePill emoji="📅" label={t('settingsUi.onboarding.pillEvents')} />
<FeaturePill emoji="🛒" label={t('settingsUi.onboarding.pillGear')} />
<FeaturePill emoji="⚡" label={t('settingsUi.onboarding.pillZaps')} />
<FeaturePill emoji="🎒" label={t('settingsUi.onboarding.pillStash')} />
<FeaturePill emoji="👥" label={t('settingsUi.onboarding.pillCrews')} />
</div>
{/* Primary CTA.
LoginArea (which hosts the multi-step SignupDialog) is kept mounted
even after login and merely hidden — otherwise logging in at the
first signup step would unmount the dialog and skip the profile +
recovery steps. */}
<div className={cn('flex-col items-center gap-3', user ? 'hidden' : 'flex')}>
<p className="text-sm text-gray-500 uppercase tracking-wider font-bold mb-1">{t('settingsUi.onboarding.createFreeAccount')}</p>
<LoginArea className="flex justify-center" />
<p className="text-xs text-gray-600 mt-2">{t('settingsUi.onboarding.noEmailNoPassword')}</p>
</div>
{user && (
<div className="flex flex-wrap gap-3 justify-center">
<Link to="/map">
<Button size="lg" className="bg-[#ff0040] hover:bg-[#ff0040]/90 gap-2 font-black uppercase shadow-[0_0_30px_rgba(255,0,64,0.3)]">
<MapPin className="h-5 w-5" />
{t('settingsUi.onboarding.findSpots')}
</Button>
</Link>
<Link to="/create-spot">
<Button size="lg" variant="outline" className="border-[#ccff00]/50 text-[#ccff00] hover:bg-[#ccff00]/10 gap-2 font-black uppercase">
{t('settingsUi.onboarding.addFirstSpot')}
</Button>
</Link>
</div>
)}
</div>
{/* ── Logged in: step 1 complete ── */}
{user && (
<div className="flex items-center gap-4 p-4 rounded-2xl bg-green-500/8 border border-green-500/25 mb-4">
<div className="w-9 h-9 rounded-full bg-green-500 flex items-center justify-center flex-shrink-0">
<CheckCircle2 className="h-5 w-5 text-white" />
</div>
<div className="flex-1">
<p className="font-bold text-green-400 text-sm">{t('settingsUi.onboarding.accountCreated')}</p>
<p className="text-xs text-gray-500">{t('settingsUi.onboarding.identityYours')}</p>
</div>
<div className="flex-shrink-0">
<FounderFollow size="sm" showBadge={true} />
</div>
</div>
)}
{/* ── First run: where do you skate? ── */}
{user && !hasHome && (
<div className="rounded-2xl border border-[#ccff00]/30 bg-gradient-to-br from-[#ccff00]/8 to-black/40 p-5 mb-6">
<div className="flex items-start gap-3 mb-4">
<div className="w-9 h-9 rounded-xl bg-[#ccff00]/15 flex items-center justify-center flex-shrink-0">
<MapPin className="h-5 w-5 text-[#ccff00]" />
</div>
<div>
<h2 className="font-black text-white uppercase tracking-wide text-sm">{t('settingsUi.onboarding.whereSkate')}</h2>
<p className="text-xs text-gray-400 mt-0.5 leading-snug">
{t('settingsUi.onboarding.whereSkateDesc')}
</p>
</div>
</div>
<div className="flex flex-wrap gap-2">
<Button
onClick={handleUseMyLocation}
disabled={locating}
className="bg-[#ccff00] text-black hover:bg-[#ccff00]/90 gap-2 font-black uppercase text-xs"
>
{locating ? (
<div className="h-3.5 w-3.5 border-2 border-current border-t-transparent rounded-full animate-spin" />
) : (
<MapPin className="h-3.5 w-3.5" />
)}
{t('settingsUi.onboarding.useMyLocation')}
</Button>
<Link to="/map">
<Button variant="outline" className="border-white/20 text-white hover:bg-white/5 text-xs font-bold">
{t('settingsUi.onboarding.pickOnMap')}
</Button>
</Link>
</div>
</div>
)}
{/* ── Login saved confirmation ── */}
{user && backupConfirmed && (
<div className="flex items-center gap-3 p-3 rounded-2xl bg-green-500/6 border border-green-500/20 mb-8">
<CheckCircle2 className="h-4 w-4 text-green-400 flex-shrink-0" />
<p className="text-xs text-green-400/90 font-medium">{t('settingsUi.onboarding.loginSaved')}</p>
</div>
)}
<div className="space-y-6">
{/* ── Why skaters care (collapsed by default — keeps the first view light) ── */}
<section>
<CollapsibleSection title={t('settingsUi.onboarding.whyBother')}>
<div className="grid sm:grid-cols-2 gap-3">
{[
{
emoji: '🗺️',
title: t('settingsUi.onboarding.why1Title'),
body: t('settingsUi.onboarding.why1Body'),
},
{
emoji: '🤙',
title: t('settingsUi.onboarding.why2Title'),
body: t('settingsUi.onboarding.why2Body'),
},
{
emoji: '⚡',
title: t('settingsUi.onboarding.why3Title'),
body: t('settingsUi.onboarding.why3Body'),
},
{
emoji: '🔑',
title: t('settingsUi.onboarding.why4Title'),
body: t('settingsUi.onboarding.why4Body'),
},
].map(({ emoji, title, body }) => (
<div key={title} className="p-4 rounded-2xl bg-white/3 border border-white/8">
<div className="flex items-center gap-2.5 mb-1.5">
<span className="text-xl">{emoji}</span>
<span className="font-black text-sm text-white uppercase tracking-wide">{title}</span>
</div>
<p className="text-xs text-gray-500 leading-relaxed">{body}</p>
</div>
))}
</div>
</CollapsibleSection>
</section>
{/* ── What you can do ── */}
<section>
<h2 className="text-xs font-black uppercase tracking-widest text-gray-600 mb-4">{t('settingsUi.onboarding.everythingFree')}</h2>
<div className="grid grid-cols-2 sm:grid-cols-3 gap-3">
{[
{ icon: MapPin, label: t('settingsUi.onboarding.featSpots'), desc: t('settingsUi.onboarding.featSpotsDesc'), href: '/map', color: '#ff0040' },
{ icon: GraduationCap,label: t('settingsUi.onboarding.featLab'), desc: t('settingsUi.onboarding.featLabDesc'), href: '/lab', color: '#ff6600' },
{ icon: Calendar, label: t('settingsUi.onboarding.featEvents'), desc: t('settingsUi.onboarding.featEventsDesc'), href: '/events', color: '#ccff00' },
{ icon: ShoppingBag, label: t('settingsUi.onboarding.featGear'), desc: t('settingsUi.onboarding.featGearDesc'), href: '/marketplace',color: '#00cc66' },
{ icon: Package, label: t('settingsUi.onboarding.featStash'), desc: t('settingsUi.onboarding.featStashDesc'), href: '/stash', color: '#a855f7' },
{ icon: Users, label: t('settingsUi.onboarding.featCrews'), desc: t('settingsUi.onboarding.featCrewsDesc'), href: '/crews', color: '#06b6d4' },
].map(({ icon: Icon, label, desc, href, color }) => (
<Link key={href} to={href}>
<div className="h-full p-3.5 rounded-2xl bg-white/3 border border-white/8 hover:bg-white/6 hover:border-white/15 transition-all group">
<Icon className="h-5 w-5 mb-2 transition-colors" style={{ color }} />
<p className="font-bold text-sm text-white leading-tight mb-1">{label}</p>
<p className="text-[11px] text-gray-600 leading-snug">{desc}</p>
</div>
</Link>
))}
</div>
</section>
{/* ── What is Nostr ── */}
<section className="rounded-2xl border border-purple-500/20 bg-gradient-to-br from-purple-500/8 to-black/60 p-5">
<div className="flex items-center gap-3 mb-4">
<div className="w-9 h-9 rounded-xl bg-purple-500/20 flex items-center justify-center">
<Globe className="h-5 w-5 text-purple-400" />
</div>
<div>
<h3 className="font-black text-white uppercase tracking-wide">{t('settingsUi.onboarding.whatIsNostr')}</h3>
<p className="text-xs text-gray-500">{t('settingsUi.onboarding.whatIsNostrSub')}</p>
</div>
</div>
<p className="text-sm text-gray-400 leading-relaxed mb-4">
{t('settingsUi.onboarding.nostrBefore')} <strong className="text-white">{t('settingsUi.onboarding.cryptoKeyPair')}</strong> {t('settingsUi.onboarding.nostrAfter')}
</p>
<div className="grid grid-cols-2 gap-2 mb-4">
{[
{ icon: Shield, label: t('settingsUi.onboarding.nostrPoint1'), color: 'text-red-400' },
{ icon: Lock, label: t('settingsUi.onboarding.nostrPoint2'), color: 'text-orange-400' },
{ icon: Users, label: t('settingsUi.onboarding.nostrPoint3'), color: 'text-purple-400' },
{ icon: Zap, label: t('settingsUi.onboarding.nostrPoint4'), color: 'text-yellow-400' },
].map(({ icon: Icon, label, color }) => (
<div key={label} className="flex items-center gap-2 p-2.5 rounded-xl bg-white/4 border border-white/6">
<Icon className={cn('h-4 w-4 flex-shrink-0', color)} />
<span className="text-xs font-bold text-gray-300">{label}</span>
</div>
))}
</div>
<Button
variant="outline"
size="sm"
onClick={() => setShowRelayEducation(true)}
className="gap-2 border-purple-500/40 text-purple-400 hover:bg-purple-500/10 text-xs"
>
<BookOpen className="h-3.5 w-3.5" />
{t('settingsUi.onboarding.deepDive')}
</Button>
{/* Calm, optional privacy note — links to the full Privacy page */}
<p className="text-[11px] text-gray-500 leading-relaxed mt-4 pt-3 border-t border-white/6">
<Shield className="h-3 w-3 inline-block mr-1 -mt-0.5 text-gray-500" />
{t('settingsUi.onboarding.privacyTip')}{' '}
<Link to="/privacy" className="text-purple-400/80 hover:text-purple-400 underline">
{t('settingsUi.onboarding.readPrivacy')}
</Link>
.
</p>
</section>
{/* ── Wallet (optional) ── */}
<section className="rounded-2xl border border-orange-500/20 bg-gradient-to-br from-orange-500/6 to-black/60 p-5">
<div className="flex items-center justify-between mb-1">
<h3 className="font-black text-white uppercase tracking-wide">{t('settingsUi.onboarding.zapsWallet')}</h3>
<span className="text-[10px] font-black uppercase tracking-wider bg-green-500/15 text-green-400 border border-green-500/30 px-2 py-0.5 rounded-full">{t('settingsUi.onboarding.optional')}</span>
</div>
<p className="text-sm text-gray-400 leading-relaxed mb-4">
{t('settingsUi.onboarding.walletDesc')}
</p>
<div className="grid sm:grid-cols-2 gap-3">
<div className="p-4 rounded-xl bg-orange-500/8 border border-orange-500/20">
<div className="flex items-center gap-2 mb-2">
<Nut className="h-4 w-4 text-orange-400" />
<span className="font-bold text-sm text-white">{t('settingsUi.onboarding.builtInWallet')}</span>
</div>
<p className="text-xs text-gray-500 mb-3">{t('settingsUi.onboarding.builtInWalletDesc')}</p>
{hasCashu && cashuBalance > 0 ? (
<div className="flex items-center justify-between p-2 bg-green-950/30 rounded-lg border border-green-500/30">
<span className="text-xs text-green-400 font-bold">{t('settingsUi.onboarding.balance')}</span>
<span className="text-sm font-black text-green-400">{cashuBalance.toLocaleString()} sats</span>
</div>
) : (
<Link to="/wallet">
<Button size="sm" className="w-full bg-orange-500 hover:bg-orange-600 gap-2 text-xs font-bold">
<Wallet className="h-3.5 w-3.5" />
{t('settingsUi.settingsPage.openWallet')}
</Button>
</Link>
)}
</div>
<div className="p-4 rounded-xl bg-purple-500/8 border border-purple-500/20">
<div className="flex items-center gap-2 mb-2">
<Zap className="h-4 w-4 text-purple-400" />
<span className="font-bold text-sm text-white">{t('settingsUi.onboarding.connectExternal')}</span>
</div>
<p className="text-xs text-gray-500 mb-3">{t('settingsUi.onboarding.connectExternalDesc')}</p>
{user ? (
<WalletModal>
<Button size="sm" variant="outline" className="w-full border-purple-500/40 text-purple-400 hover:bg-purple-500/10 gap-2 text-xs font-bold">
<Wallet className="h-3.5 w-3.5" />
{t('settingsUi.onboarding.connectViaNwc')}
</Button>
</WalletModal>
) : (
<p className="text-xs text-gray-500">{t('settingsUi.onboarding.loginToConnect')}</p>
)}
</div>
</div>
<p className="text-[11px] text-gray-600 text-center mt-3">{t('settingsUi.onboarding.fiveDollars')}</p>
</section>
{/* ── FAQ (collapsed by default to keep the page light) ── */}
<section>
<CollapsibleSection title={t('settingsUi.onboarding.commonQuestions')}>
<div className="space-y-2">
<FAQ
q={t('settingsUi.onboarding.faq1Q')}
a={t('settingsUi.onboarding.faq1A')}
/>
<FAQ
q={t('settingsUi.onboarding.faq2Q')}
a={<>{t('settingsUi.onboarding.faq2Before')} <strong className="text-white">{t('settingsUi.onboarding.faq2Strong')}</strong>{t('settingsUi.onboarding.faq2After')}</>}
/>
<FAQ
q={t('settingsUi.onboarding.faq3Q')}
a={<>{t('settingsUi.onboarding.faq3Before')} <code className="text-[#ccff00] text-xs bg-white/5 px-1 py-0.5 rounded">nsec</code> {t('settingsUi.onboarding.faq3Mid')} <strong className="text-white">{t('settingsUi.onboarding.faq3Strong')}</strong>{t('settingsUi.onboarding.faq3After')}</>}
/>
<FAQ
q={t('settingsUi.onboarding.faq4Q')}
a={
<div className="space-y-2">
<p>{t('settingsUi.onboarding.faq4Desktop')} <a href="https://getalby.com" target="_blank" rel="noopener noreferrer" className="text-[#ccff00] underline">Alby</a> {t('common.or')} <a href="https://github.com/fiatjaf/nos2x" target="_blank" rel="noopener noreferrer" className="text-[#ccff00] underline">nos2x</a> {t('settingsUi.onboarding.faq4Extensions')}</p>
<p>{t('settingsUi.onboarding.faq4Android')} <a href="https://github.com/greenart7c3/Amber" target="_blank" rel="noopener noreferrer" className="text-[#ccff00] underline">Amber</a> {t('settingsUi.onboarding.faq4AndroidAfter')}</p>
<p>{t('settingsUi.onboarding.faq4Ios')} <a href="https://apps.apple.com/app/nostore/id1666553677" target="_blank" rel="noopener noreferrer" className="text-[#ccff00] underline">Nostore</a> {t('settingsUi.onboarding.faq4IosAfter')}</p>
<p>{t('settingsUi.onboarding.faq4Note')}</p>
</div>
}
/>
<FAQ
q={t('settingsUi.onboarding.faq5Q')}
a={<>{t('settingsUi.onboarding.faq5Before')} <strong className="text-white">skate-spots.org</strong> {t('settingsUi.onboarding.faq5Mid')} <em>{t('settingsUi.onboarding.faq5Em')}</em> {t('settingsUi.onboarding.faq5After')} <a href="https://zapstore.dev/apps/org.skatespots.app" target="_blank" rel="noopener noreferrer" className="text-[#ccff00] underline">Zapstore</a> {t('settingsUi.onboarding.faq5End')}</>}
/>
<FAQ
q={t('settingsUi.onboarding.faq6Q')}
a={t('settingsUi.onboarding.faq6A')}
/>
<FAQ
q={t('settingsUi.onboarding.faq7Q')}
a={t('settingsUi.onboarding.faq7A')}
/>
<FAQ
q={t('settingsUi.onboarding.faq8Q')}
a={t('settingsUi.onboarding.faq8A')}
/>
<FAQ
q={t('settingsUi.onboarding.faq9Q')}
a={
<div className="space-y-2">
<p>{t('settingsUi.onboarding.faq9Before')}</p>
<a
href="https://cordn.net/p/npub1e2ayjlmmaksx2rtt0z4jzyskunfwh2lwe89xav4qtnhgu4wxsudsp98a4l"
target="_blank"
rel="noopener noreferrer"
className="inline-flex items-center gap-2 px-3 py-2 rounded-xl bg-yellow-500/10 border border-yellow-500/30 text-yellow-400 text-xs font-bold hover:bg-yellow-500/20 transition-colors"
>
<ExternalLink className="h-3.5 w-3.5" />
{t('settingsUi.onboarding.faq9Link')}
</a>
</div>
}
/>
</div>
</CollapsibleSection>
</section>
{/* ── Final CTA ── */}
<section className="rounded-2xl overflow-hidden relative">
<div className="absolute inset-0 pointer-events-none"
style={{ background: 'linear-gradient(135deg, rgba(255,0,64,0.15) 0%, rgba(204,255,0,0.08) 100%)' }} />
<div className="absolute top-0 left-0 right-0 h-px bg-gradient-to-r from-[#ff0040]/60 via-[#ff6600]/40 to-[#ccff00]/40" />
<div className="relative p-8 text-center">
<h2 className="text-4xl font-black uppercase mb-3" style={{ textShadow: '0 0 30px rgba(255,0,64,0.3)' }}>{t('settingsUi.onboarding.readyToShred')}</h2>
<p className="text-gray-400 mb-6 max-w-sm mx-auto">{t('settingsUi.onboarding.stopScrolling')}</p>
<div className="flex flex-wrap gap-3 justify-center">
<Link to="/map">
<Button size="lg" className="bg-[#ff0040] hover:bg-[#ff0040]/90 gap-2 font-black uppercase shadow-[0_0_30px_rgba(255,0,64,0.25)]">
<MapPin className="h-5 w-5" />
{t('settingsUi.onboarding.exploreSpots')}
</Button>
</Link>
<Link to="/lab">
<Button size="lg" variant="outline" className="border-[#ccff00]/50 text-[#ccff00] hover:bg-[#ccff00]/8 gap-2 font-black uppercase">
<GraduationCap className="h-5 w-5" />
{t('nav.lab')}
</Button>
</Link>
</div>
</div>
</section>
{/* ── Beta Testing Card ── */}
<section className="rounded-2xl border border-yellow-500/25 bg-yellow-500/5 p-5">
<div className="flex items-start gap-3 mb-4">
<div className="w-8 h-8 rounded-lg bg-yellow-500/20 flex items-center justify-center flex-shrink-0 mt-0.5">
<AlertCircle className="h-4 w-4 text-yellow-400" />
</div>
<div>
<h3 className="font-black text-yellow-400 uppercase text-sm tracking-wide">{t('settingsUi.onboarding.betaFeedbackTitle')}</h3>
<p className="text-sm text-gray-400 mt-1 leading-relaxed">
{t('settingsUi.onboarding.betaFeedbackDesc')}
</p>
</div>
</div>
<div className="grid sm:grid-cols-2 gap-3">
{/* Beta coordinator */}
<a
href="https://cordn.net/p/npub1e2ayjlmmaksx2rtt0z4jzyskunfwh2lwe89xav4qtnhgu4wxsudsp98a4l"
target="_blank"
rel="noopener noreferrer"
className="flex items-center gap-3 p-3.5 rounded-xl bg-yellow-500/8 border border-yellow-500/20 hover:bg-yellow-500/15 hover:border-yellow-500/35 transition-all group"
>
<div className="w-8 h-8 rounded-full bg-yellow-500/20 flex items-center justify-center flex-shrink-0">
<MessageCircle className="h-4 w-4 text-yellow-400" />
</div>
<div className="flex-1 min-w-0">
<p className="text-xs font-black text-yellow-400 uppercase tracking-wide">{t('settingsUi.onboarding.betaCoordinator')}</p>
<p className="text-xs text-gray-500 truncate">{t('settingsUi.onboarding.betaCoordinatorDesc')}</p>
</div>
<ExternalLink className="h-3.5 w-3.5 text-yellow-500/50 group-hover:text-yellow-400 transition-colors flex-shrink-0" />
</a>
{/* DM founder */}
<div className="flex items-center gap-3 p-3.5 rounded-xl bg-white/3 border border-white/8">
<div className="w-8 h-8 rounded-full bg-white/8 flex items-center justify-center flex-shrink-0">
<Key className="h-4 w-4 text-gray-400" />
</div>
<div className="flex-1 min-w-0">
<p className="text-xs font-black text-gray-300 uppercase tracking-wide">{t('settingsUi.onboarding.dmOnNostr')}</p>
<p className="text-[10px] text-gray-600 font-mono truncate">npub1yrnuj…</p>
</div>
<div className="flex gap-1.5 flex-shrink-0">
<button
onClick={copyNpub}
className="px-2 py-1 rounded-lg bg-white/6 border border-white/10 text-[10px] font-bold text-gray-400 hover:text-white hover:bg-white/10 transition-all"
>
{t('common.copy')}
</button>
{user && (
<Link to={bugReportUrl}>
<button className="px-2 py-1 rounded-lg bg-yellow-500/15 border border-yellow-500/30 text-[10px] font-bold text-yellow-400 hover:bg-yellow-500/25 transition-all">
DM
</button>
</Link>
)}
</div>
</div>
</div>
<p className="text-[11px] text-gray-600 mt-3 text-center">
{t('settingsUi.onboarding.fixSameDay')}
</p>
</section>
</div>
</div>
{/* Relay Education Dialog */}
<Dialog open={showRelayEducation} onOpenChange={setShowRelayEducation}>
<DialogContent className="w-[calc(100%-1rem)] sm:max-w-lg p-0 bg-transparent border-none">
<DialogTitle className="sr-only">{t('settingsUi.onboarding.relaySetup')}</DialogTitle>
<RelayEducation
onComplete={handleRelayEducationComplete}
onSkip={() => setShowRelayEducation(false)}
/>
</DialogContent>
</Dialog>
</>
);
}