All files / react-app/src/utils/ReactGA PathTracker.tsx

100% Statements 36/36
100% Branches 10/10
100% Functions 8/8
100% Lines 36/36

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                                107x   65x     65x   65x   37x 39x               37x 39x 19x 19x 19x 19x                 37x     37x 3x 3x     3x 1x       2x     2x     2x 2x       37x 37x   37x 2x 2x   37x 1x 1x       37x     37x   37x     37x 37x 37x              
import { useEffect, useRef } from "react";
import { UserRole } from "shared-types/events/legacy-user";
 
import { sendGAEvent } from "./SendGAEvent";
 
type PathTrackerProps = {
  userRole: UserRole;
  children: React.ReactNode;
};
 
/**
 * Wrap around your <C.Layout>.
 * It will send:
 *   1) a `custom_page_view` event on initial mount or after every route change
 *   2) a `page_duration` event when the user leaves the previous route
 */
export const PathTracker = ({ userRole, children }: PathTrackerProps) => {
  // keep track of the path of the page the user is leaving
  const prevPathRef = useRef<string>(window.location.pathname);
 
  // record when the current route was "entered"
  const startTimeRef = useRef<number>(Date.now());
 
  useEffect(() => {
    //for tracking page views
    const sendPageView = (path: string) => {
      sendGAEvent("page_view", {
        page_path: path,
        referrer: prevPathRef.current || "",
        ...(userRole && { user_role: userRole }),
      });
    };
 
    //for tracking duration spent on page
    const sendPageDuration = (path: string, startTs: number) => {
      if (userRole) {
        const now = Date.now();
        const deltaMs = now - startTs;
        const timeOnPageSec = Math.round(deltaMs / 1000); // nearest second
        sendGAEvent("page_duration", {
          page_path: path,
          ...(userRole && { user_role: userRole }),
          time_on_page_sec: timeOnPageSec,
        });
      }
    };
 
    // send page_view for the first load
    sendPageView(window.location.pathname);
 
    // when a route change is detected
    const onRouteChange = () => {
      const newPath = window.location.pathname;
      const oldPath = prevPathRef.current;
 
      // if the path didn’t actually change, do nothing
      if (newPath === oldPath) {
        return;
      }
 
      // 1) send page_duration for the old path
      sendPageDuration(oldPath, startTimeRef.current);
 
      // 2) send a new page_view for the new path, with referrer = old path
      sendPageView(newPath);
 
      // 3) update prevPath and reset startTime for the new page
      prevPathRef.current = newPath;
      startTimeRef.current = Date.now();
    };
 
    //pushState/replaceState so we catch in‐app ract navigation
    const origPush = window.history.pushState;
    const origReplace = window.history.replaceState;
 
    window.history.pushState = function (this: History, ...args: any[]) {
      origPush.apply(this, args);
      onRouteChange();
    };
    window.history.replaceState = function (this: History, ...args: any[]) {
      origReplace.apply(this, args);
      onRouteChange();
    };
 
    //Also catch Back/Forward browser buttons
    window.addEventListener("popstate", onRouteChange);
 
    //cleanup- when PathTracker unmounts
    return () => {
      //send duration for whichever page user was on
      sendPageDuration(prevPathRef.current, startTimeRef.current);
 
      //restore history methods and remove listener
      window.history.pushState = origPush;
      window.history.replaceState = origReplace;
      window.removeEventListener("popstate", onRouteChange);
    };
  }, [userRole]);
 
  // Render children as usual
  return <>{children}</>;
};