docs: add implementation plan 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:
MBO-Tech-IT 2026-07-21 11:12:17 +02:00
parent 8ae4e341c1
commit 84599e1e08
1 changed files with 632 additions and 0 deletions

View File

@ -0,0 +1,632 @@
# FamilyGuard Flyer-Download Implementation Plan
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
**Goal:** Der FamilyGuard-Flyer wird über ein E-Mail-Gate herunterladbar: Interessent gibt E-Mail-Adresse ein (mit DSGVO-Einwilligung), bekommt sofort einen Download-Button + eine Bestätigungsmail mit Link, und Jonny (jonny@mbo-tech-it.de) wird per Mail benachrichtigt.
**Architektur:** Neue API-Route `POST /api/familyguard-flyer` validiert Eingabe, speichert den Lead in einer neuen Supabase-Tabelle `flyer_downloads`, verschickt zwei Mails über die bestehende `lib/mailer.ts`-Infrastruktur (SMTP mit Queue-Fallback) und liefert die Download-URL zurück. Eine neue Client-Komponente `FlyerDownloadForm` kapselt Formular + Erfolgs-Zustand und wird einmalig auf der FamilyGuard-Seite eingebunden; die Homepage-Kachel verlinkt per Anchor dorthin.
**Tech Stack:** Next.js 15 App Router, TypeScript (`strict: true`), Supabase (`@supabase/supabase-js`), Nodemailer (bestehend in `lib/mailer.ts`), Tailwind CSS.
## Global Constraints
- Kein Test-Framework im Projekt (kein Jest/Vitest, keine `*.test.ts`-Dateien existieren) — Verifikation erfolgt manuell über `npx tsc --noEmit`, `npm run dev` und `curl`, wie es auch bei allen bestehenden API-Routen (z. B. `app/api/contact/route.ts`) gehandhabt wurde.
- `@/*` ist auf das Root-Verzeichnis aliased.
- `JSX.Element` nicht verwenden — `ReactElement` aus `react` importieren, falls ein Rückgabetyp explizit gebraucht wird.
- Neue Server-seitige E-Mail-Funktionen folgen exakt dem bestehenden Muster in `lib/mailer.ts` (`sendWithFallback`-Helper, gleiches HTML-Layout/Branding).
- Die Migration für `flyer_downloads` wird **nicht** als Datei unter `modules/` abgelegt (dieser Ordner ist Jonnys projektübergreifender Wiederverwendungs-Katalog für generische Module) und auch nicht als SQL-Datei im Repo getrackt (kein anderer Content-Table hat das) — das SQL wird in Task 2 dokumentiert und von Jonny manuell im Supabase SQL-Editor ausgeführt.
- Git-Commits nach jedem Task, wie in diesem Projekt bisher gehandhabt (kein `--no-verify`, keine `-i`-Flags).
---
### Task 1: Flyer-PDF nach `public/downloads/` verschieben
**Files:**
- Move: `docs/MBO_FamilyGuard_Flyer_01.pdf``public/downloads/MBO_FamilyGuard_Flyer_01.pdf`
**Interfaces:**
- Produziert: Statische URL `/downloads/MBO_FamilyGuard_Flyer_01.pdf`, die von Task 4 (API-Response) und Task 5 (Download-Button) referenziert wird.
- [ ] **Step 1: Ordner anlegen und Datei verschieben**
```bash
mkdir -p public/downloads
git mv docs/MBO_FamilyGuard_Flyer_01.pdf public/downloads/MBO_FamilyGuard_Flyer_01.pdf
```
- [ ] **Step 2: Verifizieren, dass die Datei unter der neuen URL ausgeliefert wird**
```bash
npm run dev
```
Dann im Browser (oder mit curl in einem zweiten Terminal) prüfen:
```bash
curl -s -o /dev/null -w "%{http_code}\n" http://localhost:3000/downloads/MBO_FamilyGuard_Flyer_01.pdf
```
Erwartet: `200`
- [ ] **Step 3: Commit**
```bash
git add public/downloads/MBO_FamilyGuard_Flyer_01.pdf docs/MBO_FamilyGuard_Flyer_01.pdf
git commit -m "feat: move FamilyGuard flyer PDF to public/downloads for direct serving"
```
---
### Task 2: Supabase-Tabelle `flyer_downloads` + Typdefinition
**Files:**
- Modify: `lib/supabase.ts` (Database-Typ ergänzen, nach dem `kontakt_social`-Block, vor der schließenden `};` der `Tables`)
**Interfaces:**
- Produziert: Tabellentyp `flyer_downloads` mit `Row`/`Insert`/`Update`, den Task 4 (API-Route) für `db.from("flyer_downloads").insert(...)` benötigt.
- [ ] **Step 1: SQL manuell im Supabase SQL-Editor ausführen**
Dieser Schritt kann nicht automatisiert werden (lokale `.env.local` enthält nur Platzhalter-Credentials, kein echter Zugriff auf die Produktions-Supabase-Instanz). Jonny führt folgendes SQL in seiner Supabase-Instanz aus:
```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;
```
Verifikation (im SQL-Editor):
```sql
SELECT column_name, data_type FROM information_schema.columns WHERE table_name = 'flyer_downloads';
```
Erwartet: 5 Zeilen (`id`, `email`, `flyer`, `dsgvo_einwilligung`, `created_at`).
- [ ] **Step 2: Datenbank-Typ in `lib/supabase.ts` ergänzen**
In `lib/supabase.ts` direkt vor der schließenden `};` des `Tables`-Objekts (nach dem `kontakt_social`-Eintrag, aktuell endet dieser Block bei `Relationships: [];\n };`) folgenden Eintrag einfügen:
```ts
flyer_downloads: {
Row: {
id: number;
email: string;
flyer: string;
dsgvo_einwilligung: boolean;
created_at: string;
};
Insert: {
id?: number;
email: string;
flyer?: string;
dsgvo_einwilligung: boolean;
created_at?: string;
};
Update: {
email?: string;
flyer?: string;
dsgvo_einwilligung?: boolean;
};
Relationships: [];
};
```
- [ ] **Step 3: TypeScript-Check**
```bash
npx tsc --noEmit
```
Erwartet: keine Fehler.
- [ ] **Step 4: Commit**
```bash
git add lib/supabase.ts
git commit -m "feat: add flyer_downloads table type to Supabase schema"
```
---
### Task 3: Mailer-Funktionen für Flyer-Benachrichtigung und Flyer-Link
**Files:**
- Modify: `lib/mailer.ts` (zwei neue Exporte am Ende der Datei ergänzen, nach `sendeKontaktEmail`)
**Interfaces:**
- Konsumiert: `sendWithFallback` (bereits in `lib/mailer.ts` definiert, Zeile 40).
- Produziert: `sendeFlyerBenachrichtigung(data: { email: string }): Promise<{ sent: boolean; queued: boolean }>` und `sendeFlyerLink(data: { email: string }): Promise<void>`, die Task 4 (API-Route) importiert.
- [ ] **Step 1: Funktionen in `lib/mailer.ts` ergänzen**
Am Ende der Datei (nach der schließenden `}` von `sendeKontaktEmail`) einfügen:
```ts
export async function sendeFlyerBenachrichtigung(
data: { email: string }
): Promise<{ sent: boolean; queued: boolean }> {
const html = `
<!DOCTYPE html>
<html lang="de">
<head><meta charset="UTF-8"></head>
<body style="font-family:system-ui,sans-serif;color:#e2e8f0;max-width:600px;margin:0 auto;padding:0">
<div style="background:#18212f;padding:20px 24px;border-bottom:2px solid #f97316">
<h1 style="color:#f97316;margin:0;font-size:20px;font-weight:700">MBO Tech IT</h1>
<p style="color:rgba(255,255,255,0.6);margin:4px 0 0;font-size:13px">FamilyGuard Flyer-Download</p>
</div>
<div style="padding:24px;background:#1e2a3b">
<h2 style="margin:0 0 16px;font-size:18px;color:#f8fafc">Neuer Flyer-Download</h2>
<p style="color:#94a3b8;margin:0 0 16px;line-height:1.6">
Ein Interessent hat den MBO FamilyGuard Flyer angefordert.
</p>
<table style="width:100%;border-collapse:collapse">
<tr><td style="padding:6px 0;color:#94a3b8;width:120px">E-Mail</td><td style="padding:6px 0"><a href="mailto:${data.email}" style="color:#60a5fa">${data.email}</a></td></tr>
</table>
</div>
<div style="padding:12px 24px;background:#111925;border-top:1px solid rgba(255,255,255,0.1)">
<p style="margin:0;font-size:11px;color:#64748b">MBO Tech IT · ${process.env.APP_URL ?? "https://mbo-tech-it.de"}</p>
</div>
</body>
</html>`;
return sendWithFallback(
{
from: `"MBO Tech IT" <${process.env.SMTP_FROM}>`,
to: "jonny@mbo-tech-it.de",
replyTo: data.email,
subject: `Neuer FamilyGuard-Flyer-Download: ${data.email}`,
text: `Neuer Flyer-Download\n\nE-Mail: ${data.email}`,
html,
},
`FamilyGuard-Flyer-Benachrichtigung ${data.email}`
);
}
export async function sendeFlyerLink(data: { email: string }): Promise<void> {
const downloadLink = `${process.env.APP_URL ?? "https://mbo-tech-it.de"}/downloads/MBO_FamilyGuard_Flyer_01.pdf`;
const html = `
<!DOCTYPE html>
<html lang="de">
<head><meta charset="UTF-8"></head>
<body style="font-family:system-ui,sans-serif;color:#e2e8f0;max-width:600px;margin:0 auto;padding:0">
<div style="background:#18212f;padding:20px 24px;border-bottom:2px solid #f97316">
<h1 style="color:#f97316;margin:0;font-size:20px;font-weight:700">MBO Tech IT</h1>
<p style="color:rgba(255,255,255,0.6);margin:4px 0 0;font-size:13px">MBO FamilyGuard</p>
</div>
<div style="padding:24px;background:#1e2a3b">
<h2 style="margin:0 0 16px;font-size:18px;color:#f8fafc">Ihr FamilyGuard-Flyer</h2>
<p style="color:#94a3b8;margin:0 0 24px;line-height:1.6">
Vielen Dank für Ihr Interesse an MBO FamilyGuard. Den Flyer mit allen Details
finden Sie hier zum Download:
</p>
<a href="${downloadLink}"
style="display:inline-block;background:#f97316;color:#fff;font-weight:700;padding:12px 24px;border-radius:8px;text-decoration:none;font-size:15px">
Flyer herunterladen
</a>
<p style="color:#64748b;font-size:12px;margin:24px 0 0">
Fragen? Rufen Sie uns an: <a href="tel:+4917193451093" style="color:#f97316">+49 171 9345193</a>
</p>
</div>
<div style="padding:12px 24px;background:#111925;border-top:1px solid rgba(255,255,255,0.1)">
<p style="margin:0;font-size:11px;color:#64748b">MBO Tech IT · ${process.env.APP_URL ?? "https://mbo-tech-it.de"}</p>
</div>
</body>
</html>`;
await sendWithFallback(
{
from: `"MBO Tech IT" <${process.env.SMTP_FROM}>`,
to: data.email,
subject: "Ihr MBO FamilyGuard Flyer",
text: `Hallo,\n\nvielen Dank für Ihr Interesse an MBO FamilyGuard. Den Flyer finden Sie hier:\n${downloadLink}\n\nMBO Tech IT`,
html,
},
`FamilyGuard-Flyer-Link ${data.email}`
);
}
```
- [ ] **Step 2: TypeScript-Check**
```bash
npx tsc --noEmit
```
Erwartet: keine Fehler.
- [ ] **Step 3: Commit**
```bash
git add lib/mailer.ts
git commit -m "feat: add mailer functions for FamilyGuard flyer notification and link"
```
---
### Task 4: API-Route `POST /api/familyguard-flyer`
**Files:**
- Create: `app/api/familyguard-flyer/route.ts`
**Interfaces:**
- Konsumiert: `sendeFlyerBenachrichtigung`, `sendeFlyerLink` (aus Task 3), `createServiceClient` (aus `lib/supabase.ts`), Tabelle `flyer_downloads` (aus Task 2).
- Produziert: `POST /api/familyguard-flyer` — Request `{ email: string, dsgvoEinwilligung: boolean }`, Response `{ ok: true, downloadUrl: string }` oder `{ ok: false, error: string }`. Wird von Task 5 (`FlyerDownloadForm`) konsumiert.
- [ ] **Step 1: Route-Datei erstellen**
```ts
import { NextResponse } from "next/server";
import { sendeFlyerBenachrichtigung, sendeFlyerLink } from "@/lib/mailer";
import { createServiceClient } from "@/lib/supabase";
const FLYER_DOWNLOAD_URL = "/downloads/MBO_FamilyGuard_Flyer_01.pdf";
function isValidEmail(email: string): boolean {
return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email);
}
export async function POST(request: Request) {
let body: { email?: string; dsgvoEinwilligung?: boolean };
try {
body = await request.json();
} catch {
return NextResponse.json({ ok: false, error: "Ungültige Anfrage" }, { status: 400 });
}
const email = body.email?.trim() ?? "";
if (!isValidEmail(email)) {
return NextResponse.json(
{ ok: false, error: "Bitte geben Sie eine gültige E-Mail-Adresse ein" },
{ status: 400 }
);
}
if (body.dsgvoEinwilligung !== true) {
return NextResponse.json(
{ ok: false, error: "Bitte stimmen Sie der Datenverarbeitung zu" },
{ status: 400 }
);
}
try {
const db = createServiceClient();
await db.from("flyer_downloads").insert({ email, dsgvo_einwilligung: true });
} catch (err) {
console.error("[FamilyGuard-Flyer] Supabase insert error:", err);
}
await sendeFlyerBenachrichtigung({ email });
sendeFlyerLink({ email }).catch((err) =>
console.error("[FamilyGuard-Flyer] Flyer-Mail an Interessenten fehlgeschlagen:", err)
);
return NextResponse.json({ ok: true, downloadUrl: FLYER_DOWNLOAD_URL });
}
```
- [ ] **Step 2: TypeScript-Check**
```bash
npx tsc --noEmit
```
Erwartet: keine Fehler.
- [ ] **Step 3: Manueller Funktionstest gegen den Dev-Server**
```bash
npm run dev
```
In einem zweiten Terminal:
```bash
curl -s -X POST http://localhost:3000/api/familyguard-flyer \
-H "Content-Type: application/json" \
-d '{"email":"test@beispiel.de","dsgvoEinwilligung":true}'
```
Erwartet: `{"ok":true,"downloadUrl":"/downloads/MBO_FamilyGuard_Flyer_01.pdf"}` (oder ein Fehler zu SMTP/Supabase im Server-Log, falls in der lokalen Umgebung keine echten Zugangsdaten hinterlegt sind — das ist unschädlich, siehe Global Constraints).
Zusätzlich Validierung prüfen:
```bash
curl -s -X POST http://localhost:3000/api/familyguard-flyer \
-H "Content-Type: application/json" \
-d '{"email":"keine-email","dsgvoEinwilligung":true}'
```
Erwartet: `{"ok":false,"error":"Bitte geben Sie eine gültige E-Mail-Adresse ein"}` mit Status 400.
- [ ] **Step 4: Commit**
```bash
git add app/api/familyguard-flyer/route.ts
git commit -m "feat: add API route for FamilyGuard flyer email-gate download"
```
---
### Task 5: Komponente `FlyerDownloadForm`
**Files:**
- Create: `components/FlyerDownloadForm.tsx`
**Interfaces:**
- Konsumiert: `POST /api/familyguard-flyer` (aus Task 4).
- Produziert: `export default function FlyerDownloadForm()` (keine Props), gerendert mit `id="flyer-download"` als Root-Element. Wird von Task 6 in `app/pakete/familyguard/page.tsx` eingebunden.
- [ ] **Step 1: Komponente erstellen**
```tsx
"use client";
import { useState } from "react";
type Status = "idle" | "loading" | "success" | "error";
export default function FlyerDownloadForm() {
const [email, setEmail] = useState("");
const [dsgvo, setDsgvo] = useState(false);
const [status, setStatus] = useState<Status>("idle");
const [errorMsg, setErrorMsg] = useState("");
const [downloadUrl, setDownloadUrl] = useState("");
async function handleSubmit(e: React.FormEvent) {
e.preventDefault();
setStatus("loading");
setErrorMsg("");
try {
const res = await fetch("/api/familyguard-flyer", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ email, dsgvoEinwilligung: dsgvo }),
});
const data = await res.json();
if (!res.ok || !data.ok) {
setErrorMsg(data.error ?? "Unbekannter Fehler");
setStatus("error");
} else {
setDownloadUrl(data.downloadUrl);
setStatus("success");
}
} catch {
setErrorMsg("Netzwerkfehler bitte versuchen Sie es erneut.");
setStatus("error");
}
}
return (
<div
id="flyer-download"
className="mb-16 p-6 sm:p-8 rounded-2xl bg-white dark:bg-gray-900 border border-slate-200 dark:border-gray-800 scroll-mt-24"
>
<div className="text-center mb-6">
<span className="text-orange-400 font-mono text-xs font-bold tracking-[0.25em] uppercase">
Flyer
</span>
<h3 className="text-2xl font-black text-slate-900 dark:text-white mt-1 mb-2">
FamilyGuard-Flyer herunterladen
</h3>
<p className="text-slate-600 dark:text-slate-400 text-sm max-w-xl mx-auto">
Alle Infos zu MBO FamilyGuard kompakt als PDF tragen Sie Ihre E-Mail-Adresse
ein und laden Sie den Flyer direkt herunter.
</p>
</div>
{status === "success" ? (
<div className="flex flex-col items-center text-center">
<p className="text-slate-700 dark:text-slate-300 text-sm mb-4">
Fertig! Der Flyer wurde Ihnen außerdem per E-Mail zugeschickt.
</p>
<a href={downloadUrl} download className="btn-primary inline-flex px-6 py-3 text-sm">
Flyer jetzt herunterladen
</a>
</div>
) : (
<form onSubmit={handleSubmit} className="max-w-md mx-auto space-y-4">
<input
type="email"
required
value={email}
onChange={(e) => setEmail(e.target.value)}
placeholder="max@beispiel.de"
className="w-full px-4 py-3 rounded-xl bg-slate-50 dark:bg-[#111925] border border-slate-300 dark:border-gray-700 text-slate-900 dark:text-white placeholder-slate-400 dark:placeholder-slate-600 focus:outline-none focus:border-orange-500/60 focus:ring-1 focus:ring-orange-500/20 transition-colors"
/>
<label className="flex items-start gap-2 text-xs text-slate-600 dark:text-slate-400">
<input
type="checkbox"
required
checked={dsgvo}
onChange={(e) => setDsgvo(e.target.checked)}
className="mt-0.5"
/>
<span>
Ich bin damit einverstanden, dass meine E-Mail-Adresse zum Zweck des
Flyer-Versands gespeichert wird. Details in der{" "}
<a href="/datenschutz" className="text-orange-400 hover:underline">
Datenschutzerklärung
</a>
.
</span>
</label>
{status === "error" && (
<div className="flex items-start gap-3 px-4 py-3 rounded-xl bg-red-500/10 border border-red-500/20 text-red-400 text-sm">
<span>{errorMsg || "Anfrage fehlgeschlagen. Bitte versuchen Sie es erneut."}</span>
</div>
)}
<button
type="submit"
disabled={status === "loading"}
className="btn-primary w-full py-3 text-sm disabled:opacity-60 disabled:cursor-not-allowed"
>
{status === "loading" ? "Wird gesendet..." : "Flyer herunterladen"}
</button>
</form>
)}
</div>
);
}
```
- [ ] **Step 2: TypeScript-Check**
```bash
npx tsc --noEmit
```
Erwartet: keine Fehler.
- [ ] **Step 3: Commit**
```bash
git add components/FlyerDownloadForm.tsx
git commit -m "feat: add FlyerDownloadForm component"
```
---
### Task 6: `FlyerDownloadForm` in die FamilyGuard-Seite einbinden
**Files:**
- Modify: `app/pakete/familyguard/page.tsx:1-3` (Import ergänzen), `app/pakete/familyguard/page.tsx:204-206` (Komponente einfügen)
**Interfaces:**
- Konsumiert: `FlyerDownloadForm` (aus Task 5).
- [ ] **Step 1: Import ergänzen**
In `app/pakete/familyguard/page.tsx` nach der bestehenden Import-Zeile `import Logo from "@/components/Logo";` (Zeile 3) einfügen:
```tsx
import FlyerDownloadForm from "@/components/FlyerDownloadForm";
```
- [ ] **Step 2: Komponente zwischen Preis-Block und Bottom-Callout einfügen**
Den bestehenden Codeblock
```tsx
{/* Bottom callout */}
<div className="p-6 rounded-2xl border border-blue-500/20 bg-blue-500/5 text-center">
```
ersetzen durch:
```tsx
<FlyerDownloadForm />
{/* Bottom callout */}
<div className="p-6 rounded-2xl border border-blue-500/20 bg-blue-500/5 text-center">
```
- [ ] **Step 3: TypeScript-Check**
```bash
npx tsc --noEmit
```
Erwartet: keine Fehler.
- [ ] **Step 4: Manueller Test im Browser**
```bash
npm run dev
```
`http://localhost:3000/pakete/familyguard` öffnen, zum Flyer-Download-Bereich scrollen, Formular ohne Checkbox absenden (Browser-Validierung muss greifen wegen `required`), dann mit gültiger E-Mail + Checkbox absenden und prüfen, dass der Download-Button erscheint und `http://localhost:3000/pakete/familyguard#flyer-download` direkt zur Sektion springt.
- [ ] **Step 5: Commit**
```bash
git add app/pakete/familyguard/page.tsx
git commit -m "feat: embed FlyerDownloadForm on FamilyGuard page"
```
---
### Task 7: Link zur Flyer-Sektion in der Homepage-Promo-Kachel
**Files:**
- Modify: `components/FamilyGuardPromo.tsx:42-49`
**Interfaces:**
- Konsumiert: Anchor `#flyer-download` auf `/pakete/familyguard` (aus Task 6).
- [ ] **Step 1: Dritten Button ergänzen**
Den bestehenden Block
```tsx
<div className="flex flex-col sm:flex-row gap-3">
<Link href="/pakete/familyguard" className="btn-primary inline-flex justify-center px-6 py-3 text-sm">
FamilyGuard entdecken
</Link>
<a href="#contact" className="btn-secondary inline-flex justify-center px-6 py-3 text-sm">
Jetzt beraten lassen
</a>
</div>
```
ersetzen durch:
```tsx
<div className="flex flex-col sm:flex-row gap-3">
<Link href="/pakete/familyguard" className="btn-primary inline-flex justify-center px-6 py-3 text-sm">
FamilyGuard entdecken
</Link>
<Link
href="/pakete/familyguard#flyer-download"
className="btn-secondary inline-flex justify-center px-6 py-3 text-sm"
>
Flyer herunterladen
</Link>
<a href="#contact" className="btn-secondary inline-flex justify-center px-6 py-3 text-sm">
Jetzt beraten lassen
</a>
</div>
```
- [ ] **Step 2: TypeScript-Check**
```bash
npx tsc --noEmit
```
Erwartet: keine Fehler.
- [ ] **Step 3: Manueller Test im Browser**
```bash
npm run dev
```
`http://localhost:3000/` öffnen, in der FamilyGuard-Promo-Kachel auf „Flyer herunterladen" klicken, prüfen dass die FamilyGuard-Seite direkt bei der Flyer-Download-Sektion landet.
- [ ] **Step 4: Commit**
```bash
git add components/FamilyGuardPromo.tsx
git commit -m "feat: link homepage FamilyGuard promo to flyer download section"
```
---
## Nach Abschluss aller Tasks
- `npm run build` einmal komplett durchlaufen lassen, um sicherzustellen, dass Produktions-Build fehlerfrei ist.
- Jonny muss die SQL-Migration aus Task 2 in der echten Supabase-Instanz ausführen und `SMTP_*`/`APP_URL`-Env-Variablen auf dem Server prüfen, damit die Mails im Produktivbetrieb tatsächlich zugestellt werden.