/** * Newsletter signup — posts the visitor's email to a configured serverless * endpoint using the API token from `newsletter-config.js`. * * 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_API_TOKEN, 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 the API token. * * The token travels in the `Authorization` header; it is never put in the * body, the URL, or any message. A missing token fails fast with a user-safe * error and no network call. Any non-2xx response (invalid token, endpoint * error) and any network failure map to the same generic message, so the * secret and endpoint internals are never surfaced to the visitor. * * @param {string} email * @param {{endpoint?: string, token?: string, fetchImpl?: typeof fetch}} [options] * @returns {Promise<{ok: boolean, message: string}>} */ export async function submitNewsletterSignup( email, { endpoint = NEWSLETTER_ENDPOINT, token = NEWSLETTER_API_TOKEN, fetchImpl = fetch } = {} ) { if (!token) { return { ok: false, message: NEWSLETTER_ERROR_MESSAGE }; } let response; try { response = await fetchImpl(endpoint, { method: "POST", headers: { "Content-Type": "application/json", Authorization: `Bearer ${token}`, }, 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, token?: 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, token = NEWSLETTER_API_TOKEN, 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, token, 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); }