From 88933e9c13e7516cae1334d93b2c51f402055c86 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Felix=20F=C3=B6rtsch?= Date: Thu, 12 Mar 2026 14:43:14 +0100 Subject: [PATCH] rework onboarding as 3-screen wizard, mask PGlite download two intro screens explain the app purpose, third screen is the form. remove step selector, always start at neu. dot indicators for progress, "App wird vorbereitet" while DB loads. Co-Authored-By: Claude Opus 4.6 --- .../2026-03-12-onboarding-redesign-design.md | 38 ++ .../onboarding/components/onboarding-form.tsx | 326 ++++++++++-------- 2 files changed, 223 insertions(+), 141 deletions(-) create mode 100644 docs/superpowers/specs/2026-03-12-onboarding-redesign-design.md 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