diff --git a/src/lib/components/blocks/InternalComponentBlock.svelte b/src/lib/components/blocks/InternalComponentBlock.svelte index 8bf76c5..2d6c29c 100644 --- a/src/lib/components/blocks/InternalComponentBlock.svelte +++ b/src/lib/components/blocks/InternalComponentBlock.svelte @@ -3,6 +3,7 @@ import type { InternalComponentBlockData } from "$lib/block-types"; import ContactFormComponent from "$lib/components/internal/ContactFormComponent.svelte"; import MitmachenFormComponent from "$lib/components/internal/MitmachenFormComponent.svelte"; + import FormCard from "$lib/components/internal/FormCard.svelte"; let { block }: { block: InternalComponentBlockData } = $props(); @@ -12,9 +13,13 @@
{#if component === "form-contact"} - + + + {:else if component === "form-mitmachen"} - + + + {:else}

Unbekannte interne Komponente: {component}

{/if} diff --git a/src/lib/components/internal/FormCard.svelte b/src/lib/components/internal/FormCard.svelte new file mode 100644 index 0000000..cbd11fa --- /dev/null +++ b/src/lib/components/internal/FormCard.svelte @@ -0,0 +1,27 @@ + + +
+ {#if title} +
+

{title}

+ {#if subtitle} +

{subtitle}

+ {/if} +
+ {/if} + {@render children()} +
diff --git a/src/lib/components/internal/mitmachen-form-validation.ts b/src/lib/components/internal/mitmachen-form-validation.ts index 5152ea5..f702094 100644 --- a/src/lib/components/internal/mitmachen-form-validation.ts +++ b/src/lib/components/internal/mitmachen-form-validation.ts @@ -52,8 +52,18 @@ const URL_RE = /\b(?:https?:\/\/|www\.)\S+/gi; const CONTROL_CHAR_RE = /[\x00-\x08\x0B\x0C\x0E-\x1F]/; const PHONE_RE = /^[0-9+()/\-.\s]*$/; +/** Unsichtbare Format-Zeichen: Zero-Width, Bidi-Marks (U+202A–E), Word-Joiner, + * BOM. Landen beim Kopieren aus iOS-Kontakten/WhatsApp in Telefonnummern und + * lassen sonst die Validierung durchfallen. */ +const INVISIBLE_RE = /[\u200B-\u200F\u202A-\u202E\u2060-\u206F\uFEFF]/g; + +/** Entfernt unsichtbare Format-Zeichen + trimmt. Für Validierung UND Payload. */ +export function cleanText(s: unknown): string { + return typeof s === "string" ? s.replace(INVISIBLE_RE, "").trim() : ""; +} + function trim(s: unknown): string { - return typeof s === "string" ? s.trim() : ""; + return cleanText(s); } export function validateMitmachen(input: Partial): MitmachenErrors { diff --git a/src/routes/api/mitmachen/+server.ts b/src/routes/api/mitmachen/+server.ts index ab20afd..6fa27d3 100644 --- a/src/routes/api/mitmachen/+server.ts +++ b/src/routes/api/mitmachen/+server.ts @@ -3,6 +3,7 @@ import { env as publicEnv } from "$env/dynamic/public"; import { env as privateEnv } from "$env/dynamic/private"; import type { RequestHandler } from "./$types"; import { + cleanText, HONEYPOT_FIELDS, LIMITS, validateFiles, @@ -108,12 +109,14 @@ export const POST: RequestHandler = async ({ request, getClientAddress }) => { ? `${cmsBase}/api/forms/mitmachen/submit-multipart?_environment=${encodeURIComponent(cmsEnv)}` : `${cmsBase}/api/forms/mitmachen/submit-multipart`; + // cleanText: unsichtbare Format-Zeichen (Bidi-Marks aus Copy-Paste) raus — + // gleiche Normalisierung wie in der Validierung. const payload = { - title: fields.title.trim(), - description: fields.description.trim(), - contact_name: fields.contactName.trim(), - phone: fields.phone.trim(), - email: fields.email.trim(), + title: cleanText(fields.title), + description: cleanText(fields.description), + contact_name: cleanText(fields.contactName), + phone: cleanText(fields.phone), + email: cleanText(fields.email), consent_at: new Date().toISOString(), submitted_via: "windwiderstand.de/api/mitmachen", user_agent: (request.headers.get("user-agent") ?? "").slice(0, 200),