From 4857800fca4c9973ae72cf9b2c8e60aa4abd12ae Mon Sep 17 00:00:00 2001 From: Andras Schmelczer Date: Sun, 22 Feb 2026 23:14:42 +0000 Subject: [PATCH] Add plausible --- frontend/src/App.tsx | 6 +++ frontend/src/components/home/HomePage.tsx | 44 +++++++++++++++++-- frontend/src/components/home/ScrollStory.tsx | 10 ++++- frontend/src/components/map/MapPage.tsx | 6 +++ frontend/src/components/map/POIPane.tsx | 7 ++- .../src/components/pricing/PricingPage.tsx | 5 +++ frontend/src/components/ui/AuthModal.tsx | 7 ++- frontend/src/hooks/useAuth.ts | 5 +++ frontend/src/hooks/useFilters.ts | 4 ++ frontend/src/hooks/useHexagonSelection.ts | 4 ++ frontend/src/hooks/useLicense.ts | 3 ++ frontend/src/hooks/usePlausible.ts | 1 + frontend/src/hooks/useSavedSearches.ts | 3 ++ frontend/src/lib/analytics.ts | 19 ++++++++ 14 files changed, 118 insertions(+), 6 deletions(-) create mode 100644 frontend/src/lib/analytics.ts diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index 8e208e4..d22c801 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -12,6 +12,7 @@ import LicenseSuccessModal from './components/ui/LicenseSuccessModal'; import VerificationBanner from './components/ui/VerificationBanner'; import type { FeatureMeta, FeatureGroup, POICategoriesResponse, POICategoryGroup } from './types'; import { fetchWithRetry, apiUrl } from './lib/api'; +import { trackEvent } from './lib/analytics'; import { parseUrlState } from './lib/url-state'; import { INITIAL_VIEW_STATE } from './lib/consts'; import { useTheme } from './hooks/useTheme'; @@ -91,6 +92,10 @@ export default function App() { // Restore from history state (e.g. popstate) if (window.history.state?.page) return window.history.state.page; + // Unknown path — track as 404 + if (window.location.pathname !== '/') { + trackEvent('404', { path: window.location.pathname }); + } return 'home'; }); @@ -122,6 +127,7 @@ export default function App() { ? `${window.location.pathname}?${params.toString()}` : window.location.pathname; window.history.replaceState({}, '', newUrl); + trackEvent('Purchase'); setShowLicenseSuccess(true); refreshAuth(); } diff --git a/frontend/src/components/home/HomePage.tsx b/frontend/src/components/home/HomePage.tsx index 171cd39..7c7f471 100644 --- a/frontend/src/components/home/HomePage.tsx +++ b/frontend/src/components/home/HomePage.tsx @@ -1,4 +1,4 @@ -import { useState, useEffect } from 'react'; +import { useState, useEffect, useRef } from 'react'; import { useFadeInRef } from '../../hooks/useFadeIn'; import HexCanvas from './HexCanvas'; import ScrollStory from './ScrollStory'; @@ -6,6 +6,7 @@ import BottomIllustration from './BottomIllustration'; import { TickerValue } from '../ui/TickerValue'; import { ChevronIcon } from '../ui/icons/ChevronIcon'; import { LogoIcon } from '../ui/icons/LogoIcon'; +import { trackEvent } from '../../lib/analytics'; import type { FeatureMeta } from '../../types'; export default function HomePage({ @@ -30,6 +31,35 @@ export default function HomePage({ const whyRef = useFadeInRef(); const ctaRef = useFadeInRef(); + // Scroll depth tracking + const scrolledSections = useRef(new Set()); + useEffect(() => { + const ids = ['how-it-works', 'demo']; + const observers: IntersectionObserver[] = []; + ids.forEach((id) => { + const el = document.getElementById(id); + if (!el) return; + const observer = new IntersectionObserver( + ([entry]) => { + if (entry.isIntersecting && !scrolledSections.current.has(id)) { + scrolledSections.current.add(id); + trackEvent('Scroll Depth', { section: id }); + } + }, + { threshold: 0.1 } + ); + observer.observe(el); + observers.push(observer); + }); + return () => observers.forEach((o) => o.disconnect()); + }, []); + + // 30s time-on-page event + useEffect(() => { + const timer = setTimeout(() => trackEvent('Time on Page', { seconds: '30' }), 30000); + return () => clearTimeout(timer); + }, []); + return (
@@ -54,13 +84,17 @@ export default function HomePage({