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

4.9 KiB

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):

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