insanely simple onboarding: 1-screen signup → dashboard checklist

OLD FLOW (8+ screens):
  signup (4 fields) → auto-login → setup wizard step 1 → step 2 → step 3 → step 4 → dashboard

NEW FLOW (2 screens):
  signup (3 fields) → dashboard with inline checklist

- Signup page: just charity name + email + password. No 'your name' field. One button.
- Dashboard: shows getting-started checklist when org has no pledges yet
- /api/onboarding: returns setup progress (bank, event, qr, pledge)
- Checklist: progress bar, next-step highlighting, done states with strikethrough
- Each step links directly to the right page (settings, events, pledges)
- Tip shown for brand new orgs: 'Add bank details first'
- No more separate setup wizard — guidance is inline on the dashboard
- Signup loading state: pulsing emoji while account creates
This commit is contained in:
2026-03-03 06:05:10 +08:00
parent 12ea9691c4
commit 369860d8b9
3 changed files with 156 additions and 99 deletions

View File

@@ -6,7 +6,7 @@ import { Badge } from "@/components/ui/badge"
import { Button } from "@/components/ui/button"
import { Progress } from "@/components/ui/progress"
import { formatPence } from "@/lib/utils"
import { TrendingUp, Users, Banknote, AlertTriangle, Calendar, Clock, CheckCircle2, ArrowRight, Loader2, MessageCircle, ExternalLink } from "lucide-react"
import { TrendingUp, Users, Banknote, AlertTriangle, Calendar, Clock, CheckCircle2, ArrowRight, Loader2, MessageCircle, ExternalLink, Circle } from "lucide-react"
import Link from "next/link"
interface DashboardData {
@@ -31,6 +31,7 @@ export default function DashboardPage() {
const [data, setData] = useState<DashboardData | null>(null)
const [loading, setLoading] = useState(true)
const [whatsappStatus, setWhatsappStatus] = useState<boolean | null>(null)
const [onboarding, setOnboarding] = useState<{ steps: Array<{ id: string; label: string; desc: string; done: boolean; href: string }>; completed: number; total: number; allDone: boolean } | null>(null)
const fetchData = useCallback(() => {
fetch("/api/dashboard")
@@ -43,6 +44,7 @@ export default function DashboardPage() {
useEffect(() => {
fetchData()
fetch("/api/whatsapp/send").then(r => r.json()).then(d => setWhatsappStatus(d.connected)).catch(() => {})
fetch("/api/onboarding").then(r => r.json()).then(d => { if (d.steps) setOnboarding(d) }).catch(() => {})
const interval = setInterval(fetchData, 15000)
return () => clearInterval(interval)
}, [fetchData])
@@ -55,22 +57,61 @@ export default function DashboardPage() {
)
}
if (!data) {
if (!data || (data.summary.totalPledges === 0 && onboarding && !onboarding.allDone)) {
// Show getting-started checklist
const ob = onboarding
return (
<div className="text-center py-20 space-y-4">
<Calendar className="h-12 w-12 text-muted-foreground mx-auto" />
<h2 className="text-xl font-bold">Welcome to Pledge Now, Pay Later</h2>
<p className="text-muted-foreground max-w-md mx-auto">
Start by configuring your organisation&apos;s bank details, then create your first event.
</p>
<div className="flex gap-3 justify-center">
<Link href="/dashboard/settings">
<Button variant="outline">Configure Bank Details</Button>
</Link>
<Link href="/dashboard/events">
<Button>Create First Event </Button>
</Link>
<div className="max-w-lg mx-auto space-y-6 py-4">
<div className="text-center space-y-2">
<div className="inline-flex h-14 w-14 rounded-2xl bg-gradient-to-br from-trust-blue to-blue-600 items-center justify-center shadow-lg shadow-trust-blue/20">
<span className="text-white text-2xl">🤲</span>
</div>
<h1 className="text-2xl font-black text-gray-900">Let&apos;s get you set up</h1>
<p className="text-sm text-muted-foreground">4 quick steps, then you&apos;re collecting pledges</p>
</div>
{ob && (
<>
<Progress value={(ob.completed / ob.total) * 100} className="h-2" indicatorClassName="bg-gradient-to-r from-trust-blue to-success-green" />
<p className="text-xs text-center text-muted-foreground">{ob.completed} of {ob.total} done</p>
<div className="space-y-2">
{ob.steps.map((step, i) => {
const isNext = !step.done && ob.steps.slice(0, i).every(s => s.done)
return (
<Link key={step.id} href={step.href}>
<div className={`flex items-center gap-3 rounded-xl border p-4 transition-all ${
step.done ? "bg-success-green/5 border-success-green/20" :
isNext ? "bg-trust-blue/5 border-trust-blue/20 shadow-sm" :
"bg-white border-gray-100"
}`}>
{step.done ? (
<CheckCircle2 className="h-5 w-5 text-success-green flex-shrink-0" />
) : isNext ? (
<div className="h-5 w-5 rounded-full bg-trust-blue text-white text-xs font-bold flex items-center justify-center flex-shrink-0">{i + 1}</div>
) : (
<Circle className="h-5 w-5 text-gray-300 flex-shrink-0" />
)}
<div className="flex-1">
<p className={`text-sm font-medium ${step.done ? "text-success-green line-through" : isNext ? "text-gray-900" : "text-gray-400"}`}>{step.label}</p>
<p className="text-xs text-muted-foreground">{step.desc}</p>
</div>
{isNext && <ArrowRight className="h-4 w-4 text-trust-blue flex-shrink-0" />}
</div>
</Link>
)
})}
</div>
</>
)}
{(!ob || ob.completed === 0) && (
<div className="bg-warm-amber/5 rounded-xl border border-warm-amber/20 p-4 text-center">
<p className="text-xs text-muted-foreground">
💡 <strong>Tip:</strong> Add your bank details first that&apos;s the only thing you need before donors can pledge.
</p>
</div>
)}
</div>
)
}