feat: add onboarding flow and test data

This commit is contained in:
Manohar Gupta 2026-05-10 04:23:32 +05:30
parent 507487c7d9
commit 6740ee338b
2 changed files with 202 additions and 0 deletions

View file

@ -0,0 +1,63 @@
import { NextResponse } from "next/server";
import { db } from "@/db";
import { auth } from "@/auth";
import { families, familyMembers, children } from "@/db/schema/family";
import { users } from "@/db/schema/auth";
import { eq } from "drizzle-orm";
export async function POST(request: Request) {
const session = await auth();
if (!session?.user?.email) {
return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
}
const body = await request.json();
const { familyName, memberName, childName, birthDate, sex } = body;
// Get or create user
let user = await db.query.users.findFirst({
where: eq(users.email, session.user.email),
});
if (!user) {
return NextResponse.json({ error: "User not found" }, { status: 404 });
}
// Create family
const [family] = await db
.insert(families)
.values({
name: familyName || "The Family",
})
.returning();
// Add member
await db.insert(familyMembers).values({
familyId: family.id,
userId: user.id,
role: "admin",
displayName: memberName,
});
// Calculate stage
const birth = new Date(birthDate);
const months = Math.floor((Date.now() - birth.getTime()) / (1000 * 60 * 60 * 24 * 30));
let stage: "newborn" | "infant" | "solids_start" | "toddler_early" | "toddler_late" | "preschool" = "newborn";
if (months >= 36) stage = "preschool";
else if (months >= 24) stage = "toddler_late";
else if (months >= 12) stage = "toddler_early";
else if (months >= 6) stage = "solids_start";
else if (months >= 3) stage = "infant";
// Create child
await db.insert(children).values({
familyId: family.id,
name: childName,
birthDate: birth,
sex,
currentStage: stage,
});
return NextResponse.json({ success: true });
}

139
src/app/onboarding/page.tsx Normal file
View file

@ -0,0 +1,139 @@
"use client";
import { useState } from "react";
import { useRouter } from "next/navigation";
export default function OnboardingPage() {
const router = useRouter();
const [step, setStep] = useState(1);
const [loading, setLoading] = useState(false);
const [form, setForm] = useState({
familyName: "",
memberName: "",
childName: "",
birthDate: "",
sex: "" as "male" | "female" | "other",
});
const handleSubmit = async () => {
setLoading(true);
try {
const res = await fetch("/api/onboarding", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(form),
});
if (res.ok) {
router.push("/");
}
} catch (e) {
console.error(e);
}
setLoading(false);
};
return (
<div className="min-h-screen flex items-center justify-center bg-gradient-to-br from-rose-50 to-amber-50 p-4">
<div className="w-full max-w-md">
<div className="text-center mb-8">
<span className="text-4xl">👶</span>
<h1 className="text-2xl font-bold mt-4">Welcome to Tia</h1>
<p className="text-gray-600">Let's set up your family</p>
</div>
<div className="bg-white rounded-3xl shadow-lg p-6 space-y-4">
{step === 1 && (
<>
<label className="block">
<span className="text-sm font-medium">Family Name</span>
<input
type="text"
value={form.familyName}
onChange={(e) => setForm({ ...form, familyName: e.target.value })}
placeholder="The Gupta Family"
className="w-full p-3 border rounded-xl mt-1"
/>
</label>
<label className="block">
<span className="text-sm font-medium">Your Name</span>
<input
type="text"
value={form.memberName}
onChange={(e) => setForm({ ...form, memberName: e.target.value })}
placeholder="Mama"
className="w-full p-3 border rounded-xl mt-1"
/>
</label>
<button
onClick={() => setStep(2)}
disabled={!form.familyName || !form.memberName}
className="w-full p-3 bg-rose-400 text-white rounded-xl font-medium disabled:opacity-50"
>
Next
</button>
</>
)}
{step === 2 && (
<>
<label className="block">
<span className="text-sm font-medium">Baby's Name</span>
<input
type="text"
value={form.childName}
onChange={(e) => setForm({ ...form, childName: e.target.value })}
placeholder="Tia"
className="w-full p-3 border rounded-xl mt-1"
/>
</label>
<label className="block">
<span className="text-sm font-medium">Birth Date</span>
<input
type="date"
value={form.birthDate}
onChange={(e) => setForm({ ...form, birthDate: e.target.value })}
className="w-full p-3 border rounded-xl mt-1"
/>
</label>
<label className="block">
<span className="text-sm font-medium">Sex</span>
<div className="flex gap-2 mt-1">
{(["male", "female", "other"] as const).map((s) => (
<button
key={s}
type="button"
onClick={() => setForm({ ...form, sex: s })}
className={`flex-1 p-3 rounded-xl border capitalize ${
form.sex === s
? "bg-rose-400 text-white border-rose-400"
: "bg-white"
}`}
>
{s}
</button>
))}
</div>
</label>
<div className="flex gap-2">
<button
type="button"
onClick={() => setStep(1)}
className="flex-1 p-3 border rounded-xl font-medium"
>
Back
</button>
<button
onClick={handleSubmit}
disabled={!form.childName || !form.birthDate || !form.sex || loading}
className="flex-1 p-3 bg-rose-400 text-white rounded-xl font-medium disabled:opacity-50"
>
{loading ? "Creating..." : "Get Started"}
</button>
</div>
</>
)}
</div>
</div>
</div>
);
}