import { useState } from 'react'; import { useSeoMeta } from '@unhead/react'; import { Link } from 'react-router-dom'; import { Layout } from '@/components/Layout'; 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 { 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'; // ─── Accordion FAQ ──────────────────────────────────────────────────────────── function FAQ({ q, a }: { q: string; a: React.ReactNode }) { const [open, setOpen] = useState(false); return ( ); } // ─── 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 (
{open && (
{children}
)}
); } // ─── Feature pill ───────────────────────────────────────────────────────────── function FeaturePill({ emoji, label }: { emoji: string; label: string }) { return (
{emoji} {label}
); } export default function OnboardingPage() { useSeoMeta({ title: 'Get Started — SkateSpots', description: 'Join the decentralized skateboarding community. Find spots, connect with coaches, send zaps. Free forever.', }); 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('backup-confirmed', false); const handleRelayEducationComplete = () => { setShowRelayEducation(false); setHasSeenRelayEducation(true); toast({ title: '🎉 Nice!', description: "You now understand how Nostr works. Let's go skate!", }); }; const copyNpub = () => { navigator.clipboard.writeText('npub1yrnuj56rnen08zp2h9h7p74ghgjx6ma39spmpj6w9hzxywutevsst7k5cx'); toast({ title: 'Copied!', description: 'npub copied to clipboard.', }); }; const bugReportUrl = `/messages/npub1yrnuj56rnen08zp2h9h7p74ghgjx6ma39spmpj6w9hzxywutevsst7k5cx`; return (
{/* ── 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 && (

Save your login — do this now

Your recovery code is the only way back into your account. No email, no password reset.

Save
)} {/* ── Hero ── */}
Beta — Open to skaters worldwide

SKATE
YOUR WORLD

A decentralized skate community. No company owns it. No algorithm buries your spots. No ban hammer. Just skaters.

{/* Feature pills */}
{/* 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. */}

Create your free account

No email. No password. Own your identity forever.

{user && (
)}
{/* ── Logged in: step 1 complete ── */} {user && (

You're in! Account created ✓

Your identity is yours — stored in your key, not on our servers.

)} {/* ── Login saved confirmation ── */} {user && backupConfirmed && (

Login saved — your account is safe to recover.

)}
{/* ── Why skaters care (collapsed by default — keeps the first view light) ── */}
{[ { emoji: '🗺️', title: 'Every spot, one map', body: 'Ledges, rails, DIY, parks — mapped by skaters who actually ride them. Check the status before you roll out so you don\u2019t waste a session on a bust.', }, { emoji: '🤙', title: 'Built by skaters, not suits', body: 'No ads, no algorithm shoving junk in your face, no data harvesting. Open source. The map belongs to the community — including you.', }, { emoji: '⚡', title: 'Get paid to put on', body: 'Map a gem, film a banger, run a session — and get zapped real Bitcoin straight to your wallet. No platform cut, no middleman.', }, { emoji: '🔑', title: 'Your account is actually yours', body: 'One key logs you into every Nostr app. No company can lock you out or delete your account on a whim. Take your identity anywhere.', }, ].map(({ emoji, title, body }) => (
{emoji} {title}

{body}

))}
{/* ── What you can do ── */}

Everything you can do — free

{[ { icon: MapPin, label: 'Discover Spots', desc: 'Interactive map with thousands of spots worldwide', href: '/map', color: '#ff0040' }, { icon: GraduationCap,label: 'The Lab', desc: 'Find coaches, filmers & video editors near you', href: '/lab', color: '#ff6600' }, { icon: Calendar, label: 'Events', desc: 'Competitions, sessions, meetups', href: '/events', color: '#ccff00' }, { icon: ShoppingBag, label: 'Buy / Sell Gear', desc: 'Peer-to-peer marketplace for used skate gear', href: '/marketplace',color: '#00cc66' }, { icon: Package, label: 'My Stash', desc: 'Private encrypted inventory of everything you own', href: '/stash', color: '#a855f7' }, { icon: Users, label: 'Crews', desc: 'Roll with your crew — shared spots, chat & sessions', href: '/crews', color: '#06b6d4' }, ].map(({ icon: Icon, label, desc, href, color }) => (

{label}

{desc}

))}
{/* ── What is Nostr ── */}

What is Nostr?

The protocol that powers SkateSpots

Nostr is an open protocol — like email, but for social apps. Your account is a cryptographic key pair that you own. No single company controls your account or your data — you choose which relays store it, and you can even run your own. Your identity works across every Nostr app.

{[ { icon: Shield, label: 'You own your data & keys', color: 'text-red-400' }, { icon: Lock, label: 'No company can ban you', color: 'text-orange-400' }, { icon: Users, label: 'Identity works everywhere', color: 'text-purple-400' }, { icon: Zap, label: 'Direct Bitcoin tips', color: 'text-yellow-400' }, ].map(({ icon: Icon, label, color }) => (
{label}
))}
{/* Calm, optional privacy note — links to the full Privacy page */}

Privacy tip: your GPS never leaves your device. Servers you connect to (like map tiles) can still see your IP — if you want to hide that too, a VPN or Tor works. Totally optional.{' '} Read our privacy page .

{/* ── Wallet (optional) ── */}

⚡ Zaps & Wallet

Optional

Zaps are Bitcoin tips you send directly to a skater who dropped a sick spot, filmed a banger clip, or ran a great session. No platform fee. No middleman. 100% goes to them.

Built-in Cashu wallet

Receive Lightning payments right in the app. No setup.

{hasCashu && cashuBalance > 0 ? (
Balance {cashuBalance.toLocaleString()} sats
) : ( )}
Connect external wallet

Have Alby, Mutiny, or another wallet? Connect it via NWC.

{user ? ( ) : ( )}

💡 $5 of Bitcoin = hundreds of zaps to spread across the community

{/* ── FAQ (collapsed by default to keep the page light) ── */}
Nostr is an open protocol for decentralized apps — think of it like the internet itself, not owned by any company. Your SkateSpots account is a cryptographic key pair that you own. No single company can ban your account or hold your data hostage — you choose which relays store your data, and you can even run your own. Your identity also works on other Nostr apps like Primal, Damus, or Amethyst.} /> Your nsec is your private key — it's the only proof you own your account. Unlike passwords, there is no "forgot my password". If you lose your nsec, your account is gone forever. When you create an account, download your backup immediately and store it somewhere safe (password manager, written down offline).} />

On desktop: Alby or nos2x browser extensions.

On Android: Amber (best experience with the native app).

On iOS: Nostore or paste your nsec directly (less secure).

You can also log in by pasting your nsec directly — but using a signer extension is safer.

} /> Yes! SkateSpots is a PWA (Progressive Web App). Visit skate-spots.org in your phone's browser and tap "Add to Home Screen" for a full app experience. Or download it from Zapstore — a decentralized app store.} />

DM the SkateSpots team directly on Nostr, or reach out to our beta coordinator:

Beta feedback → cordn.net
} />
{/* ── Final CTA ── */}

READY TO SHRED?

Stop scrolling. Get the app. Find a spot. Skate it.

{/* ── Beta Testing Card ── */}

Beta — We want your feedback

SkateSpots is under active development. You may hit rough edges — that's on us, not you. Report bugs, suggest features, tell us what sucks.

{/* Beta coordinator */}

Beta Coordinator

cordn.net → message us

{/* DM founder */}

DM on Nostr

npub1yrnuj…

{user && ( )}

Found a bug? We fix it same day. Your reports make the app better for every skater.

{/* Relay Education Dialog */} Relay setup setShowRelayEducation(false)} />
); }