-
+
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);
+ });
});
}