From 4aa47ca61de09963d00da9fd7194d3804de67d91 Mon Sep 17 00:00:00 2001 From: Arnaud Date: Fri, 24 Jul 2026 11:46:07 +0200 Subject: [PATCH] feat: post-signup onboarding wizard (dietary/allergen prefs, notifications, skippable) (v0.77.0) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- CHANGELOG.md | 8 + FEATURE_AUDIT.md | 1 + apps/web/app/(app)/layout.tsx | 15 +- .../app/api/v1/users/me/onboarding/route.ts | 75 + apps/web/app/onboarding/page.tsx | 22 + .../onboarding/onboarding-wizard.tsx | 161 + apps/web/components/ui/checkbox.tsx | 29 + apps/web/lib/changelog.ts | 12 +- apps/web/lib/openapi.ts | 14 + apps/web/messages/en.json | 33 +- apps/web/messages/fr.json | 33 +- apps/web/package.json | 2 +- package.json | 2 +- .../db/src/migrations/0064_thick_drax.sql | 5 + .../db/src/migrations/meta/0064_snapshot.json | 6196 +++++++++++++++++ packages/db/src/migrations/meta/_journal.json | 7 + packages/db/src/schema/users.ts | 19 + 17 files changed, 6628 insertions(+), 6 deletions(-) create mode 100644 apps/web/app/api/v1/users/me/onboarding/route.ts create mode 100644 apps/web/app/onboarding/page.tsx create mode 100644 apps/web/components/onboarding/onboarding-wizard.tsx create mode 100644 apps/web/components/ui/checkbox.tsx create mode 100644 packages/db/src/migrations/0064_thick_drax.sql create mode 100644 packages/db/src/migrations/meta/0064_snapshot.json diff --git a/CHANGELOG.md b/CHANGELOG.md index 4dcee7b..6569c1e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,14 @@ All notable changes to Epicure are documented here. This file is mirrored in-app at `/changelog` (and in the admin dashboard) via `apps/web/lib/changelog.ts` — update both together. +## 0.77.0 — 2026-07-24 13:00 + +### Added +- New post-signup onboarding wizard: a quick 3-step flow (welcome, dietary preferences & allergens, notification opt-in) shown once for new accounts, skippable at every step. Existing accounts are unaffected — only new signups see it. + +### Fixed +- Allergen preferences (the `user_allergens` table) had a GDPR-export read path but no way to actually set them — the onboarding wizard's dietary/allergen step is the first UI that writes to it. + ## 0.76.0 — 2026-07-24 12:15 ### Added diff --git a/FEATURE_AUDIT.md b/FEATURE_AUDIT.md index bdf7b1e..aa979b4 100644 --- a/FEATURE_AUDIT.md +++ b/FEATURE_AUDIT.md @@ -71,6 +71,7 @@ Status legend: **Exists** (fully working) · **Partial** (works but with a real | Auto-deduct pantry on cook | Exists (closed 2026-07-24) | Both UI callers that previously hardcoded `deductFromPantry: false` (`batch-cook-dishes.tsx`, `meal-planner.tsx`) now pass `true`. The new general "Mark cooked" feature (see below) additionally exposes it as a per-cook checkbox, default checked, rather than a silent always-on. | `apps/web/app/api/v1/recipes/[id]/cooked/route.ts`, `apps/web/components/recipe/batch-cook-dishes.tsx`, `apps/web/components/meal-plan/meal-planner.tsx` | | Mark recipe as cooked (any recipe, not just batch-cook) | Exists (new, 2026-07-24) | A recipe can be logged as cooked any number of times — each log is a new `cookingHistory` row, no unique constraint, this was already true at the schema level, just not exposed for plain (non-batch) recipes before. New dialog on the recipe page: date (defaults today, can backdate), servings, and a deduct-from-pantry toggle (default on). Shows a "cooked N times · last DATE" indicator once logged. | `apps/web/components/recipe/{mark-cooked-dialog,mark-cooked-section}.tsx`, `apps/web/app/api/v1/recipes/[id]/cooked/route.ts` (`cookedAt` field, new) | | Billing/invoice details (full name, address, phone) | Exists (new, 2026-07-24) | New `userBillingDetails` table (1:1 with `users`), separate from the display `name` field. `fullName` is required by the form/API but nullable at the DB level (no backfill for existing users); address lines/city/postal code/country/phone are all optional. Lives under `Settings → Billing`, feeds future invoice generation — not wired into Stripe yet. | `packages/db/src/schema/billing.ts` (`userBillingDetails`), `apps/web/app/api/v1/users/me/billing-details/route.ts`, `apps/web/components/settings/billing-details-form.tsx` | +| Post-signup onboarding wizard | Exists (new, 2026-07-24) | 3-step wizard (welcome → dietary/allergen prefs → notification opt-in) shown once per account, right after the app shell layout loads for a user with no `onboardingCompletedAt`. Skippable at every step; existing accounts were backfilled as already-onboarded so the wizard only appears for new signups. Also closes a real pre-existing gap: `userAllergens` had a table and a GDPR-export read, but **no write path at all** — this ships the first one. Dietary tags are a new `users.dietaryTags` jsonb column (mirrors `recipes.dietaryTags`'s 7-tag shape); neither is yet consumed by AI generation or recipe matching. | `apps/web/app/onboarding/page.tsx`, `apps/web/components/onboarding/onboarding-wizard.tsx`, `apps/web/app/api/v1/users/me/onboarding/route.ts`, `apps/web/app/(app)/layout.tsx` (redirect gate) | | Expiring-soon tracking | Exists | With "use it up" recipe suggestions. The leftover-expiry push/email cron only covers batch-cook dishes — plain pantry items only get an in-app badge, no reminder. | `apps/web/components/pantry/expiring-soon-suggestions.tsx`, `apps/web/app/api/internal/cron/leftover-reminders/route.ts` | | **Barcode scan (pantry)** | **Exists** | Real Open Food Facts API lookup by UPC/EAN | `apps/web/app/api/v1/pantry/scan/barcode` | | Photo scan (pantry) | Exists | Vision-model based | `apps/web/app/api/v1/pantry/scan/photo` | diff --git a/apps/web/app/(app)/layout.tsx b/apps/web/app/(app)/layout.tsx index 4439a4f..d53b6c3 100644 --- a/apps/web/app/(app)/layout.tsx +++ b/apps/web/app/(app)/layout.tsx @@ -1,6 +1,19 @@ +import { headers } from "next/headers"; +import { redirect } from "next/navigation"; import { Nav } from "@/components/layout/nav"; +import { auth } from "@/lib/auth/server"; +import { db, users, eq } from "@epicure/db"; + +export default async function AppLayout({ children }: { children: React.ReactNode }) { + const session = await auth.api.getSession({ headers: await headers() }); + if (session) { + const dbUser = await db.query.users.findFirst({ + where: eq(users.id, session.user.id), + columns: { onboardingCompletedAt: true }, + }); + if (!dbUser?.onboardingCompletedAt) redirect("/onboarding"); + } -export default function AppLayout({ children }: { children: React.ReactNode }) { return ( <>