Kontaktformular: sendet an Directus (Flow → Mail an den Verein)
- Formular POSTet an die öffentliche Directus-Collection contact_messages (Name, E-Mail, Betreff, Nachricht) + Honeypot gegen Spam + Sende-/Fehler-Status mit mailto-Fallback. Öffentliche CMS-URL hartkodiert (Build-Env wäre die interne Docker-Adresse). - Datenschutz §5 angepasst: Formular speichert jetzt in selbstgehostetem Directus und benachrichtigt den Vorstand per Mail (statt mailto). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -91,7 +91,7 @@ const settings = await getSettings();
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h2 class="font-bold text-[var(--color-text)] mb-3">5. Kontaktaufnahme per E-Mail</h2>
|
||||
<h2 class="font-bold text-[var(--color-text)] mb-3">5. Kontaktaufnahme per E-Mail und Formular</h2>
|
||||
<p>
|
||||
Wenn du uns per E-Mail kontaktierst, werden deine Angaben (E-Mail-Adresse,
|
||||
ggf. Name und Nachrichteninhalt) zum Zweck der Bearbeitung deiner Anfrage
|
||||
@@ -104,8 +104,11 @@ const settings = await getSettings();
|
||||
Aufbewahrungspflichten entgegenstehen.
|
||||
</p>
|
||||
<p class="mt-3">
|
||||
Das Kontaktformular auf dieser Website funktioniert als mailto-Link — es werden
|
||||
keine Formulardaten auf unseren Servern gespeichert oder verarbeitet.
|
||||
Das Kontaktformular übermittelt deine Angaben (Name, E-Mail, Betreff, Nachricht) an
|
||||
unser selbst gehostetes Redaktionssystem (Directus, Server in Deutschland) und
|
||||
benachrichtigt den Vorstand per E-Mail. Die Daten werden ausschließlich zur Bearbeitung
|
||||
deiner Anfrage verwendet, nicht an Dritte weitergegeben und nach Erledigung gelöscht,
|
||||
soweit keine gesetzlichen Aufbewahrungspflichten bestehen.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -4,6 +4,10 @@ import PageHeader from "../components/PageHeader.astro";
|
||||
import { getSettings } from "../lib/directus";
|
||||
|
||||
const settings = await getSettings();
|
||||
// Öffentliche CMS-URL für den Browser-Fetch — NICHT import.meta.env.DIRECTUS_URL,
|
||||
// das ist beim Build (im Rebuild-Container) die interne Docker-Adresse.
|
||||
const cmsPublicUrl = "https://cms.kitafreunde-regenbogen.de";
|
||||
const contactEmail = settings?.contact_email ?? "info@kitafreunde-regenbogen.de";
|
||||
---
|
||||
|
||||
<Layout title="Kontakt" description="Kontakt zum Kitafreunde Regenbogen e.V. — schreib uns, werde Mitglied oder bring eine Idee ein.">
|
||||
@@ -54,39 +58,99 @@ const settings = await getSettings();
|
||||
)}
|
||||
</div>
|
||||
|
||||
<!-- Formular (statisches HTML, ohne Backend) -->
|
||||
<!-- Formular: sendet an Directus → Flow benachrichtigt den Verein per Mail -->
|
||||
<div class="card">
|
||||
<h2 class="font-bold mb-4">Direkte Nachricht</h2>
|
||||
<p class="text-sm text-[var(--color-text-muted)] mb-6">
|
||||
Dieses Formular öffnet deinen E-Mail-Client.
|
||||
</p>
|
||||
<form
|
||||
action={`mailto:${settings?.contact_email ?? "info@kitafreunde-regenbogen.de"}`}
|
||||
method="get"
|
||||
enctype="text/plain"
|
||||
id="contact-form"
|
||||
class="space-y-4"
|
||||
data-endpoint={`${cmsPublicUrl}/items/contact_messages`}
|
||||
data-fallback={contactEmail}
|
||||
>
|
||||
<div class="grid sm:grid-cols-2 gap-4">
|
||||
<div>
|
||||
<label for="cf-name" class="block text-sm font-medium mb-1">Name</label>
|
||||
<input id="cf-name" name="name" type="text" required autocomplete="name"
|
||||
class="w-full border border-[var(--color-border)] rounded-xl px-4 py-2.5 text-sm focus:outline-none focus:border-[var(--color-primary)]" />
|
||||
</div>
|
||||
<div>
|
||||
<label for="cf-email" class="block text-sm font-medium mb-1">E-Mail</label>
|
||||
<input id="cf-email" name="email" type="email" required autocomplete="email"
|
||||
class="w-full border border-[var(--color-border)] rounded-xl px-4 py-2.5 text-sm focus:outline-none focus:border-[var(--color-primary)]" />
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<label for="subject" class="block text-sm font-medium mb-1">Betreff</label>
|
||||
<select id="subject" name="subject" class="w-full border border-[var(--color-border)] rounded-xl px-4 py-2.5 text-sm focus:outline-none focus:border-[var(--color-primary)]">
|
||||
<label for="cf-subject" class="block text-sm font-medium mb-1">Betreff</label>
|
||||
<select id="cf-subject" name="subject"
|
||||
class="w-full border border-[var(--color-border)] rounded-xl px-4 py-2.5 text-sm focus:outline-none focus:border-[var(--color-primary)]">
|
||||
<option value="Mitgliedschaft">Mitgliedschaft</option>
|
||||
<option value="Projektidee">Projektidee einbringen</option>
|
||||
<option value="Spende/Kooperation">Spende / Kooperation</option>
|
||||
<option value="Spende / Kooperation">Spende / Kooperation</option>
|
||||
<option value="Presseanfrage">Presseanfrage</option>
|
||||
<option value="Allgemeine Anfrage">Allgemeine Anfrage</option>
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
<label for="body" class="block text-sm font-medium mb-1">Nachricht</label>
|
||||
<textarea id="body" name="body" rows="5"
|
||||
<label for="cf-message" class="block text-sm font-medium mb-1">Nachricht</label>
|
||||
<textarea id="cf-message" name="message" rows="5" required
|
||||
class="w-full border border-[var(--color-border)] rounded-xl px-4 py-2.5 text-sm focus:outline-none focus:border-[var(--color-primary)] resize-none"
|
||||
placeholder="Deine Nachricht..."></textarea>
|
||||
</div>
|
||||
<button type="submit" class="btn-primary w-full justify-center">
|
||||
E-Mail öffnen →
|
||||
<!-- Honeypot gegen Spam: für Menschen unsichtbar, bitte leer lassen -->
|
||||
<div aria-hidden="true" style="position:absolute; left:-9999px; width:1px; height:1px; overflow:hidden;">
|
||||
<label>Website<input type="text" name="website" tabindex="-1" autocomplete="off" /></label>
|
||||
</div>
|
||||
<button type="submit" id="cf-submit" class="btn-primary w-full justify-center">
|
||||
<span aria-hidden="true">✉</span> Nachricht senden
|
||||
</button>
|
||||
<p id="cf-status" class="text-sm hidden" role="status" aria-live="polite"></p>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<script>
|
||||
const form = document.getElementById("contact-form");
|
||||
if (form) {
|
||||
const statusEl = document.getElementById("cf-status");
|
||||
const btn = document.getElementById("cf-submit");
|
||||
const setStatus = (msg, color) => {
|
||||
statusEl.innerHTML = msg;
|
||||
statusEl.style.color = color;
|
||||
statusEl.classList.remove("hidden");
|
||||
};
|
||||
form.addEventListener("submit", async (e) => {
|
||||
e.preventDefault();
|
||||
const data = new FormData(form);
|
||||
if (data.get("website")) return; // Honeypot: stiller Bot-Abbruch
|
||||
const endpoint = form.dataset.endpoint;
|
||||
const fallback = form.dataset.fallback;
|
||||
const payload = {
|
||||
name: (data.get("name") || "").toString().trim(),
|
||||
email: (data.get("email") || "").toString().trim(),
|
||||
subject: (data.get("subject") || "").toString(),
|
||||
message: (data.get("message") || "").toString().trim(),
|
||||
};
|
||||
btn.disabled = true;
|
||||
setStatus("Senden…", "var(--color-text-muted)");
|
||||
try {
|
||||
const res = await fetch(endpoint, {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify(payload),
|
||||
});
|
||||
if (!res.ok) throw new Error("HTTP " + res.status);
|
||||
form.reset();
|
||||
setStatus("Danke! Deine Nachricht ist angekommen — wir melden uns bald.", "var(--color-rb-green)");
|
||||
} catch (err) {
|
||||
setStatus(
|
||||
`Senden hat gerade nicht geklappt. Schreib uns gern direkt: <a class="underline font-medium" href="mailto:${fallback}">${fallback}</a>`,
|
||||
"var(--color-rb-red)",
|
||||
);
|
||||
} finally {
|
||||
btn.disabled = false;
|
||||
}
|
||||
});
|
||||
}
|
||||
</script>
|
||||
</Layout>
|
||||
|
||||
Reference in New Issue
Block a user