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 ( ); } // ─── 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() { 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 ( <>
{/* ── 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 && (

{t('settingsUi.onboarding.saveLoginNow')}

{t('settingsUi.onboarding.saveLoginNowDesc')}

{t('common.save')}
)} {/* ── Hero ── */}
{t('settingsUi.onboarding.betaBadge')}

{t('settingsUi.onboarding.headline1')}
{t('settingsUi.onboarding.headline2')}

{t('settingsUi.onboarding.heroBefore')} {t('settingsUi.onboarding.decentralized')} {t('settingsUi.onboarding.heroAfter')}

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

{t('settingsUi.onboarding.createFreeAccount')}

{t('settingsUi.onboarding.noEmailNoPassword')}

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

{t('settingsUi.onboarding.accountCreated')}

{t('settingsUi.onboarding.identityYours')}

)} {/* ── First run: where do you skate? ── */} {user && !hasHome && (

{t('settingsUi.onboarding.whereSkate')}

{t('settingsUi.onboarding.whereSkateDesc')}

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

{t('settingsUi.onboarding.loginSaved')}

)}
{/* ── Why skaters care (collapsed by default — keeps the first view light) ── */}
{[ { 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 }) => (
{emoji} {title}

{body}

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

{t('settingsUi.onboarding.everythingFree')}

{[ { 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 }) => (

{label}

{desc}

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

{t('settingsUi.onboarding.whatIsNostr')}

{t('settingsUi.onboarding.whatIsNostrSub')}

{t('settingsUi.onboarding.nostrBefore')} {t('settingsUi.onboarding.cryptoKeyPair')} {t('settingsUi.onboarding.nostrAfter')}

{[ { 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 }) => (
{label}
))}
{/* Calm, optional privacy note — links to the full Privacy page */}

{t('settingsUi.onboarding.privacyTip')}{' '} {t('settingsUi.onboarding.readPrivacy')} .

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

{t('settingsUi.onboarding.zapsWallet')}

{t('settingsUi.onboarding.optional')}

{t('settingsUi.onboarding.walletDesc')}

{t('settingsUi.onboarding.builtInWallet')}

{t('settingsUi.onboarding.builtInWalletDesc')}

{hasCashu && cashuBalance > 0 ? (
{t('settingsUi.onboarding.balance')} {cashuBalance.toLocaleString()} sats
) : ( )}
{t('settingsUi.onboarding.connectExternal')}

{t('settingsUi.onboarding.connectExternalDesc')}

{user ? ( ) : (

{t('settingsUi.onboarding.loginToConnect')}

)}

{t('settingsUi.onboarding.fiveDollars')}

{/* ── FAQ (collapsed by default to keep the page light) ── */}
{t('settingsUi.onboarding.faq2Before')} {t('settingsUi.onboarding.faq2Strong')}{t('settingsUi.onboarding.faq2After')}} /> {t('settingsUi.onboarding.faq3Before')} nsec {t('settingsUi.onboarding.faq3Mid')} {t('settingsUi.onboarding.faq3Strong')}{t('settingsUi.onboarding.faq3After')}} />

{t('settingsUi.onboarding.faq4Desktop')} Alby {t('common.or')} nos2x {t('settingsUi.onboarding.faq4Extensions')}

{t('settingsUi.onboarding.faq4Android')} Amber {t('settingsUi.onboarding.faq4AndroidAfter')}

{t('settingsUi.onboarding.faq4Ios')} Nostore {t('settingsUi.onboarding.faq4IosAfter')}

{t('settingsUi.onboarding.faq4Note')}

} /> {t('settingsUi.onboarding.faq5Before')} skate-spots.org {t('settingsUi.onboarding.faq5Mid')} {t('settingsUi.onboarding.faq5Em')} {t('settingsUi.onboarding.faq5After')} Zapstore {t('settingsUi.onboarding.faq5End')}} />

{t('settingsUi.onboarding.faq9Before')}

{t('settingsUi.onboarding.faq9Link')}
} />
{/* ── Final CTA ── */}

{t('settingsUi.onboarding.readyToShred')}

{t('settingsUi.onboarding.stopScrolling')}

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

{t('settingsUi.onboarding.betaFeedbackTitle')}

{t('settingsUi.onboarding.betaFeedbackDesc')}

{/* Beta coordinator */}

{t('settingsUi.onboarding.betaCoordinator')}

{t('settingsUi.onboarding.betaCoordinatorDesc')}

{/* DM founder */}

{t('settingsUi.onboarding.dmOnNostr')}

npub1yrnuj…

{user && ( )}

{t('settingsUi.onboarding.fixSameDay')}

{/* Relay Education Dialog */} {t('settingsUi.onboarding.relaySetup')} setShowRelayEducation(false)} /> ); }