fix(forms): unsichtbare Unicode-Zeichen in Eingaben strippen + FormCard-Rahmen
- cleanText(): Zero-Width-/Bidi-Marks/BOM (Copy-Paste aus iOS/WhatsApp) vor Validierung und im Payload entfernen — Telefonnummern mit U+202D/U+202C fielen sonst durch die Validierung - FormCard.svelte: gerahmter Container, umschließt Kontakt- und Mitmachen-Formular Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -3,6 +3,7 @@
|
|||||||
import type { InternalComponentBlockData } from "$lib/block-types";
|
import type { InternalComponentBlockData } from "$lib/block-types";
|
||||||
import ContactFormComponent from "$lib/components/internal/ContactFormComponent.svelte";
|
import ContactFormComponent from "$lib/components/internal/ContactFormComponent.svelte";
|
||||||
import MitmachenFormComponent from "$lib/components/internal/MitmachenFormComponent.svelte";
|
import MitmachenFormComponent from "$lib/components/internal/MitmachenFormComponent.svelte";
|
||||||
|
import FormCard from "$lib/components/internal/FormCard.svelte";
|
||||||
|
|
||||||
let { block }: { block: InternalComponentBlockData } = $props();
|
let { block }: { block: InternalComponentBlockData } = $props();
|
||||||
|
|
||||||
@@ -12,9 +13,13 @@
|
|||||||
|
|
||||||
<div class={layoutClasses} data-block="InternalComponent" data-block-type="internal_component" data-block-slug={block._slug}>
|
<div class={layoutClasses} data-block="InternalComponent" data-block-type="internal_component" data-block-slug={block._slug}>
|
||||||
{#if component === "form-contact"}
|
{#if component === "form-contact"}
|
||||||
|
<FormCard>
|
||||||
<ContactFormComponent />
|
<ContactFormComponent />
|
||||||
|
</FormCard>
|
||||||
{:else if component === "form-mitmachen"}
|
{:else if component === "form-mitmachen"}
|
||||||
|
<FormCard>
|
||||||
<MitmachenFormComponent />
|
<MitmachenFormComponent />
|
||||||
|
</FormCard>
|
||||||
{:else}
|
{:else}
|
||||||
<p class="text-sm text-stein-500">Unbekannte interne Komponente: <code>{component}</code></p>
|
<p class="text-sm text-stein-500">Unbekannte interne Komponente: <code>{component}</code></p>
|
||||||
{/if}
|
{/if}
|
||||||
|
|||||||
@@ -0,0 +1,27 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import type { Snippet } from "svelte";
|
||||||
|
|
||||||
|
/** Gerahmter Container für interne Formulare (Kontakt, Mitmachen).
|
||||||
|
* Optionaler Titel/Untertitel als Kopfzeile. */
|
||||||
|
let {
|
||||||
|
title = "",
|
||||||
|
subtitle = "",
|
||||||
|
children,
|
||||||
|
}: {
|
||||||
|
title?: string;
|
||||||
|
subtitle?: string;
|
||||||
|
children: Snippet;
|
||||||
|
} = $props();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="rounded-lg border border-stein-200 bg-white p-5 shadow-sm md:p-7">
|
||||||
|
{#if title}
|
||||||
|
<div class="mb-4 border-b border-stein-200 pb-3">
|
||||||
|
<h2 class="text-base font-semibold text-stein-800">{title}</h2>
|
||||||
|
{#if subtitle}
|
||||||
|
<p class="mt-0.5 text-sm text-stein-500">{subtitle}</p>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
{@render children()}
|
||||||
|
</div>
|
||||||
@@ -52,8 +52,18 @@ const URL_RE = /\b(?:https?:\/\/|www\.)\S+/gi;
|
|||||||
const CONTROL_CHAR_RE = /[\x00-\x08\x0B\x0C\x0E-\x1F]/;
|
const CONTROL_CHAR_RE = /[\x00-\x08\x0B\x0C\x0E-\x1F]/;
|
||||||
const PHONE_RE = /^[0-9+()/\-.\s]*$/;
|
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 {
|
function trim(s: unknown): string {
|
||||||
return typeof s === "string" ? s.trim() : "";
|
return cleanText(s);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function validateMitmachen(input: Partial<MitmachenFields>): MitmachenErrors {
|
export function validateMitmachen(input: Partial<MitmachenFields>): MitmachenErrors {
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import { env as publicEnv } from "$env/dynamic/public";
|
|||||||
import { env as privateEnv } from "$env/dynamic/private";
|
import { env as privateEnv } from "$env/dynamic/private";
|
||||||
import type { RequestHandler } from "./$types";
|
import type { RequestHandler } from "./$types";
|
||||||
import {
|
import {
|
||||||
|
cleanText,
|
||||||
HONEYPOT_FIELDS,
|
HONEYPOT_FIELDS,
|
||||||
LIMITS,
|
LIMITS,
|
||||||
validateFiles,
|
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?_environment=${encodeURIComponent(cmsEnv)}`
|
||||||
: `${cmsBase}/api/forms/mitmachen/submit-multipart`;
|
: `${cmsBase}/api/forms/mitmachen/submit-multipart`;
|
||||||
|
|
||||||
|
// cleanText: unsichtbare Format-Zeichen (Bidi-Marks aus Copy-Paste) raus —
|
||||||
|
// gleiche Normalisierung wie in der Validierung.
|
||||||
const payload = {
|
const payload = {
|
||||||
title: fields.title.trim(),
|
title: cleanText(fields.title),
|
||||||
description: fields.description.trim(),
|
description: cleanText(fields.description),
|
||||||
contact_name: fields.contactName.trim(),
|
contact_name: cleanText(fields.contactName),
|
||||||
phone: fields.phone.trim(),
|
phone: cleanText(fields.phone),
|
||||||
email: fields.email.trim(),
|
email: cleanText(fields.email),
|
||||||
consent_at: new Date().toISOString(),
|
consent_at: new Date().toISOString(),
|
||||||
submitted_via: "windwiderstand.de/api/mitmachen",
|
submitted_via: "windwiderstand.de/api/mitmachen",
|
||||||
user_agent: (request.headers.get("user-agent") ?? "").slice(0, 200),
|
user_agent: (request.headers.get("user-agent") ?? "").slice(0, 200),
|
||||||
|
|||||||
Reference in New Issue
Block a user