Load contact form JS as external script

This commit is contained in:
Calum Muir 2025-12-28 13:04:14 +00:00
parent 481c6a1627
commit 4c0bdbdbac
2 changed files with 263 additions and 203 deletions

55
public/contact-form.js Normal file
View file

@ -0,0 +1,55 @@
(() => {
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;
form.addEventListener("submit", async (event) => {
event.preventDefault();
if (submitButton.disabled) return;
submitButton.disabled = true;
submitButton.textContent = "Sending...";
message.textContent = "";
try {
const response = await fetch(form.action, {
method: "POST",
body: new FormData(form),
});
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";
}
});
})();

View file

@ -19,10 +19,13 @@ const turnstileSiteKey = import.meta.env.PUBLIC_TURNSTILE_SITE_KEY ?? "";
src="https://challenges.cloudflare.com/turnstile/v0/api.js" src="https://challenges.cloudflare.com/turnstile/v0/api.js"
async async
defer defer
></script> />
</Fragment> </Fragment>
) )
} }
<Fragment slot="head">
<script src="/contact-form.js" defer></script>
</Fragment>
<div class="flex flex-col min-h-screen items-center bg-[#e2e2e2]"> <div class="flex flex-col min-h-screen items-center bg-[#e2e2e2]">
<main class="w-full"> <main class="w-full">
<section class="w-full max-w-[1200px] mx-auto px-0 sm:px-8 mt-0"> <section class="w-full max-w-[1200px] mx-auto px-0 sm:px-8 mt-0">
@ -31,19 +34,35 @@ const turnstileSiteKey = import.meta.env.PUBLIC_TURNSTILE_SITE_KEY ?? "";
style="background-image: url(/222957077_6028824210491349_4146132303877993139_n-1920w.webp);" style="background-image: url(/222957077_6028824210491349_4146132303877993139_n-1920w.webp);"
> >
<div class="absolute inset-0 bg-black/45"></div> <div class="absolute inset-0 bg-black/45"></div>
<SiteHeader navigationItems={navigationItems} theme="dark" className="px-6 sm:px-4" /> <SiteHeader
<div class="relative px-6 sm:px-8 pb-32 pt-6 sm:pb-24 sm:pt-8"> navigationItems={navigationItems}
<div class="max-w-none sm:max-w-[760px] rounded-[7px] bg-black/45 px-5 py-5 sm:px-6 sm:py-6 backdrop-blur-sm"> theme="dark"
<p class="[font-family:'Public_Sans',Helvetica] font-semibold text-xs sm:text-sm uppercase tracking-[0.2em] text-white/90"> className="px-6 sm:px-4"
/>
<div
class="relative px-6 sm:px-8 pb-32 pt-6 sm:pb-24 sm:pt-8"
>
<div
class="max-w-none sm:max-w-[760px] rounded-[7px] bg-black/45 px-5 py-5 sm:px-6 sm:py-6 backdrop-blur-sm"
>
<p
class="[font-family:'Public_Sans',Helvetica] font-semibold text-xs sm:text-sm uppercase tracking-[0.2em] text-white/90"
>
Get in touch Get in touch
</p> </p>
<div class="mt-3 h-[2px] w-10 bg-white/70"></div> <div class="mt-3 h-[2px] w-10 bg-white/70"></div>
<h1 class="mt-4 [font-family:'Merriweather',Helvetica] font-bold text-white text-[34px] sm:text-[44px] leading-[1.15]"> <h1
class="mt-4 [font-family:'Merriweather',Helvetica] font-bold text-white text-[34px] sm:text-[44px] leading-[1.15]"
>
Contact Highland Group RDA Contact Highland Group RDA
</h1> </h1>
<p class="mt-4 [font-family:'Public_Sans',Helvetica] font-semibold text-white text-base sm:text-lg leading-[1.6] max-w-[720px]"> <p
We would love to hear from you about sessions, volunteering, or ways to support the group. Reach out class="mt-4 [font-family:'Public_Sans',Helvetica] font-semibold text-white text-base sm:text-lg leading-[1.6] max-w-[720px]"
and we will get back to you as soon as we can. >
We would love to hear from you about sessions,
volunteering, or ways to support the group.
Reach out and we will get back to you as soon as
we can.
</p> </p>
</div> </div>
</div> </div>
@ -53,22 +72,37 @@ const turnstileSiteKey = import.meta.env.PUBLIC_TURNSTILE_SITE_KEY ?? "";
target="_blank" target="_blank"
rel="noreferrer" rel="noreferrer"
> >
<img class="w-full h-full object-contain" alt="Donate via JustGiving" src="/Button.webp" /> <img
class="w-full h-full object-contain"
alt="Donate via JustGiving"
src="/Button.webp"
/>
</a> </a>
</div> </div>
</section> </section>
<section class="w-full max-w-[1200px] mx-auto px-0 sm:px-8 mt-0 pb-0"> <section
class="w-full max-w-[1200px] mx-auto px-0 sm:px-8 mt-0 pb-0"
>
<div class="bg-[#d6d6d6] px-6 sm:px-8 py-6 sm:py-8"> <div class="bg-[#d6d6d6] px-6 sm:px-8 py-6 sm:py-8">
<div class="grid grid-cols-1 gap-8 sm:grid-cols-[1.1fr_0.9fr]"> <div
class="grid grid-cols-1 gap-8 sm:grid-cols-[1.1fr_0.9fr]"
>
<div> <div>
<h2 class="[font-family:'Merriweather',Helvetica] font-bold text-black text-2xl sm:text-3xl"> <h2
class="[font-family:'Merriweather',Helvetica] font-bold text-black text-2xl sm:text-3xl"
>
Contact details Contact details
</h2> </h2>
<p class="mt-4 [font-family:'Public_Sans',Helvetica] font-semibold text-black text-base sm:text-lg leading-[1.6]"> <p
Highland Group RDA, Sandycroft, Reelig, Kirkhill, IV5 7PP class="mt-4 [font-family:'Public_Sans',Helvetica] font-semibold text-black text-base sm:text-lg leading-[1.6]"
>
Highland Group RDA, Sandycroft, Reelig,
Kirkhill, IV5 7PP
</p> </p>
<p class="mt-3 [font-family:'Public_Sans',Helvetica] font-semibold text-black text-base sm:text-lg leading-[1.6]"> <p
class="mt-3 [font-family:'Public_Sans',Helvetica] font-semibold text-black text-base sm:text-lg leading-[1.6]"
>
Tel: +447500 203226 Tel: +447500 203226
<br /> <br />
Email: info@highlandgrouprda.org.uk Email: info@highlandgrouprda.org.uk
@ -84,19 +118,31 @@ const turnstileSiteKey = import.meta.env.PUBLIC_TURNSTILE_SITE_KEY ?? "";
</div> </div>
<div class="bg-white/70 px-6 sm:px-8 py-6 sm:py-8"> <div class="bg-white/70 px-6 sm:px-8 py-6 sm:py-8">
<h2 class="[font-family:'Merriweather',Helvetica] font-bold text-black text-2xl sm:text-3xl"> <h2
class="[font-family:'Merriweather',Helvetica] font-bold text-black text-2xl sm:text-3xl"
>
Send a message Send a message
</h2> </h2>
<form class="mt-4 space-y-4" action="/api/contact" method="post" data-contact-form> <form
class="mt-4 space-y-4"
action="/api/contact"
method="post"
data-contact-form
>
{ {
!turnstileSiteKey && ( !turnstileSiteKey && (
<p class="rounded-md border border-amber-300 bg-amber-50 px-3 py-2 text-sm font-semibold text-amber-900"> <p class="rounded-md border border-amber-300 bg-amber-50 px-3 py-2 text-sm font-semibold text-amber-900">
Turnstile is not configured. Set PUBLIC_TURNSTILE_SITE_KEY and redeploy to enable the form. Turnstile is not configured. Set
PUBLIC_TURNSTILE_SITE_KEY and
redeploy to enable the form.
</p> </p>
) )
} }
<div> <div>
<label class="block text-sm font-semibold text-black">Name</label> <label
class="block text-sm font-semibold text-black"
>Name</label
>
<input <input
class="mt-2 w-full rounded-md border border-black/20 bg-white px-3 py-2 text-black" class="mt-2 w-full rounded-md border border-black/20 bg-white px-3 py-2 text-black"
type="text" type="text"
@ -106,7 +152,10 @@ const turnstileSiteKey = import.meta.env.PUBLIC_TURNSTILE_SITE_KEY ?? "";
/> />
</div> </div>
<div> <div>
<label class="block text-sm font-semibold text-black">Email</label> <label
class="block text-sm font-semibold text-black"
>Email</label
>
<input <input
class="mt-2 w-full rounded-md border border-black/20 bg-white px-3 py-2 text-black" class="mt-2 w-full rounded-md border border-black/20 bg-white px-3 py-2 text-black"
type="email" type="email"
@ -116,16 +165,26 @@ const turnstileSiteKey = import.meta.env.PUBLIC_TURNSTILE_SITE_KEY ?? "";
/> />
</div> </div>
<div> <div>
<label class="block text-sm font-semibold text-black">Message</label> <label
class="block text-sm font-semibold text-black"
>Message</label
>
<textarea <textarea
class="mt-2 w-full rounded-md border border-black/20 bg-white px-3 py-2 text-black" class="mt-2 w-full rounded-md border border-black/20 bg-white px-3 py-2 text-black"
name="message" name="message"
rows="4" rows="4"
required required></textarea>
></textarea>
</div> </div>
<div class="cf-turnstile" data-sitekey={turnstileSiteKey}></div> <div
<p class="text-sm font-semibold text-black" data-form-message></p> class="cf-turnstile"
data-sitekey={turnstileSiteKey}
>
</div>
<p
class="text-sm font-semibold text-black"
data-form-message
>
</p>
<button <button
class="inline-flex items-center rounded-full border border-black/20 px-5 py-2 text-sm font-semibold uppercase tracking-wide text-black" class="inline-flex items-center rounded-full border border-black/20 px-5 py-2 text-sm font-semibold uppercase tracking-wide text-black"
type="submit" type="submit"
@ -138,10 +197,14 @@ const turnstileSiteKey = import.meta.env.PUBLIC_TURNSTILE_SITE_KEY ?? "";
</div> </div>
</div> </div>
</section> </section>
<section class="w-full max-w-[1200px] mx-auto px-0 sm:px-8 mt-0 pb-0"> <section
class="w-full max-w-[1200px] mx-auto px-0 sm:px-8 mt-0 pb-0"
>
<div class="bg-[#d6d6d6] px-6 sm:px-8 py-6 sm:py-8"> <div class="bg-[#d6d6d6] px-6 sm:px-8 py-6 sm:py-8">
<div class="h-px w-full bg-black/20 mb-6"></div> <div class="h-px w-full bg-black/20 mb-6"></div>
<div class="overflow-hidden rounded-[7px] border border-black/10"> <div
class="overflow-hidden rounded-[7px] border border-black/10"
>
<iframe <iframe
title="Highland Group RDA location" title="Highland Group RDA location"
src="https://www.google.com/maps?q=IV5%207PP&z=11&output=embed" src="https://www.google.com/maps?q=IV5%207PP&z=11&output=embed"
@ -155,62 +218,4 @@ const turnstileSiteKey = import.meta.env.PUBLIC_TURNSTILE_SITE_KEY ?? "";
<SiteFooter fullBleedOnMobile /> <SiteFooter fullBleedOnMobile />
</main> </main>
</div> </div>
<script type="module">
const turnstileSiteKey = {JSON.stringify(turnstileSiteKey)};
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) {
form.addEventListener("submit", async (event) => {
event.preventDefault();
if (submitButton.disabled) return;
submitButton.disabled = true;
submitButton.textContent = "Sending...";
message.textContent = "";
try {
const response = await fetch(form.action, {
method: "POST",
body: new FormData(form),
});
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";
}
});
}
</script>
</Base> </Base>