From f6f4b16624f559cbafd63c891a3f6da6a0ddba8e Mon Sep 17 00:00:00 2001 From: "Bo (borice1984)" Date: Fri, 17 Apr 2026 16:38:43 -0500 Subject: [PATCH] fix: improve S3 static deployment readiness - forms.js: add Formspree fetch support as primary submit path; mailto: remains as fallback. Set data-formspree-endpoint on the form to enable. Also fixes a UX bug where success state was shown after location.href redirect in the fetch path. - contact.html: add data-formspree-endpoint placeholder to form element - All pages: add defer to non-module script tags to prevent render-blocking - Add error.html for S3 error document routing (S3 requires a separate error document distinct from 404.html for access-denied / server errors) --- templates/static/base/404.html | 2 +- templates/static/base/contact.html | 4 +- templates/static/base/error.html | 74 +++++++++++++++++++++++++++++ templates/static/base/index.html | 4 +- templates/static/base/js/forms.js | 75 ++++++++++++++++++++---------- templates/static/base/page-2.html | 2 +- templates/static/base/page-3.html | 2 +- templates/static/base/page-4.html | 2 +- 8 files changed, 132 insertions(+), 33 deletions(-) create mode 100644 templates/static/base/error.html diff --git a/templates/static/base/404.html b/templates/static/base/404.html index add6667..9a3a8d8 100644 --- a/templates/static/base/404.html +++ b/templates/static/base/404.html @@ -69,6 +69,6 @@

Contact

- + \ No newline at end of file diff --git a/templates/static/base/contact.html b/templates/static/base/contact.html index 8cb6ed7..0a24827 100644 --- a/templates/static/base/contact.html +++ b/templates/static/base/contact.html @@ -49,7 +49,7 @@

Get in Touch

-
+
@@ -139,6 +139,6 @@

Contact

- + \ No newline at end of file diff --git a/templates/static/base/error.html b/templates/static/base/error.html new file mode 100644 index 0000000..fe98793 --- /dev/null +++ b/templates/static/base/error.html @@ -0,0 +1,74 @@ + + + + + + Error — {{business.name}} + + + + + + + +
+
+ + + Contact Us + +
+
+ + +
+ + +
+
+

Error

+

Something went wrong

+

An unexpected error occurred. Please try again or go back to the homepage.

+ Back to Home +
+
+ + +
+
+ + +
+
+ + + + + diff --git a/templates/static/base/index.html b/templates/static/base/index.html index 6978d5f..60e7194 100644 --- a/templates/static/base/index.html +++ b/templates/static/base/index.html @@ -193,7 +193,7 @@ - - + + diff --git a/templates/static/base/js/forms.js b/templates/static/base/js/forms.js index 0b08a2c..91af8f7 100644 --- a/templates/static/base/js/forms.js +++ b/templates/static/base/js/forms.js @@ -1,5 +1,13 @@ /** - * forms.js — Contact form validation and mailto: fallback submission + * forms.js — Contact form validation and submission + * + * Submission strategy (in priority order): + * 1. If the form has data-formspree-endpoint set, POST via fetch (works on S3/static hosts) + * 2. Otherwise, fall back to a mailto: link (opens local mail client) + * + * To enable Formspree, create a free form at https://formspree.io and add + * the endpoint to your contact form: + * */ function initForms() { @@ -13,7 +21,7 @@ function initForms() { }); } -function handleSubmit(form) { +async function handleSubmit(form) { // Check honeypot var honeypot = form.querySelector('[data-honeypot]'); if (honeypot && honeypot.value) return; @@ -35,37 +43,54 @@ function handleSubmit(form) { if (msg) msg.textContent = error.message; } }); - // Focus first error errors[0].element.focus(); return; } - // Gather form data - var name = form.querySelector('[name="name"]'); - var email = form.querySelector('[name="email"]'); - var phone = form.querySelector('[name="phone"]'); - var message = form.querySelector('[name="message"]'); + var endpoint = form.getAttribute('data-formspree-endpoint'); - // Build mailto link - var businessEmail = ''; - if (window.siteData && window.siteData.business) { - businessEmail = window.siteData.business.email || ''; - } + if (endpoint) { + var submitBtn = form.querySelector('[type="submit"]'); + if (submitBtn) submitBtn.disabled = true; + + try { + var res = await fetch(endpoint, { + method: 'POST', + body: new FormData(form), + headers: { 'Accept': 'application/json' } + }); + if (!res.ok) throw new Error('Submission failed'); + } catch (err) { + console.error('Form submission error:', err); + if (submitBtn) submitBtn.disabled = false; + return; + } + } else { + // Fallback: mailto + var name = form.querySelector('[name="name"]'); + var email = form.querySelector('[name="email"]'); + var phone = form.querySelector('[name="phone"]'); + var message = form.querySelector('[name="message"]'); + + var businessEmail = ''; + if (window.siteData && window.siteData.business) { + businessEmail = window.siteData.business.email || ''; + } - var subject = 'Contact from ' + (name ? name.value : 'Website'); - var body = ''; - if (name) body += 'Name: ' + name.value + '\n'; - if (email) body += 'Email: ' + email.value + '\n'; - if (phone && phone.value) body += 'Phone: ' + phone.value + '\n'; - if (message) body += '\nMessage:\n' + message.value; + var subject = 'Contact from ' + (name ? name.value : 'Website'); + var body = ''; + if (name) body += 'Name: ' + name.value + '\n'; + if (email) body += 'Email: ' + email.value + '\n'; + if (phone && phone.value) body += 'Phone: ' + phone.value + '\n'; + if (message) body += '\nMessage:\n' + message.value; - var mailtoLink = 'mailto:' + encodeURIComponent(businessEmail) + - '?subject=' + encodeURIComponent(subject) + - '&body=' + encodeURIComponent(body); + var mailtoLink = 'mailto:' + encodeURIComponent(businessEmail) + + '?subject=' + encodeURIComponent(subject) + + '&body=' + encodeURIComponent(body); - window.location.href = mailtoLink; + window.location.href = mailtoLink; + } - // Show success state var successEl = form.parentElement.querySelector('.form-success'); if (successEl) { form.style.display = 'none'; @@ -102,7 +127,7 @@ function validate(form) { } function isValidEmail(email) { - return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email); + return /^[a-zA-Z0-9._%+\-]+@[a-zA-Z0-9.\-]+\.[a-zA-Z]{2,}$/.test(email); } export { initForms }; diff --git a/templates/static/base/page-2.html b/templates/static/base/page-2.html index d2dbeb2..1a70d1b 100644 --- a/templates/static/base/page-2.html +++ b/templates/static/base/page-2.html @@ -91,6 +91,6 @@

Contact

- + \ No newline at end of file diff --git a/templates/static/base/page-3.html b/templates/static/base/page-3.html index d2dbeb2..1a70d1b 100644 --- a/templates/static/base/page-3.html +++ b/templates/static/base/page-3.html @@ -91,6 +91,6 @@

Contact

- + \ No newline at end of file diff --git a/templates/static/base/page-4.html b/templates/static/base/page-4.html index 1c15677..868fbf7 100644 --- a/templates/static/base/page-4.html +++ b/templates/static/base/page-4.html @@ -90,6 +90,6 @@

Contact

- +