From 199264c0a6e4a4865b9d5fd28d9057115c5d3897 Mon Sep 17 00:00:00 2001 From: Kristijan Popovski Date: Thu, 9 Apr 2026 14:44:29 +0200 Subject: [PATCH] Enable live Formspree contact submissions --- contact/index.html | 8 +++-- css/style.css | 10 ++++++ js/script.js | 89 +++++++++++++++++++++++++++++++++------------- 3 files changed, 79 insertions(+), 28 deletions(-) diff --git a/contact/index.html b/contact/index.html index 91cded4..cb8ff52 100644 --- a/contact/index.html +++ b/contact/index.html @@ -62,7 +62,9 @@

Fill out to connect

-
+ + +
@@ -92,7 +94,7 @@
-

Replace the placeholder Formspree action with your real form ID to enable live submissions.

+

Your message is sent securely and reviewed personally. You will only see a confirmation after sending.

@@ -139,7 +141,7 @@ CV Email - + diff --git a/css/style.css b/css/style.css index 2c6e0b6..de8f4e0 100644 --- a/css/style.css +++ b/css/style.css @@ -477,6 +477,12 @@ textarea { width: 100%; } +.button:disabled { + opacity: 0.7; + cursor: not-allowed; + transform: none; +} + .button__icon, .text-link__icon { width: 1rem; @@ -1420,6 +1426,10 @@ textarea { color: #0d6b3f; } +.form-status.is-error { + color: #ba1a1a; +} + .link-grid { margin-top: var(--space-10); } diff --git a/js/script.js b/js/script.js index 904a4ea..6c47306 100644 --- a/js/script.js +++ b/js/script.js @@ -136,14 +136,37 @@ function setupContactForm() { const fields = Array.from(form.querySelectorAll("[data-required], [type='email']")); const status = form.querySelector("[data-form-status]"); + const submitButton = form.querySelector("button[type='submit']"); const action = (form.getAttribute("action") || "").trim(); const hasExternalEndpoint = /^https?:\/\//i.test(action); const hasPlaceholderEndpoint = action.includes("YOUR_ID_HERE"); + const method = (form.getAttribute("method") || "post").toUpperCase(); + const buttonText = submitButton ? submitButton.textContent : ""; const validators = { email: (value) => /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value) }; + const setStatus = (message, state = "") => { + if (!status) { + return; + } + + status.textContent = message; + status.classList.toggle("is-success", state === "success"); + status.classList.toggle("is-error", state === "error"); + }; + + const setSubmitting = (isSubmitting) => { + if (!submitButton) { + return; + } + + submitButton.disabled = isSubmitting; + submitButton.setAttribute("aria-busy", String(isSubmitting)); + submitButton.textContent = isSubmitting ? "Sending..." : buttonText; + }; + const setFieldError = (field, message) => { const wrapper = field.closest(".form-field"); const error = wrapper ? wrapper.querySelector(".form-field__error") : null; @@ -197,11 +220,7 @@ function setupContactForm() { if (!isValid) { event.preventDefault(); - - if (status) { - status.textContent = "Please review the highlighted fields and try again."; - status.classList.remove("is-success"); - } + setStatus("Please review the highlighted fields and try again.", "error"); const firstInvalid = form.querySelector("[aria-invalid='true']"); @@ -212,30 +231,50 @@ function setupContactForm() { return; } - if (status) { - status.classList.remove("is-success"); - } - if (!hasExternalEndpoint || hasPlaceholderEndpoint) { event.preventDefault(); - - if (status) { - status.textContent = hasPlaceholderEndpoint - ? "Replace the Formspree form ID to enable live message delivery." - : "Thanks. Your message structure is validated and ready for backend integration."; - status.classList.toggle("is-success", !hasPlaceholderEndpoint); - } - - if (!hasPlaceholderEndpoint) { - form.reset(); - fields.forEach((field) => setFieldError(field, "")); - } - + setStatus("Contact form is currently unavailable. Please try again shortly.", "error"); return; } - if (status) { - status.textContent = "Submitting your message..."; - } + event.preventDefault(); + setSubmitting(true); + setStatus("Sending your message..."); + + fetch(action, { + method, + headers: { + Accept: "application/json" + }, + body: new FormData(form) + }) + .then(async (response) => { + if (response.ok) { + form.reset(); + fields.forEach((field) => setFieldError(field, "")); + setStatus("Thank you. Your contact request was sent and noted.", "success"); + return; + } + + let errorMessage = "Sorry, your message could not be sent. Please try again shortly."; + + try { + const payload = await response.json(); + + if (payload && Array.isArray(payload.errors) && payload.errors.length > 0) { + errorMessage = payload.errors.map((item) => item.message).join(" "); + } + } catch (parseError) { + // Keep fallback message when response body is not JSON. + } + + throw new Error(errorMessage); + }) + .catch((error) => { + setStatus(error.message, "error"); + }) + .finally(() => { + setSubmitting(false); + }); }); }