tp-energy-advisors / script.js
flyguy001's picture
Fix: use FormSubmit AJAX JSON endpoint; accept first-time activation handshake
9f2be18 verified
Raw
History Blame Contribute Delete
7.39 kB
/* ===== 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;
})();