diff --git a/app/actions/contact-submit.php b/app/actions/contact-submit.php index 0bfa98c..12310c9 100644 --- a/app/actions/contact-submit.php +++ b/app/actions/contact-submit.php @@ -4,25 +4,33 @@ declare(strict_types=1); /** * POST /kontakt-senden — Kontaktformular validieren und via Brevo SMTP versenden. - * Antwort: PRG-Redirect zu /#kontakt (?sent=1 | ?error=…) oder JSON bei fetch (form.js). + * Antwort: PRG-Redirect zurück zur absendenden Seite (#kontakt, ?sent=1 | ?error=…) + * oder JSON bei fetch (form.js). Das Formular kommt von / wie auch von /kontakt. */ use PHPMailer\PHPMailer\PHPMailer; +$field = static fn (string $key): string => trim((string) ($_POST[$key] ?? '')); + +// Rücksprungziel gegen die Slug-Whitelist (Single Source of Truth) prüfen — kein Open-Redirect. +$return = $field('return'); +$routes = require APP_PATH . '/routes.php'; +if (!array_key_exists($return, $routes)) { + $return = ''; +} + $wantsJson = str_contains($_SERVER['HTTP_ACCEPT'] ?? '', 'application/json'); -$respond = static function (bool $ok, string $error = '') use ($wantsJson): never { +$respond = static function (bool $ok, string $error = '') use ($wantsJson, $return): never { if ($wantsJson) { header('Content-Type: application/json; charset=utf-8'); echo json_encode(['ok' => $ok, 'error' => $error ?: null]); } else { - header('Location: ' . url('') . ($ok ? '?sent=1' : '?error=' . $error) . '#kontakt', true, 303); + header('Location: ' . url($return) . ($ok ? '?sent=1' : '?error=' . $error) . '#kontakt', true, 303); } exit; }; -$field = static fn (string $key): string => trim((string) ($_POST[$key] ?? '')); - $route = 'kontakt'; $token = $field('ft'); diff --git a/app/components/contact-form.php b/app/components/contact-form.php index 55b9551..3a0a2ff 100644 --- a/app/components/contact-form.php +++ b/app/components/contact-form.php @@ -4,12 +4,18 @@ declare(strict_types=1); /** * Kontaktformular (Felder wie auf der alten Seite). Props: - * $intro array{title, text} + * $intro array{title, text}|null — optionaler Intro-Block (Überschrift, Teaser, Direkt-Mail). + * Weglassen → nur das Formular (z. B. auf /kontakt, wo der Hero den Kontext liefert). + * $return string optional — Slug der absendenden Seite (Default '' = Startseite). Die Action + * leitet nach PRG hierher zurück (whitelist-validiert), damit /kontakt nicht auf die + * Startseite springt. * Felder rendern ausschließlich über component('form-field') — siehe CLAUDE.md. * Liest Flash-Status aus Query-Params (?sent=1 / ?error=...) — PRG-Muster. * Spam-Schutz: Honeypot "company_url" + signierter Timestamp "ft" (form_token()). */ $club = json_load('club'); +$return = $return ?? ''; +$intro = $intro ?? null; $interests = ['Herrenfußball', 'Damenfußball', 'Jugendfußball', 'Turnen', 'Sonstiges']; @@ -37,11 +43,13 @@ $f = [ ?>
+

Oder direkt per Mail an

+
@@ -73,6 +81,7 @@ $f = [
+

diff --git a/app/pages/kontakt.php b/app/pages/kontakt.php new file mode 100644 index 0000000..769f885 --- /dev/null +++ b/app/pages/kontakt.php @@ -0,0 +1,24 @@ + 'Kontakt', + 'description' => 'Kontakt zum TSV 08 Kulmbach: Schreib uns über das Formular – zu Fußball, Turnen oder einem allgemeinen Anliegen. Wir melden uns so schnell wie möglich.', + 'og_image' => 'img/pages/vereinsgelaende-1600.jpg', + 'scripts' => ['form.js'], + 'schema' => page_schema([ + ['name' => 'Startseite', 'slug' => ''], + ['name' => 'Kontakt', 'slug' => 'kontakt'], + ]), +]; + +component('hero', ['hero' => $page['hero']]); +// Kein Intro-Block: Der Hero liefert bereits Überschrift + Teaser, hier folgt nur das Formular. +component('contact-form', ['return' => 'kontakt']); diff --git a/app/routes.php b/app/routes.php index c826fa4..841e7d7 100644 --- a/app/routes.php +++ b/app/routes.php @@ -28,6 +28,7 @@ return [ 'historie' => ['file' => 'historie.php', 'sitemap' => true], 'sportheimbuchung' => ['file' => 'sportheimbuchung.php', 'sitemap' => true], 'ansprechpartner' => ['file' => 'ansprechpartner.php', 'sitemap' => true], + 'kontakt' => ['file' => 'kontakt.php', 'sitemap' => true], 'partner-werden' => ['file' => 'partner-werden.php', 'sitemap' => true], 'mitglied-werden' => ['file' => 'mitglied-werden.php', 'sitemap' => true], 'impressum' => ['file' => 'impressum.php', 'sitemap' => true], diff --git a/data/kontakt.json b/data/kontakt.json new file mode 100644 index 0000000..e72a7e0 --- /dev/null +++ b/data/kontakt.json @@ -0,0 +1,13 @@ +{ + "hero": { + "compact": true, + "align": "bottom-left", + "title": "Kontakt", + "text": "Fragen zu unseren Abteilungen, Lust mitzumachen oder ein Anliegen an den Verein? Schreib uns – wir melden uns so schnell wie möglich bei dir.", + "image": { + "base": "img/pages/vereinsgelaende", + "widths": [640, 1000, 1600], + "alt": "Vereinsgelände des TSV 08 Kulmbach im Katzbachtal" + } + } +} diff --git a/data/navigation.json b/data/navigation.json index b319011..93dda01 100644 --- a/data/navigation.json +++ b/data/navigation.json @@ -31,6 +31,7 @@ "children": [ { "label": "Übersicht", "slug": "verein" }, { "label": "Ansprechpartner", "slug": "ansprechpartner" }, + { "label": "Kontakt", "slug": "kontakt" }, { "label": "Historie", "slug": "historie" }, { "label": "Sportheimbuchung", "slug": "sportheimbuchung", "separator": true }, { "label": "Partner werden", "slug": "partner-werden" } @@ -42,6 +43,7 @@ { "label": "Mitglied werden", "slug": "mitglied-werden", "style": "primary" } ], "legal": [ + { "label": "Kontakt", "slug": "kontakt" }, { "label": "Impressum", "slug": "impressum" }, { "label": "Datenschutz", "slug": "datenschutz" } ] diff --git a/public/assets/img/pages/vereinsgelaende-640.jpg b/public/assets/img/pages/vereinsgelaende-640.jpg new file mode 100644 index 0000000..6c4ec63 Binary files /dev/null and b/public/assets/img/pages/vereinsgelaende-640.jpg differ