feat: add plan selection onboarding screen after signup

This commit is contained in:
2026-09-13 21:30:58 +05:30
parent 5dc45bb90e
commit 2438ec55d4
3 changed files with 248 additions and 3 deletions

238
src/app/onboarding/page.tsx Normal file
View File

@@ -0,0 +1,238 @@
"use client";
import { useState } from "react";
import { useRouter } from "next/navigation";
import { motion } from "framer-motion";
import {
CheckCircle2,
Zap,
Brain,
BarChart3,
BookOpen,
MessageSquareText,
Trophy,
ArrowRight,
Sparkles,
Clock,
} from "lucide-react";
import { Logo } from "@/components/Logo";
const PRO_FEATURES = [
{ icon: BookOpen, text: "Unlimited Daily Practice Sheets" },
{ icon: BarChart3, text: "Weekly Test Series & Mock Tests" },
{ icon: Trophy, text: "Detailed Performance Analytics" },
{ icon: Zap, text: "Parent Dashboard Access" },
];
const AI_COACH_FEATURES = [
{ icon: BookOpen, text: "Everything in Pro" },
{ icon: Brain, text: "AI Spaced Repetition (Mistake Engine)" },
{ icon: MessageSquareText, text: "24/7 Streaming AI Tutor Chat" },
{ icon: Trophy, text: "Predictive Rank & College Predictor" },
];
export default function OnboardingPage() {
const router = useRouter();
const [selecting, setSelecting] = useState<"Pro" | "Al_Coach" | null>(null);
const handleSelect = async (plan: "Pro" | "Al_Coach") => {
setSelecting(plan);
// Save chosen plan preference to localStorage so dashboard can read it
if (typeof window !== "undefined") {
localStorage.setItem("chosen_plan", plan);
}
// Small UX delay so the button animation feels intentional
await new Promise((r) => setTimeout(r, 600));
router.push("/dashboard");
};
return (
<div className="min-h-screen bg-background flex flex-col items-center justify-center p-6 relative overflow-hidden selection:bg-primary/20">
{/* Background glows */}
<div className="pointer-events-none fixed inset-0 -z-10">
<div className="absolute top-0 left-1/4 size-[600px] bg-primary/5 blur-[140px] rounded-full" />
<div className="absolute bottom-0 right-1/4 size-[500px] bg-[#00F0FF]/5 blur-[120px] rounded-full" />
</div>
<div className="w-full max-w-5xl space-y-12 animate-in fade-in slide-in-from-bottom-6 duration-700">
{/* Logo */}
<div className="flex justify-center">
<Logo href="/" className="w-56" imageClassName="object-contain" height={64} />
</div>
{/* Header */}
<motion.div
initial={{ opacity: 0, y: 24 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.6 }}
className="text-center space-y-4"
>
<div className="inline-flex items-center gap-2 px-4 py-2 rounded-full bg-primary/10 border border-primary/20 text-primary font-bold text-sm mb-2">
<Sparkles size={14} className="animate-pulse" />
Account Created Successfully!
</div>
<h1 className="text-4xl md:text-5xl font-black tracking-tighter">
Choose Your Plan
</h1>
<p className="text-muted-foreground text-lg max-w-xl mx-auto font-medium">
Both plans include a{" "}
<span className="text-primary font-bold">15-day free trial</span>{" "}
with full AI Coach access — no card needed.
</p>
{/* Trial banner */}
<div className="inline-flex items-center gap-3 px-6 py-3 rounded-2xl bg-emerald-500/10 border border-emerald-500/20 text-emerald-400 font-semibold text-sm">
<Clock size={16} />
Your 15-day free trial with all features starts now — Day 1 of 15
</div>
</motion.div>
{/* Plan Cards */}
<div className="grid grid-cols-1 md:grid-cols-2 gap-6 max-w-4xl mx-auto">
{/* Pro Plan */}
<motion.div
initial={{ opacity: 0, x: -40 }}
animate={{ opacity: 1, x: 0 }}
transition={{ duration: 0.6, delay: 0.15, type: "spring" }}
>
<div
className="relative rounded-[2rem] p-[1px] h-full group cursor-pointer"
style={{
background:
"linear-gradient(135deg, rgba(234,179,8,0.4) 0%, rgba(234,179,8,0.08) 100%)",
}}
onClick={() => !selecting && handleSelect("Pro")}
>
<div className="bg-background rounded-[calc(2rem-1px)] p-7 flex flex-col h-full transition-all duration-300 group-hover:bg-secondary/20">
{/* Header */}
<div className="mb-6">
<div className="inline-flex items-center gap-2 px-3 py-1 rounded-full bg-yellow-500/10 border border-yellow-500/20 text-yellow-500 text-xs font-bold mb-3 uppercase tracking-wider">
<Zap size={12} /> Pro Plan
</div>
<div className="flex items-end gap-2 mb-2">
<span className="text-5xl font-black tracking-tight text-yellow-500">₹3,499</span>
<span className="text-muted-foreground text-base font-medium mb-2">/year</span>
</div>
<p className="text-muted-foreground text-sm font-medium">
Perfect for self-paced, structured practice.
</p>
</div>
{/* Features */}
<ul className="space-y-3 mb-8 flex-1">
{PRO_FEATURES.map(({ icon: Icon, text }) => (
<li key={text} className="flex items-center gap-3 text-sm font-medium">
<CheckCircle2 size={18} className="text-yellow-500 shrink-0" />
<span>{text}</span>
</li>
))}
</ul>
{/* CTA */}
<button
onClick={(e) => {
e.stopPropagation();
if (!selecting) handleSelect("Pro");
}}
disabled={!!selecting}
className="w-full h-12 rounded-xl bg-yellow-500 text-black font-bold text-base hover:bg-yellow-400 transition-all duration-300 shadow-[0_0_20px_-5px_rgba(234,179,8,0.5)] hover:shadow-[0_0_30px_-3px_rgba(234,179,8,0.7)] hover:-translate-y-0.5 disabled:opacity-70 disabled:cursor-not-allowed flex items-center justify-center gap-2"
>
{selecting === "Pro" ? (
<>
<span className="size-4 border-2 border-black/30 border-t-black rounded-full animate-spin" />
Starting Trial…
</>
) : (
<>
Select Pro & Start Trial
<ArrowRight size={16} />
</>
)}
</button>
</div>
</div>
</motion.div>
{/* AI Coach Plan */}
<motion.div
initial={{ opacity: 0, x: 40 }}
animate={{ opacity: 1, x: 0 }}
transition={{ duration: 0.6, delay: 0.25, type: "spring" }}
>
<div
className="relative rounded-[2rem] p-[1px] h-full group cursor-pointer"
style={{
background:
"linear-gradient(135deg, rgba(0,240,255,0.5) 0%, rgba(0,240,255,0.08) 100%)",
}}
onClick={() => !selecting && handleSelect("Al_Coach")}
>
{/* Most Popular Badge */}
<div className="absolute -top-4 left-1/2 -translate-x-1/2 z-10 inline-flex items-center gap-1.5 px-4 py-1.5 rounded-full bg-[#00F0FF] text-black text-xs font-black uppercase tracking-wider shadow-[0_0_20px_-3px_#00F0FF]">
<Sparkles size={12} /> Most Popular
</div>
<div className="bg-background rounded-[calc(2rem-1px)] p-7 flex flex-col h-full transition-all duration-300 group-hover:bg-secondary/20">
{/* Header */}
<div className="mb-6 pt-2">
<div className="inline-flex items-center gap-2 px-3 py-1 rounded-full bg-[#00F0FF]/10 border border-[#00F0FF]/20 text-[#00F0FF] text-xs font-bold mb-3 uppercase tracking-wider">
<Brain size={12} /> AI Coach Plan
</div>
<div className="flex items-end gap-2 mb-2">
<span className="text-5xl font-black tracking-tight text-[#00F0FF]">₹11,999</span>
<span className="text-muted-foreground text-base font-medium mb-2">/year</span>
</div>
<p className="text-muted-foreground text-sm font-medium">
The complete AI-driven preparation system.
</p>
</div>
{/* Features */}
<ul className="space-y-3 mb-8 flex-1">
{AI_COACH_FEATURES.map(({ icon: Icon, text }) => (
<li key={text} className="flex items-center gap-3 text-sm font-medium">
<CheckCircle2 size={18} className="text-[#00F0FF] shrink-0" />
<span>{text}</span>
</li>
))}
</ul>
{/* CTA */}
<button
onClick={(e) => {
e.stopPropagation();
if (!selecting) handleSelect("Al_Coach");
}}
disabled={!!selecting}
className="w-full h-12 rounded-xl bg-[#00F0FF] text-black font-bold text-base hover:bg-[#00F0FF]/90 transition-all duration-300 shadow-[0_0_20px_-5px_rgba(0,240,255,0.5)] hover:shadow-[0_0_30px_-3px_rgba(0,240,255,0.8)] hover:-translate-y-0.5 disabled:opacity-70 disabled:cursor-not-allowed flex items-center justify-center gap-2"
>
{selecting === "Al_Coach" ? (
<>
<span className="size-4 border-2 border-black/30 border-t-black rounded-full animate-spin" />
Starting Trial…
</>
) : (
<>
Select AI Coach & Start Trial
<ArrowRight size={16} />
</>
)}
</button>
</div>
</div>
</motion.div>
</div>
{/* Bottom note */}
<motion.p
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
transition={{ duration: 0.6, delay: 0.5 }}
className="text-center text-xs text-muted-foreground font-medium pb-8"
>
🔒 No credit card required for trial &nbsp;·&nbsp; Cancel anytime &nbsp;·&nbsp; All prices inclusive of GST
</motion.p>
</div>
</div>
);
}

View File

@@ -119,9 +119,9 @@ export default function SignupPage() {
You have full AI Coach access for your trial period. Let&apos;s build your streak starting today. You have full AI Coach access for your trial period. Let&apos;s build your streak starting today.
</p> </p>
</div> </div>
<Link href="/dashboard" className="w-full mt-4"> <Link href="/onboarding" className="w-full mt-4">
<Button className="w-full rounded-xl h-12 font-bold text-base bg-primary text-primary-foreground hover:bg-primary/90 transition-all shadow-[0_0_20px_-5px_var(--color-primary)]"> <Button className="w-full rounded-xl h-12 font-bold text-base bg-primary text-primary-foreground hover:bg-primary/90 transition-all shadow-[0_0_20px_-5px_var(--color-primary)]">
Start Day 1 → Go to Dashboard Choose Your Plan →
</Button> </Button>
</Link> </Link>
</CardContent> </CardContent>

View File

@@ -19,9 +19,16 @@ export function proxy(request: NextRequest) {
} }
} }
// Protect /onboarding route
if (path.startsWith('/onboarding')) {
if (!token) {
return NextResponse.redirect(new URL('/login', request.url));
}
}
return NextResponse.next(); return NextResponse.next();
} }
export const config = { export const config = {
matcher: ['/dashboard/:path*', '/admin/:path*'] matcher: ['/dashboard/:path*', '/admin/:path*', '/onboarding/:path*', '/onboarding']
}; };