Skip to content

Commit 8c815a0

Browse files
feat(analytics): integrate Google Analytics for tracking page views
- Added analytics module to load Google Analytics (GA4) only when a measurement ID is set. - Implemented functions to initialize analytics, track virtual page views, and send events. - Updated App component to track virtual page views based on active view state.
1 parent f69dc53 commit 8c815a0

3 files changed

Lines changed: 66 additions & 0 deletions

File tree

packages/web/src/App.tsx

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ import { ResizeHandle } from "./components/ResizeHandle";
2727
import { useIsMobile } from "./hooks/useIsMobile";
2828
import { MobileLayout } from "./components/MobileLayout";
2929
import { dlog } from "./debug-log";
30+
import { gtagPageView } from "./analytics";
3031

3132
export default function App() {
3233
const [state, dispatch] = useReducer(appReducer, initialState, (init): AppState => {
@@ -85,6 +86,11 @@ export default function App() {
8586
localStorage.setItem("botschat_active_view", state.activeView);
8687
}, [state.activeView]);
8788

89+
// Google Analytics: track virtual page views for SPA tabs
90+
useEffect(() => {
91+
gtagPageView(state.activeView);
92+
}, [state.activeView]);
93+
8894
// Persist selected cron task for automations view
8995
useEffect(() => {
9096
if (state.selectedCronTaskId) {

packages/web/src/analytics.ts

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
/**
2+
* Google Analytics (GA4). Only loads when VITE_GA_MEASUREMENT_ID is set (e.g. in .env.production).
3+
* Set VITE_GA_MEASUREMENT_ID to your GA4 Measurement ID (e.g. G-XXXXXXXXXX) to enable.
4+
*/
5+
6+
declare global {
7+
interface Window {
8+
dataLayer: unknown[];
9+
gtag: (...args: unknown[]) => void;
10+
}
11+
}
12+
13+
const MEASUREMENT_ID = import.meta.env.VITE_GA_MEASUREMENT_ID as string | undefined;
14+
15+
function loadGtag(): boolean {
16+
if (!MEASUREMENT_ID || typeof window === "undefined") return false;
17+
if (window.gtag) return true;
18+
19+
window.dataLayer = window.dataLayer || [];
20+
window.gtag = function gtag() {
21+
window.dataLayer.push(arguments);
22+
};
23+
window.gtag("js", new Date());
24+
window.gtag("config", MEASUREMENT_ID);
25+
26+
const script = document.createElement("script");
27+
script.async = true;
28+
script.src = `https://www.googletagmanager.com/gtag/js?id=${MEASUREMENT_ID}`;
29+
document.head.appendChild(script);
30+
return true;
31+
}
32+
33+
let initialized = false;
34+
35+
export function initAnalytics(): void {
36+
if (initialized) return;
37+
initialized = loadGtag();
38+
}
39+
40+
export function isAnalyticsEnabled(): boolean {
41+
return initialized && !!window.gtag;
42+
}
43+
44+
/**
45+
* Send a page_view or custom event to GA4. Use after route/view changes in the SPA.
46+
*/
47+
export function gtagEvent(name: string, params?: Record<string, string>): void {
48+
if (!MEASUREMENT_ID || !window.gtag) return;
49+
window.gtag("event", name, params);
50+
}
51+
52+
/**
53+
* Track a virtual page view (e.g. "Messages" / "Automations" tab).
54+
*/
55+
export function gtagPageView(page: string): void {
56+
gtagEvent("page_view", { page_path: `/${page}`, page_title: page });
57+
}

packages/web/src/main.tsx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,9 @@ import React from "react";
22
import ReactDOM from "react-dom/client";
33
import App from "./App";
44
import "./index.css";
5+
import { initAnalytics } from "./analytics";
6+
7+
initAnalytics();
58

69
ReactDOM.createRoot(document.getElementById("root")!).render(
710
<React.StrictMode>

0 commit comments

Comments
 (0)