docs: add design spec for FamilyGuard flyer email-gate download
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LhY1QhDXGWfyhrxaJvfpNt
This commit is contained in:
parent
260aa80f42
commit
1cf9c0701e
|
|
@ -0,0 +1,88 @@
|
|||
# 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
|
||||
Loading…
Reference in New Issue