Files
luna-recipes/features/AUTH-V2-SPEC.md
clawd 301e42b1dc v2.1.2026 — PostgreSQL, Auth, Household, Shopping Smart-Add, Docker
Backend:
- SQLite → PostgreSQL (pg_trgm search, async services)
- All services rewritten to async with pg Pool
- Data imported (50 recipes, 8 categories)
- better-sqlite3 removed

Frontend:
- ProfilePage complete (edit profile, change password, no more stubs)
- HouseholdCard (create, join via code, manage members, leave)
- Shopping scope toggle (personal/household)
- IngredientPickerModal (smart add with basics filter)
- Auth token auto-attached to all API calls (token.ts)
- Removed PlaceholderPage

Infrastructure:
- Docker Compose (backend + frontend + postgres)
- Dockerfile for backend (node:22-alpine + tsx)
- Dockerfile for frontend (vite build + nginx)
- nginx.conf with API proxy + SPA fallback
- .env.example for production secrets

Spec:
- AUTH-V2-SPEC updated: household join flow, manual shopping items
2026-02-18 17:26:24 +00:00

23 KiB
Raw Permalink Blame History

Luna Recipes — Auth v2 Feature-Spezifikation

Übersicht

Implementierung eines vollständigen Authentifizierungssystems für die Luna Rezept-App mit Multi-User-Support und Haushaltsfunktionalität. Das System ermöglicht es mehreren Nutzern, die App zu verwenden, dabei ihre Daten zu trennen und optional einen gemeinsamen Haushalt zu teilen.

1. Datenmodell

1.1 Neue Tabellen

users

CREATE TABLE users (
  id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
  email TEXT UNIQUE NOT NULL,
  password_hash TEXT NOT NULL,
  display_name TEXT NOT NULL,
  avatar_url TEXT,
  created_at TIMESTAMPTZ DEFAULT NOW(),
  updated_at TIMESTAMPTZ DEFAULT NOW()
);

CREATE UNIQUE INDEX idx_users_email ON users(email);

households

CREATE TABLE households (
  id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
  name TEXT NOT NULL,
  invite_code TEXT UNIQUE NOT NULL,
  created_at TIMESTAMPTZ DEFAULT NOW()
);

CREATE UNIQUE INDEX idx_households_invite_code ON households(invite_code);

household_members

CREATE TABLE household_members (
  household_id UUID REFERENCES households(id) ON DELETE CASCADE,
  user_id UUID REFERENCES users(id) ON DELETE CASCADE,
  role TEXT NOT NULL CHECK (role IN ('owner', 'member')),
  joined_at TIMESTAMPTZ DEFAULT NOW(),
  PRIMARY KEY (household_id, user_id)
);

user_favorites (Neue Tabelle)

CREATE TABLE user_favorites (
  id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
  user_id UUID REFERENCES users(id) ON DELETE CASCADE,
  recipe_id UUID REFERENCES recipes(id) ON DELETE CASCADE,
  created_at TIMESTAMPTZ DEFAULT NOW(),
  UNIQUE(user_id, recipe_id)
);

1.2 Erweiterte bestehende Tabellen

shopping_items

-- Erweitern um User- und Haushaltszuordnung
ALTER TABLE shopping_items ADD COLUMN user_id UUID REFERENCES users(id);
ALTER TABLE shopping_items ADD COLUMN household_id UUID REFERENCES households(id);

-- Migration: NULL user_id wird beim ersten Login zugewiesen

notes

-- Erweitern um User-Zuordnung
ALTER TABLE notes ADD COLUMN user_id UUID REFERENCES users(id);

-- Migration: Bestehende Notizen werden dem ersten registrierten User zugewiesen

recipes

-- Recipes bleiben global, aber mit Ersteller-Info
ALTER TABLE recipes ADD COLUMN created_by UUID REFERENCES users(id);

-- Migration: Bestehende Rezepte werden dem ersten registrierten User zugewiesen

1.3 Daten-Ownership

  • Recipes: Global sichtbar, created_by zur Information
  • Shopping Items: Pro User ODER pro Haushalt (wenn household_id gesetzt)
  • Favorites: Pro User (user_favorites Tabelle)
  • Notes: Pro User
  • Households: Gemeinsam für alle Mitglieder

2. API Endpoints

2.1 Authentication

POST /api/auth/register

// Request
{
  "email": "luna@example.com",
  "password": "secure123!",
  "display_name": "Luna"
}

// Response (201 Created)
{
  "success": true,
  "user": {
    "id": "uuid-here",
    "email": "luna@example.com",
    "display_name": "Luna",
    "avatar_url": null,
    "created_at": "2026-02-18T15:30:00Z"
  },
  "access_token": "jwt-token-here"
}

// Error (400 Bad Request)
{
  "success": false,
  "error": "EMAIL_EXISTS",
  "message": "Ein Account mit dieser E-Mail existiert bereits"
}

POST /api/auth/login

// Request
{
  "email": "luna@example.com",
  "password": "secure123!"
}

// Response (200 OK)
{
  "success": true,
  "user": {
    "id": "uuid-here",
    "email": "luna@example.com",
    "display_name": "Luna",
    "avatar_url": null
  },
  "access_token": "jwt-token-here"
}

// Error (401 Unauthorized)
{
  "success": false,
  "error": "INVALID_CREDENTIALS",
  "message": "E-Mail oder Passwort ungültig"
}

POST /api/auth/logout

// Response (200 OK)
{
  "success": true,
  "message": "Erfolgreich abgemeldet"
}

GET /api/auth/me

// Response (200 OK)
{
  "success": true,
  "user": {
    "id": "uuid-here",
    "email": "luna@example.com",
    "display_name": "Luna",
    "avatar_url": "https://example.com/avatar.jpg",
    "household": {
      "id": "household-uuid",
      "name": "Luna & Marc",
      "role": "owner"
    }
  }
}

PUT /api/auth/me

// Request (Profil bearbeiten)
{
  "display_name": "Luna Schmidt",
  "avatar_url": "https://example.com/new-avatar.jpg"
}

// Response (200 OK)
{
  "success": true,
  "user": {
    "id": "uuid-here",
    "email": "luna@example.com",
    "display_name": "Luna Schmidt",
    "avatar_url": "https://example.com/new-avatar.jpg"
  }
}

PUT /api/auth/me/password

// Request
{
  "current_password": "old123!",
  "new_password": "new456!"
}

// Response (200 OK)
{
  "success": true,
  "message": "Passwort erfolgreich geändert"
}

// Error (400 Bad Request)
{
  "success": false,
  "error": "INVALID_CURRENT_PASSWORD",
  "message": "Aktuelles Passwort ist falsch"
}

2.2 Households

POST /api/households

// Request (Haushalt erstellen)
{
  "name": "Luna & Marc"
}

// Response (201 Created)
{
  "success": true,
  "household": {
    "id": "household-uuid",
    "name": "Luna & Marc",
    "invite_code": "COOK2024",
    "created_at": "2026-02-18T15:30:00Z",
    "members": [
      {
        "user_id": "user-uuid",
        "display_name": "Luna",
        "role": "owner",
        "joined_at": "2026-02-18T15:30:00Z"
      }
    ]
  }
}

POST /api/households/:id/invite

// Response (200 OK) - Neuen Einladungscode generieren
{
  "success": true,
  "invite_code": "COOK2025",
  "expires_at": "2026-02-25T15:30:00Z"
}

POST /api/households/join

// Request
{
  "invite_code": "COOK2024"
}

// Response (200 OK)
{
  "success": true,
  "household": {
    "id": "household-uuid",
    "name": "Luna & Marc",
    "role": "member"
  }
}

// Error (400 Bad Request)
{
  "success": false,
  "error": "INVALID_INVITE_CODE",
  "message": "Einladungscode ungültig oder abgelaufen"
}

GET /api/households/mine

// Response (200 OK)
{
  "success": true,
  "household": {
    "id": "household-uuid",
    "name": "Luna & Marc",
    "invite_code": "COOK2024",
    "role": "owner",
    "members": [
      {
        "user_id": "user1-uuid",
        "display_name": "Luna",
        "role": "owner",
        "joined_at": "2026-02-18T15:30:00Z"
      },
      {
        "user_id": "user2-uuid",
        "display_name": "Marc",
        "role": "member",
        "joined_at": "2026-02-19T10:15:00Z"
      }
    ]
  }
}

3. Frontend Pages

3.1 Authentication Pages

/login

  • Design: Vollbild-Login mit Luna Recipes Logo
  • Felder: E-Mail, Passwort, "Angemeldet bleiben" Checkbox
  • Actions: Login, "Registrieren" Link, "Passwort vergessen" Link (v2.1)
  • Mobile: Touch-optimierte Input-Felder, große Login-Button
  • Validation: Client-side Validation mit sofortigem Feedback
  • States: Loading State beim Login-Prozess

/register

  • Design: Gleiche Basis wie Login-Page
  • Felder: E-Mail, Passwort, Passwort wiederholen, Display Name
  • Validation:
    • E-Mail Format-Check
    • Passwort-Stärke Indikator
    • Passwort-Match Validation
    • Display Name min. 2 Zeichen
  • Actions: Registrieren, "Schon Account?" Login-Link
  • Flow: Nach erfolgreicher Registrierung → automatisch eingeloggt → Dashboard

3.2 Profile Pages

/profile

  • Layout: Card-basiert, vertikal gestapelt (kein Tab-Interface)
  • Profil-Karte:
    • Avatar (Upload oder URL)
    • Display Name (inline editierbar)
    • E-Mail (nicht editierbar, mit "Ändern" Link für v2.1)
    • "Passwort ändern" Button
  • Haushalt-Karte: (immer sichtbar, direkt unter Profil)
    • Kein Haushalt: Card mit 🏠 Icon, "Haushalt erstellen" + "Beitreten" Buttons
    • In Haushalt: Haushalt-Name, Mitglieder-Avatare, "Verwalten" Button → /profile/household
    • Visuell prominent — nicht versteckt in einem Tab!
  • Quick-Actions: Logout-Button unten
  • Mobile: Große Avatar-Anzeige, Touch-freundliche Edit-Buttons, 44px Targets

/profile/edit

  • Modal oder Fullscreen (Mobile): Profil bearbeiten
  • Felder: Display Name, Avatar URL/Upload
  • Actions: Speichern, Abbrechen
  • Validation: Display Name required

/profile/password

  • Layout: Focused Passwort-Ändern Page
  • Felder: Aktuelles Passwort, Neues Passwort, Bestätigung
  • Security:
    • Passwort-Stärke Anzeige
    • "Passwort anzeigen" Toggle
    • Session-Refresh nach Änderung
  • UX: Erfolgs-Toast + Redirect nach Speichern

/profile/household

  • States:
    • Kein Haushalt: "Haushalt erstellen" oder "Haushalt beitreten"
    • Haushalt Owner: Mitglieder-Liste, Einladungscode verwalten
    • Haushalt Member: Mitglieder-Liste, "Haushalt verlassen"
  • Features:
    • Einladungscode kopieren/teilen
    • QR-Code für Einladung (Mobile-optimiert)
    • Mitglieder-Management (nur Owner)

4. Authentication Flow

4.1 Token-Strategy

JWT Access Token

  • Laufzeit: 15 Minuten
  • Storage: Memory (React State + Context)
  • Payload:
    {
      "sub": "user-uuid",
      "email": "luna@example.com",
      "display_name": "Luna",
      "household_id": "household-uuid",
      "iat": 1708272600,
      "exp": 1708273500
    }
    

Refresh Token

  • Laufzeit: 30 Tage
  • Storage: httpOnly Cookie, Secure, SameSite=Strict
  • Rotation: Neuer Refresh Token bei jedem Access Token Refresh
  • Cookie Name: luna_refresh_token

4.2 Auto-Refresh Flow

// Auth Context implementiert Auto-Refresh
const authContext = {
  // Access Token im Memory
  accessToken: string | null,
  // User Info aus Token dekodiert
  user: User | null,
  // Auto-refresh 2 Minuten vor Ablauf
  refreshToken: () => Promise<void>,
  logout: () => void
}

// Axios Interceptor für Auto-Refresh
axios.interceptors.response.use(
  (response) => response,
  async (error) => {
    if (error.response?.status === 401) {
      await authContext.refreshToken()
      // Retry original request
      return axios.request(error.config)
    }
    return Promise.reject(error)
  }
)

4.3 Route Protection

// Protected Route Component
function ProtectedRoute({ children }: { children: React.ReactNode }) {
  const { user, loading } = useAuth()
  
  if (loading) return <LoadingSpinner />
  if (!user) return <Navigate to="/login" replace />
  
  return <>{children}</>
}

// App Router
<Routes>
  <Route path="/login" element={<LoginPage />} />
  <Route path="/register" element={<RegisterPage />} />
  <Route path="/" element={
    <ProtectedRoute>
      <Dashboard />
    </ProtectedRoute>
  } />
</Routes>

4.4 Login Flow States

  1. Unauthenticated: Redirect zu /login
  2. Login Success:
    • Set Access Token in Memory
    • Set Refresh Token in Cookie
    • Redirect zu ursprünglicher Route oder /
  3. Token Expired: Auto-refresh, bei Fehler → Logout
  4. Logout: Clear Token + Cookie, Redirect zu /login

5. Haushalt-Feature

5.1 Haushalt erstellen

  1. User klickt "Haushalt erstellen" in /profile/household
  2. Modal: Haushalt-Name eingeben
  3. Backend erstellt Haushalt + generiert Einladungscode
  4. User wird automatisch als Owner hinzugefügt
  5. Einladungscode wird angezeigt zum Teilen

5.2 Haushalt beitreten

Flow

  1. User erhält Einladungscode (per Link, QR-Code oder manuell)
  2. In /profile/household → Button "Haushalt beitreten"
  3. Eingabe: 6-stelliger Code (uppercase, alphanumerisch, z.B. COOK2A)
  4. Backend validiert Code → fügt User als member hinzu
  5. Erfolg: Konfetti 🎉 + Toast "Willkommen bei [Haushalt-Name]!"
  6. Einkaufsliste zeigt ab sofort Haushalt-Items
  • URL-Format: https://luna.supertoll.xyz/join/{invite_code}
  • Wenn eingeloggt → direkt beitreten (mit Bestätigungs-Dialog)
  • Wenn nicht eingeloggt → Redirect zu /login?redirect=/join/{code}
  • Nach Login → automatisch Join-Flow fortsetzen

QR-Code

  • Owner kann QR-Code generieren (enthält Deep Link)
  • Share-Button: QR als Bild teilen oder Code kopieren
  • Library: qrcode.react (lightweight)

Fehlerbehandlung

  • INVALID_INVITE_CODE → "Code ungültig oder abgelaufen"
  • ALREADY_IN_HOUSEHOLD → "Du bist bereits in einem Haushalt. Zuerst verlassen?"
  • HOUSEHOLD_FULL → "Haushalt hat maximale Mitgliederzahl erreicht" (Limit: 10)
  • Netzwerkfehler → Retry-Button

Haushalt verlassen

  • Member können jederzeit verlassen (Bestätigungs-Dialog)
  • Owner kann nur verlassen wenn ein anderer Member zum Owner gemacht wird
  • Letzter Member → Haushalt wird gelöscht
  • Nach Verlassen: eigene Shopping-Items bleiben privat erhalten

5.3 Gemeinsame Einkaufsliste

Datenlogik

  • Shopping Items mit household_id sind für alle Haushaltsmitglieder sichtbar
  • Shopping Items mit nur user_id sind privat
  • Standard: Neue Items werden Haushalt zugeordnet (wenn User in Haushalt ist)
  • Toggle: "Privat" Checkbox beim Hinzufügen

UI/UX

  • Indicator: Haushalt-Items haben kleines Haushalt-Icon
  • Filter: "Alle", "Haushalt", "Privat" Tabs in Shopping Liste
  • Mobile: Swipe-Actions: "Als privat markieren" / "Mit Haushalt teilen"

5.4 Smarte Mengenabfrage beim Rezept-Einkauf

Aktuell werden beim "Zur Einkaufsliste hinzufügen" alle Zutaten 1:1 übernommen. Aber oft hat man schon Mehl, Eier, Zucker etc. daheim. Statt blind alles draufzupacken → User fragen was noch fehlt.

Flow: "Was brauchst du noch?"

  1. User klickt "Zutaten zur Einkaufsliste" auf der Rezeptseite
  2. Modal/Sheet öffnet sich mit allen Zutaten als Checkliste
  3. Jede Zutat hat:
    • ☑️ Checkbox (Standard: alle an)
    • Name + Menge aus Rezept
    • Optional: Menge anpassen (z.B. "hab noch 200g, brauch nur 300g")
  4. User deaktiviert was schon da ist
  5. Button: "X Artikel hinzufügen" (zeigt Anzahl der aktiven)
  6. Nur ausgewählte Zutaten landen auf der Liste

Quick-Actions im Modal

  • "Alles" — alle Checkboxen an (default)
  • "Basics abwählen" — typische Vorrats-Zutaten automatisch deaktivieren (Salz, Pfeffer, Öl, Wasser)
  • "Nichts" — alle aus, manuell auswählen

Basics-Liste (konfigurierbar pro User/Haushalt)

Standard-Basics die man meistens daheim hat:

  • Salz, Pfeffer, Zucker, Mehl, Öl, Wasser, Butter, Eier
  • User kann in Settings eigene Basics definieren
  • Passt zu Luna: Mehl Zucker Eier Backpulver (Butter muss immer drauf 😄)

Späterer Ausbau (v2+: Vorratskammer/Pantry)

  • Automatischer Abgleich mit Vorratskammer
  • "Hab ich schon" wird automatisch vorausgewählt
  • Fehlmengen werden berechnet (Rezept braucht 500g, hast 200g → 300g auf Liste)

5.5 Manuelle Artikel zur Einkaufsliste hinzufügen

Aktuell kommen Shopping-Items nur aus Rezepten. User sollen auch eigene Artikel hinzufügen können (z.B. "Klopapier", "Spülmittel", Zutaten ohne Rezept).

API

POST /api/shopping/manual
// Request
{
  "name": "Klopapier",
  "amount": "1",
  "unit": "Packung",
  "private": false
}

// Response (201 Created)
{
  "success": true,
  "item": {
    "id": "uuid",
    "name": "Klopapier",
    "amount": "1",
    "unit": "Packung",
    "checked": false,
    "recipe_id": null,
    "user_id": "user-uuid",
    "household_id": "household-uuid",
    "created_at": "2026-02-18T16:00:00Z"
  }
}

DB-Erweiterung

-- recipe_id wird NULLABLE (ist es vermutlich schon)
-- Manuelle Items haben recipe_id = NULL
-- Optional: source-Feld um Herkunft zu unterscheiden
ALTER TABLE shopping_items ADD COLUMN source TEXT DEFAULT 'recipe' 
  CHECK (source IN ('recipe', 'manual'));

Frontend UI

Eingabefeld (oben in der Einkaufsliste)
  • Sticky Input-Bar oben auf der Shopping-Page
  • Textfeld mit Placeholder "Artikel hinzufügen..." + Button
  • Smart Parsing: "2 Liter Milch" → amount: "2", unit: "Liter", name: "Milch"
  • Autocomplete: Vorschläge aus bisherigen Items (häufig gekaufte)
  • Enter oder fügt hinzu, Feld wird geleert
  • Standard: Haushalt-Item (wenn in Haushalt), sonst privat
Smart Parsing Regeln
"Milch"              → name: "Milch", amount: null, unit: null
"2 Milch"            → name: "Milch", amount: "2", unit: null  
"2 Liter Milch"      → name: "Milch", amount: "2", unit: "Liter"
"500g Mehl"          → name: "Mehl", amount: "500", unit: "g"
"1 Packung Butter"   → name: "Butter", amount: "1", unit: "Packung"
Visuelle Unterscheidung
  • Rezept-Items: normaler Style + Rezept-Name als Subtitle
  • Manuelle Items: leicht anderer Style (z.B. 📝 Icon oder kursiver Subtitle "Manuell hinzugefügt")
  • Haushalt-Items: 🏠 Badge
  • Private Items: 🔒 Badge
Quick-Add Vorschläge
  • Unter dem Input: Chips mit häufig hinzugefügten Artikeln
  • Max 5 Vorschläge, basierend auf History
  • Tap = sofort hinzufügen

5.4 Persönliche Favoriten

  • Favoriten sind immer pro User (user_favorites Tabelle)
  • Keine Sharing-Option für Favoriten in v2
  • Favoriten-Liste zeigt nur eigene Favoriten

6. Migration Strategy

6.1 Backwards Compatibility

  • Anonymous Access: Rezepte bleiben öffentlich zugänglich ohne Login
  • URLs: Alle bestehenden Recipe-URLs funktionieren weiterhin
  • Features: Basis-Funktionen (Rezepte suchen, ansehen) ohne Auth

6.2 Daten-Migration

Phase 1: Schema Updates

-- Neue Tabellen erstellen (siehe Datenmodell)
-- Bestehende Tabellen erweitern (users_id Spalten als optional)

-- Default User erstellen für Migration
INSERT INTO users (id, email, display_name, password_hash) 
VALUES (
  'migration-user-uuid', 
  'migration@luna-recipes.local', 
  'Luna (Migration)', 
  'no-login'
);

Phase 2: Daten zuweisen

-- Bestehende Rezepte dem Migration User zuweisen
UPDATE recipes 
SET created_by = 'migration-user-uuid' 
WHERE created_by IS NULL;

-- Bestehende Notes dem Migration User zuweisen
UPDATE notes 
SET user_id = 'migration-user-uuid' 
WHERE user_id IS NULL;

-- Shopping Items vorerst ohne user_id lassen
-- Werden beim ersten User-Login zugewiesen

Phase 3: Erste echte User

  • Erste User die sich registrieren bekommen alle bestehenden Daten zugewiesen
  • Migration User wird nach erstem echten User gelöscht
  • Shopping Items ohne user_id werden beim Login zugewiesen

6.3 Rollback Plan

  • Auth-Features sind additiv, keine Breaking Changes
  • Bei Problemen: Auth-Routes deaktivieren, App läuft weiter ohne Auth
  • Datenbank-Rollback: user_id Spalten auf NULL setzen

7. Sicherheit

7.1 Passwort-Sicherheit

// bcrypt mit 12 Rounds (Backend)
const passwordHash = await bcrypt.hash(password, 12)

// Passwort-Validierung (Frontend + Backend)
const passwordRules = {
  minLength: 8,
  requireUppercase: false, // UX-freundlich für v2
  requireNumbers: false,
  requireSpecialChars: false
}

7.2 JWT Security

// JWT Signing (Backend)
const accessToken = jwt.sign(
  {
    sub: user.id,
    email: user.email,
    display_name: user.display_name,
    household_id: user.household_id
  },
  process.env.JWT_SECRET,
  { expiresIn: '15m' }
)

// httpOnly Cookie Config
res.cookie('luna_refresh_token', refreshToken, {
  httpOnly: true,
  secure: process.env.NODE_ENV === 'production',
  sameSite: 'strict',
  maxAge: 30 * 24 * 60 * 60 * 1000 // 30 days
})

7.3 Rate Limiting

// Login Rate Limiting
const loginLimiter = rateLimit({
  windowMs: 15 * 60 * 1000, // 15 minutes
  max: 5, // 5 attempts per IP
  message: 'Zu viele Login-Versuche, bitte warten Sie 15 Minuten',
  standardHeaders: true
})

// Registration Rate Limiting
const registerLimiter = rateLimit({
  windowMs: 60 * 60 * 1000, // 1 hour
  max: 3, // 3 registrations per IP per hour
  message: 'Zu viele Registrierungen, bitte warten Sie'
})

7.4 Input Validation

// Zod Schema für API Validation (Backend)
const registerSchema = z.object({
  email: z.string().email('Ungültige E-Mail-Adresse'),
  password: z.string().min(8, 'Passwort muss mindestens 8 Zeichen haben'),
  display_name: z.string().min(2, 'Name muss mindestens 2 Zeichen haben')
    .max(50, 'Name darf maximal 50 Zeichen haben')
})

// XSS Protection für User-generierte Inhalte
const sanitizedDisplayName = DOMPurify.sanitize(display_name)

8. Mobile-First UX Considerations

8.1 Touch Targets

  • Minimum Size: 44px × 44px für alle Buttons
  • Spacing: 8px minimum zwischen clickbaren Elementen
  • Form Fields: 48px Höhe für bessere Touch-Ergonomie

8.2 Responsive Breakpoints

/* Mobile First */
.auth-form {
  width: 100%;
  padding: 1rem;
}

/* Tablet */
@media (min-width: 768px) {
  .auth-form {
    max-width: 400px;
    margin: 0 auto;
    padding: 2rem;
    border-radius: 8px;
    box-shadow: 0 4px 12px rgba(0,0,0,0.1);
  }
}

8.3 Keyboard & Input Handling

// Input Types für bessere Mobile Keyboards
<input 
  type="email" 
  inputMode="email"
  autoComplete="username"
  placeholder="luna@example.com"
/>

<input 
  type="password"
  autoComplete="current-password"
  placeholder="Passwort"
/>

// Auto-focus Management
<input ref={emailRef} autoFocus onSubmit={focusPassword} />

8.4 Loading States

  • Skeleton Loading: Für User Profile, Haushalt-Listen
  • Button States: Loading Spinner in Buttons während API Calls
  • Toast Messages: Für Success/Error Feedback
  • Optimistic Updates: Favoriten, Haushalt beitreten

8.5 Offline Considerations

  • Service Worker: Caching für Auth-relevante Pages
  • Network Awareness: Retry-Mechanismus für failed Requests
  • Local Storage: Backup für kritische User Preferences

9. Testing Strategy

9.1 Unit Tests

  • Auth Context / Hook Tests
  • Password Validation Tests
  • JWT Token Handling Tests
  • API Response Validation Tests

9.2 Integration Tests

  • Login/Register Flow End-to-End
  • Token Refresh Flow
  • Haushalt erstellen/beitreten Flow
  • Migration Logic Tests

9.3 Security Tests

  • SQL Injection Prevention
  • XSS Prevention
  • CSRF Protection Validation
  • Rate Limiting Effectiveness

10. Performance

10.1 Bundle Size

  • Code Splitting: Auth-Pages lazy loaded
  • Tree Shaking: Nur genutzte Auth-Libraries
  • JWT Library: Lightweight jwt-decode statt vollständiger Library

10.2 API Optimization

  • Response Caching: User Profile Daten
  • Debounced Requests: Profile Updates
  • Batch Requests: Initial App Load (User + Household in einem Call)

10.3 Database Performance

-- Wichtige Indizes für Auth Queries
CREATE INDEX idx_users_email ON users(email);
CREATE INDEX idx_household_members_user_id ON household_members(user_id);
CREATE INDEX idx_shopping_items_user_household ON shopping_items(user_id, household_id);
CREATE INDEX idx_user_favorites_user_id ON user_favorites(user_id);

Implementierungs-Prioritäten

Phase 1 (MVP)

  1. Datenbank Schema + Migration
  2. Backend Auth Endpoints
  3. Frontend Login/Register Pages
  4. JWT Token Flow + Auto-Refresh
  5. Route Protection

Phase 2 (Multi-User)

  1. User Profile Management
  2. Shopping Items User-Zuordnung
  3. Favorites pro User
  4. Notes pro User

Phase 3 (Households)

  1. Household Creation + Joining
  2. Shared Shopping Lists
  3. Household Management UI
  4. Invite Code Generation

Phase 4 (Polish)

  1. Avatar Upload
  2. Advanced Security Features
  3. Performance Optimizations
  4. Mobile UX Improvements

Geschätzte Entwicklungszeit: 3-4 Wochen für komplette Implementierung