From c8ae9caa35c31e6de9f7a27e6c100f6dbfd68662 Mon Sep 17 00:00:00 2001 From: Calum Muir Date: Sun, 28 Dec 2025 13:18:55 +0000 Subject: [PATCH] Send form as urlencoded to avoid multipart issues --- functions/api/contact.ts | 30 +++++++++-- public/contact-form.js | 107 ++++++++++++++++++++++----------------- 2 files changed, 85 insertions(+), 52 deletions(-) diff --git a/functions/api/contact.ts b/functions/api/contact.ts index 21e9f1a..4abe67a 100644 --- a/functions/api/contact.ts +++ b/functions/api/contact.ts @@ -13,13 +13,33 @@ const jsonResponse = (data: Record, status = 200) => const readFormField = (value: FormDataEntryValue | null) => typeof value === "string" ? value.trim() : ""; +const readTextField = (value: string | null) => (value ?? "").trim(); + +const parseFormFields = async (request: Request) => { + const contentType = request.headers.get("content-type") ?? ""; + if (contentType.includes("application/x-www-form-urlencoded")) { + const bodyText = await request.text(); + const params = new URLSearchParams(bodyText); + return { + name: readTextField(params.get("name")), + email: readTextField(params.get("email")), + message: readTextField(params.get("message")), + turnstileToken: readTextField(params.get("cf-turnstile-response")), + }; + } + + const formData = await request.formData(); + return { + name: readFormField(formData.get("name")), + email: readFormField(formData.get("email")), + message: readFormField(formData.get("message")), + turnstileToken: readFormField(formData.get("cf-turnstile-response")), + }; +}; const handlePost: PagesFunction = async ({ request, env }) => { - const formData = await request.formData(); - const name = readFormField(formData.get("name")); - const email = readFormField(formData.get("email")); - const message = readFormField(formData.get("message")); - const turnstileToken = readFormField(formData.get("cf-turnstile-response")); + const { name, email, message, turnstileToken } = + await parseFormFields(request); if (!name || !email || !message) { return jsonResponse( diff --git a/public/contact-form.js b/public/contact-form.js index a4fc103..f6ceab5 100644 --- a/public/contact-form.js +++ b/public/contact-form.js @@ -1,55 +1,68 @@ (() => { - const form = document.querySelector("[data-contact-form]"); - const message = document.querySelector("[data-form-message]"); - const submitButton = document.querySelector("[data-form-submit]"); + const form = document.querySelector("[data-contact-form]"); + const message = document.querySelector("[data-form-message]"); + const submitButton = document.querySelector("[data-form-submit]"); - if (!form || !message || !submitButton) return; + if (!form || !message || !submitButton) return; - form.addEventListener("submit", async (event) => { - event.preventDefault(); - if (submitButton.disabled) return; + form.addEventListener("submit", async (event) => { + event.preventDefault(); + if (submitButton.disabled) return; - submitButton.disabled = true; - submitButton.textContent = "Sending..."; - message.textContent = ""; + submitButton.disabled = true; + submitButton.textContent = "Sending..."; + message.textContent = ""; - try { - const response = await fetch(form.action, { - method: "POST", - body: new FormData(form), - }); + try { + const formData = new FormData(form); + const body = new URLSearchParams(); + for (const [key, value] of formData.entries()) { + if (typeof value === "string") { + body.append(key, value); + } + } + const response = await fetch(form.action, { + method: "POST", + headers: { + "Content-Type": "application/x-www-form-urlencoded", + }, + body, + }); - if (!response.ok) { - let errorMessage = "Request failed"; - const errorData = await response.json().catch(() => null); - if (errorData?.error) { - errorMessage = errorData.details - ? `${errorData.error} (${errorData.details})` - : errorData.error; - } else { - const errorText = await response.text().catch(() => ""); - if (errorText.trim()) { - errorMessage = `${response.status} ${response.statusText}: ${errorText}`; - } else { - errorMessage = `${response.status} ${response.statusText}`; - } + if (!response.ok) { + let errorMessage = "Request failed"; + const errorData = await response.json().catch(() => null); + if (errorData?.error) { + errorMessage = errorData.details + ? `${errorData.error} (${errorData.details})` + : errorData.error; + } else { + const errorText = await response.text().catch(() => ""); + if (errorText.trim()) { + errorMessage = `${response.status} ${response.statusText}: ${errorText}`; + } else { + errorMessage = `${response.status} ${response.statusText}`; + } + } + throw new Error(errorMessage); + } + + message.textContent = "Thanks! Your message has been sent."; + form.reset(); + if ( + window.turnstile && + typeof window.turnstile.reset === "function" + ) { + window.turnstile.reset(); + } + } catch (error) { + message.textContent = + error instanceof Error + ? error.message + : "Sorry, something went wrong. Please try again."; + } finally { + submitButton.disabled = false; + submitButton.textContent = "Send message"; } - throw new Error(errorMessage); - } - - message.textContent = "Thanks! Your message has been sent."; - form.reset(); - if (window.turnstile && typeof window.turnstile.reset === "function") { - window.turnstile.reset(); - } - } catch (error) { - message.textContent = - error instanceof Error - ? error.message - : "Sorry, something went wrong. Please try again."; - } finally { - submitButton.disabled = false; - submitButton.textContent = "Send message"; - } - }); + }); })();