Add PostHog analytics — full event tracking across all pages

Tracks pageviews, hero/CTA clicks, product teaser clicks, app card
and detail views, newsletter signups, nav interactions, and footer
link clicks with structured properties for PostHog dashboards.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Tim Leikauf
2026-07-01 20:59:29 +02:00
parent 7f67d123d6
commit 0101447d84
14 changed files with 248 additions and 64 deletions

View File

@@ -1,10 +1,8 @@
import type { Metadata } from 'next' 'use client'
import Link from 'next/link'
export const metadata: Metadata = { import Link from 'next/link'
title: 'Fittimo Bewegung im Alltag | HyggeCraftery', import { useEffect } from 'react'
description: 'Sanfte Bewegung und Atemübungen für Körper und Geist im Einklang.', import { posthog } from '@/lib/posthog'
}
const accent = '#7DAF8A' const accent = '#7DAF8A'
@@ -16,6 +14,10 @@ const features = [
] ]
export default function FittimoPage() { export default function FittimoPage() {
useEffect(() => {
posthog.capture('app_detail_viewed', { app: 'fittimo' })
}, [])
return ( return (
<div style={{ fontFamily: 'var(--font-mulish, Mulish, sans-serif)', color: '#3D2B1F', background: '#FAFAF7', overflow: 'hidden' }}> <div style={{ fontFamily: 'var(--font-mulish, Mulish, sans-serif)', color: '#3D2B1F', background: '#FAFAF7', overflow: 'hidden' }}>
@@ -98,7 +100,11 @@ export default function FittimoPage() {
<p style={{ fontSize: 16, color: 'rgba(250,250,247,0.65)', margin: '0 0 40px', lineHeight: 1.7 }}> <p style={{ fontSize: 16, color: 'rgba(250,250,247,0.65)', margin: '0 0 40px', lineHeight: 1.7 }}>
Fittimo erscheint bald. Trage dich in unseren Newsletter ein und erfahre als Erste davon. Fittimo erscheint bald. Trage dich in unseren Newsletter ein und erfahre als Erste davon.
</p> </p>
<Link href="/#newsletter" style={{ textDecoration: 'none', display: 'inline-flex', alignItems: 'center', gap: 10, background: accent, color: '#FAFAF7', fontSize: 15.5, fontWeight: 600, padding: '16px 36px', borderRadius: 44 }}> <Link
href="/#newsletter"
onClick={() => posthog.capture('newsletter_cta_clicked', { source: 'app_page', app_name: 'fittimo' })}
style={{ textDecoration: 'none', display: 'inline-flex', alignItems: 'center', gap: 10, background: accent, color: '#FAFAF7', fontSize: 15.5, fontWeight: 600, padding: '16px 36px', borderRadius: 44 }}
>
Benachrichtigen lassen Benachrichtigen lassen
</Link> </Link>
</div> </div>

View File

@@ -1,10 +1,7 @@
import type { Metadata } from 'next' 'use client'
import Link from 'next/link'
export const metadata: Metadata = { import Link from 'next/link'
title: 'Apps HyggeCraftery', import { posthog } from '@/lib/posthog'
description: 'Drei sanfte Apps für einen langsameren, bewussteren Alltag.',
}
const apps = [ const apps = [
{ {
@@ -56,6 +53,7 @@ export default function AppsPage() {
<Link <Link
key={app.name} key={app.name}
href={app.href} href={app.href}
onClick={() => posthog.capture('app_card_clicked', { app_name: app.name })}
style={{ style={{
textDecoration: 'none', textDecoration: 'none',
display: 'grid', display: 'grid',

View File

@@ -1,10 +1,8 @@
import type { Metadata } from 'next' 'use client'
import Link from 'next/link'
export const metadata: Metadata = { import Link from 'next/link'
title: 'Rezeptimo Kochen mit Freude | HyggeCraftery', import { useEffect } from 'react'
description: 'Saisonale Rezepte zum langsamen Kochen — ehrlich, einfach, nährend.', import { posthog } from '@/lib/posthog'
}
const accent = '#C4896A' const accent = '#C4896A'
@@ -16,6 +14,10 @@ const features = [
] ]
export default function RezeptimoPage() { export default function RezeptimoPage() {
useEffect(() => {
posthog.capture('app_detail_viewed', { app: 'rezeptimo' })
}, [])
return ( return (
<div style={{ fontFamily: 'var(--font-mulish, Mulish, sans-serif)', color: '#3D2B1F', background: '#FAFAF7', overflow: 'hidden' }}> <div style={{ fontFamily: 'var(--font-mulish, Mulish, sans-serif)', color: '#3D2B1F', background: '#FAFAF7', overflow: 'hidden' }}>
@@ -98,7 +100,11 @@ export default function RezeptimoPage() {
<p style={{ fontSize: 16, color: 'rgba(250,250,247,0.65)', margin: '0 0 40px', lineHeight: 1.7 }}> <p style={{ fontSize: 16, color: 'rgba(250,250,247,0.65)', margin: '0 0 40px', lineHeight: 1.7 }}>
Rezeptimo erscheint bald. Trage dich in unseren Newsletter ein und erfahre als Erste davon. Rezeptimo erscheint bald. Trage dich in unseren Newsletter ein und erfahre als Erste davon.
</p> </p>
<Link href="/#newsletter" style={{ textDecoration: 'none', display: 'inline-flex', alignItems: 'center', gap: 10, background: accent, color: '#FAFAF7', fontSize: 15.5, fontWeight: 600, padding: '16px 36px', borderRadius: 44 }}> <Link
href="/#newsletter"
onClick={() => posthog.capture('newsletter_cta_clicked', { source: 'app_page', app_name: 'rezeptimo' })}
style={{ textDecoration: 'none', display: 'inline-flex', alignItems: 'center', gap: 10, background: accent, color: '#FAFAF7', fontSize: 15.5, fontWeight: 600, padding: '16px 36px', borderRadius: 44 }}
>
Benachrichtigen lassen Benachrichtigen lassen
</Link> </Link>
</div> </div>

View File

@@ -1,10 +1,8 @@
import type { Metadata } from 'next' 'use client'
import Link from 'next/link'
export const metadata: Metadata = { import Link from 'next/link'
title: 'Snakkimo Sprachen lernen | HyggeCraftery', import { useEffect } from 'react'
description: 'Sprachen lernen, das sich anfühlt wie durch einen schönen Feed scrollen. Kleine Lektionen, großer Effekt.', import { posthog } from '@/lib/posthog'
}
const accent = '#7BA7BC' const accent = '#7BA7BC'
@@ -16,6 +14,10 @@ const features = [
] ]
export default function SnakkimoPage() { export default function SnakkimoPage() {
useEffect(() => {
posthog.capture('app_detail_viewed', { app: 'snakkimo' })
}, [])
return ( return (
<div style={{ fontFamily: 'var(--font-mulish, Mulish, sans-serif)', color: '#3D2B1F', background: '#FAFAF7', overflow: 'hidden' }}> <div style={{ fontFamily: 'var(--font-mulish, Mulish, sans-serif)', color: '#3D2B1F', background: '#FAFAF7', overflow: 'hidden' }}>
@@ -98,7 +100,11 @@ export default function SnakkimoPage() {
<p style={{ fontSize: 16, color: 'rgba(250,250,247,0.65)', margin: '0 0 40px', lineHeight: 1.7 }}> <p style={{ fontSize: 16, color: 'rgba(250,250,247,0.65)', margin: '0 0 40px', lineHeight: 1.7 }}>
Snakkimo erscheint bald. Trage dich in unseren Newsletter ein und erfahre als Erste davon. Snakkimo erscheint bald. Trage dich in unseren Newsletter ein und erfahre als Erste davon.
</p> </p>
<Link href="/#newsletter" style={{ textDecoration: 'none', display: 'inline-flex', alignItems: 'center', gap: 10, background: accent, color: '#FAFAF7', fontSize: 15.5, fontWeight: 600, padding: '16px 36px', borderRadius: 44 }}> <Link
href="/#newsletter"
onClick={() => posthog.capture('newsletter_cta_clicked', { source: 'app_page', app_name: 'snakkimo' })}
style={{ textDecoration: 'none', display: 'inline-flex', alignItems: 'center', gap: 10, background: accent, color: '#FAFAF7', fontSize: 15.5, fontWeight: 600, padding: '16px 36px', borderRadius: 44 }}
>
Benachrichtigen lassen Benachrichtigen lassen
</Link> </Link>
</div> </div>

3
app/layout.tsx Normal file → Executable file
View File

@@ -3,6 +3,7 @@ import { Cormorant_Garamond, Mulish } from 'next/font/google'
import './globals.css' import './globals.css'
import Nav from '@/components/ui/Nav' import Nav from '@/components/ui/Nav'
import Footer from '@/components/ui/Footer' import Footer from '@/components/ui/Footer'
import PostHogProvider from '@/components/PostHogProvider'
const cormorant = Cormorant_Garamond({ const cormorant = Cormorant_Garamond({
subsets: ['latin'], subsets: ['latin'],
@@ -45,9 +46,11 @@ export default function RootLayout({
backgroundImage: `url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='200' height='200'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.85' numOctaves='3' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)'/%3E%3C/svg%3E")`, backgroundImage: `url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='200' height='200'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.85' numOctaves='3' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)'/%3E%3C/svg%3E")`,
}} }}
/> />
<PostHogProvider>
<Nav /> <Nav />
<main>{children}</main> <main>{children}</main>
<Footer /> <Footer />
</PostHogProvider>
</body> </body>
</html> </html>
) )

View File

@@ -1,6 +1,9 @@
'use client'
import Image from 'next/image' import Image from 'next/image'
import Link from 'next/link' import Link from 'next/link'
import NewsletterForm from '@/components/ui/NewsletterForm' import NewsletterForm from '@/components/ui/NewsletterForm'
import { posthog } from '@/lib/posthog'
export default function HomePage() { export default function HomePage() {
return ( return (
@@ -21,11 +24,19 @@ export default function HomePage() {
Handgemachte Stücke und sanfte Rituale für ein Zuhause, das zur Ruhe einlädt. Wir feiern das Langsame, das Echte, das Wesentliche. Handgemachte Stücke und sanfte Rituale für ein Zuhause, das zur Ruhe einlädt. Wir feiern das Langsame, das Echte, das Wesentliche.
</p> </p>
<div style={{ display: 'flex', alignItems: 'center', gap: 22 }}> <div style={{ display: 'flex', alignItems: 'center', gap: 22 }}>
<Link href="/shop" style={{ textDecoration: 'none', display: 'inline-flex', alignItems: 'center', gap: 11, background: '#C4896A', color: '#FAFAF7', fontSize: 15.5, fontWeight: 600, letterSpacing: 0.3, padding: '16px 32px', borderRadius: 44 }}> <Link
href="/shop"
onClick={() => posthog.capture('hero_cta_clicked', { destination: '/shop', label: 'Entdecken' })}
style={{ textDecoration: 'none', display: 'inline-flex', alignItems: 'center', gap: 11, background: '#C4896A', color: '#FAFAF7', fontSize: 15.5, fontWeight: 600, letterSpacing: 0.3, padding: '16px 32px', borderRadius: 44 }}
>
Entdecken Entdecken
<span style={{ display: 'inline-block', width: 18, height: 18, borderRadius: '50%', border: '1.5px solid rgba(250,250,247,0.6)' }} /> <span style={{ display: 'inline-block', width: 18, height: 18, borderRadius: '50%', border: '1.5px solid rgba(250,250,247,0.6)' }} />
</Link> </Link>
<Link href="/#ueber" style={{ textDecoration: 'none', fontSize: 15, fontWeight: 600, color: '#3D2B1F', borderBottom: '1.5px solid #A8B89A', paddingBottom: 3 }}> <Link
href="/#ueber"
onClick={() => posthog.capture('hero_cta_clicked', { destination: '/#ueber', label: 'Unsere Geschichte' })}
style={{ textDecoration: 'none', fontSize: 15, fontWeight: 600, color: '#3D2B1F', borderBottom: '1.5px solid #A8B89A', paddingBottom: 3 }}
>
Unsere Geschichte Unsere Geschichte
</Link> </Link>
</div> </div>
@@ -68,14 +79,23 @@ export default function HomePage() {
Stücke fürs Zuhause Stücke fürs Zuhause
</h2> </h2>
</div> </div>
<Link href="/shop" style={{ textDecoration: 'none', display: 'inline-flex', alignItems: 'center', gap: 11, border: '1.5px solid #3D2B1F', color: '#3D2B1F', fontSize: 15, fontWeight: 600, padding: '14px 28px', borderRadius: 44 }}> <Link
href="/shop"
onClick={() => posthog.capture('hero_cta_clicked', { destination: '/shop', label: 'Zum Shop' })}
style={{ textDecoration: 'none', display: 'inline-flex', alignItems: 'center', gap: 11, border: '1.5px solid #3D2B1F', color: '#3D2B1F', fontSize: 15, fontWeight: 600, padding: '14px 28px', borderRadius: 44 }}
>
Zum Shop <span style={{ fontSize: 18, lineHeight: 1 }}></span> Zum Shop <span style={{ fontSize: 18, lineHeight: 1 }}></span>
</Link> </Link>
</div> </div>
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(3,1fr)', gap: 30 }}> <div style={{ display: 'grid', gridTemplateColumns: 'repeat(3,1fr)', gap: 30 }}>
{shopProducts.map((p) => ( {shopProducts.map((p) => (
<Link key={p.name} href="/shop" style={{ textDecoration: 'none', color: 'inherit', display: 'block' }}> <Link
key={p.name}
href="/shop"
onClick={() => posthog.capture('product_teaser_clicked', { product_name: p.name, price: p.price })}
style={{ textDecoration: 'none', color: 'inherit', display: 'block' }}
>
<div style={{ position: 'relative', height: 360, borderRadius: 30, overflow: 'hidden', background: p.bg, display: 'flex', alignItems: 'flex-end', justifyContent: 'center', padding: 18 }}> <div style={{ position: 'relative', height: 360, borderRadius: 30, overflow: 'hidden', background: p.bg, display: 'flex', alignItems: 'flex-end', justifyContent: 'center', padding: 18 }}>
{p.badge && ( {p.badge && (
<span style={{ position: 'absolute', top: 18, left: 18, background: '#A8B89A', color: '#FAFAF7', fontSize: 11.5, fontWeight: 700, letterSpacing: 0.6, padding: '6px 13px', borderRadius: 30 }}> <span style={{ position: 'absolute', top: 18, left: 18, background: '#A8B89A', color: '#FAFAF7', fontSize: 11.5, fontWeight: 700, letterSpacing: 0.6, padding: '6px 13px', borderRadius: 30 }}>
@@ -117,7 +137,11 @@ export default function HomePage() {
</div> </div>
<h3 style={{ fontFamily: 'var(--font-cormorant, "Cormorant Garamond", serif)', fontWeight: 600, fontSize: 27, margin: '0 0 10px', color: '#FAFAF7' }}>{app.name}</h3> <h3 style={{ fontFamily: 'var(--font-cormorant, "Cormorant Garamond", serif)', fontWeight: 600, fontSize: 27, margin: '0 0 10px', color: '#FAFAF7' }}>{app.name}</h3>
<p style={{ fontSize: 14.5, color: 'rgba(250,250,247,0.6)', margin: '0 0 22px' }}>{app.desc}</p> <p style={{ fontSize: 14.5, color: 'rgba(250,250,247,0.6)', margin: '0 0 22px' }}>{app.desc}</p>
<Link href={app.href} style={{ textDecoration: 'none', fontSize: 14.5, fontWeight: 600, color: app.linkColor, borderBottom: `1.5px solid ${app.linkColor}50`, paddingBottom: 2 }}> <Link
href={app.href}
onClick={() => posthog.capture('app_teaser_clicked', { app_name: app.name })}
style={{ textDecoration: 'none', fontSize: 14.5, fontWeight: 600, color: app.linkColor, borderBottom: `1.5px solid ${app.linkColor}50`, paddingBottom: 2 }}
>
Mehr erfahren Mehr erfahren
</Link> </Link>
</div> </div>
@@ -139,7 +163,7 @@ export default function HomePage() {
<p style={{ fontSize: 16, color: '#8a7a68', margin: '0 auto 36px', maxWidth: 460 }}> <p style={{ fontSize: 16, color: '#8a7a68', margin: '0 auto 36px', maxWidth: 460 }}>
Sanfte Geschichten, saisonale Rituale und stille Inspiration etwa einmal im Monat, ganz ohne Eile. Sanfte Geschichten, saisonale Rituale und stille Inspiration etwa einmal im Monat, ganz ohne Eile.
</p> </p>
<NewsletterForm /> <NewsletterForm source="homepage" />
</div> </div>
</div> </div>
</section> </section>

View File

@@ -1,10 +1,7 @@
import type { Metadata } from 'next' 'use client'
import Link from 'next/link'
export const metadata: Metadata = { import Link from 'next/link'
title: 'Shop HyggeCraftery', import { posthog } from '@/lib/posthog'
description: 'Handgemachte Stücke für ein langsameres, schöneres Zuhause.',
}
const products = [ const products = [
{ name: 'Leinen-Kissen «Fjord»', price: '€ 48', desc: 'Sanftes Leinen in gedämpftem Salbei.', badge: 'Neu', bg: 'repeating-linear-gradient(40deg,#F0E8DA,#F0E8DA 11px,#E7DDCC 11px,#E7DDCC 22px)' }, { name: 'Leinen-Kissen «Fjord»', price: '€ 48', desc: 'Sanftes Leinen in gedämpftem Salbei.', badge: 'Neu', bg: 'repeating-linear-gradient(40deg,#F0E8DA,#F0E8DA 11px,#E7DDCC 11px,#E7DDCC 22px)' },
@@ -46,7 +43,11 @@ export default function ShopPage() {
{/* Product grid */} {/* Product grid */}
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(3,1fr)', gap: 30 }}> <div style={{ display: 'grid', gridTemplateColumns: 'repeat(3,1fr)', gap: 30 }}>
{products.map((p) => ( {products.map((p) => (
<div key={p.name} style={{ display: 'block', color: 'inherit' }}> <div
key={p.name}
onClick={() => posthog.capture('product_viewed', { product_name: p.name, price: p.price })}
style={{ display: 'block', color: 'inherit', cursor: 'pointer' }}
>
<div style={{ position: 'relative', height: 360, borderRadius: 30, overflow: 'hidden', background: p.bg, display: 'flex', alignItems: 'flex-end', justifyContent: 'center', padding: 18 }}> <div style={{ position: 'relative', height: 360, borderRadius: 30, overflow: 'hidden', background: p.bg, display: 'flex', alignItems: 'flex-end', justifyContent: 'center', padding: 18 }}>
{p.badge && ( {p.badge && (
<span style={{ position: 'absolute', top: 18, left: 18, background: p.badge === 'Bald' ? '#C4896A' : '#A8B89A', color: '#FAFAF7', fontSize: 11.5, fontWeight: 700, letterSpacing: 0.6, padding: '6px 13px', borderRadius: 30 }}> <span style={{ position: 'absolute', top: 18, left: 18, background: p.badge === 'Bald' ? '#C4896A' : '#A8B89A', color: '#FAFAF7', fontSize: 11.5, fontWeight: 700, letterSpacing: 0.6, padding: '6px 13px', borderRadius: 30 }}>

View File

@@ -0,0 +1,21 @@
'use client'
import { useEffect } from 'react'
import { usePathname } from 'next/navigation'
import { initPostHog, posthog } from '@/lib/posthog'
export default function PostHogProvider({ children }: { children: React.ReactNode }) {
useEffect(() => {
initPostHog()
}, [])
const pathname = usePathname()
useEffect(() => {
if (posthog.__loaded) {
posthog.capture('$pageview', { $current_url: window.location.href })
}
}, [pathname])
return <>{children}</>
}

View File

@@ -1,4 +1,7 @@
'use client'
import Link from 'next/link' import Link from 'next/link'
import { posthog } from '@/lib/posthog'
export default function Footer() { export default function Footer() {
return ( return (
@@ -25,10 +28,10 @@ export default function Footer() {
Entdecken Entdecken
</h4> </h4>
<div style={{ display: 'flex', flexDirection: 'column', gap: 12 }}> <div style={{ display: 'flex', flexDirection: 'column', gap: 12 }}>
<FooterLink href="/shop">Shop</FooterLink> <FooterLink href="/shop" section="Entdecken">Shop</FooterLink>
<FooterLink href="/apps">Apps</FooterLink> <FooterLink href="/apps" section="Entdecken">Apps</FooterLink>
<FooterLink href="/#ueber">Über uns</FooterLink> <FooterLink href="/#ueber" section="Entdecken">Über uns</FooterLink>
<FooterLink href="/#newsletter">Newsletter</FooterLink> <FooterLink href="/#newsletter" section="Entdecken">Newsletter</FooterLink>
</div> </div>
</div> </div>
@@ -38,10 +41,10 @@ export default function Footer() {
Service Service
</h4> </h4>
<div style={{ display: 'flex', flexDirection: 'column', gap: 12 }}> <div style={{ display: 'flex', flexDirection: 'column', gap: 12 }}>
<FooterLink href="#">Versand</FooterLink> <FooterLink href="#" section="Service">Versand</FooterLink>
<FooterLink href="#">Kontakt</FooterLink> <FooterLink href="#" section="Service">Kontakt</FooterLink>
<FooterLink href="#">Impressum</FooterLink> <FooterLink href="#" section="Service">Impressum</FooterLink>
<FooterLink href="#">Datenschutz</FooterLink> <FooterLink href="#" section="Service">Datenschutz</FooterLink>
</div> </div>
</div> </div>
@@ -56,6 +59,7 @@ export default function Footer() {
key={abbr} key={abbr}
href="#" href="#"
title={label} title={label}
onClick={() => posthog.capture('footer_link_clicked', { label, section: 'Folgen' })}
style={{ style={{
textDecoration: 'none', textDecoration: 'none',
width: 42, width: 42,
@@ -86,9 +90,13 @@ export default function Footer() {
) )
} }
function FooterLink({ href, children }: { href: string; children: React.ReactNode }) { function FooterLink({ href, section, children }: { href: string; section: string; children: React.ReactNode }) {
return ( return (
<Link href={href} style={{ textDecoration: 'none', fontSize: 14.5, color: 'rgba(250,250,247,0.7)' }}> <Link
href={href}
onClick={() => posthog.capture('footer_link_clicked', { label: String(children), section })}
style={{ textDecoration: 'none', fontSize: 14.5, color: 'rgba(250,250,247,0.7)' }}
>
{children} {children}
</Link> </Link>
) )

View File

@@ -2,6 +2,7 @@
import Link from 'next/link' import Link from 'next/link'
import { useState } from 'react' import { useState } from 'react'
import { posthog } from '@/lib/posthog'
const apps = [ const apps = [
{ name: 'Snakkimo', href: '/apps/snakkimo', accent: '#7BA7BC' }, { name: 'Snakkimo', href: '/apps/snakkimo', accent: '#7BA7BC' },
@@ -26,7 +27,7 @@ export default function Nav() {
}} }}
> >
{/* Logo */} {/* Logo */}
<Link href="/" style={{ textDecoration: 'none', display: 'flex', alignItems: 'center', gap: 12 }}> <Link href="/" onClick={() => posthog.capture('nav_link_clicked', { label: 'Logo', href: '/' })} style={{ textDecoration: 'none', display: 'flex', alignItems: 'center', gap: 12 }}>
<div <div
style={{ style={{
width: 34, width: 34,
@@ -50,8 +51,8 @@ export default function Nav() {
{/* Nav links */} {/* Nav links */}
<nav style={{ display: 'flex', alignItems: 'center', gap: 38 }}> <nav style={{ display: 'flex', alignItems: 'center', gap: 38 }}>
<Link href="/#ueber" style={navLinkStyle}>Über uns</Link> <Link href="/#ueber" onClick={() => posthog.capture('nav_link_clicked', { label: 'Über uns', href: '/#ueber' })} style={navLinkStyle}>Über uns</Link>
<Link href="/shop" style={navLinkStyle}>Shop</Link> <Link href="/shop" onClick={() => posthog.capture('nav_link_clicked', { label: 'Shop', href: '/shop' })} style={navLinkStyle}>Shop</Link>
{/* Apps dropdown */} {/* Apps dropdown */}
<div <div
@@ -61,7 +62,11 @@ export default function Nav() {
> >
<button <button
style={{ ...navLinkStyle, background: 'none', border: 'none', cursor: 'pointer', padding: 0 }} style={{ ...navLinkStyle, background: 'none', border: 'none', cursor: 'pointer', padding: 0 }}
onClick={() => setAppsOpen((v) => !v)} onClick={() => {
const next = !appsOpen
setAppsOpen(next)
if (next) posthog.capture('nav_apps_dropdown_opened')
}}
aria-expanded={appsOpen} aria-expanded={appsOpen}
> >
Apps Apps
@@ -98,7 +103,7 @@ export default function Nav() {
fontSize: 14.5, fontSize: 14.5,
fontWeight: 500, fontWeight: 500,
}} }}
onClick={() => setAppsOpen(false)} onClick={() => { setAppsOpen(false); posthog.capture('nav_link_clicked', { label: app.name, href: app.href }) }}
> >
<span <span
style={{ style={{
@@ -118,6 +123,7 @@ export default function Nav() {
<Link <Link
href="/#newsletter" href="/#newsletter"
onClick={() => posthog.capture('newsletter_cta_clicked', { source: 'nav' })}
style={{ style={{
textDecoration: 'none', textDecoration: 'none',
color: '#FAFAF7', color: '#FAFAF7',

8
components/ui/NewsletterForm.tsx Normal file → Executable file
View File

@@ -1,8 +1,9 @@
'use client' 'use client'
import { useState } from 'react' import { useState } from 'react'
import { posthog } from '@/lib/posthog'
export default function NewsletterForm() { export default function NewsletterForm({ source = 'homepage' }: { source?: string }) {
const [email, setEmail] = useState('') const [email, setEmail] = useState('')
const [submitted, setSubmitted] = useState(false) const [submitted, setSubmitted] = useState(false)
@@ -21,7 +22,10 @@ export default function NewsletterForm() {
<form <form
onSubmit={(e) => { onSubmit={(e) => {
e.preventDefault() e.preventDefault()
if (email.trim()) setSubmitted(true) if (email.trim()) {
posthog.capture('newsletter_signup', { source })
setSubmitted(true)
}
}} }}
style={{ display: 'flex', gap: 12, maxWidth: 480, margin: '0 auto', flexWrap: 'wrap' }} style={{ display: 'flex', gap: 12, maxWidth: 480, margin: '0 auto', flexWrap: 'wrap' }}
> >

13
lib/posthog.ts Normal file
View File

@@ -0,0 +1,13 @@
import posthog from 'posthog-js'
export function initPostHog() {
if (typeof window !== 'undefined' && !posthog.__loaded) {
posthog.init('phc_BHgg9S7CQqVShe7EMCdi86PxA49qcNaTsR9Nn5EGxRCT', {
api_host: 'https://analytics.hyggecraftery.com',
capture_pageview: false,
persistence: 'localStorage',
})
}
}
export { posthog }

87
package-lock.json generated
View File

@@ -9,6 +9,7 @@
"version": "0.1.0", "version": "0.1.0",
"dependencies": { "dependencies": {
"next": "14.2.35", "next": "14.2.35",
"posthog-js": "^1.396.4",
"react": "^18", "react": "^18",
"react-dom": "^18" "react-dom": "^18"
}, },
@@ -273,6 +274,21 @@
"node": ">= 8" "node": ">= 8"
} }
}, },
"node_modules/@posthog/core": {
"version": "1.39.3",
"resolved": "https://registry.npmjs.org/@posthog/core/-/core-1.39.3.tgz",
"integrity": "sha512-oR+B8Q5O61N+W2+HVOBG9dbxAT/+OVxX+XvpNj6KYgptN2EB14JiKQ9Rm7DNpErNTLV7WApZEK/URkZgldOxfg==",
"license": "MIT",
"dependencies": {
"@posthog/types": "^1.392.0"
}
},
"node_modules/@posthog/types": {
"version": "1.392.0",
"resolved": "https://registry.npmjs.org/@posthog/types/-/types-1.392.0.tgz",
"integrity": "sha512-nctNujXL3FC1v99FktaTMSugSD9ZOZekEpahUSafkU2TSvW+XGKNkQZbokuJtiWvPBK208dwMJva8UfBkChqpw==",
"license": "MIT"
},
"node_modules/@swc/counter": { "node_modules/@swc/counter": {
"version": "0.1.3", "version": "0.1.3",
"resolved": "https://registry.npmjs.org/@swc/counter/-/counter-0.1.3.tgz", "resolved": "https://registry.npmjs.org/@swc/counter/-/counter-0.1.3.tgz",
@@ -327,6 +343,13 @@
"@types/react": "^18.0.0" "@types/react": "^18.0.0"
} }
}, },
"node_modules/@types/trusted-types": {
"version": "2.0.7",
"resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.7.tgz",
"integrity": "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==",
"license": "MIT",
"optional": true
},
"node_modules/any-promise": { "node_modules/any-promise": {
"version": "1.3.0", "version": "1.3.0",
"resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz", "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz",
@@ -476,6 +499,17 @@
"node": ">= 6" "node": ">= 6"
} }
}, },
"node_modules/core-js": {
"version": "3.49.0",
"resolved": "https://registry.npmjs.org/core-js/-/core-js-3.49.0.tgz",
"integrity": "sha512-es1U2+YTtzpwkxVLwAFdSpaIMyQaq0PBgm3YD1W3Qpsn1NAmO3KSgZfu+oGSWVu6NvLHoHCV/aYcsE5wiB7ALg==",
"hasInstallScript": true,
"license": "MIT",
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/core-js"
}
},
"node_modules/cssesc": { "node_modules/cssesc": {
"version": "3.0.0", "version": "3.0.0",
"resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz",
@@ -510,6 +544,15 @@
"dev": true, "dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/dompurify": {
"version": "3.4.11",
"resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.4.11.tgz",
"integrity": "sha512-zhlUV12GsaRzMsf9q5M254YhA4+VuF0fG+QFqu6aYpoGlKtz+w8//jBcGVYBgQkR5GHjUomejY84AV+/uPbWdw==",
"license": "(MPL-2.0 OR Apache-2.0)",
"optionalDependencies": {
"@types/trusted-types": "^2.0.7"
}
},
"node_modules/es-errors": { "node_modules/es-errors": {
"version": "1.3.0", "version": "1.3.0",
"resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz",
@@ -560,6 +603,12 @@
"reusify": "^1.0.4" "reusify": "^1.0.4"
} }
}, },
"node_modules/fflate": {
"version": "0.4.8",
"resolved": "https://registry.npmjs.org/fflate/-/fflate-0.4.8.tgz",
"integrity": "sha512-FJqqoDBR00Mdj9ppamLa/Y7vxm+PRmNWA67N846RvsoYVMKB4q3y/de5PA7gUmRMYK/8CMz2GDZQmCRN1wBcWA==",
"license": "MIT"
},
"node_modules/fill-range": { "node_modules/fill-range": {
"version": "7.1.1", "version": "7.1.1",
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz",
@@ -1111,6 +1160,38 @@
"dev": true, "dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/posthog-js": {
"version": "1.396.4",
"resolved": "https://registry.npmjs.org/posthog-js/-/posthog-js-1.396.4.tgz",
"integrity": "sha512-PycBmwKQD1T7YFYrGRb8rjQET/UVnexgUy8gVe6UBEhwHXEIhZF4na5VakJbn4zu1wg4tzjt8r7PA4VLu6bDjg==",
"license": "SEE LICENSE IN LICENSE",
"dependencies": {
"@posthog/core": "^1.39.3",
"@posthog/types": "^1.392.0",
"core-js": "^3.38.1",
"dompurify": "^3.3.2",
"fflate": "^0.4.8",
"preact": "^10.29.2",
"query-selector-shadow-dom": "^1.0.1",
"web-vitals": "^5.3.0"
}
},
"node_modules/preact": {
"version": "10.29.3",
"resolved": "https://registry.npmjs.org/preact/-/preact-10.29.3.tgz",
"integrity": "sha512-D9NL1GAnJZhc3RndVs4gDdxEeU9TcHgywMrhhOsnpdlvFjdbx0gAsLUnH6JEhlJH5giL7Tx5biWPUSEXE/HPzw==",
"license": "MIT",
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/preact"
}
},
"node_modules/query-selector-shadow-dom": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/query-selector-shadow-dom/-/query-selector-shadow-dom-1.0.1.tgz",
"integrity": "sha512-lT5yCqEBgfoMYpf3F2xQRK7zEr1rhIIZuceDK6+xRkJQ4NMbHTwXqk4NkwDwQMNqXgG9r9fyHnzwNVs6zV5KRw==",
"license": "MIT"
},
"node_modules/queue-microtask": { "node_modules/queue-microtask": {
"version": "1.2.3", "version": "1.2.3",
"resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz",
@@ -1484,6 +1565,12 @@
"integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==",
"dev": true, "dev": true,
"license": "MIT" "license": "MIT"
},
"node_modules/web-vitals": {
"version": "5.3.0",
"resolved": "https://registry.npmjs.org/web-vitals/-/web-vitals-5.3.0.tgz",
"integrity": "sha512-q6LWsLatGYZp5VGBIOvbTj6JBV2nOmC8KvWztXBmwJcfFAzhwKwbOxhUH306XY3CcaZDUlSmSuNPBsCn0bFu+g==",
"license": "Apache-2.0"
} }
} }
} }

9
package.json Normal file → Executable file
View File

@@ -9,16 +9,17 @@
"lint": "next lint" "lint": "next lint"
}, },
"dependencies": { "dependencies": {
"next": "14.2.35",
"posthog-js": "^1.396.4",
"react": "^18", "react": "^18",
"react-dom": "^18", "react-dom": "^18"
"next": "14.2.35"
}, },
"devDependencies": { "devDependencies": {
"typescript": "^5",
"@types/node": "^20", "@types/node": "^20",
"@types/react": "^18", "@types/react": "^18",
"@types/react-dom": "^18", "@types/react-dom": "^18",
"postcss": "^8", "postcss": "^8",
"tailwindcss": "^3.4.1" "tailwindcss": "^3.4.1",
"typescript": "^5"
} }
} }