From 1bdd45d09770f29058ac836488e0dadb48bc4889 Mon Sep 17 00:00:00 2001 From: Milian Tomzig Date: Tue, 11 Aug 2026 17:03:53 +0200 Subject: [PATCH 1/3] Implement contact form with hCaptcha and update documentation --- .nvmrc | 1 + README.md | 37 +++ src/components/ContactForm.astro | 382 +++++++++++++++++++++++++++++++ src/i18n/ui/de.ts | 30 +++ src/i18n/ui/en.ts | 29 +++ src/pages/contact.mdx | 7 +- src/pages/de/contact.mdx | 7 +- src/pages/de/imprint.mdx | 7 +- src/pages/de/privacy.mdx | 66 ++++++ src/pages/imprint.mdx | 7 +- src/pages/privacy.mdx | 24 ++ 11 files changed, 585 insertions(+), 12 deletions(-) create mode 100644 .nvmrc create mode 100644 src/components/ContactForm.astro diff --git a/.nvmrc b/.nvmrc new file mode 100644 index 0000000..2bd5a0a --- /dev/null +++ b/.nvmrc @@ -0,0 +1 @@ +22 diff --git a/README.md b/README.md index b517749..a88f66c 100644 --- a/README.md +++ b/README.md @@ -36,6 +36,21 @@ The actual pages are in [`src/pages/`](/src/pages/) and are mostly Markdown/MDX. ## Development Commands +### Node version + +The repo ships an [`.nvmrc`](./.nvmrc), so with +[nvm](https://github.com/nvm-sh/nvm) installed you can pick the right version automatically: + +```sh +nvm install # first time only – installs the version from .nvmrc +nvm use # switch the current shell to it +``` + +If `npm run build` fails with `Node.js vXX is not supported by Astro!`, you are on an +older version – run `nvm use` and try again. + +### Commands + All commands are run from the root of the project: | Command | Action | @@ -52,6 +67,28 @@ All commands are run from the root of the project: --- +## Testing the contact form + +The contact forms ([`ContactForm.astro`](/src/components/ContactForm.astro)) use hCaptcha for +spam protection, and **sending cannot be tested on a local dev server**: + +- hCaptcha refuses to run on `localhost` and `127.0.0.1`. +- Its official test key renders the widget anywhere, but produces a token that only validates + against the matching test secret – which Web3Forms does not hold. Submitting then fails with + `Could not validate hCaptcha`. + +So test the full send on the deployed site. Locally you can still work on everything else by +rendering the form without the captcha: + +```astro + +``` + +If a submission fails, the browser console carries the message Web3Forms actually returned; +the visitor only ever sees the generic localised error. + +--- + ## Further Reading - [Astro documentation](https://docs.astro.build) diff --git a/src/components/ContactForm.astro b/src/components/ContactForm.astro new file mode 100644 index 0000000..1f85ffa --- /dev/null +++ b/src/components/ContactForm.astro @@ -0,0 +1,382 @@ +--- +import { getRelativeLocaleUrl } from 'astro:i18n'; +import { getUiTranslations } from '../i18n/ui/ui-i18n-helper'; + +interface Props { + /** Which mailbox the message is delivered to. */ + recipient?: 'info' | 'vorstand'; + locale?: string; + /** + * Renders the hCaptcha widget. Set to false to fall back to honeypot-only + * spam protection – see the privacy note in AGENTS.md before changing. + */ + captcha?: boolean; +} + +/** + * Web3Forms access keys. These are public by design – they only authorise + * delivery to the mailbox they were issued for and carry no account access. + */ +const ACCESS_KEYS: Record = { + info: '1800e3ce-5da4-4b3e-bfa3-6c3f1d5ca463', + vorstand: '0d08005a-a531-481f-9cd7-851a4be6b2f8', +}; + +/** Shared hCaptcha site key of the Web3Forms free plan (zero-config integration). */ +const HCAPTCHA_SITE_KEY = '50b2fe65-b00b-4b9e-ad62-3ba471098be2'; + +const { recipient = 'info', locale = Astro.currentLocale ?? 'en', captcha = true } = Astro.props; +const t = getUiTranslations(locale).contactForm; +const accessKey = ACCESS_KEYS[recipient] ?? ACCESS_KEYS.info; +const privacyUrl = getRelativeLocaleUrl(locale, '/privacy'); +--- + +
+ + + {/* Honeypot: hidden from users, bots that tick it get rejected. */} + + +
+ + + +
+ +
+ + + + +
+ +
+ + + +
+ +
+ + + +

{t.fileNote}

+
+ + { + captcha && ( +
+ {/* + hCaptcha sets cookies and transfers data to the US, so it is not + loaded until the visitor asks for it. That keeps the page free of + consent-requiring storage until they actively opt in. + */} + + + ) + } + + + +

+ {t.privacyNote}{' '} + {t.privacyLinkLabel} +

+ +

+ + + + + diff --git a/src/i18n/ui/de.ts b/src/i18n/ui/de.ts index f37dc3d..035ff39 100644 --- a/src/i18n/ui/de.ts +++ b/src/i18n/ui/de.ts @@ -31,4 +31,34 @@ export const de: Translations = { privacyPolicy: 'Datenschutzerklärung', editOnGithub: 'Seite bearbeiten auf', }, + contactForm: { + name: 'Name', + namePlaceholder: 'Dein Name', + nameMissing: 'Bitte gib deinen Namen an.', + email: 'E-Mail-Adresse', + emailPlaceholder: 'du@beispiel.de', + emailMissing: 'Bitte gib deine E-Mail-Adresse an.', + emailInvalid: 'Bitte gib eine gültige E-Mail-Adresse an.', + subject: 'Betreff', + subjectPlaceholder: 'Worum geht es?', + subjectMissing: 'Bitte gib einen Betreff an.', + message: 'Nachricht', + messagePlaceholder: 'Wie können wir helfen?', + messageMissing: 'Bitte gib deine Nachricht ein.', + fileNote: + 'Über dieses Formular können keine Dateien verschickt werden. Wenn du uns etwas schicken möchtest, schreib es einfach dazu – wir antworten per E-Mail und du kannst die Dateien an deine Antwort anhängen.', + submit: 'Nachricht senden', + sending: 'Wird gesendet …', + success: 'Danke! Deine Nachricht wurde gesendet.', + error: 'Da ist etwas schiefgelaufen. Bitte versuche es später noch einmal.', + captchaMissing: 'Bitte lade den Spamschutz und löse ihn, bevor du absendest.', + captchaConsent: + 'Zum Schutz vor Spam nutzen wir hCaptcha. Beim Laden werden Cookies gesetzt und deine IP-Adresse an die Intuition Machines, Inc. in den USA übertragen. Vor deinem Klick wird nichts geladen.', + captchaConsentButton: 'Spamschutz laden', + captchaTrialNote: + 'Wir testen hCaptcha zunächst, um Spam langfristig in den Griff zu bekommen. Uns ist bewusst, dass das nicht die datenschutzfreundlichste Lösung ist – wir wollen sie bald ersetzen.', + privacyNote: + 'Deine Angaben werden ausschließlich zur Beantwortung deiner Anfrage übermittelt. Mehr dazu in unserer', + privacyLinkLabel: 'Datenschutzerklärung', + }, }; diff --git a/src/i18n/ui/en.ts b/src/i18n/ui/en.ts index 11e3e0e..bcc9348 100644 --- a/src/i18n/ui/en.ts +++ b/src/i18n/ui/en.ts @@ -29,4 +29,33 @@ export const en = { privacyPolicy: 'Privacy Policy', editOnGithub: 'Edit this page on', }, + contactForm: { + name: 'Name', + namePlaceholder: 'Your name', + nameMissing: 'Please provide your name.', + email: 'Email address', + emailPlaceholder: 'you@example.com', + emailMissing: 'Please provide your email address.', + emailInvalid: 'Please provide a valid email address.', + subject: 'Subject', + subjectPlaceholder: 'What is this about?', + subjectMissing: 'Please provide a subject.', + message: 'Message', + messagePlaceholder: 'How can we help?', + messageMissing: 'Please enter your message.', + fileNote: + 'This form cannot take attachments. If you need to send us files, just mention it here – we will reply by email and you can attach them to your answer.', + submit: 'Send message', + sending: 'Sending …', + success: 'Thanks! Your message has been sent.', + error: 'Something went wrong. Please try again later.', + captchaMissing: 'Please load and solve the spam protection before sending.', + captchaConsent: + 'To protect this form from spam we use hCaptcha. Loading it sets cookies and transfers your IP address to Intuition Machines, Inc. in the USA. Nothing is loaded until you click the button.', + captchaConsentButton: 'Load spam protection', + captchaTrialNote: + 'We are trialling hCaptcha for now to get a long-term handle on spam. We know it is not the most privacy-friendly option and intend to replace it soon.', + privacyNote: 'Your details are transmitted solely to answer your enquiry. More information in our', + privacyLinkLabel: 'privacy policy', + }, } as const; diff --git a/src/pages/contact.mdx b/src/pages/contact.mdx index b2db10b..d173a36 100644 --- a/src/pages/contact.mdx +++ b/src/pages/contact.mdx @@ -8,10 +8,13 @@ sub_headline: 'Any questions? Contact us!' --- import SocialLinks from '../components/SocialLinks.astro'; +import ContactForm from '../components/ContactForm.astro'; -export const emailScript = ``; +If you have any questions, send us a message with the contact form below or join our slack. -If you have any questions, drop us an email at or join our slack below +## Contact form + + ## Messenger Slack diff --git a/src/pages/de/contact.mdx b/src/pages/de/contact.mdx index 3f45bdf..ce290ed 100644 --- a/src/pages/de/contact.mdx +++ b/src/pages/de/contact.mdx @@ -8,10 +8,13 @@ sub_headline: 'Noch Fragen? Melde dich gerne bei uns!' --- import SocialLinks from '../../components/SocialLinks.astro'; +import ContactForm from '../../components/ContactForm.astro'; -export const emailScript = ``; +Du hast Fragen? Schreib uns einfach über das Kontaktformular unten oder komm in unseren Slack. -Du hast Fragen? Schreib uns einfach eine E-Mail an oder komm in unseren Slack. +## Kontaktformular + + ## Messenger Slack diff --git a/src/pages/de/imprint.mdx b/src/pages/de/imprint.mdx index 29519d6..1ddfc0f 100644 --- a/src/pages/de/imprint.mdx +++ b/src/pages/de/imprint.mdx @@ -6,9 +6,8 @@ headline: 'Impressum' sub_headline: 'Noch mehr rechtliches...' --- -export const emailScript = ``; - import { getRelativeLocaleUrl } from 'astro:i18n'; +import ContactForm from '../../components/ContactForm.astro'; Diese Webseite und alle weiteren Angebote unter der Domain munichmakerlab.de werden betrieben vom Munich Maker Lab e.V. (Amtsgericht München VR 205448). @@ -24,9 +23,9 @@ der Vorstand. - 2. Vorsitzender: Adrian Menzel - Kassenwart: Charel Lallemang -## Kontakt +## Kontakt Vorstand -E-Mail: + ## Anschrift diff --git a/src/pages/de/privacy.mdx b/src/pages/de/privacy.mdx index 447cfde..7771b51 100644 --- a/src/pages/de/privacy.mdx +++ b/src/pages/de/privacy.mdx @@ -303,6 +303,41 @@ relevant sein können. Die Erforderlichkeit der Aufbewahrung der Daten wird alle drei Jahre überprüft; im Übrigen gelten die gesetzlichen Aufbewahrungspflichten. +### Kontaktaufnahme über unsere Kontaktformulare + +Auf den Seiten "Kontakt" und "Impressum" stellen wir Kontaktformulare +bereit, über die Sie uns eine Nachricht senden können. Verarbeitet +werden dabei die von Ihnen eingegebenen Angaben, d.h. Name, +E-Mail-Adresse, Betreff und Nachrichtentext, sowie die technischen +Übertragungsdaten (insbesondere IP-Adresse und Zeitpunkt der +Absendung). + +Die Verarbeitung erfolgt auf Grundlage von Art. 6 Abs. 1 lit. f DSGVO +(berechtigtes Interesse an der Beantwortung von Anfragen). Richtet +sich Ihre Anfrage auf den Abschluss oder die Durchführung einer +Mitgliedschaft, ist zusätzlich Art. 6 Abs. 1 lit. b DSGVO +Rechtsgrundlage. + +Für die Zustellung der Formularnachrichten setzen wir den Dienst +"Web3Forms" des Anbieters Web3Creative ein. Datenschutzerklärung: +[https://web3forms.com/privacy](https://web3forms.com/privacy), +Auftragsverarbeitungsvertrag: +[https://web3forms.com/dpa](https://web3forms.com/dpa). Der Anbieter +verarbeitet die Formulardaten ausschließlich weisungsgebunden zur +Weiterleitung an unser E-Mail-Postfach. Eine Verarbeitung außerhalb +der EU kann dabei nicht ausgeschlossen werden; Grundlage hierfür sind +die Standardvertragsklauseln der EU-Kommission. + +Zum Schutz der Formulare vor automatisierten Eingaben nutzen wir ein +Captcha, siehe hierzu den Abschnitt "hCaptcha". Dieses wird erst nach +Ihrer ausdrücklichen Einwilligung durch Klick auf die entsprechende +Schaltfläche geladen. + +Die über die Formulare übermittelten Anfragen löschen wir, sobald sie +zur Bearbeitung nicht mehr erforderlich sind und keine gesetzlichen +Aufbewahrungspflichten entgegenstehen. Die Erforderlichkeit +überprüfen wir alle zwei Jahre. + ### Hosting Die von uns in Anspruch genommenen Hosting-Leistungen dienen der @@ -431,4 +466,35 @@ Die Daten können in den USA verarbeitet werden. Weitere Informationen können Sie der Datenschutzerklärung von OpenStreetMap entnehmen: [https://wiki.openstreetmap.org/wiki/Privacy_Policy](https://wiki.openstreetmap.org/wiki/Privacy_Policy). +### hCaptcha + +Zum Schutz unserer Kontaktformulare vor missbräuchlicher +automatisierter Nutzung (Spam) setzen wir den Dienst "hCaptcha" des +Anbieters Intuition Machines, Inc., USA, ein. Datenschutzerklärung: +[https://www.hcaptcha.com/privacy](https://www.hcaptcha.com/privacy). + +Beim Laden des Captchas werden Cookies gesetzt und weitere +Informationen auf Ihrem Endgerät gespeichert bzw. ausgelesen. Der +Anbieter verarbeitet dabei insbesondere Ihre IP-Adresse, Angaben zu +Browser und Betriebssystem sowie Interaktionsdaten wie +Mausbewegungen, Scroll-Position, Tastatur- und Touch-Ereignisse, um +zwischen menschlichen Nutzern und Bots zu unterscheiden. + +**hCaptcha wird nicht automatisch geladen.** Das Captcha wird erst +dann von den Servern des Anbieters abgerufen, wenn Sie im +Kontaktformular aktiv auf die Schaltfläche "Spamschutz laden" +klicken. Bis zu diesem Zeitpunkt findet keine Verbindung zu hCaptcha +und keine Speicherung von Informationen auf Ihrem Endgerät statt. + +Rechtsgrundlage für das Speichern und Auslesen von Informationen auf +Ihrem Endgerät ist Ihre Einwilligung nach § 25 Abs. 1 TDDDG, für die +anschließende Verarbeitung Ihrer Daten Ihre Einwilligung nach Art. 6 +Abs. 1 lit. a DSGVO. Sie können Ihre Einwilligung jederzeit mit +Wirkung für die Zukunft widerrufen, indem Sie die Seite neu laden, +ohne den Spamschutz zu aktivieren. + +Die Daten können in den USA verarbeitet werden. Der Anbieter ist +nach dem EU-U.S. Data Privacy Framework zertifiziert; ergänzend +gelten die Standardvertragsklauseln der EU-Kommission. + [Erstellt mit Datenschutz-Generator.de von RA Dr. Thomas Schwenke](https://datenschutz-generator.de) diff --git a/src/pages/imprint.mdx b/src/pages/imprint.mdx index 8ee20eb..0dc66ab 100644 --- a/src/pages/imprint.mdx +++ b/src/pages/imprint.mdx @@ -6,9 +6,8 @@ headline: 'Legal Notice' sub_headline: 'More legal stuff' --- -export const emailScript = ``; - import { getRelativeLocaleUrl } from 'astro:i18n'; +import ContactForm from '../components/ContactForm.astro'; > This English translation is provided for convenience only and is not legally binding. The legally binding version is the German original. @@ -25,9 +24,9 @@ The executive board represents the association in accordance with Section 5 Para - 2nd Chairperson: Adrian Menzel - Treasurer: Charel Lallemang -## Contact +## Contact the executive board -Email: + ## Address diff --git a/src/pages/privacy.mdx b/src/pages/privacy.mdx index 0018185..3ee0bed 100644 --- a/src/pages/privacy.mdx +++ b/src/pages/privacy.mdx @@ -117,6 +117,18 @@ We process data of our members, supporters, interested parties and other persons Processed data includes master data, contact data, contract data and payment data where applicable. We delete data when it is no longer required for statutory or business purposes, subject to statutory retention obligations. +### Contact via Our Contact Forms + +On our “Contact” and “Legal Notice” pages we provide contact forms through which you can send us a message. We process the details you enter, namely name, email address, subject and message text, together with technical transmission data such as your IP address and the time of submission. + +Processing is based on our legitimate interests pursuant to Article 6(1)(f) GDPR in responding to enquiries. Where your enquiry concerns entering into or performing a membership, Article 6(1)(b) GDPR applies in addition. + +To deliver form messages we use the service “Web3Forms”, provided by Web3Creative. Privacy policy: https://web3forms.com/privacy, data processing agreement: https://web3forms.com/dpa. The provider processes form data solely on our instructions in order to forward it to our mailbox. Processing outside the EU cannot be excluded; it is based on the European Commission’s Standard Contractual Clauses. + +To protect the forms against automated submissions we use a captcha – see the “hCaptcha” section below. It is only loaded after you have given your explicit consent by clicking the corresponding button. + +We delete enquiries submitted through the forms as soon as they are no longer required and no statutory retention obligations prevent deletion. We review this every two years. + ### Hosting Hosting services used by us serve to provide infrastructure, platform services, computing capacity, storage, database services, security services and technical maintenance. In doing so, we or our hosting provider process master data, contact data, content data, contract data, usage data and meta data based on our legitimate interests pursuant to Article 6(1)(f) GDPR in conjunction with Article 28 GDPR. @@ -157,4 +169,16 @@ According to our knowledge, user data is used solely for displaying map function Data may be processed in the United States. +### hCaptcha + +To protect our contact forms against abusive automated use (spam), we use the service “hCaptcha”, provided by Intuition Machines, Inc., USA. Privacy policy: https://www.hcaptcha.com/privacy. + +When the captcha is loaded, cookies are set and further information is stored on or read from your device. The provider processes in particular your IP address, browser and operating system information as well as interaction data such as mouse movements, scroll position, keypress and touch events in order to distinguish human users from bots. + +**hCaptcha is not loaded automatically.** It is only retrieved from the provider’s servers once you actively click the “Load spam protection” button in the contact form. Until then, no connection to hCaptcha is established and no information is stored on your device. + +The legal basis for storing and reading information on your device is your consent pursuant to Section 25(1) TDDDG, and for the subsequent processing of your data your consent pursuant to Article 6(1)(a) GDPR. You may withdraw your consent at any time with effect for the future by reloading the page without activating the spam protection. + +Data may be processed in the United States. The provider is certified under the EU-U.S. Data Privacy Framework; the European Commission’s Standard Contractual Clauses apply in addition. + Created with Datenschutz-Generator.de by Attorney Dr. Thomas Schwenke. From 22917487329f3897366b6aaa50e40fef3b76d77b Mon Sep 17 00:00:00 2001 From: Milian Tomzig Date: Mon, 31 Aug 2026 18:07:12 +0200 Subject: [PATCH 2/3] Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- src/components/ContactForm.astro | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/components/ContactForm.astro b/src/components/ContactForm.astro index 1f85ffa..ee18880 100644 --- a/src/components/ContactForm.astro +++ b/src/components/ContactForm.astro @@ -8,7 +8,8 @@ interface Props { locale?: string; /** * Renders the hCaptcha widget. Set to false to fall back to honeypot-only - * spam protection – see the privacy note in AGENTS.md before changing. + * spam protection – review the “Privacy & Security (DSGVO / GDPR)” section in + * AGENTS.md before changing the default. */ captcha?: boolean; } From 2b2bd4b2b90916f7ecee1a0694f4eb42ea276af9 Mon Sep 17 00:00:00 2001 From: Milian Tomzig Date: Mon, 31 Aug 2026 18:09:18 +0200 Subject: [PATCH 3/3] Change contactform component code from ts to pure javascript. Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- src/components/ContactForm.astro | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/ContactForm.astro b/src/components/ContactForm.astro index ee18880..677f675 100644 --- a/src/components/ContactForm.astro +++ b/src/components/ContactForm.astro @@ -108,10 +108,10 @@ const privacyUrl = getRelativeLocaleUrl(locale, '/privacy');