85 lines
5.2 KiB
Markdown
85 lines
5.2 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
|
|
|
|
```
|
|
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`. Die übrigen Content-Tabellen dieses Projekts (`anfragen`, `hero_content`, `kontakt_info` etc.) haben kein getracktes Migrations-SQL im Repo — sie wurden direkt im Supabase SQL-Editor angelegt. Diesem Muster folgend wird das SQL unten nur im Plan dokumentiert und von Jonny manuell ausgeführt, nicht als Datei im `modules/`-Ordner abgelegt (der Ordner ist Jonnys projektübergreifender Wiederverwendungs-Katalog für generische Module wie Auth/Analytics — ein Feature, das nur für diese eine Website gilt, gehört da nicht rein):
|
|
|
|
```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 im Supabase SQL-Editor seiner Instanz aus.
|
|
|
|
## 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
|