Onboarding: "Save your login" banner + collapse FAQ to reduce load (1.3.7)

Two greenlit onboarding improvements, keeping the existing layout intact.

#5 Save-your-login banner (the safety one):
- Right under the green "You're in!" bar, logged-in users who haven't backed up
  see a prominent amber banner — "Save your login — do this now" — that taps
  straight to Security settings. It reads the shared `backup-confirmed` flag
  (same source of truth as BackupReminder), so it PERSISTS until they actually
  back up; unlike the dismissible reminder sheet, it can't silently turn into
  "never". Once confirmed it flips to a calm green "Login saved" note. Plain
  language ("save your login", not "back up your nsec"). Pulse dot respects
  reduced-motion.

#1 Lighten first-glance load:
- The 9-item FAQ ("Common questions") is now a collapse-by-default section — one
  tidy row until tapped — via a small reusable CollapsibleSection (aria-expanded,
  reduced-motion aware). Nothing removed; the page just feels lighter on arrival.

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
This commit is contained in:
shakespeare.diy 2026-09-09 10:59:55 +07:00
parent 67e422307a
commit 1d769ed564
2 changed files with 83 additions and 4 deletions

View file

@ -1,7 +1,7 @@
{ {
"name": "skate-spots", "name": "skate-spots",
"private": true, "private": true,
"version": "1.3.6", "version": "1.3.7",
"type": "module", "type": "module",
"homepage": "https://skate-spots.com", "homepage": "https://skate-spots.com",
"scripts": { "scripts": {

View file

@ -35,6 +35,7 @@ import {
MessageCircle, MessageCircle,
Lock, Lock,
Users, Users,
Download,
} from 'lucide-react'; } from 'lucide-react';
import { FounderFollow } from '@/components/FounderFollow'; import { FounderFollow } from '@/components/FounderFollow';
@ -69,6 +70,47 @@ function FAQ({ q, a }: { q: string; a: React.ReactNode }) {
); );
} }
// ─── 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 ───────────────────────────────────────────────────────────── // ─── Feature pill ─────────────────────────────────────────────────────────────
function FeaturePill({ emoji, label }: { emoji: string; label: string }) { function FeaturePill({ emoji, label }: { emoji: string; label: string }) {
return ( return (
@ -90,6 +132,10 @@ export default function OnboardingPage() {
const { toast } = useToast(); const { toast } = useToast();
const [showRelayEducation, setShowRelayEducation] = useState(false); const [showRelayEducation, setShowRelayEducation] = useState(false);
const [, setHasSeenRelayEducation] = useLocalStorage('seen-relay-education', 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 = () => { const handleRelayEducationComplete = () => {
setShowRelayEducation(false); setShowRelayEducation(false);
@ -169,7 +215,7 @@ export default function OnboardingPage() {
{/* ── Logged in: step 1 complete ── */} {/* ── Logged in: step 1 complete ── */}
{user && ( {user && (
<div className="flex items-center gap-4 p-4 rounded-2xl bg-green-500/8 border border-green-500/25 mb-8"> <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"> <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" /> <CheckCircle2 className="h-5 w-5 text-white" />
</div> </div>
@ -183,6 +229,38 @@ export default function OnboardingPage() {
</div> </div>
)} )}
{/* ── Save your login (critical) — stays until actually backed up ── */}
{user && !backupConfirmed && (
<Link to="/settings?section=security" className="block mb-8 group">
<div className="flex items-center gap-4 p-4 rounded-2xl bg-amber-500/10 border border-amber-500/30 hover:bg-amber-500/15 hover:border-amber-500/45 transition-all">
<div className="relative flex-shrink-0">
<div className="w-9 h-9 rounded-full bg-amber-500/20 flex items-center justify-center">
<Key className="h-4 w-4 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-bold text-amber-300 text-sm">Save your login — do this now</p>
<p className="text-xs text-amber-100/70 leading-snug mt-0.5">
It's the only way back into your account. Lose it and there's no reset — no email, no password recovery.
</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" />
Save
</div>
</div>
</Link>
)}
{/* ── 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">Login saved — your account is safe to recover.</p>
</div>
)}
<div className="space-y-6"> <div className="space-y-6">
{/* ── Why skaters care ── */} {/* ── Why skaters care ── */}
@ -347,9 +425,9 @@ export default function OnboardingPage() {
<p className="text-[11px] text-gray-600 text-center mt-3">💡 $5 of Bitcoin = hundreds of zaps to spread across the community</p> <p className="text-[11px] text-gray-600 text-center mt-3">💡 $5 of Bitcoin = hundreds of zaps to spread across the community</p>
</section> </section>
{/* ── FAQ ── */} {/* ── FAQ (collapsed by default to keep the page light) ── */}
<section> <section>
<h2 className="text-xs font-black uppercase tracking-widest text-gray-600 mb-4">Common questions</h2> <CollapsibleSection title="Common questions">
<div className="space-y-2"> <div className="space-y-2">
<FAQ <FAQ
q="Is SkateSpots free?" q="Is SkateSpots free?"
@ -408,6 +486,7 @@ export default function OnboardingPage() {
} }
/> />
</div> </div>
</CollapsibleSection>
</section> </section>
{/* ── Final CTA ── */} {/* ── Final CTA ── */}