All files / app/features/analytics provider.tsx

97.77% Statements 44/45
88.88% Branches 8/9
100% Functions 10/10
100% Lines 43/43

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157                                                    1x 1x           1x             14x 14x 14x             70x 70x   14x               17x 17x 17x 13x 5x     17x 17x                           15x         18x 18x 18x 18x     18x 14x 14x     14x   14x                       14x             14x 14x       18x 14x 3x 3x 3x   3x             3x 3x     14x 14x 14x         18x 14x 14x            
/**
 * Analytics Provider
 *
 * Wraps your app to automatically track key user behaviors without any additional setup.
 * Just wrap your app once and you're done!
 *
 * WHAT IT TRACKS AUTOMATICALLY:
 * - Session starts (when users first land on your site)
 * - Page views (including time spent on each page)
 * - Device info (screen size, browser, language)
 * - Marketing attribution (UTM parameters from ads/campaigns)
 * - Referral sources (where users came from)
 *
 * HOW TO USE:
 * Wrap your app's root component with this provider. That's it!
 *
 * @example
 * // In your _app.tsx or root layout
 * <AnalyticsProvider>
 *   <YourApp />
 * </AnalyticsProvider>
 *
 * For custom event tracking beyond automatic page views, use the hooks from
 * journeyHooks.ts or call logEvent() directly from eventCollector.ts.
 */
 
import { useRouter } from "next/router";
import { ReactNode, useEffect, useRef } from "react";
 
import {
  destroyCollector,
  initializeCollector,
  logEvent,
} from "./eventCollector";
 
/**
 * Extract UTM parameters from the URL for marketing attribution.
 * These help track which campaigns, ads, or links brought users to your site.
 */
function getUtmParams(): Record<string, string> {
  const params = new URLSearchParams(window.location.search);
  const utm: Record<string, string> = {};
  for (const key of [
    "utm_source",
    "utm_medium",
    "utm_campaign",
    "utm_term",
    "utm_content",
  ]) {
    const val = params.get(key);
    if (val) utm[key] = val;
  }
  return utm;
}
 
/**
 * Get query parameters excluding UTM params (for cleaner page.viewed events).
 * This keeps your analytics focused on actual page parameters, not marketing tags.
 */
function getFilteredSearch(queryString: string): string | null {
  const params = new URLSearchParams(queryString);
  const filtered = new URLSearchParams();
  params.forEach((value, key) => {
    if (!key.startsWith("utm_")) {
      filtered.set(key, value);
    }
  });
  const result = filtered.toString();
  return result || null;
}
 
/**
 * Provider component that enables automatic analytics tracking.
 *
 * Handles the complete analytics lifecycle:
 * - Initializes event collection on mount
 * - Tracks session start with device/attribution data
 * - Tracks all page navigation automatically
 * - Cleans up and flushes events on unmount
 *
 * @param children - Your app's components
 */
export default function AnalyticsProvider({
  children,
}: {
  children: ReactNode;
}) {
  const router = useRouter();
  const previousPathRef = useRef<string | null>(null);
  const previousTimestampRef = useRef<number>(Date.now());
  const initializedRef = useRef(false);
 
  // Session start + initial page view (runs once on mount)
  useEffect(() => {
    Iif (initializedRef.current) return;
    initializedRef.current = true;
 
    // Initialize event listeners
    initializeCollector();
 
    logEvent("session.started", {
      ...getUtmParams(),
      referrer: document.referrer || null,
      landing_page: window.location.pathname,
      user_agent: navigator.userAgent,
      screen_width: window.screen.width,
      screen_height: window.screen.height,
      viewport_width: window.innerWidth,
      viewport_height: window.innerHeight,
      language: navigator.language,
    });
 
    logEvent("page.viewed", {
      path: window.location.pathname,
      search: getFilteredSearch(window.location.search),
      previous_path: null,
      time_on_previous_page: null,
    });
 
    previousPathRef.current = window.location.pathname;
    previousTimestampRef.current = Date.now();
  }, []);
 
  // Route change tracking
  useEffect(() => {
    const handleRouteChange = (url: string) => {
      const [path, queryString] = url.split("?");
      const now = Date.now();
      const timeOnPreviousPage = (now - previousTimestampRef.current) / 1000;
 
      logEvent("page.viewed", {
        path,
        search: getFilteredSearch(queryString ?? ""),
        previous_path: previousPathRef.current,
        time_on_previous_page: timeOnPreviousPage,
      });
 
      previousPathRef.current = path;
      previousTimestampRef.current = now;
    };
 
    router.events.on("routeChangeComplete", handleRouteChange);
    return () => {
      router.events.off("routeChangeComplete", handleRouteChange);
    };
  }, [router.events]);
 
  // Cleanup on unmount
  useEffect(() => {
    return () => {
      destroyCollector();
    };
  }, []);
 
  return <>{children}</>;
}