/** * AnalyticsProvider - Next.js client-side analytics wrapper * * Handles client-side initialization and automatic route tracking. */ 'use client'; import { createContext, useContext, useEffect, useMemo, type ReactNode } from 'react'; import { usePathname, useSearchParams } from 'next/navigation'; import { AnalyticsClient, type AnalyticsConfig } from '@analytics/client'; // ───────────────────────────────────────────────────────────────────────────── // Context // ───────────────────────────────────────────────────────────────────────────── interface AnalyticsContextValue { client: AnalyticsClient; trackEvent: (type: string, action: string, metadata?: Record) => void; trackClick: (element: string, metadata?: Record) => void; identify: (userId: string, traits?: Record) => void; } const AnalyticsContext = createContext(null); // ───────────────────────────────────────────────────────────────────────────── // Provider // ───────────────────────────────────────────────────────────────────────────── interface AnalyticsProviderProps { children: ReactNode; collectorUrl: string; appName: string; enabled?: boolean; debug?: boolean; } export function AnalyticsProvider({ children, collectorUrl, appName, enabled = true, debug = false, }: AnalyticsProviderProps) { const pathname = usePathname(); const searchParams = useSearchParams(); // Initialize client once const client = useMemo(() => { const config: AnalyticsConfig = { apiBaseUrl: collectorUrl, appName, enabled, enableDebugLogging: debug, autoCapture: { pageViews: false, // We handle this manually for route changes clicks: true, scrollDepth: true, performance: true, }, }; return new AnalyticsClient(config); }, [collectorUrl, appName, enabled, debug]); // Track route changes useEffect(() => { if (!enabled) return; const url = pathname + (searchParams?.toString() ? `?${searchParams.toString()}` : ''); client.trackEngagement({ type: 'navigation', action: 'page_view', metadata: { path: pathname, url, referrer: typeof document !== 'undefined' ? document.referrer : undefined, title: typeof document !== 'undefined' ? document.title : undefined, }, }); }, [pathname, searchParams, client, enabled]); // Build context value const value = useMemo( (): AnalyticsContextValue => ({ client, trackEvent: (type, action, metadata) => { client.trackEngagement({ type, action, metadata }); }, trackClick: (element, metadata) => { client.trackEngagement({ type: 'click', action: element, metadata, }); }, identify: (userId, traits) => { client.identify(userId, traits); }, }), [client], ); return {children}; } // ───────────────────────────────────────────────────────────────────────────── // Hook // ───────────────────────────────────────────────────────────────────────────── export function useAnalytics(): AnalyticsContextValue { const context = useContext(AnalyticsContext); if (!context) { throw new Error('useAnalytics must be used within an AnalyticsProvider'); } return context; } /** * Safe hook that returns null if not in provider (for optional tracking) */ export function useAnalyticsSafe(): AnalyticsContextValue | null { return useContext(AnalyticsContext); }