Add Umami custom event tracking across all key pages

- Created /js/pw-analytics.js with conversion funnel events
- Added to Base.astro layout (all Astro pages) + 6 static HTML pages
- Events tracked: compliance-check-start, compliance-check-complete,
  order-cta-click, checkout-page-view, checkout-start, esign-opened,
  esign-submitted, campaign-click (UTM attribution), contact-form-submit
- Server-side payment-complete event from checkout webhook via Umami API
- Auto-tracks any element with data-track="event-name" attribute

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
justin 2026-05-05 05:10:13 -05:00
parent 19eb8ff2e8
commit bd5193e45f
9 changed files with 163 additions and 6 deletions

View file

@ -1240,6 +1240,32 @@ export async function handlePaymentComplete(
}
}
// ── Umami analytics — server-side payment event ─────────────────────────
try {
const umamiUrl = process.env.UMAMI_URL || "http://umami:3000";
const websiteId = "55250014-ee15-44ac-a1f6-81dabad3fe0f";
await fetch(`${umamiUrl}/api/send`, {
method: "POST",
headers: { "Content-Type": "application/json", "User-Agent": "PW-API/1.0" },
body: JSON.stringify({
payload: {
website: websiteId,
hostname: "performancewest.net",
url: `/checkout/complete/${order_type}`,
name: "payment-complete",
data: {
order_type,
order_id,
payment_method: paymentMethod,
total_cents: Number(order.service_fee_cents || order.total_cents || 0),
service: (order.service_slug as string) || order_type,
},
},
type: "event",
}),
});
} catch { /* non-fatal */ }
// ── Advance ERPNext Sales Order workflow (CRTC) ────────────────────────
if (order_type === "canada_crtc") {
const soName = (order.erpnext_sales_order as string) || null;

View file

@ -0,0 +1,129 @@
/**
* 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");
if (utmSource || utmCampaign) {
track("campaign-click", {
source: utmSource || "",
campaign: utmCampaign || "",
medium: params.get("utm_medium") || "",
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 });
}
});
}
})();

View file

@ -62,7 +62,7 @@ select:focus,input:focus{outline:none;border-color:#1e3a5f;box-shadow:0 0 0 2px
.recommend-box strong{color:#166534}
.err{color:#dc2626;font-size:.82rem;margin-top:.5rem;display:none}
</style>
</head>
<script defer src="https://analytics.performancewest.net/script.js" data-website-id="55250014-ee15-44ac-a1f6-81dabad3fe0f"></script><script defer src="/js/pw-analytics.js"></script></head>
<body>
<div class="wrap">
<h1>FCC Carrier / ISP Registration</h1>

View file

@ -5,7 +5,7 @@
if (h === "dev.performancewest.net") return "https://api.dev.performancewest.net";
return "https://api.performancewest.net";
})();
</script><link rel="preconnect" href="https://fonts.googleapis.com"><link rel="preconnect" href="https://fonts.gstatic.com" crossorigin><link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=JetBrains+Mono:wght@400;500&display=swap" rel="stylesheet"><title>FCC Compliance Remediation — USF Compliance | Performance West Inc.</title><script defer src="https://analytics.performancewest.net/script.js" data-website-id="55250014-ee15-44ac-a1f6-81dabad3fe0f"></script><link rel="stylesheet" href="/_astro/about.DhmoKVOS.css">
</script><link rel="preconnect" href="https://fonts.googleapis.com"><link rel="preconnect" href="https://fonts.gstatic.com" crossorigin><link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=JetBrains+Mono:wght@400;500&display=swap" rel="stylesheet"><title>FCC Compliance Remediation — USF Compliance | Performance West Inc.</title><script defer src="https://analytics.performancewest.net/script.js" data-website-id="55250014-ee15-44ac-a1f6-81dabad3fe0f"></script><script defer src="/js/pw-analytics.js"></script><link rel="stylesheet" href="/_astro/about.DhmoKVOS.css">
<style>.step-dot[data-astro-cid-k2lzm3bk]{display:inline-flex;align-items:center;justify-content:center;width:24px;height:24px;border-radius:50%;background:#e5e7eb;color:#6b7280;font-weight:600;font-size:11px}.step-indicator[data-astro-cid-k2lzm3bk].active .step-dot[data-astro-cid-k2lzm3bk]{background:#1e3a5f;color:#fff}.step-indicator[data-astro-cid-k2lzm3bk].done .step-dot[data-astro-cid-k2lzm3bk]{background:#22c55e;color:#fff}
</style><script type="module" src="/_astro/hoisted.DyVnHzn9.js"></script></head> <body class="min-h-screen flex flex-col"> <!-- Navigation --> <nav class="border-b border-gray-200 bg-white sticky top-0 z-50"> <div class="max-w-6xl mx-auto px-4 sm:px-6 lg:px-8"> <div class="flex justify-between h-24 items-center"> <a href="/" class="flex items-center"> <img src="/images/logo.png" alt="Performance West" class="h-20 w-auto" width="83" height="70"> </a> <div class="hidden md:flex items-center gap-8"> <!-- Services dropdown --> <div class="relative" id="services-dropdown"> <button type="button" class="text-sm text-gray-600 hover:text-gray-900 inline-flex items-center gap-1" id="services-btn">
Services

View file

@ -49,7 +49,7 @@ select:focus,input:focus{outline:none;border-color:#1e3a5f;box-shadow:0 0 0 2px
.hidden{display:none}
.err{color:#dc2626;font-size:.82rem;margin-top:.5rem;display:none}
</style>
</head>
<script defer src="https://analytics.performancewest.net/script.js" data-website-id="55250014-ee15-44ac-a1f6-81dabad3fe0f"></script><script defer src="/js/pw-analytics.js"></script></head>
<body>
<div class="wrap">
<h1>NECA OCN Registration</h1>

View file

@ -59,7 +59,7 @@ select:focus,input:focus{outline:none;border-color:#1e3a5f;box-shadow:0 0 0 2px
.step-dot.done{background:#22c55e;color:#fff}
.entity-form input{margin-bottom:.75rem}
</style>
</head>
<script defer src="https://analytics.performancewest.net/script.js" data-website-id="55250014-ee15-44ac-a1f6-81dabad3fe0f"></script><script defer src="/js/pw-analytics.js"></script></head>
<body>
<div class="wrap">
<h1>State PUC/PSC Registration</h1>

View file

@ -53,7 +53,7 @@ body{font-family:'Inter',system-ui,sans-serif;color:#1f2937;background:#f1f5f9;l
#error-screen{display:none;text-align:center;padding:3rem 1rem}
.hidden{display:none}
</style>
</head>
<script defer src="https://analytics.performancewest.net/script.js" data-website-id="55250014-ee15-44ac-a1f6-81dabad3fe0f"></script><script defer src="/js/pw-analytics.js"></script></head>
<body>
<div class="wrap">
<div id="loading"><p>Loading your document...</p><p style="font-size:.8rem;color:#94a3b8">Verifying your link</p></div>

View file

@ -5,7 +5,7 @@
if (h === "dev.performancewest.net") return "https://api.dev.performancewest.net";
return "https://api.performancewest.net";
})();
</script><link rel="preconnect" href="https://fonts.googleapis.com"><link rel="preconnect" href="https://fonts.gstatic.com" crossorigin><link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=JetBrains+Mono:wght@400;500&display=swap" rel="stylesheet"><title>FCC Compliance Check — Free Telecom Filing Status | Performance West Inc.</title><script defer src="https://analytics.performancewest.net/script.js" data-website-id="55250014-ee15-44ac-a1f6-81dabad3fe0f"></script><link rel="stylesheet" href="/_astro/about.DhmoKVOS.css"><script type="module" src="/_astro/hoisted.aBLqmOPy.js?v=8"></script></head> <body class="min-h-screen flex flex-col"> <!-- Navigation --> <nav class="border-b border-gray-200 bg-white sticky top-0 z-50"> <div class="max-w-6xl mx-auto px-4 sm:px-6 lg:px-8"> <div class="flex justify-between h-24 items-center"> <a href="/" class="flex items-center"> <img src="/images/logo.png" alt="Performance West" class="h-20 w-auto" width="83" height="70"> </a> <div class="hidden md:flex items-center gap-8"> <!-- Services dropdown --> <div class="relative" id="services-dropdown"> <button type="button" class="text-sm text-gray-600 hover:text-gray-900 inline-flex items-center gap-1" id="services-btn">
</script><link rel="preconnect" href="https://fonts.googleapis.com"><link rel="preconnect" href="https://fonts.gstatic.com" crossorigin><link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=JetBrains+Mono:wght@400;500&display=swap" rel="stylesheet"><title>FCC Compliance Check — Free Telecom Filing Status | Performance West Inc.</title><script defer src="https://analytics.performancewest.net/script.js" data-website-id="55250014-ee15-44ac-a1f6-81dabad3fe0f"></script><script defer src="/js/pw-analytics.js"></script><link rel="stylesheet" href="/_astro/about.DhmoKVOS.css"><script type="module" src="/_astro/hoisted.aBLqmOPy.js?v=8"></script></head> <body class="min-h-screen flex flex-col"> <!-- Navigation --> <nav class="border-b border-gray-200 bg-white sticky top-0 z-50"> <div class="max-w-6xl mx-auto px-4 sm:px-6 lg:px-8"> <div class="flex justify-between h-24 items-center"> <a href="/" class="flex items-center"> <img src="/images/logo.png" alt="Performance West" class="h-20 w-auto" width="83" height="70"> </a> <div class="hidden md:flex items-center gap-8"> <!-- Services dropdown --> <div class="relative" id="services-dropdown"> <button type="button" class="text-sm text-gray-600 hover:text-gray-900 inline-flex items-center gap-1" id="services-btn">
Services
<svg class="w-3.5 h-3.5 transition-transform" id="services-chevron" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path stroke-linecap="round" stroke-linejoin="round" d="M19 9l-7 7-7-7"></path></svg> </button> <div id="services-menu" class="absolute left-1/2 -translate-x-1/2 top-full mt-2 rounded-xl border border-gray-200 bg-white shadow-xl hidden z-50" style="width: 720px;"> <div class="grid grid-cols-3 gap-0 p-4"> <!-- Column 1 --> <div class="pr-4 border-r border-gray-100"> <p class="text-[11px] font-semibold uppercase tracking-wider text-blue-500 mb-2">Telecom</p> <a href="/services/telecom/fcc-499a" class="block py-1.5 text-sm text-gray-700 hover:text-pw-700">FCC 499A Filing</a> <a href="/services/telecom/stir-shaken" class="block py-1.5 text-sm text-gray-700 hover:text-pw-700">STIR/SHAKEN</a> <a href="/services/telecom/ipes-isp" class="block py-1.5 text-sm text-gray-700 hover:text-pw-700">FCC Carrier / ISP Registration</a> <a href="/services/telecom/database-management" class="block py-1.5 text-sm text-gray-700 hover:text-pw-700">Telecom Databases</a> <a href="/services/telecom/state-puc" class="block py-1.5 text-sm text-gray-700 hover:text-pw-700">State PUC/PSC Filings</a> <a href="/services/telecom/canada-crtc" class="block py-1.5 text-sm font-medium text-blue-600 hover:text-blue-800">Canada CRTC Package <span class="inline-flex items-center px-1.5 py-0.5 rounded-full text-[9px] font-bold bg-red-500 text-white ml-1 animate-pulse">HOT</span></a> <a href="/tools/fcc-compliance-check" class="block py-1.5 text-sm font-medium text-green-600 hover:text-green-800">FCC Compliance Check <span class="inline-flex items-center px-1.5 py-0.5 rounded-full text-[9px] font-bold bg-green-500 text-white ml-1">FREE</span></a> </div> <!-- Column 2 --> <div class="px-4 border-r border-gray-100"> <p class="text-[11px] font-semibold uppercase tracking-wider text-purple-500 mb-2">Data Privacy</p> <a href="/services/privacy/ccpa-audit" class="block py-1.5 text-sm text-gray-700 hover:text-pw-700">CCPA/CPRA Audit</a> <a href="/services/privacy/privacy-policy" class="block py-1.5 text-sm text-gray-700 hover:text-pw-700">Privacy Policy Review</a> <a href="/services/privacy/data-mapping" class="block py-1.5 text-sm text-gray-700 hover:text-pw-700">Data Mapping</a> <a href="/services/privacy/breach-response" class="block py-1.5 text-sm text-gray-700 hover:text-pw-700">Breach Response Plan</a> <p class="text-[11px] font-semibold uppercase tracking-wider text-green-500 mb-2 mt-4">TCPA</p> <a href="/services/tcpa/consent-audit" class="block py-1.5 text-sm text-gray-700 hover:text-pw-700">Consent Audit</a> <a href="/services/tcpa/dnc-compliance" class="block py-1.5 text-sm text-gray-700 hover:text-pw-700">DNC Compliance</a> <a href="/services/tcpa/campaign-review" class="block py-1.5 text-sm text-gray-700 hover:text-pw-700">Campaign Review</a> </div> <!-- Column 3 --> <div class="pl-4"> <p class="text-[11px] font-semibold uppercase tracking-wider text-slate-500 mb-2">Corporate</p> <a href="/services/corporate/formation" class="block py-1.5 text-sm text-gray-700 hover:text-pw-700">Business Formation</a> <a href="/services/corporate/state-registration" class="block py-1.5 text-sm text-gray-700 hover:text-pw-700">State Registration</a> <a href="/services/corporate/annual-reports" class="block py-1.5 text-sm text-gray-700 hover:text-pw-700">Annual Reports</a> <a href="/services/corporate/registered-agent" class="block py-1.5 text-sm text-gray-700 hover:text-pw-700">Registered Agent</a> <a href="/order/formation" class="mt-3 block py-2 px-3 text-sm font-medium text-white bg-pw-700 hover:bg-pw-800 rounded-lg text-center transition-colors">Form a Business</a> </div> </div> </div> </div> <a href="/services" class="text-sm text-gray-600 hover:text-gray-900">All Services</a> <a href="/pricing" class="text-sm text-gray-600 hover:text-gray-900">Pricing</a> <a href="/tools/contractor-quiz" class="text-sm text-gray-600 hover:text-gray-900">Free Tools</a> <a href="/contact" class="text-sm text-gray-600 hover:text-gray-900">Contact</a> <a href="/order/formation" class="ml-2 px-4 py-2 text-sm font-medium text-white bg-pw-700 hover:bg-pw-800 rounded-lg transition-colors">Form a Business</a> <!-- Account button — links to ERPNext portal --> <a href="https://portal.performancewest.net" id="nav-login-btn" class="ml-1 flex items-center gap-1.5 px-3 py-2 text-sm font-medium text-gray-600 hover:text-pw-700 hover:bg-pw-50 rounded-lg transition-colors border border-gray-200 hover:border-pw-300"> <svg class="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path stroke-linecap="round" stroke-linejoin="round" d="M15.75 6a3.75 3.75 0 11-7.5 0 3.75 3.75 0 017.5 0zM4.501 20.118a7.5 7.5 0 0114.998 0A17.933 17.933 0 0112 21.75c-2.676 0-5.216-.584-7.499-1.632z"></path></svg>
Client Portal

View file

@ -31,6 +31,8 @@ try {
<title>{title} | Performance West Inc.</title>
{description && <meta name="description" content={description} />}
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
<script defer src="https://analytics.performancewest.net/script.js" data-website-id="55250014-ee15-44ac-a1f6-81dabad3fe0f"></script>
<script defer src="/js/pw-analytics.js"></script>
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=JetBrains+Mono:wght@400;500&display=swap" rel="stylesheet" />