diff --git a/package-lock.json b/package-lock.json
index 8039180..44edbf1 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -5789,9 +5789,9 @@
}
},
"caniuse-lite": {
- "version": "1.0.30001680",
- "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001680.tgz",
- "integrity": "sha512-rPQy70G6AGUMnbwS1z6Xg+RkHYPAi18ihs47GH0jcxIG7wArmPgY3XbS2sRdBbxJljp3thdT8BIqv9ccCypiPA==",
+ "version": "1.0.30001687",
+ "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001687.tgz",
+ "integrity": "sha512-0S/FDhf4ZiqrTUiQ39dKeUjYRjkv7lOZU1Dgif2rIqrTzX/1wV2hfKu9TOm1IHkdSijfLswxTFzl/cvir+SLSQ==",
"dev": true
},
"case-sensitive-paths-webpack-plugin": {
@@ -14635,6 +14635,11 @@
"resolved": "https://registry.npmjs.org/react-fast-compare/-/react-fast-compare-2.0.4.tgz",
"integrity": "sha512-suNP+J1VU1MWFKcyt7RtjiSWUjvidmQSlqu+eHslq+342xCbGTYmC0mEhPCOHxlW0CywylOC1u2DFAT+bv4dBw=="
},
+ "react-ga4": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/react-ga4/-/react-ga4-2.1.0.tgz",
+ "integrity": "sha512-ZKS7PGNFqqMd3PJ6+C2Jtz/o1iU9ggiy8Y8nUeksgVuvNISbmrQtJiZNvC/TjDsqD0QlU5Wkgs7i+w9+OjHhhQ=="
+ },
"react-helmet-async": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/react-helmet-async/-/react-helmet-async-1.3.0.tgz",
diff --git a/package.json b/package.json
index 1166b75..8c51519 100644
--- a/package.json
+++ b/package.json
@@ -27,6 +27,7 @@
"react-dates": "^21.8.0",
"react-dates-gte-react-17-21.8.0-version-fixed": "^21.8.0",
"react-dom": "^18.2.0",
+ "react-ga4": "^2.1.0",
"react-helmet-async": "^1.3.0",
"react-i18next": "^11.17.2",
"react-markdown": "^8.0.0",
diff --git a/src/App.tsx b/src/App.tsx
index 091adf1..f432461 100644
--- a/src/App.tsx
+++ b/src/App.tsx
@@ -2,6 +2,7 @@ import React from "react";
import { apolloClient } from "api/apolloClient";
import Footer from "components/Footer";
import Header from "components/Header";
+import { AnalyticsWrapper } from "features/analytics";
import StateModal from "features/confirmation/components/StateModal";
import "features/i18n";
import ThemeWrapper from "features/theme/components/ThemeWrapper";
@@ -15,20 +16,22 @@ import { AppLayoutWrapper } from "./components/layout/AppLayoutWrapper";
function App() {
return (
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
);
}
diff --git a/src/features/analytics/AnalyticsWrapper.tsx b/src/features/analytics/AnalyticsWrapper.tsx
new file mode 100644
index 0000000..66ed3e0
--- /dev/null
+++ b/src/features/analytics/AnalyticsWrapper.tsx
@@ -0,0 +1,9 @@
+import React, { PropsWithChildren } from "react";
+import { AnalyticsContext } from "./contexts/AnalyticsContext";
+import { useGoogleAnalytics } from "./hooks";
+
+export const AnalyticsWrapper: React.FC = ({ children }) => {
+ const googleAnalytics = useGoogleAnalytics();
+
+ return {children};
+};
diff --git a/src/features/analytics/contexts/AnalyticsContext.tsx b/src/features/analytics/contexts/AnalyticsContext.tsx
new file mode 100644
index 0000000..67f1ae6
--- /dev/null
+++ b/src/features/analytics/contexts/AnalyticsContext.tsx
@@ -0,0 +1,4 @@
+import React from "react";
+import { UseGoogleAnalyticsReturn } from "../types";
+
+export const AnalyticsContext = React.createContext({} as UseGoogleAnalyticsReturn);
diff --git a/src/features/analytics/hooks/index.ts b/src/features/analytics/hooks/index.ts
new file mode 100644
index 0000000..2fa7f11
--- /dev/null
+++ b/src/features/analytics/hooks/index.ts
@@ -0,0 +1 @@
+export { useGoogleAnalytics } from "./useGoogleAnalytics";
diff --git a/src/features/analytics/hooks/useGoogleAnalytics.ts b/src/features/analytics/hooks/useGoogleAnalytics.ts
new file mode 100644
index 0000000..035e271
--- /dev/null
+++ b/src/features/analytics/hooks/useGoogleAnalytics.ts
@@ -0,0 +1,38 @@
+import { useState } from "react";
+import ReactGA from "react-ga4";
+import { GoogleAnalyticsEvent, UseGoogleAnalytics } from "../types";
+
+export const useGoogleAnalytics: UseGoogleAnalytics = () => {
+ const [trackingId, setTrackingId] = useState("");
+
+ const init = (id: string) => {
+ if (!id) {
+ throw new Error("Google Analytics tracking ID is required");
+ }
+ ReactGA.initialize(id);
+ setTrackingId(id);
+ console.log(`Google Analytics initialized with tracking ID: ${id}`);
+ };
+
+ const sendEvent = (event: GoogleAnalyticsEvent) => {
+ if (!trackingId) {
+ throw new Error("Google Analytics is not initialized");
+ }
+ ReactGA.event(event);
+ console.log(`Google Analytics event sent: ${JSON.stringify(event)}`);
+ };
+
+ const send = (path: any) => {
+ if (!trackingId) {
+ throw new Error("Google Analytics is not initialized");
+ }
+ ReactGA.send(path);
+ console.log(`Google Analytics pageview sent: ${path}`);
+ };
+
+ return {
+ init,
+ sendEvent,
+ send,
+ };
+};
diff --git a/src/features/analytics/index.ts b/src/features/analytics/index.ts
new file mode 100644
index 0000000..ce37b5f
--- /dev/null
+++ b/src/features/analytics/index.ts
@@ -0,0 +1 @@
+export { AnalyticsWrapper } from "./AnalyticsWrapper";
diff --git a/src/features/analytics/types.ts b/src/features/analytics/types.ts
new file mode 100644
index 0000000..562b65d
--- /dev/null
+++ b/src/features/analytics/types.ts
@@ -0,0 +1,14 @@
+export interface UseGoogleAnalyticsReturn {
+ init: (trackingId: string) => void;
+ sendEvent: (event: any) => void;
+ send: (fieldObject: any) => void;
+}
+
+export type UseGoogleAnalytics = () => UseGoogleAnalyticsReturn;
+
+export interface GoogleAnalyticsEvent {
+ category: string;
+ action: string;
+ label?: string;
+ value?: number;
+}
diff --git a/src/features/booking/components/BookingCard/BookingCardBottom/BackToServiceButton.tsx b/src/features/booking/components/BookingCard/BookingCardBottom/BackToServiceButton.tsx
index a0bccc5..3c09972 100644
--- a/src/features/booking/components/BookingCard/BookingCardBottom/BackToServiceButton.tsx
+++ b/src/features/booking/components/BookingCard/BookingCardBottom/BackToServiceButton.tsx
@@ -1,5 +1,7 @@
+import { useContext } from "react";
import { ContextButton } from "components/ContextButton";
import { Typography } from "components/Typography";
+import { AnalyticsContext } from "features/analytics/contexts/AnalyticsContext";
import { getPath } from "helpers/functions";
import { useIsEmbeddedPage } from "helpers/hooks/useIsEmbeddedPage";
import { useTranslation } from "react-i18next";
@@ -16,6 +18,7 @@ const BackToServiceButton = () => {
const service = useRecoilValue(serviceAtom);
const [searchParams] = useSearchParams();
const urlSearchParams = Object.fromEntries(searchParams.entries());
+ const { send } = useContext(AnalyticsContext);
if (service === undefined) return null;
@@ -33,6 +36,15 @@ const BackToServiceButton = () => {
},
}),
);
+ send({
+ path: getPath({
+ url: `${PAGES.SERVICE}:query`,
+ params: {
+ id: service.serviceId,
+ query: `?${createSearchParams(urlSearchParams).toString()}`,
+ },
+ }),
+ });
}}
>
diff --git a/src/features/service/components/ServiceHeaders.tsx b/src/features/service/components/ServiceHeaders.tsx
index b94ca09..3d1ff64 100644
--- a/src/features/service/components/ServiceHeaders.tsx
+++ b/src/features/service/components/ServiceHeaders.tsx
@@ -1,4 +1,5 @@
-import React from "react";
+import React, { useContext, useEffect } from "react";
+import { AnalyticsContext } from "features/analytics/contexts/AnalyticsContext";
import { Helmet, HelmetProvider } from "react-helmet-async";
import { useRecoilValue } from "recoil";
import { serviceAtom } from "state/atoms/service";
@@ -7,6 +8,11 @@ import { headerSelector } from "state/selectors/headerSelector";
const ServiceHeaders = () => {
const icon = useRecoilValue(headerSelector)?.logoUrl;
const service = useRecoilValue(serviceAtom);
+ const { init } = useContext(AnalyticsContext);
+
+ useEffect(() => {
+ init("G-1624QD7NDN");
+ }, [init]);
if (service === undefined) return null;