mirror of
https://github.com/Memo-2023/mana-monorepo.git
synced 2026-05-14 21: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
|
|
@ -1,5 +1,6 @@
|
|||
---
|
||||
import '../styles/global.css';
|
||||
import Analytics from '@manacore/shared-landing-ui/atoms/Analytics.astro';
|
||||
|
||||
interface Props {
|
||||
title?: string;
|
||||
|
|
@ -52,5 +53,6 @@ const {
|
|||
</head>
|
||||
<body class="antialiased">
|
||||
<slot />
|
||||
<Analytics />
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
---
|
||||
import '../styles/global.css';
|
||||
import Analytics from '@manacore/shared-landing-ui/atoms/Analytics.astro';
|
||||
|
||||
interface Props {
|
||||
title: string;
|
||||
|
|
@ -57,5 +58,6 @@ const {
|
|||
</head>
|
||||
<body class="min-h-screen bg-background-page text-text-primary antialiased">
|
||||
<slot />
|
||||
<Analytics />
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
---
|
||||
import '../styles/global.css';
|
||||
import Analytics from '@manacore/shared-landing-ui/atoms/Analytics.astro';
|
||||
|
||||
interface Props {
|
||||
title?: string;
|
||||
|
|
@ -52,5 +53,6 @@ const {
|
|||
</head>
|
||||
<body class="antialiased">
|
||||
<slot />
|
||||
<Analytics />
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
---
|
||||
import AlternateLinks from '../components/seo/AlternateLinks.astro';
|
||||
import Sidebar from '../components/navigation/Sidebar.astro';
|
||||
import Analytics from '@manacore/shared-landing-ui/atoms/Analytics.astro';
|
||||
import { getLangFromUrl } from '../lib/i18n/config';
|
||||
|
||||
export interface Props {
|
||||
|
|
@ -138,6 +139,7 @@ const lang = getLangFromUrl(Astro.url);
|
|||
|
||||
<!-- FAB Sidebar Navigation -->
|
||||
<Sidebar />
|
||||
<Analytics />
|
||||
</body>
|
||||
</html>
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
---
|
||||
import '../styles/global.css';
|
||||
import Analytics from '@manacore/shared-landing-ui/atoms/Analytics.astro';
|
||||
|
||||
interface Props {
|
||||
title: string;
|
||||
|
|
@ -57,5 +58,6 @@ const {
|
|||
</head>
|
||||
<body class="min-h-screen bg-background-page text-text-primary antialiased">
|
||||
<slot />
|
||||
<Analytics />
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
|||
|
|
@ -1,4 +1,6 @@
|
|||
---
|
||||
import Analytics from '@manacore/shared-landing-ui/atoms/Analytics.astro';
|
||||
|
||||
interface Props {
|
||||
title: string;
|
||||
}
|
||||
|
|
@ -54,5 +56,6 @@ const { title } = Astro.props;
|
|||
</head>
|
||||
<body>
|
||||
<slot />
|
||||
<Analytics />
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
|||
|
|
@ -1,4 +1,6 @@
|
|||
---
|
||||
import Analytics from '@manacore/shared-landing-ui/atoms/Analytics.astro';
|
||||
|
||||
interface Props {
|
||||
title: string;
|
||||
description?: string;
|
||||
|
|
@ -38,6 +40,7 @@ const { title, description = 'NutriPhi - KI-gestützte Ernährungsanalyse per Fo
|
|||
</head>
|
||||
<body class="bg-[#0F1F0F] text-gray-100 antialiased">
|
||||
<slot />
|
||||
<Analytics />
|
||||
</body>
|
||||
</html>
|
||||
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ import '../styles/global.css';
|
|||
// import { HeadHrefLangs } from 'astro-i18next/components';
|
||||
import { t } from '../i18n';
|
||||
import LanguageSwitcher from '@components/LanguageSwitcher.astro';
|
||||
import Analytics from '@manacore/shared-landing-ui/atoms/Analytics.astro';
|
||||
|
||||
interface Props {
|
||||
title?: string;
|
||||
|
|
@ -55,6 +56,7 @@ const {
|
|||
</div>
|
||||
|
||||
<slot />
|
||||
<Analytics />
|
||||
</body>
|
||||
</html>
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
---
|
||||
import '../styles/global.css';
|
||||
import Analytics from '@manacore/shared-landing-ui/atoms/Analytics.astro';
|
||||
|
||||
interface Props {
|
||||
title: string;
|
||||
|
|
@ -57,5 +58,6 @@ const {
|
|||
</head>
|
||||
<body class="min-h-screen bg-background-page text-text-primary antialiased">
|
||||
<slot />
|
||||
<Analytics />
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
---
|
||||
import '../styles/global.css';
|
||||
import Analytics from '@manacore/shared-landing-ui/atoms/Analytics.astro';
|
||||
|
||||
interface Props {
|
||||
title?: string;
|
||||
|
|
@ -52,5 +53,6 @@ const {
|
|||
</head>
|
||||
<body class="antialiased">
|
||||
<slot />
|
||||
<Analytics />
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
|||
|
|
@ -88,6 +88,27 @@ Diese Events erfordern **keinen Code in den einzelnen Apps** — sie werden auto
|
|||
|
||||
---
|
||||
|
||||
## Landing Page Event Tracking
|
||||
|
||||
Alle Landing Pages binden `<Analytics />` aus `@manacore/shared-landing-ui` ein. Das Script trackt automatisch:
|
||||
|
||||
| Event | Wann | Data |
|
||||
|-------|------|------|
|
||||
| `cta_click` | Klick auf CTA-Button/Link | `{ location: 'hero' \| 'pricing' \| 'footer' \| ... }` |
|
||||
| `pricing_viewed` | Pricing-Section wird sichtbar | - |
|
||||
| `pricing_plan_selected` | Klick auf Pricing-Plan CTA | `{ plan: 'free' \| 'pro' \| ... }` |
|
||||
|
||||
**Auto-Detection:** Das Script erkennt die Section automatisch aus `id`-Attributen oder der Position im DOM (erster/letzter Abschnitt).
|
||||
|
||||
**Explizite Attribute (optional):**
|
||||
```html
|
||||
<a href="/register" data-track-cta="hero">Jetzt starten</a>
|
||||
<a href="/pro" data-track-cta="pricing" data-track-pricing="pro">Pro starten</a>
|
||||
<section data-track-section="pricing">...</section>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Custom Event Tracking
|
||||
|
||||
### Installation
|
||||
|
|
|
|||
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