131 lines
4.5 KiB
TypeScript
131 lines
4.5 KiB
TypeScript
/**
|
|
* 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<string, unknown>) => void;
|
|
trackClick: (element: string, metadata?: Record<string, unknown>) => void;
|
|
identify: (userId: string, traits?: Record<string, unknown>) => void;
|
|
}
|
|
|
|
const AnalyticsContext = createContext<AnalyticsContextValue | null>(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 <AnalyticsContext.Provider value={value}>{children}</AnalyticsContext.Provider>;
|
|
}
|
|
|
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
// 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);
|
|
}
|