diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml
index 6123356..d72682a 100644
--- a/.github/workflows/deploy.yml
+++ b/.github/workflows/deploy.yml
@@ -34,6 +34,8 @@ jobs:
run: npm install
- name: Build
+ env:
+ VITE_GA_ID: ${{ secrets.VITE_GA_ID }}
run: npm run build
- name: Setup Pages
diff --git a/package.json b/package.json
index 15c4693..0671f94 100644
--- a/package.json
+++ b/package.json
@@ -54,6 +54,7 @@
"react": "^18.3.1",
"react-day-picker": "^8.10.1",
"react-dom": "^18.3.1",
+ "react-ga4": "^2.1.0",
"react-hook-form": "^7.61.1",
"react-resizable-panels": "^2.1.9",
"react-router-dom": "^6.30.1",
diff --git a/src/App.tsx b/src/App.tsx
index 492b8d4..ad44f5c 100644
--- a/src/App.tsx
+++ b/src/App.tsx
@@ -16,12 +16,28 @@ import { useGameStore } from "@/store/gameStore";
import { toast } from "sonner";
import { formatCurrency } from "@/lib/utils";
import { GAME_CONFIG, calculateCurrentIncome } from "@/config/gameConfig";
+import { initGA, trackPageView } from "@/lib/analytics";
+import { useLocation } from "react-router-dom";
const queryClient = new QueryClient();
+const AnalyticsTracker = () => {
+ const location = useLocation();
+
+ useEffect(() => {
+ trackPageView(location.pathname + location.search);
+ }, [location]);
+
+ return null;
+};
+
const App = () => {
const offlineProcessed = useRef(false);
+ useEffect(() => {
+ initGA();
+ }, []);
+
// Offline income calculation — runs once on mount
useEffect(() => {
if (offlineProcessed.current) return;
@@ -65,6 +81,7 @@ const App = () => {
+
} />
} />
diff --git a/src/lib/analytics.ts b/src/lib/analytics.ts
new file mode 100644
index 0000000..c8fab25
--- /dev/null
+++ b/src/lib/analytics.ts
@@ -0,0 +1,43 @@
+import ReactGA from "react-ga4";
+
+const GA_MEASUREMENT_ID = import.meta.env.VITE_GA_ID;
+
+export const initGA = () => {
+ if (GA_MEASUREMENT_ID) {
+ ReactGA.initialize(GA_MEASUREMENT_ID);
+ console.log("GA Initialized");
+ }
+};
+
+export const trackPageView = (path: string) => {
+ if (GA_MEASUREMENT_ID) {
+ ReactGA.send({ hitType: "pageview", page: path });
+ }
+};
+
+export const trackEvent = (category: string, action: string, label?: string, value?: number) => {
+ if (GA_MEASUREMENT_ID) {
+ ReactGA.event({
+ category,
+ action,
+ label,
+ value,
+ });
+ }
+};
+
+export const trackPackOpening = (packName: string, cost: number) => {
+ trackEvent("Game", "Pack Opened", packName, cost);
+};
+
+export const trackUpgrade = (upgradeType: string, level: number, cost: number) => {
+ trackEvent("Game", "Upgrade Purchased", `${upgradeType} - Level ${level}`, cost);
+};
+
+export const trackDimensionStart = (level: number) => {
+ trackEvent("Game", "Dimension Started", `Level ${level}`);
+};
+
+export const trackDimensionEnd = (level: number, reward: number) => {
+ trackEvent("Game", "Dimension Ended", `Reached Level ${level}`, reward);
+};
diff --git a/src/store/gameStore.ts b/src/store/gameStore.ts
index 11e4e8e..92940d4 100644
--- a/src/store/gameStore.ts
+++ b/src/store/gameStore.ts
@@ -10,6 +10,12 @@ import {
Character,
GameState as BaseGameState,
} from "@/types/game";
+import {
+ trackPackOpening,
+ trackUpgrade,
+ trackDimensionStart,
+ trackDimensionEnd,
+} from "@/lib/analytics";
interface GameState extends BaseGameState {
addCard: (card: GameCard) => boolean;
@@ -191,6 +197,8 @@ export const useGameStore = create()(
seeds: state.seeds - pack.cost,
}));
+ trackPackOpening(pack.name, pack.cost);
+
return newCards;
},
@@ -230,6 +238,7 @@ export const useGameStore = create()(
dimensionLevel: 1,
currentEnemy: enemy,
}));
+ trackDimensionStart(1);
return true;
},
@@ -268,6 +277,8 @@ export const useGameStore = create()(
},
resetDimension: (reward) => {
+ const { dimensionLevel } = get();
+ trackDimensionEnd(dimensionLevel, reward);
set((s) => ({
seeds: s.seeds + reward,
isDimensionActive: false,
@@ -296,13 +307,17 @@ export const useGameStore = create()(
if (seeds < cost) return false;
- set((s) => ({
- seeds: s.seeds - cost,
- upgrades: {
- ...s.upgrades,
- [type]: s.upgrades[type] + 1,
- },
- }));
+ set((s) => {
+ const newLevel = s.upgrades[type] + 1;
+ trackUpgrade(type, newLevel, cost);
+ return {
+ seeds: s.seeds - cost,
+ upgrades: {
+ ...s.upgrades,
+ [type]: newLevel,
+ },
+ };
+ });
return true;
},