4aa47ca61d
New accounts land on a 3-step wizard after signup — welcome, dietary preferences & allergens, notification opt-in — skippable at every step, shown once via a users.onboardingCompletedAt gate in the (app) layout. Existing accounts are backfilled as already-onboarded. Also gives the allergen-preferences step a real write path: user_allergens previously only had a GDPR-export read. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
76 lines
2.4 KiB
TypeScript
76 lines
2.4 KiB
TypeScript
import { NextRequest, NextResponse } from "next/server";
|
|
import { db, users, userAllergens, eq } from "@epicure/db";
|
|
import { requireSession } from "@/lib/api-auth";
|
|
import { z } from "zod";
|
|
|
|
const ALLERGEN_TAGS = ["peanuts", "treeNuts", "dairy", "eggs", "shellfish", "fish", "soy", "gluten"] as const;
|
|
|
|
const CompleteSchema = z.object({
|
|
dietaryTags: z
|
|
.object({
|
|
vegan: z.boolean().optional(),
|
|
vegetarian: z.boolean().optional(),
|
|
glutenFree: z.boolean().optional(),
|
|
dairyFree: z.boolean().optional(),
|
|
nutFree: z.boolean().optional(),
|
|
halal: z.boolean().optional(),
|
|
kosher: z.boolean().optional(),
|
|
})
|
|
.optional(),
|
|
allergens: z.array(z.enum(ALLERGEN_TAGS)).optional(),
|
|
});
|
|
|
|
export async function GET() {
|
|
const { session, response } = await requireSession();
|
|
if (response) return response;
|
|
|
|
const [dbUser, allergenRows] = await Promise.all([
|
|
db.query.users.findFirst({
|
|
where: eq(users.id, session!.user.id),
|
|
columns: { onboardingCompletedAt: true, dietaryTags: true },
|
|
}),
|
|
db.select().from(userAllergens).where(eq(userAllergens.userId, session!.user.id)),
|
|
]);
|
|
|
|
return NextResponse.json({
|
|
data: {
|
|
completed: !!dbUser?.onboardingCompletedAt,
|
|
dietaryTags: dbUser?.dietaryTags ?? {},
|
|
allergens: allergenRows.map((r) => r.allergenTag),
|
|
},
|
|
});
|
|
}
|
|
|
|
// Marks onboarding done regardless of what (if anything) was filled in — a
|
|
// user can skip every step and this still fires so the wizard never shows
|
|
// again. dietaryTags/allergens are optional and only written if provided.
|
|
export async function POST(req: NextRequest) {
|
|
const { session, response } = await requireSession();
|
|
if (response) return response;
|
|
|
|
const parsed = CompleteSchema.safeParse(await req.json().catch(() => ({})));
|
|
if (!parsed.success) {
|
|
return NextResponse.json({ error: parsed.error.flatten() }, { status: 400 });
|
|
}
|
|
|
|
const { dietaryTags, allergens } = parsed.data;
|
|
const userId = session!.user.id;
|
|
|
|
await db
|
|
.update(users)
|
|
.set({
|
|
onboardingCompletedAt: new Date(),
|
|
...(dietaryTags ? { dietaryTags } : {}),
|
|
})
|
|
.where(eq(users.id, userId));
|
|
|
|
if (allergens) {
|
|
await db.delete(userAllergens).where(eq(userAllergens.userId, userId));
|
|
if (allergens.length > 0) {
|
|
await db.insert(userAllergens).values(allergens.map((allergenTag) => ({ userId, allergenTag })));
|
|
}
|
|
}
|
|
|
|
return NextResponse.json({ ok: true });
|
|
}
|