MBO-Tech-IT-Webseite/docs/superpowers/specs/2026-07-21-familyguard-flye...

89 lines
4.9 KiB
Markdown

# Design: FamilyGuard Flyer-Download mit E-Mail-Gate
**Datum:** 2026-07-21
**Status:** Approved
## Ziel
Der FamilyGuard-Flyer (`docs/MBO_FamilyGuard_Flyer_01.pdf`) soll auf der Website herunterladbar sein, aber erst nachdem der Interessent seine E-Mail-Adresse eingegeben und der Datenverarbeitung zugestimmt hat. Jonny (jonny@mbo-tech-it.de) soll bei jedem Download per Mail benachrichtigt werden. Die eingegebenen Adressen werden zusätzlich als Lead in Supabase gespeichert.
## Architektur
```
modules/08-familyguard-flyer/
migrations/
MIGRATIONS_FLYER_DOWNLOADS.sql ← neue Tabelle flyer_downloads
app/api/familyguard-flyer/
route.ts ← POST: Lead speichern, 2 Mails senden, Download-URL zurückgeben
components/
FlyerDownloadForm.tsx ← Client-Komponente: E-Mail + DSGVO-Checkbox + Submit
public/downloads/
MBO_FamilyGuard_Flyer_01.pdf ← verschoben aus docs/
```
`FlyerDownloadForm` wird nur einmal eingebunden, auf `app/pakete/familyguard/page.tsx` als neue Sektion (`id="flyer-download"`) vor dem Bottom-Callout. Um das Formular nicht doppelt pflegen zu müssen, bekommt `components/FamilyGuardPromo.tsx` auf der Startseite stattdessen einen Button „Flyer herunterladen", der per Anchor-Link zu `/pakete/familyguard#flyer-download` springt. Das erfüllt „FamilyGuard-Seite + Homepage-Promo-Kachel", ohne die Formular-Logik zweimal zu implementieren.
## Datenmodell
Neue Tabelle `flyer_downloads` (Migration nach dem Muster der bestehenden `modules/*/migrations/*.sql`):
```sql
CREATE TABLE IF NOT EXISTS flyer_downloads (
id BIGINT PRIMARY KEY GENERATED ALWAYS AS IDENTITY,
email TEXT NOT NULL,
flyer TEXT NOT NULL DEFAULT 'familyguard',
dsgvo_einwilligung BOOLEAN NOT NULL DEFAULT false,
created_at TIMESTAMPTZ DEFAULT now()
);
CREATE INDEX IF NOT EXISTS idx_flyer_downloads_email ON flyer_downloads(email);
CREATE INDEX IF NOT EXISTS idx_flyer_downloads_created_at ON flyer_downloads(created_at DESC);
ALTER TABLE flyer_downloads DISABLE ROW LEVEL SECURITY;
```
`flyer` als Textfeld (statt hartcodiert) hält die Tabelle offen für künftige weitere Flyer, ohne dass das jetzt genutzt wird (YAGNI: nur ein Default-Wert, keine weitere Logik dafür).
Da im lokalen `.env.local` nur Platzhalter-Credentials für Supabase liegen, kann die Migration nicht von hier aus ausgeführt werden — Jonny führt das SQL manuell in seiner Supabase-Instanz aus (wie bei den bestehenden Modulen).
## API-Route `POST /api/familyguard-flyer`
Request: `{ email: string, dsgvoEinwilligung: boolean }`
Ablauf (analog zu `app/api/contact/route.ts`):
1. Validierung: `email` vorhanden + Format, `dsgvoEinwilligung === true` → sonst 400
2. Insert in `flyer_downloads` via `createServiceClient()` (Fehler werden geloggt, blockieren aber nicht den Download)
3. `sendeFlyerBenachrichtigung({ email })` → an `process.env.SMTP_TO`-Äquivalent, konkret hartcodiert an `jonny@mbo-tech-it.de` (kein Konfigurationsfeld nötig, da fest gewünscht)
4. `sendeFlyerLink({ email })` → an den Interessenten, mit Link zum PDF (`${APP_URL}/downloads/MBO_FamilyGuard_Flyer_01.pdf`)
5. Response: `{ ok: true, downloadUrl: "/downloads/MBO_FamilyGuard_Flyer_01.pdf" }`
Beide Mail-Funktionen kommen als neue Exporte in `lib/mailer.ts`, nutzen den bestehenden `sendWithFallback`-Helper (SMTP mit Queue-Fallback), gleiches HTML-Layout wie die bestehenden Mails.
## Komponente `FlyerDownloadForm.tsx`
Client-Komponente nach dem Muster von `Contact.tsx`:
- State: `email`, `dsgvoChecked`, `status` (`idle | loading | success | error`)
- Submit → `fetch("/api/familyguard-flyer", { method: "POST", ... })`
- Bei Erfolg: Formular wird durch einen Download-Button (`<a href={downloadUrl} download>`) ersetzt
- Checkbox-Label verlinkt auf `/datenschutz`
- Bei Fehler: Fehlermeldung wie im bestehenden Kontaktformular
## Fehlerbehandlung
- SMTP nicht erreichbar → automatischer Fallback in die bestehende `email_queue` (kein Nutzer-Impact)
- Supabase-Insert schlägt fehl → wird geloggt, Download funktioniert trotzdem
- Ungültige/leere E-Mail oder fehlende Einwilligung → 400 mit Fehlermeldung, Formular zeigt Inline-Fehler
- PDF-Datei ist eine normale statische Datei unter `/public/downloads` — kein Access-Control-Layer, das Gate ist rein UX-seitig (bewusste Entscheidung, siehe Architektur-Diskussion: für einen Marketing-Flyer ist ein hartes Access-Gating unverhältnismäßig)
## Testing
Manueller Test über `npm run dev`:
1. Formular auf `/pakete/familyguard` ausfüllen (mit und ohne Checkbox) → Validierung prüfen
2. Nach Erfolg: Download-Button erscheint, PDF öffnet sich korrekt
3. Eintrag in `flyer_downloads` prüfen (sofern echte Supabase-Zugangsdaten vorhanden sind)
4. Beide Mails prüfen (Empfang bei SMTP_TO/jonny@mbo-tech-it.de und bei der Test-E-Mail-Adresse) bzw. Queue-Eintrag bei fehlendem SMTP
5. Homepage-Kachel: Link führt korrekt zur Formular-Sektion auf der FamilyGuard-Seite