Spaces:
Running
Running
| /* ===== 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/<inbox> | |
| 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; | |
| })(); | |