diff --git a/docs/superpowers/specs/2026-03-12-onboarding-redesign-design.md b/docs/superpowers/specs/2026-03-12-onboarding-redesign-design.md new file mode 100644 index 0000000..7632d15 --- /dev/null +++ b/docs/superpowers/specs/2026-03-12-onboarding-redesign-design.md @@ -0,0 +1,38 @@ +# Onboarding Redesign (Issue #2, Sub-project 1) + +## Goal + +Replace the single-screen onboarding form with a 3-screen wizard that explains the app's purpose and masks the PGlite download time with static content. + +## Screen flow + +3 screens with dot progress indicators and a "Weiter" button. Forward-only navigation. + +### Screen 1 — Was ist TherapyFinder? + +Headline + 2-3 sentences: the app helps organize your path to therapy — tracking contacts, documenting rejections, preparing your Kostenerstattungsantrag. + +### Screen 2 — Warum dieses Tool? + +Headline + 2-3 sentences: the process is long and bureaucratic, this tool keeps track so you don't have to. Data stays on device. + +### Screen 3 — Über dich + +Form fields (no step selector): +- Name +- PLZ + Ort (grid, 2 columns) +- Krankenkasse (with GKV datalist autocomplete) +- "Weiter" button — creates nutzer with `aktueller_schritt = 'neu'`, navigates to `/prozess` +- Button shows "App wird vorbereitet…" if DB isn't ready yet + +### PGlite masking + +`getDb()` is already called on component mount. Screens 1-2 provide reading time. No changes to loading logic needed. + +### Implementation + +Rewrite `OnboardingForm` as a stateful wizard (`step` state: 0, 1, 2). Screens 0-1 are static JSX. Screen 2 is the form. Dot indicators at the bottom (3 dots, active one highlighted). Remove `aktueller_schritt` form field and its import of `PROZESS_SCHRITTE`. + +## Files changed + +- **Modify:** `src/features/onboarding/components/onboarding-form.tsx` — wizard with 3 screens diff --git a/src/features/onboarding/components/onboarding-form.tsx b/src/features/onboarding/components/onboarding-form.tsx index 68065cb..7db4fcf 100644 --- a/src/features/onboarding/components/onboarding-form.tsx +++ b/src/features/onboarding/components/onboarding-form.tsx @@ -1,22 +1,21 @@ import { useForm } from "@tanstack/react-form"; import { useNavigate } from "@tanstack/react-router"; -import { Loader2 } from "lucide-react"; +import { ClipboardList, Loader2, Lock, Route } from "lucide-react"; import { useEffect, useState } from "react"; import { Button } from "@/shared/components/ui/button"; import { Input } from "@/shared/components/ui/input"; import { Label } from "@/shared/components/ui/label"; import { getDb } from "@/shared/db/client"; -import type { ProzessSchritt } from "@/shared/db/schema"; import { dbExec } from "@/shared/hooks/use-db"; -import { PROZESS_SCHRITTE } from "@/shared/lib/constants"; import { GKV_KASSEN } from "@/shared/lib/gkv-kassen"; +import { cn } from "@/shared/lib/utils"; export function OnboardingForm() { const navigate = useNavigate(); + const [step, setStep] = useState(0); const [dbReady, setDbReady] = useState(false); const [submitting, setSubmitting] = useState(false); - // Pre-warm DB on mount so submit is instant useEffect(() => { getDb().then(() => setDbReady(true)); }, []); @@ -27,21 +26,14 @@ export function OnboardingForm() { plz: "", ort: "", krankenkasse: "", - aktueller_schritt: "neu" as ProzessSchritt, }, onSubmit: async ({ value }) => { setSubmitting(true); try { await dbExec( `INSERT INTO nutzer (name, plz, ort, krankenkasse, aktueller_schritt) - VALUES ($1, $2, $3, $4, $5)`, - [ - value.name, - value.plz, - value.ort, - value.krankenkasse, - value.aktueller_schritt, - ], + VALUES ($1, $2, $3, $4, 'neu')`, + [value.name, value.plz, value.ort, value.krankenkasse], ); navigate({ to: "/prozess" }); } finally { @@ -55,141 +47,193 @@ export function OnboardingForm() { return (
-
-

Willkommen bei TherapyFinder

-

- Erzähl uns ein wenig über dich, damit wir dich auf deinem Weg zur - Therapie unterstützen können. + {step === 0 && } + {step === 1 && } + {step === 2 && ( + <> +

+

Über dich

+

+ Diese Angaben helfen uns, deinen Kostenerstattungsantrag + vorzubereiten. +

+
+ +
{ + e.preventDefault(); + e.stopPropagation(); + form.handleSubmit(); + }} + className="space-y-4" + > + + {(field) => ( +
+ + field.handleChange(e.target.value)} + placeholder="Max Mustermann" + /> + +
+ )} +
+ +
+ + {(field) => ( +
+ + field.handleChange(e.target.value)} + placeholder="10115" + inputMode="numeric" + maxLength={5} + /> + +
+ )} +
+ + + {(field) => ( +
+ + field.handleChange(e.target.value)} + placeholder="Berlin" + /> + +
+ )} +
+
+ + + {(field) => ( +
+ + field.handleChange(e.target.value)} + placeholder="z.B. Techniker Krankenkasse" + /> + + {GKV_KASSEN.map((k) => ( + + +
+ )} +
+ + +
+ + )} + + {step < 2 && ( + + )} + + +
+
+ ); +} + +function IntroScreen1() { + return ( +
+
+ +
+

Willkommen bei TherapyFinder

+

+ TherapyFinder begleitet dich auf dem Weg zur Psychotherapie. Die App + hilft dir, Kontaktversuche zu dokumentieren, Absagen festzuhalten und + deinen Kostenerstattungsantrag vorzubereiten. +

+
+
+ +

+ Verwalte deine Therapeutensuche an einem Ort — vom Erstgespräch bis + zum fertigen Antrag.

- -
{ - e.preventDefault(); - e.stopPropagation(); - form.handleSubmit(); - }} - className="space-y-4" - > - - {(field) => ( -
- - field.handleChange(e.target.value)} - placeholder="Max Mustermann" - /> - -
- )} -
- -
- - {(field) => ( -
- - field.handleChange(e.target.value)} - placeholder="10115" - inputMode="numeric" - maxLength={5} - /> - -
- )} -
- - - {(field) => ( -
- - field.handleChange(e.target.value)} - placeholder="Berlin" - /> - -
- )} -
-
- - - {(field) => ( -
- - field.handleChange(e.target.value)} - placeholder="z.B. Techniker Krankenkasse" - /> - - {GKV_KASSEN.map((k) => ( - - -
- )} -
- - - {(field) => ( -
- - - -
- )} -
- - -
); } +function IntroScreen2() { + return ( +
+
+ +
+

Dein Begleiter im Prozess

+

+ Einen Therapieplatz zu finden dauert oft Wochen oder Monate. Der Weg ist + bürokratisch und braucht Ausdauer. TherapyFinder behält den Überblick, + damit du dich auf das Wesentliche konzentrieren kannst. +

+

+ Alle Daten bleiben auf deinem Gerät — es gibt keine Accounts und keine + Cloud. +

+
+ ); +} + +function DotIndicator({ current }: { current: number }) { + const dots = ["intro", "motivation", "form"] as const; + return ( +
+ {dots.map((id, i) => ( +
+ ))} +
+ ); +} + function FieldErrors({ errors }: { errors: readonly unknown[] }) { if (errors.length === 0) return null; const messages = errors