mirror of
https://github.com/Memo-2023/mana-monorepo.git
synced 2026-05-14 19:01:08 +02:00
feat(analytics): add automatic CTA tracking to all 10 landing pages
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>
This commit is contained in:
parent
420926aef1
commit
287bbed86e
12 changed files with 155 additions and 0 deletions
112
packages/shared-landing-ui/src/atoms/Analytics.astro
Normal file
112
packages/shared-landing-ui/src/atoms/Analytics.astro
Normal file
|
|
@ -0,0 +1,112 @@
|
|||
---
|
||||
/**
|
||||
* 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>
|
||||
Loading…
Add table
Add a link
Reference in a new issue