From 39060c4e8e886f97c94de4fee7b89835d9381a2b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Felix=20F=C3=B6rtsch?= Date: Wed, 11 Mar 2026 11:11:16 +0100 Subject: [PATCH] fix onboarding form type errors, remove unused imports Co-Authored-By: Claude Opus 4.6 --- .../plans/2026-03-11-therapyfinder-v1.md | 1249 +++++++++++++++++ .../onboarding/components/onboarding-form.tsx | 22 +- 2 files changed, 1259 insertions(+), 12 deletions(-) create mode 100644 docs/superpowers/plans/2026-03-11-therapyfinder-v1.md diff --git a/docs/superpowers/plans/2026-03-11-therapyfinder-v1.md b/docs/superpowers/plans/2026-03-11-therapyfinder-v1.md new file mode 100644 index 0000000..c014510 --- /dev/null +++ b/docs/superpowers/plans/2026-03-11-therapyfinder-v1.md @@ -0,0 +1,1249 @@ +# TherapyFinder V1 Implementation Plan + +> **For agentic workers:** REQUIRED: Use superpowers:subagent-driven-development (if subagents available) or superpowers:executing-plans to implement this plan. Steps use checkbox (`- [ ]`) syntax for tracking. + +**Goal:** Build a local-first PWA that guides GKV-insured users through the psychotherapy access process — from first Sprechstunde to Kostenerstattungsantrag — with structured contact/rejection tracking and PDF export. + +**Architecture:** Local-first PWA with all data stored in PGlite (IndexedDB). No backend for V1 — sensitive health data never leaves the device. The app is a multi-step process guide with a contact tracker that produces exportable rejection documentation. Therapist directory integration is deferred to V2 (data source still open). + +**Tech Stack:** Bun, Vite, React 19, TanStack Router (file-based), shadcn/ui + Tailwind CSS v4, PGlite, Zustand, TanStack Form + Zod, vite-plugin-pwa, Vitest, Playwright, Biome + +--- + +## Scope + +**In scope (V1):** +- Project scaffolding and tooling +- PGlite database with schema and migrations +- Onboarding flow (capture insurance info, current process step) +- Process stepper dashboard (5 phases, interactive checklist) +- Contact tracker (log therapist contacts, outcomes, notes) +- Rejection list PDF export (for Kostenerstattungsantrag) +- Kostenerstattungs-Assistent (checklist + guidance) +- PWA (offline-capable, installable) + +**Deferred (V2+):** +- Therapist directory / KV-Verzeichnis integration +- Push notifications / reminders +- Multi-language support (i18n) +- Backend / cloud sync + +--- + +## File Structure + +``` +tpf/ +├── .mise.toml +├── biome.json +├── package.json +├── tsconfig.json +├── vite.config.ts +├── index.html +├── public/ +│ ├── manifest.webmanifest +│ └── icons/ +│ ├── icon-192.png +│ └── icon-512.png +├── src/ +│ ├── main.tsx +│ ├── app.tsx +│ ├── routes/ +│ │ ├── __root.tsx +│ │ ├── index.tsx # redirects to /prozess or /onboarding +│ │ ├── onboarding/ +│ │ │ └── index.tsx # Onboarding form +│ │ ├── prozess/ +│ │ │ └── index.tsx # Process stepper dashboard +│ │ ├── kontakte/ +│ │ │ ├── index.tsx # Contact list +│ │ │ └── neu.tsx # Add/edit contact +│ │ └── antrag/ +│ │ └── index.tsx # Kostenerstattungs-Assistent +│ ├── features/ +│ │ ├── onboarding/ +│ │ │ ├── components/ +│ │ │ │ └── onboarding-form.tsx +│ │ │ ├── schema.ts +│ │ │ └── index.ts +│ │ ├── prozess/ +│ │ │ ├── components/ +│ │ │ │ ├── process-stepper.tsx +│ │ │ │ └── phase-card.tsx +│ │ │ ├── schema.ts +│ │ │ ├── hooks.ts +│ │ │ └── index.ts +│ │ ├── kontakte/ +│ │ │ ├── components/ +│ │ │ │ ├── contact-form.tsx +│ │ │ │ ├── contact-list.tsx +│ │ │ │ └── contact-card.tsx +│ │ │ ├── schema.ts +│ │ │ ├── hooks.ts +│ │ │ └── index.ts +│ │ └── antrag/ +│ │ ├── components/ +│ │ │ ├── antrag-checklist.tsx +│ │ │ └── pdf-export-button.tsx +│ │ ├── schema.ts +│ │ ├── pdf.ts # PDF generation logic +│ │ └── index.ts +│ └── shared/ +│ ├── components/ +│ │ └── ui/ # shadcn/ui components (generated) +│ ├── hooks/ +│ │ └── use-db.ts # PGlite query hook +│ ├── db/ +│ │ ├── client.ts # PGlite singleton +│ │ ├── migrations/ +│ │ │ └── 001_init.sql +│ │ └── schema.ts # Zod schemas mirroring DB tables +│ └── lib/ +│ ├── utils.ts # cn() and helpers +│ └── constants.ts # Process phases, enum labels +├── e2e/ +│ ├── onboarding.spec.ts +│ └── kontakte.spec.ts +└── scripts/ + └── deploy.sh +``` + +--- + +## Chunk 1: Project Scaffolding + +### Task 1: Initialize project with Vite + React + TypeScript + +**Files:** +- Create: `.mise.toml` +- Create: `package.json` (via `bun create`) +- Create: `tsconfig.json` +- Create: `vite.config.ts` +- Create: `index.html` +- Create: `src/main.tsx` +- Create: `biome.json` + +- [ ] **Step 1: Create `.mise.toml`** + +```toml +[tools] +bun = "1.2.5" +``` + +- [ ] **Step 2: Install Bun via mise** + +Run: `mise install` + +- [ ] **Step 3: Scaffold Vite project** + +Run: `bun create vite . --template react-ts` + +Accept overwrite prompts for existing files. This generates `package.json`, `tsconfig.json`, `vite.config.ts`, `index.html`, `src/main.tsx`, `src/App.tsx`, etc. + +- [ ] **Step 4: Install dependencies** + +```bash +bun add react@latest react-dom@latest +bun add -d @types/react @types/react-dom typescript vite @vitejs/plugin-react +``` + +- [ ] **Step 5: Configure Biome** + +Create `biome.json`: +```json +{ + "$schema": "https://biomejs.dev/schemas/2.0.0-beta.1/schema.json", + "organizeImports": { + "enabled": true + }, + "formatter": { + "indentStyle": "tab" + }, + "linter": { + "enabled": true, + "rules": { + "recommended": true + } + } +} +``` + +Run: `bun add -d @biomejs/biome` + +- [ ] **Step 6: Add scripts to `package.json`** + +Ensure these scripts exist: +```json +{ + "scripts": { + "dev": "vite", + "build": "tsc && vite build", + "preview": "vite preview", + "test": "vitest run", + "e2e": "playwright test", + "lint": "biome check .", + "format": "biome check --write ." + } +} +``` + +- [ ] **Step 7: Verify dev server starts** + +Run: `bun dev` — should open on `http://localhost:5173` with the default Vite template. + +- [ ] **Step 8: Initialize git repo and commit** + +```bash +git init +``` + +Add `.gitignore` with: `node_modules/`, `dist/`, `.env`, `.env.local`, `.mise.local.toml` + +```bash +git add -A && git commit -m "scaffold vite + react + typescript project" +``` + +--- + +### Task 2: Add TanStack Router (file-based routing) + +**Files:** +- Modify: `vite.config.ts` +- Modify: `src/main.tsx` +- Create: `src/app.tsx` +- Create: `src/routes/__root.tsx` +- Create: `src/routes/index.tsx` + +- [ ] **Step 1: Install TanStack Router** + +```bash +bun add @tanstack/react-router @tanstack/router-plugin +``` + +- [ ] **Step 2: Configure Vite plugin** + +Update `vite.config.ts`: +```ts +import { TanStackRouterVite } from "@tanstack/router-plugin/vite" +import react from "@vitejs/plugin-react" +import { defineConfig } from "vite" + +export default defineConfig({ + plugins: [TanStackRouterVite(), react()], +}) +``` + +- [ ] **Step 3: Create root layout** + +Create `src/routes/__root.tsx`: +```tsx +import { Outlet, createRootRoute } from "@tanstack/react-router" + +export const Route = createRootRoute({ + component: () => ( +
+
+ +
+
+ ), +}) +``` + +- [ ] **Step 4: Create index route** + +Create `src/routes/index.tsx`: +```tsx +import { createFileRoute } from "@tanstack/react-router" + +export const Route = createFileRoute("/")({ + component: () =>

TherapyFinder

, +}) +``` + +- [ ] **Step 5: Create app entry** + +Create `src/app.tsx`: +```tsx +import { RouterProvider, createRouter } from "@tanstack/react-router" +import { routeTree } from "./routeTree.gen" + +const router = createRouter({ routeTree }) + +declare module "@tanstack/react-router" { + interface Register { + router: typeof router + } +} + +export function App() { + return +} +``` + +- [ ] **Step 6: Update `src/main.tsx`** + +```tsx +import { StrictMode } from "react" +import { createRoot } from "react-dom/client" +import { App } from "./app" +import "./index.css" + +createRoot(document.getElementById("root")!).render( + + + , +) +``` + +- [ ] **Step 7: Delete unused template files** + +Remove: `src/App.tsx`, `src/App.css`, `src/assets/` (Vite template leftovers). + +- [ ] **Step 8: Verify routing works** + +Run: `bun dev` — navigate to `/` and confirm "TherapyFinder" heading appears. + +- [ ] **Step 9: Commit** + +```bash +git add -A && git commit -m "add tanstack router with file-based routing" +``` + +--- + +### Task 3: Add Tailwind CSS v4 + shadcn/ui + +**Files:** +- Modify: `package.json` +- Modify: `src/index.css` +- Create: `src/shared/lib/utils.ts` +- Create: `src/shared/components/ui/` (generated by shadcn) + +- [ ] **Step 1: Install Tailwind CSS v4** + +```bash +bun add tailwindcss @tailwindcss/vite +``` + +Update `vite.config.ts` to add the Tailwind plugin: +```ts +import tailwindcss from "@tailwindcss/vite" +import { TanStackRouterVite } from "@tanstack/router-plugin/vite" +import react from "@vitejs/plugin-react" +import { defineConfig } from "vite" + +export default defineConfig({ + plugins: [TanStackRouterVite(), react(), tailwindcss()], +}) +``` + +- [ ] **Step 2: Configure CSS entry** + +Replace `src/index.css` contents with: +```css +@import "tailwindcss"; +``` + +- [ ] **Step 3: Install and configure shadcn/ui** + +Run: `bunx shadcn@latest init` + +Select: New York style, Zinc color, CSS variables: yes. + +This creates `components.json` and sets up the `src/shared/components/ui/` directory. + +- [ ] **Step 4: Create `cn()` utility** + +Create `src/shared/lib/utils.ts`: +```ts +import { type ClassValue, clsx } from "clsx" +import { twMerge } from "tailwind-merge" + +export function cn(...inputs: ClassValue[]) { + return twMerge(clsx(inputs)) +} +``` + +Run: `bun add clsx tailwind-merge` + +- [ ] **Step 5: Add initial shadcn components** + +```bash +bunx shadcn@latest add button card badge progress separator +``` + +- [ ] **Step 6: Verify styling works** + +Update `src/routes/index.tsx` to use a Button, run `bun dev`, confirm styled button renders. + +- [ ] **Step 7: Commit** + +```bash +git add -A && git commit -m "add tailwind css v4, shadcn/ui with initial components" +``` + +--- + +### Task 4: Add PGlite (local database) + +**Files:** +- Create: `src/shared/db/client.ts` +- Create: `src/shared/db/migrations/001_init.sql` +- Create: `src/shared/db/schema.ts` +- Create: `src/shared/hooks/use-db.ts` + +- [ ] **Step 1: Install PGlite** + +Run: `bun add @electric-sql/pglite` + +- [ ] **Step 2: Create PGlite singleton** + +Create `src/shared/db/client.ts`: +```ts +import { PGlite } from "@electric-sql/pglite" + +let _db: PGlite | null = null + +export async function getDb(): Promise { + if (!_db) { + _db = new PGlite("idb://therapyfinder") + await runMigrations(_db) + } + return _db +} + +async function runMigrations(db: PGlite): Promise { + await db.exec(` + CREATE TABLE IF NOT EXISTS _migrations ( + id SERIAL PRIMARY KEY, + name TEXT NOT NULL UNIQUE, + applied_at TIMESTAMPTZ NOT NULL DEFAULT NOW() + ) + `) + + const migrations = import.meta.glob("./migrations/*.sql", { + query: "?raw", + import: "default", + }) + + const sortedPaths = Object.keys(migrations).sort() + + for (const path of sortedPaths) { + const name = path.split("/").pop()! + const result = await db.query( + "SELECT 1 FROM _migrations WHERE name = $1", + [name], + ) + if (result.rows.length > 0) continue + + const sql = (await migrations[path]()) as string + await db.exec(sql) + await db.query( + "INSERT INTO _migrations (name) VALUES ($1)", + [name], + ) + } +} +``` + +- [ ] **Step 3: Create initial migration** + +Create `src/shared/db/migrations/001_init.sql`: +```sql +-- User profile (single row per device) +CREATE TABLE nutzer ( + id SERIAL PRIMARY KEY, + name TEXT, + plz TEXT, + ort TEXT, + krankenkasse TEXT, + aktueller_schritt TEXT NOT NULL DEFAULT 'neu', + dringlichkeitscode BOOLEAN NOT NULL DEFAULT FALSE, + dringlichkeitscode_datum DATE, + tss_beantragt BOOLEAN NOT NULL DEFAULT FALSE, + tss_beantragt_datum DATE, + erstellt_am TIMESTAMPTZ NOT NULL DEFAULT NOW(), + aktualisiert_am TIMESTAMPTZ NOT NULL DEFAULT NOW() +); + +-- Therapist contacts +CREATE TABLE therapeut ( + id SERIAL PRIMARY KEY, + name TEXT NOT NULL, + adresse TEXT, + plz TEXT, + stadt TEXT, + telefon TEXT, + email TEXT, + website TEXT, + therapieform TEXT, + kassenzulassung TEXT DEFAULT 'gkv', + erstellt_am TIMESTAMPTZ NOT NULL DEFAULT NOW() +); + +-- Contact attempts +CREATE TABLE kontakt ( + id SERIAL PRIMARY KEY, + therapeut_id INTEGER NOT NULL REFERENCES therapeut(id) ON DELETE CASCADE, + datum DATE NOT NULL DEFAULT CURRENT_DATE, + kanal TEXT NOT NULL DEFAULT 'telefon', + ergebnis TEXT NOT NULL DEFAULT 'keine_antwort', + notiz TEXT, + antwort_datum DATE, + erstellt_am TIMESTAMPTZ NOT NULL DEFAULT NOW() +); + +-- Sprechstunden visits +CREATE TABLE sprechstunde ( + id SERIAL PRIMARY KEY, + therapeut_id INTEGER NOT NULL REFERENCES therapeut(id) ON DELETE CASCADE, + datum DATE NOT NULL, + ergebnis TEXT, + diagnose TEXT, + dringlichkeitscode BOOLEAN NOT NULL DEFAULT FALSE, + erstellt_am TIMESTAMPTZ NOT NULL DEFAULT NOW() +); +``` + +- [ ] **Step 4: Create Zod schemas** + +Create `src/shared/db/schema.ts`: +```ts +import { z } from "zod" + +export const prozessSchrittEnum = z.enum([ + "neu", + "sprechstunde_absolviert", + "diagnose_erhalten", + "tss_beantragt", + "eigensuche", + "antrag_gestellt", +]) +export type ProzessSchritt = z.infer + +export const kontaktKanalEnum = z.enum([ + "telefon", + "email", + "online_formular", + "persoenlich", +]) +export type KontaktKanal = z.infer + +export const kontaktErgebnisEnum = z.enum([ + "keine_antwort", + "absage", + "warteliste", + "zusage", +]) +export type KontaktErgebnis = z.infer + +export const therapieformEnum = z.enum([ + "verhaltenstherapie", + "tiefenpsychologisch", + "analytisch", + "systemisch", +]) +export type Therapieform = z.infer + +export const nutzerSchema = z.object({ + id: z.number(), + name: z.string().nullable(), + plz: z.string().nullable(), + ort: z.string().nullable(), + krankenkasse: z.string().nullable(), + aktueller_schritt: prozessSchrittEnum, + dringlichkeitscode: z.boolean(), + dringlichkeitscode_datum: z.string().nullable(), + tss_beantragt: z.boolean(), + tss_beantragt_datum: z.string().nullable(), +}) + +export const therapeutSchema = z.object({ + id: z.number(), + name: z.string(), + adresse: z.string().nullable(), + plz: z.string().nullable(), + stadt: z.string().nullable(), + telefon: z.string().nullable(), + email: z.string().nullable(), + website: z.string().nullable(), + therapieform: z.string().nullable(), + kassenzulassung: z.string().nullable(), +}) + +export const kontaktSchema = z.object({ + id: z.number(), + therapeut_id: z.number(), + datum: z.string(), + kanal: kontaktKanalEnum, + ergebnis: kontaktErgebnisEnum, + notiz: z.string().nullable(), + antwort_datum: z.string().nullable(), +}) + +export const sprechstundeSchema = z.object({ + id: z.number(), + therapeut_id: z.number(), + datum: z.string(), + ergebnis: z.string().nullable(), + diagnose: z.string().nullable(), + dringlichkeitscode: z.boolean(), +}) +``` + +Run: `bun add zod` + +- [ ] **Step 5: Create database hook** + +Create `src/shared/hooks/use-db.ts`: +```ts +import { useCallback, useEffect, useState } from "react" +import { getDb } from "../db/client" + +export function useDbQuery( + query: string, + params: unknown[] = [], + deps: unknown[] = [], +) { + const [data, setData] = useState([]) + const [loading, setLoading] = useState(true) + const [error, setError] = useState(null) + + const refetch = useCallback(async () => { + setLoading(true) + try { + const db = await getDb() + const result = await db.query(query, params) + setData(result.rows as T[]) + setError(null) + } catch (e) { + setError(e instanceof Error ? e : new Error(String(e))) + } finally { + setLoading(false) + } + }, [query, ...deps]) + + useEffect(() => { + refetch() + }, [refetch]) + + return { data, loading, error, refetch } +} + +export async function dbExec(query: string, params: unknown[] = []) { + const db = await getDb() + return db.query(query, params) +} +``` + +- [ ] **Step 6: Verify database initializes** + +Update `src/routes/index.tsx` temporarily to test DB connection. Run `bun dev` — confirm DB initializes (check DevTools → Application → IndexedDB for `therapyfinder`). + +- [ ] **Step 7: Commit** + +```bash +git add -A && git commit -m "add pglite with migration runner, zod schemas, db hook" +``` + +--- + +### Task 5: Add Vitest + lint-staged + simple-git-hooks + +**Files:** +- Modify: `package.json` +- Create: `vitest.config.ts` +- Create: `src/test-setup.ts` +- Create: `src/shared/db/schema.test.ts` + +- [ ] **Step 1: Install test and code quality tooling** + +```bash +bun add -d vitest @testing-library/react @testing-library/jest-dom jsdom +bun add -d lint-staged simple-git-hooks +``` + +- [ ] **Step 2: Create Vitest config** + +Create `vitest.config.ts`: +```ts +import { defineConfig } from "vitest/config" +import react from "@vitejs/plugin-react" +import { resolve } from "node:path" + +export default defineConfig({ + plugins: [react()], + test: { + environment: "jsdom", + setupFiles: ["./src/test-setup.ts"], + include: ["src/**/*.test.{ts,tsx}"], + }, + resolve: { + alias: { + "@": resolve(__dirname, "src"), + }, + }, +}) +``` + +Create `src/test-setup.ts`: +```ts +import "@testing-library/jest-dom/vitest" +``` + +- [ ] **Step 3: Configure lint-staged + simple-git-hooks in `package.json`** + +Add to `package.json`: +```json +{ + "simple-git-hooks": { + "pre-commit": "bunx lint-staged" + }, + "lint-staged": { + "*.{ts,tsx,json,css}": ["biome check --write"] + } +} +``` + +Run: `bunx simple-git-hooks` + +- [ ] **Step 4: Write a smoke test to verify setup** + +Create `src/shared/db/schema.test.ts`: +```ts +import { describe, expect, it } from "vitest" +import { prozessSchrittEnum } from "./schema" + +describe("prozessSchrittEnum", () => { + it("accepts valid steps", () => { + expect(prozessSchrittEnum.parse("neu")).toBe("neu") + expect(prozessSchrittEnum.parse("eigensuche")).toBe("eigensuche") + }) + + it("rejects invalid steps", () => { + expect(() => prozessSchrittEnum.parse("invalid")).toThrow() + }) +}) +``` + +- [ ] **Step 5: Run tests** + +Run: `bun test` +Expected: 2 tests pass. + +- [ ] **Step 6: Commit** + +```bash +git add -A && git commit -m "add vitest, testing library, lint-staged, simple-git-hooks" +``` + +--- + +### Task 6: Add PWA support + +**Files:** +- Modify: `vite.config.ts` +- Create: `public/manifest.webmanifest` +- Create: `public/icons/icon-192.png` (placeholder) +- Create: `public/icons/icon-512.png` (placeholder) + +- [ ] **Step 1: Install vite-plugin-pwa** + +Run: `bun add -d vite-plugin-pwa` + +- [ ] **Step 2: Configure PWA plugin** + +Update `vite.config.ts` to add `VitePWA` with `registerType: "autoUpdate"` and `globPatterns: ["**/*.{js,css,html,wasm,data}"]`. Use `manifest: false` (external manifest file). + +- [ ] **Step 3: Create manifest** + +Create `public/manifest.webmanifest`: +```json +{ + "name": "TherapyFinder", + "short_name": "TherapyFinder", + "start_url": "/", + "display": "standalone", + "background_color": "#ffffff", + "theme_color": "#09090b", + "icons": [ + { + "src": "/icons/icon-192.png", + "sizes": "192x192", + "type": "image/png" + }, + { + "src": "/icons/icon-512.png", + "sizes": "512x512", + "type": "image/png" + } + ] +} +``` + +- [ ] **Step 4: Add manifest link to `index.html`** + +Add to ``: +```html + + +``` + +- [ ] **Step 5: Create placeholder icons** + +Generate simple placeholder PNGs (solid color squares) for 192x192 and 512x512. Replace with proper icons later. + +- [ ] **Step 6: Commit** + +```bash +git add -A && git commit -m "add pwa support with vite-plugin-pwa, manifest, placeholder icons" +``` + +--- + +## Chunk 2: Onboarding and Process Stepper + +### Task 7: Define constants and label maps + +**Files:** +- Create: `src/shared/lib/constants.ts` +- Create: `src/shared/lib/constants.test.ts` + +- [ ] **Step 1: Create constants file** + +Create `src/shared/lib/constants.ts` with: +- `PROZESS_SCHRITTE`: array of `{ key, label, beschreibung }` for all 6 process steps (neu → antrag_gestellt) +- `KANAL_LABELS`: `Record` mapping enum values to German labels +- `ERGEBNIS_LABELS`: `Record` mapping enum values to German labels +- `THERAPIEFORM_LABELS`: `Record` mapping enum values to German labels + +Labels: +- neu → "Noch nicht begonnen" +- sprechstunde_absolviert → "Sprechstunde absolviert" +- diagnose_erhalten → "Diagnose erhalten" +- tss_beantragt → "TSS kontaktiert" +- eigensuche → "Eigensuche läuft" +- antrag_gestellt → "Kostenerstattung beantragt" +- telefon → "Telefon", email → "E-Mail", online_formular → "Online-Formular", persoenlich → "Persönlich" +- keine_antwort → "Keine Antwort", absage → "Absage", warteliste → "Warteliste", zusage → "Zusage" +- verhaltenstherapie → "Verhaltenstherapie (VT)", tiefenpsychologisch → "Tiefenpsychologisch fundierte PT (TP)", analytisch → "Analytische Psychotherapie (AP)", systemisch → "Systemische Therapie" + +- [ ] **Step 2: Write test for constants** + +Create `src/shared/lib/constants.test.ts`: verify PROZESS_SCHRITTE has 6 entries, correct order, and all have non-empty label/beschreibung. + +- [ ] **Step 3: Run tests, commit** + +Run: `bun test` + +```bash +git add -A && git commit -m "add process step constants, channel/result label maps" +``` + +--- + +### Task 8: Onboarding flow + +**Files:** +- Create: `src/features/onboarding/schema.ts` +- Create: `src/features/onboarding/schema.test.ts` +- Create: `src/features/onboarding/components/onboarding-form.tsx` +- Create: `src/features/onboarding/index.ts` +- Create: `src/routes/onboarding/index.tsx` +- Modify: `src/routes/index.tsx` (redirect logic) + +- [ ] **Step 1: Install TanStack Form** + +```bash +bun add @tanstack/react-form +``` + +- [ ] **Step 2: Add shadcn form components** + +```bash +bunx shadcn@latest add input label select +``` + +- [ ] **Step 3: Create onboarding schema** + +Create `src/features/onboarding/schema.ts` with a Zod schema for: `name` (min 1), `plz` (regex 5 digits), `ort` (min 1), `krankenkasse` (min 1), `aktueller_schritt` (prozessSchrittEnum). + +- [ ] **Step 4: Write schema tests** + +Create `src/features/onboarding/schema.test.ts`: test valid data accepts, invalid PLZ rejects, empty name rejects. + +- [ ] **Step 5: Run tests** + +Run: `bun test` — all pass. + +- [ ] **Step 6: Create onboarding form component** + +Create `src/features/onboarding/components/onboarding-form.tsx`: +- Uses `useForm` from TanStack Form with the onboarding schema +- Fields: name, PLZ + Ort (grid), Krankenkasse, aktueller_schritt (select dropdown using PROZESS_SCHRITTE) +- On submit: INSERT into `nutzer` table via `dbExec`, then navigate to `/prozess` +- Welcoming header text explaining the app's purpose + +- [ ] **Step 7: Create onboarding route** + +Create `src/routes/onboarding/index.tsx` rendering ``. + +- [ ] **Step 8: Create feature index** + +Create `src/features/onboarding/index.ts` re-exporting form and schema. + +- [ ] **Step 9: Update root route with redirect logic** + +Update `src/routes/index.tsx`: in `beforeLoad`, query nutzer table. If no rows → redirect to `/onboarding`. If rows exist → redirect to `/prozess`. + +- [ ] **Step 10: Verify onboarding flow** + +Run: `bun dev` → auto-redirect to `/onboarding` → fill form → submit → redirect to `/prozess`. + +- [ ] **Step 11: Commit** + +```bash +git add -A && git commit -m "add onboarding flow with form, validation, db persistence" +``` + +--- + +### Task 9: Process stepper dashboard + +**Files:** +- Create: `src/features/prozess/hooks.ts` +- Create: `src/features/prozess/components/phase-card.tsx` +- Create: `src/features/prozess/components/process-stepper.tsx` +- Create: `src/features/prozess/index.ts` +- Create: `src/routes/prozess/index.tsx` + +- [ ] **Step 1: Create process data hooks** + +Create `src/features/prozess/hooks.ts`: +- `useNutzer()`: query nutzer table, return first row +- `useKontaktStats()`: aggregate query on kontakt table returning gesamt, absagen, warteliste, keine_antwort counts +- `updateSchritt(schritt)`: UPDATE nutzer aktueller_schritt + +- [ ] **Step 2: Create phase card component** + +Create `src/features/prozess/components/phase-card.tsx`: +- Props: label, beschreibung, status ("erledigt" | "aktuell" | "offen"), index +- Renders a Card with a numbered circle (checkmark if erledigt), label, "Aktuell" badge if current, description only shown for current step +- Visual styling: ring/border highlight for current, opacity-60 for completed + +- [ ] **Step 3: Create process stepper component** + +Create `src/features/prozess/components/process-stepper.tsx`: +- Props: aktuellerSchritt, kontaktGesamt, absagen +- Renders heading "Dein Fortschritt" with step X of 6 +- Maps PROZESS_SCHRITTE to PhaseCard components with correct status +- Shows contact stats card with links to `/kontakte` and `/antrag` when step >= "tss_beantragt" + +- [ ] **Step 4: Create process route** + +Create `src/routes/prozess/index.tsx`: uses `useNutzer()` and `useKontaktStats()` hooks, renders ``. + +- [ ] **Step 5: Create feature index** + +Create `src/features/prozess/index.ts` re-exporting components and hooks. + +- [ ] **Step 6: Verify process stepper** + +Run: `bun dev` → complete onboarding → verify stepper shows correct highlighted phase. + +- [ ] **Step 7: Commit** + +```bash +git add -A && git commit -m "add process stepper dashboard with phase cards, contact stats" +``` + +--- + +## Chunk 3: Contact Tracker + +### Task 10: Contact tracker — data layer and schemas + +**Files:** +- Create: `src/features/kontakte/schema.ts` +- Create: `src/features/kontakte/schema.test.ts` +- Create: `src/features/kontakte/hooks.ts` +- Create: `src/features/kontakte/index.ts` + +- [ ] **Step 1: Create contact feature schemas** + +Create `src/features/kontakte/schema.ts`: +- `therapeutFormSchema`: name (required), optional adresse, plz (5-digit regex or empty), stadt, telefon, email (valid or empty), website, therapieform +- `kontaktFormSchema`: therapeut_id (number), datum (required), kanal (kontaktKanalEnum), ergebnis (kontaktErgebnisEnum), notiz (optional) + +- [ ] **Step 2: Write schema tests** + +Create `src/features/kontakte/schema.test.ts`: test valid therapist, required name, valid contact, required date. + +- [ ] **Step 3: Run tests** + +Run: `bun test` — all pass. + +- [ ] **Step 4: Create contact hooks** + +Create `src/features/kontakte/hooks.ts`: +- `useTherapeutenListe()`: query therapeut table joined with latest kontakt subqueries (letzter_kontakt, letztes_ergebnis, kontakte_gesamt) +- `useKontakteForTherapeut(therapeutId)`: query kontakt table filtered by therapeut_id +- `createTherapeut(data)`: INSERT into therapeut, RETURNING id +- `createKontakt(data)`: INSERT into kontakt + +- [ ] **Step 5: Create feature index** + +Create `src/features/kontakte/index.ts` re-exporting schemas and hooks. + +- [ ] **Step 6: Commit** + +```bash +git add -A && git commit -m "add contact tracker data layer: schemas, hooks, db queries" +``` + +--- + +### Task 11: Contact tracker — UI components + +**Files:** +- Create: `src/features/kontakte/components/contact-card.tsx` +- Create: `src/features/kontakte/components/contact-list.tsx` +- Create: `src/features/kontakte/components/contact-form.tsx` +- Create: `src/routes/kontakte/index.tsx` +- Create: `src/routes/kontakte/neu.tsx` +- Modify: `src/routes/__root.tsx` (add bottom navigation) + +- [ ] **Step 1: Add shadcn components** + +```bash +bunx shadcn@latest add textarea dialog +``` + +- [ ] **Step 2: Create contact card** + +Create `src/features/kontakte/components/contact-card.tsx`: +- Shows therapist name, city, last contact date, result badge (color-coded: zusage=default, warteliste=secondary, absage=destructive, keine_antwort=outline), contact count +- Entire card is a Link to `/kontakte/neu?therapeutId=` + +- [ ] **Step 3: Create contact list** + +Create `src/features/kontakte/components/contact-list.tsx`: +- Heading "Kontakte" with "+ Neu" button linking to `/kontakte/neu` +- Empty state with CTA to add first contact +- Maps useTherapeutenListe() results to ContactCard components + +- [ ] **Step 4: Create combined contact form** + +Create `src/features/kontakte/components/contact-form.tsx`: +- Two sections separated by ``: "Therapeut:in" (name, stadt, telefon, email) and "Kontaktversuch" (datum, kanal select, ergebnis select, notiz textarea) +- On submit: createTherapeut() then createKontakt() with returned ID, navigate to `/kontakte` +- Cancel button navigates back + +- [ ] **Step 5: Create contact routes** + +Create `src/routes/kontakte/index.tsx` rendering ``. +Create `src/routes/kontakte/neu.tsx` rendering ``. + +- [ ] **Step 6: Add bottom navigation to root layout** + +Update `src/routes/__root.tsx`: add a `