new-site/site/public/js/pw-analytics.js
justin 60d5d3b9d8 analytics: attribute price-test arm per click + A/B/C scoreboard
So we can see whether a discount helps the CTA (clicks) AND the sale (paid
orders), per arm:

1. pw-analytics.js: include the ?code= (the price-test arm) on the campaign-click
   Umami event, and fire it when EITHER a utm OR a code is present (coupon links
   often carry code with no utm, so those human clicks were being dropped). Also
   strip a stray @TrackLink suffix Listmonk glues onto the last query value.

2. coupon_ab_scoreboard.py: one report combining HUMAN clicks (Umami, already
   bot-filtered) and PAID conversions (compliance_orders) per arm. Discount arms
   map by code->pct (campaign-daily:<date>:<pct> marker); the no-code control arm
   is recovered by re-hashing customer_email the same way the builder bucketed it.
   Prints clicks, paid orders, revenue, and click->order per arm.
2026-06-30 16:39:26 -05:00

139 lines
5.9 KiB
JavaScript

/**
* Performance West — Umami custom event tracking.
* Loaded after the Umami script on all pages.
* Uses umami.track() for conversion funnel events.
*/
(function() {
// Wait for umami to be available
function track(name, data) {
if (window.umami) {
window.umami.track(name, data || {});
}
}
// Expose globally for inline usage
window.pwTrack = track;
// ── Auto-track common interactions ────────────────────────────────────
// Track all CTA button clicks with data-track attribute
document.addEventListener("click", function(e) {
var el = e.target.closest("[data-track]");
if (el) {
track(el.getAttribute("data-track"), {
label: el.textContent.trim().slice(0, 50),
href: el.href || "",
page: location.pathname,
});
}
});
// ── FCC Compliance Checker events ─────────────────────────────────────
// Check if we're on the compliance checker page
if (location.pathname.indexOf("/tools/fcc-compliance-check") === 0) {
// Track when check starts (form submit / button click)
var checkBtn = document.querySelector("#check-btn, [onclick*='runCheck'], button[type='submit']");
if (checkBtn) {
checkBtn.addEventListener("click", function() {
var frn = (document.querySelector("#frn-input, input[name='frn']") || {}).value || "";
track("compliance-check-start", { frn: frn });
});
}
// Track when results load (observe DOM for results container)
var observer = new MutationObserver(function(mutations) {
for (var i = 0; i < mutations.length; i++) {
var target = mutations[i].target;
if (target.id === "checks-container" || target.id === "results") {
var issues = document.querySelectorAll(".check-fail, .issue-card, [data-severity]").length;
var frn = (document.querySelector("#frn-input, input[name='frn']") || {}).value || "";
track("compliance-check-complete", { frn: frn, issues: issues });
observer.disconnect();
break;
}
}
});
var resultsEl = document.getElementById("checks-container") || document.getElementById("results");
if (resultsEl) {
observer.observe(resultsEl, { childList: true, subtree: true });
}
// Track "Order" CTA clicks from results
document.addEventListener("click", function(e) {
var btn = e.target.closest("a[href*='/order/'], button[data-slug]");
if (btn && (btn.closest("#checks-container") || btn.closest("#results") || btn.closest(".cta"))) {
var slug = btn.getAttribute("data-slug") || btn.href.split("/order/")[1] || "";
track("order-cta-click", {
slug: slug.split("/")[0] || slug.split("?")[0],
source: "compliance-checker",
page: location.pathname,
});
}
});
}
// ── Order / Checkout pages ────────────────────────────────────────────
if (location.pathname.indexOf("/order/") === 0) {
// Track checkout page view with service info
var slug = location.pathname.replace("/order/", "").replace(/\/$/, "");
track("checkout-page-view", { slug: slug, referrer: document.referrer });
// Track payment button click
document.addEventListener("click", function(e) {
var btn = e.target.closest("#pay-btn, #submit-btn, .checkout-btn, button[type='submit']");
if (btn && (btn.textContent.indexOf("Pay") >= 0 || btn.textContent.indexOf("Checkout") >= 0 || btn.textContent.indexOf("Place Order") >= 0)) {
track("checkout-start", { slug: slug, page: location.pathname });
}
});
}
// ── eSign portal ──────────────────────────────────────────────────────
if (location.pathname.indexOf("/portal/esign") === 0 || location.pathname.indexOf("/portal/sign") === 0) {
track("esign-opened", { page: location.pathname });
document.addEventListener("click", function(e) {
var btn = e.target.closest("#submit-btn, .submit-btn");
if (btn && btn.textContent.indexOf("Submit") >= 0) {
track("esign-submitted", { page: location.pathname });
}
});
}
// ── Email campaign click attribution ──────────────────────────────────
var params = new URLSearchParams(location.search);
var utmSource = params.get("utm_source");
var utmCampaign = params.get("utm_campaign");
// Strip a stray "@TrackLink" suffix Listmonk can leave glued onto the last
// query value (e.g. utm_campaign=deficiency@TrackLink), so the arm/campaign
// reads cleanly in analytics.
var clean = function (v) { return (v || "").split("@TrackLink")[0]; };
// The price-test arm rides in ?code= (BGQAX=20%, MLTGA=30%, the 40% code; the
// full-price control arm has no code). Coupon links frequently carry code with
// NO utm, so fire campaign-click whenever EITHER a utm OR a code is present,
// and always include the code so each A/B/C arm is attributable per click.
var couponCode = clean(params.get("code"));
if (utmSource || utmCampaign || couponCode) {
track("campaign-click", {
source: clean(utmSource),
campaign: clean(utmCampaign),
medium: clean(params.get("utm_medium")),
code: couponCode,
page: location.pathname,
});
}
// ── Contact form submission ───────────────────────────────────────────
if (location.pathname.indexOf("/contact") === 0) {
document.addEventListener("submit", function(e) {
if (e.target.closest("form")) {
track("contact-form-submit", { page: location.pathname });
}
});
}
})();