/* ===== T&P Energy Advisors — Lead Form Script ===== */ (function () { "use strict"; const FORM_ID = "leadForm"; const SUCCESS_CLASS = "visible"; const FIELDS = { postal_code: { required: true, label: "Postal Code" }, heating_source: { required: true, label: "Heating Source" }, income: { required: true, label: "Household Income" }, email: { required: true, label: "Email Address" }, }; const VALIDATORS = { postal_code: function (val) { // Canadian postal code: A#A #A# return /^[A-Za-z]\d[A-Za-z][ ]?\d[A-Za-z]\d$/.test(val.trim()) ? null : "Enter a valid Canadian postal code (e.g., A1A 1A1)."; }, heating_source: function (val) { return ["oil", "gas", "electric", "propane", "other"].includes(val) ? null : "Select your primary heating source."; }, income: function (val) { return val !== "" ? null : "Select your household income range."; }, email: function (val) { return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(val.trim()) ? null : "Enter a valid email address."; }, }; var form = document.getElementById(FORM_ID); if (!form) return; var successEl = document.getElementById("formSuccess") || document.createElement("div"); var failEl = document.getElementById("formError"); // ---- UTM attribution capture ---- // Attribution params ride along as hidden fields so every lead POST is // traceable back to the ad that sent it. var UTM_PARAMS = [ "utm_source", "utm_medium", "utm_campaign", "utm_term", "utm_content", "ref", ]; function safeDecode(str) { try { return decodeURIComponent(str.replace(/\+/g, " ")); } catch (e) { return str; } } (function captureAttribution() { var qs = window.location.search.replace(/^\?/, ""); if (!qs) return; qs.split("&").forEach(function (pair) { if (!pair) return; var eq = pair.indexOf("="); var key = eq === -1 ? pair : pair.slice(0, eq); var val = eq === -1 ? "" : safeDecode(pair.slice(eq + 1)); if (UTM_PARAMS.indexOf(key) === -1 || val === "") return; var input = document.createElement("input"); input.type = "hidden"; input.name = key; input.value = val; form.appendChild(input); }); })(); // ---- Validation ---- function validateField(name) { var input = form.querySelector('[name="' + name + '"]'); if (!input) return true; var errorEl = input.parentNode.querySelector(".field-error"); var val = input.value; var validator = VALIDATORS[name]; var errMsg = validator ? validator(val) : null; if (!errMsg && FIELDS[name].required && val.trim() === "") { errMsg = FIELDS[name].label + " is required."; } if (errMsg) { input.classList.add("error"); if (errorEl) { errorEl.textContent = errMsg; errorEl.classList.add(SUCCESS_CLASS); } return false; } input.classList.remove("error"); if (errorEl) { errorEl.textContent = ""; errorEl.classList.remove(SUCCESS_CLASS); } return true; } function validateAll() { var result = true; Object.keys(FIELDS).forEach(function (name) { if (!validateField(name)) result = false; }); return result; } function clearErrors() { form.querySelectorAll(".error").forEach(function (el) { el.classList.remove("error"); }); form.querySelectorAll(".field-error").forEach(function (el) { el.textContent = ""; el.classList.remove(SUCCESS_CLASS); }); } // ---- Real-time blur validation ---- Object.keys(FIELDS).forEach(function (name) { var input = form.querySelector('[name="' + name + '"]'); if (!input) return; input.addEventListener("blur", function () { validateField(name); }); input.addEventListener("input", function () { // Clear error as user types if (input.classList.contains("error")) { validateField(name); } }); }); // ---- FormSubmit endpoint (no account needed; first POST triggers a // one-time activation email to the inbox address) ---- // Use the documented AJAX endpoint so fetch gets a JSON reply instead of // a redirect: https://formsubmit.co/ajax/ var SUBMIT_ENDPOINT = "https://formsubmit.co/ajax/SaveEnergy@TandP.com"; function setSubmitting(submitting) { var btn = form.querySelector("button[type='submit']"); if (!btn) return; btn.disabled = submitting; btn.setAttribute("aria-busy", submitting ? "true" : "false"); } function showFailure(message) { if (failEl) { failEl.textContent = message; failEl.classList.add(SUCCESS_CLASS); failEl.scrollIntoView({ behavior: "smooth", block: "center" }); } setSubmitting(false); } function hideFailure() { if (failEl) { failEl.textContent = ""; failEl.classList.remove(SUCCESS_CLASS); } } // ---- Submit handler ---- form.addEventListener("submit", function (e) { e.preventDefault(); clearErrors(); hideFailure(); if (!validateAll()) { // Focus the first error field var firstError = form.querySelector(".error"); if (firstError) firstError.focus(); return; } // Gather form fields + hidden attribution fields (UTM/ref) var data = {}; Object.keys(FIELDS).forEach(function (name) { var input = form.querySelector('[name="' + name + '"]'); data[name] = input ? input.value.trim() : ""; }); form.querySelectorAll('input[type="hidden"]').forEach(function (input) { if (input.value.trim() !== "") data[input.name] = input.value.trim(); }); // FormSubmit control field: keep the spam CAPTCHA off so leads land data._captcha = "false"; console.log("[T&P Lead Capture]", JSON.stringify(data, null, 2)); setSubmitting(true); fetch(SUBMIT_ENDPOINT, { method: "POST", headers: { "Content-Type": "application/json", Accept: "application/json", }, body: JSON.stringify(data), }) .then(function (res) { if (!res.ok) throw new Error("FormSubmit responded " + res.status); return res.json(); }) .then(function (json) { var accepted = json && (json.success === "true" || json.success === true || // First submission for a brand-new FormSubmit inbox returns an // activation handshake instead of success — the lead is still // captured and the inbox owner just needs to click the // activation link in the email FormSubmit sent. /activation/i.test(json.message || "")); if (!accepted) throw new Error("FormSubmit did not accept the lead"); // Success — show the thanks state form.reset(); clearErrors(); form.style.display = "none"; if (successEl) successEl.classList.add(SUCCESS_CLASS); }) .catch(function (err) { console.error("[T&P Lead Capture] POST failed:", err); showFailure( "Hoser alert: we couldn't send your info just now. Please try again in a moment \u2014 eh, sorry!" ); }); }); // ---- Expose for debugging ---- window.TP = window.TP || {}; window.TP.validateForm = validateAll; })();