diff --git a/js/newsletter.js b/js/newsletter.js deleted file mode 100644 index 6a0b5cd..0000000 --- a/js/newsletter.js +++ /dev/null @@ -1,128 +0,0 @@ -/** - * Newsletter signup — posts the visitor's email to a configured serverless - * endpoint. No credential is ever sent: the serverless function authenticates - * with an API token it reads from platform env/secrets at deploy time. - * - * Pure-ish by design (no DOM, injectable fetch), so every behaviour is unit - * testable in Node; the browser wiring at the bottom is guarded accordingly. - */ -import { NEWSLETTER_ENDPOINT } from "./newsletter-config.js"; - -/** User-safe message shown when the signup cannot be completed. */ -export const NEWSLETTER_ERROR_MESSAGE = - "Sorry, the newsletter signup isn't available right now. Please try again later."; - -/** Confirmation message shown after a successful signup. */ -export const NEWSLETTER_SUCCESS_MESSAGE = "Thanks for subscribing!"; - -/** Simple email shape check; the HTML5 `type="email"` input is the primary gate. */ -export function isValidEmail(value) { - return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(String(value).trim()); -} - -/** - * Read the trimmed email value from a form. - * Works with a real DOM form and with the minimal fake form used in tests. - * - * @param {{querySelectorAll(selector: string): ArrayLike<{name: string, value: string}>}} form - * @returns {string} - */ -export function readFormEmail(form) { - for (const el of form.querySelectorAll("[name]")) { - if (el.name === "email") return el.value.trim(); - } - return ""; -} - -/** - * POST the email to the serverless endpoint with no credential in the request - * — no auth header, no token in the body or URL. The serverless function reads - * its API token from platform env/secrets, never from the client. A clearly - * invalid email fails fast with a user-safe error and no network call. Any - * non-2xx response and any network failure map to the same generic message, so - * the endpoint internals, any status code, and any raw response body are never - * surfaced to the visitor. - * - * @param {string} email - * @param {{endpoint?: string, fetchImpl?: typeof fetch}} [options] - * @returns {Promise<{ok: boolean, message: string}>} - */ -export async function submitNewsletterSignup( - email, - { endpoint = NEWSLETTER_ENDPOINT, fetchImpl = fetch } = {}, -) { - if (!isValidEmail(email)) { - return { ok: false, message: NEWSLETTER_ERROR_MESSAGE }; - } - - let response; - try { - response = await fetchImpl(endpoint, { - method: "POST", - headers: { - "Content-Type": "application/json", - }, - body: JSON.stringify({ email }), - }); - } catch { - return { ok: false, message: NEWSLETTER_ERROR_MESSAGE }; - } - - if (!response.ok) { - return { ok: false, message: NEWSLETTER_ERROR_MESSAGE }; - } - return { ok: true, message: NEWSLETTER_SUCCESS_MESSAGE }; -} - -/** - * Handle a newsletter form submit: validate the email, POST it, and surface - * the result (confirmation or user-safe error) through `setStatus`. - * - * @param {HTMLFormElement} form - * @param {{endpoint?: string, fetchImpl?: typeof fetch, setStatus?: (message: string, kind: "success"|"error") => void}} [options] - * @returns {Promise<{ok: boolean, message: string}>} - */ -export async function handleNewsletterSubmit( - form, - { - endpoint = NEWSLETTER_ENDPOINT, - fetchImpl = fetch, - setStatus = defaultSetStatus, - } = {}, -) { - const email = readFormEmail(form); - if (!isValidEmail(email)) { - setStatus(NEWSLETTER_ERROR_MESSAGE, "error"); - return { ok: false, message: NEWSLETTER_ERROR_MESSAGE }; - } - - const result = await submitNewsletterSignup(email, { endpoint, fetchImpl }); - setStatus(result.message, result.ok ? "success" : "error"); - return result; -} - -/** Default status renderer — puts the message into `#newsletter-status`. */ -function defaultSetStatus(message, kind) { - const el = document.getElementById("newsletter-status"); - if (el) { - el.textContent = message; - el.dataset.kind = kind; - } -} - -/** - * Wire the submit handler onto a newsletter form. - * @param {HTMLFormElement} form - */ -export function initNewsletterForm(form, options = {}) { - form.addEventListener("submit", (event) => { - event.preventDefault(); - handleNewsletterSubmit(form, options); - }); -} - -// Browser-only wiring — guarded so this module stays importable in Node tests. -if (typeof window !== "undefined" && typeof document !== "undefined") { - const form = document.getElementById("newsletter-form"); - if (form) initNewsletterForm(form); -}