mirror of
https://github.com/Memo-2023/mana-monorepo.git
synced 2026-05-28 12:17:44 +02:00
Create Analytics.astro component in @manacore/shared-landing-ui that automatically tracks CTA clicks and pricing section views via Umami. The component uses event delegation and auto-detection of section context (hero/pricing/footer) from section IDs or DOM position, requiring zero changes to existing landing page content. Tracked events: cta_click (with location), pricing_viewed, pricing_plan_selected (with plan name) Added to all 10 landing page Layout.astro files. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
112 lines
3.1 KiB
Text
112 lines
3.1 KiB
Text
---
|
|
/**
|
|
* Landing Page Analytics
|
|
*
|
|
* Add this component before </body> in your Layout.astro to automatically
|
|
* track CTA clicks and pricing section views via Umami.
|
|
*
|
|
* Works automatically with two approaches:
|
|
*
|
|
* 1. **Auto-detection** (zero config): Tracks all <a> and <button> clicks
|
|
* inside sections, inferring the location from the closest section's id
|
|
* or position on the page.
|
|
*
|
|
* 2. **Explicit attributes** (optional, more precise):
|
|
* - data-track-cta="hero|pricing|footer" → cta_click event
|
|
* - data-track-pricing="free|pro|team" → pricing_plan_selected event
|
|
* - data-track-section="pricing" → pricing_viewed on scroll
|
|
*
|
|
* @example
|
|
* ```astro
|
|
* <body>
|
|
* <slot />
|
|
* <Analytics />
|
|
* </body>
|
|
* ```
|
|
*/
|
|
---
|
|
|
|
<script>
|
|
function track(event: string, data?: Record<string, string | number | boolean>) {
|
|
if ((window as any).umami?.track) {
|
|
try {
|
|
(window as any).umami.track(event, data);
|
|
} catch {
|
|
// ignore
|
|
}
|
|
}
|
|
}
|
|
|
|
// Infer section location from element context
|
|
function getLocation(el: HTMLElement): string | null {
|
|
// 1. Check explicit data attribute
|
|
const explicit = el.closest('[data-track-cta]');
|
|
if (explicit) return (explicit as HTMLElement).dataset.trackCta || null;
|
|
|
|
// 2. Check closest section with id
|
|
const section = el.closest('section[id], div[id]');
|
|
if (section) {
|
|
const id = section.id.toLowerCase();
|
|
if (id.includes('hero')) return 'hero';
|
|
if (id.includes('pricing') || id.includes('plans')) return 'pricing';
|
|
if (id.includes('cta') || id.includes('download')) return 'footer';
|
|
if (id.includes('feature')) return 'features';
|
|
if (id.includes('faq')) return 'faq';
|
|
return id;
|
|
}
|
|
|
|
// 3. Check if it's in the first or last section of main
|
|
const main = document.querySelector('main');
|
|
if (main) {
|
|
const sections = main.querySelectorAll(':scope > *');
|
|
if (sections.length > 0) {
|
|
if (sections[0].contains(el)) return 'hero';
|
|
if (sections[sections.length - 1].contains(el)) return 'footer';
|
|
}
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
// Track CTA button/link clicks
|
|
document.addEventListener('click', (e) => {
|
|
const target = e.target as HTMLElement;
|
|
const link = target.closest('a[href], button') as HTMLElement | null;
|
|
if (!link) return;
|
|
|
|
// Skip navigation links (same-page anchors to sections)
|
|
const href = link.getAttribute('href') || '';
|
|
if (href.startsWith('#') && !href.includes('download')) return;
|
|
|
|
const location = getLocation(link);
|
|
if (!location) return;
|
|
|
|
track('cta_click', { location });
|
|
|
|
// Track pricing plan selection
|
|
const plan = link.dataset?.trackPricing;
|
|
if (plan) {
|
|
track('pricing_plan_selected', { plan });
|
|
}
|
|
});
|
|
|
|
// Track pricing section visibility
|
|
const pricingSection =
|
|
document.querySelector('[data-track-section="pricing"]') ||
|
|
document.querySelector('#pricing, #plans, [id*="pricing"]');
|
|
|
|
if (pricingSection) {
|
|
let tracked = false;
|
|
const observer = new IntersectionObserver(
|
|
(entries) => {
|
|
if (entries[0].isIntersecting && !tracked) {
|
|
tracked = true;
|
|
track('pricing_viewed');
|
|
observer.disconnect();
|
|
}
|
|
},
|
|
{ threshold: 0.3 }
|
|
);
|
|
observer.observe(pricingSection);
|
|
}
|
|
</script>
|