Skip to content

Commit

Permalink
feat(GoogleAnalytics): added react-ga (#68)
Browse files Browse the repository at this point in the history
* feat(GoogleAnalytics): added react-ga

* add events

* add events

---------

Co-authored-by: Daniel Dobkowski <[email protected]>
  • Loading branch information
dobeck and Daniel Dobkowski authored Dec 16, 2024
1 parent aa5fc74 commit 626aad8
Show file tree
Hide file tree
Showing 20 changed files with 182 additions and 23 deletions.
11 changes: 8 additions & 3 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
31 changes: 17 additions & 14 deletions src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand All @@ -15,20 +16,22 @@ import { AppLayoutWrapper } from "./components/layout/AppLayoutWrapper";
function App() {
return (
<RecoilRoot>
<Router>
<ApolloProvider client={apolloClient}>
<ThemeWrapper>
<GlobalStyles />
<AppLayoutWrapper>
<Header />
<PageSwitcher>
<StateModal />
</PageSwitcher>
<Footer />
</AppLayoutWrapper>
</ThemeWrapper>
</ApolloProvider>
</Router>
<AnalyticsWrapper>
<Router>
<ApolloProvider client={apolloClient}>
<ThemeWrapper>
<GlobalStyles />
<AppLayoutWrapper>
<Header />
<PageSwitcher>
<StateModal />
</PageSwitcher>
<Footer />
</AppLayoutWrapper>
</ThemeWrapper>
</ApolloProvider>
</Router>
</AnalyticsWrapper>
</RecoilRoot>
);
}
Expand Down
9 changes: 9 additions & 0 deletions src/features/analytics/AnalyticsWrapper.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import React, { PropsWithChildren } from "react";
import { AnalyticsContext } from "./contexts/AnalyticsContext";
import { useGoogleAnalytics } from "./hooks";

export const AnalyticsWrapper: React.FC<PropsWithChildren> = ({ children }) => {
const googleAnalytics = useGoogleAnalytics();

return <AnalyticsContext.Provider value={googleAnalytics}>{children}</AnalyticsContext.Provider>;
};
4 changes: 4 additions & 0 deletions src/features/analytics/contexts/AnalyticsContext.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
import React from "react";
import { UseGoogleAnalyticsReturn } from "../types";

export const AnalyticsContext = React.createContext({} as UseGoogleAnalyticsReturn);
1 change: 1 addition & 0 deletions src/features/analytics/hooks/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { useGoogleAnalytics } from "./useGoogleAnalytics";
29 changes: 29 additions & 0 deletions src/features/analytics/hooks/useGoogleAnalytics.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { useState } from "react";
import ReactGA from "react-ga4";
import { GoogleAnalyticsEvent, UseGoogleAnalytics } from "../types";

export const useGoogleAnalytics: UseGoogleAnalytics = () => {
const [trackingId, setTrackingId] = useState<string>("");

const init = (id: string) => {
if (!id) return;
ReactGA.initialize(id);
setTrackingId(id);
};

const sendEvent = (event: GoogleAnalyticsEvent) => {
if (!trackingId) return;
ReactGA.event(event);
};

const send = (path: any) => {
if (!trackingId) return;
ReactGA.send(path);
};

return {
init,
sendEvent,
send,
};
};
1 change: 1 addition & 0 deletions src/features/analytics/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { AnalyticsWrapper } from "./AnalyticsWrapper";
14 changes: 14 additions & 0 deletions src/features/analytics/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
export interface UseGoogleAnalyticsReturn {
init: (trackingId: string) => void;
sendEvent: (event: GoogleAnalyticsEvent) => void;
send: (path: any) => void;
}

export type UseGoogleAnalytics = () => UseGoogleAnalyticsReturn;

export interface GoogleAnalyticsEvent {
category: string;
action: string;
label?: string;
value?: number;
}
Original file line number Diff line number Diff line change
@@ -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";
Expand All @@ -16,6 +18,7 @@ const BackToServiceButton = () => {
const service = useRecoilValue(serviceAtom);
const [searchParams] = useSearchParams();
const urlSearchParams = Object.fromEntries(searchParams.entries());
const { sendEvent } = useContext(AnalyticsContext);

if (service === undefined) return null;

Expand All @@ -33,6 +36,11 @@ const BackToServiceButton = () => {
},
}),
);
sendEvent({
category: "navigation",
action: "Back To Service Button",
label: t("go-back"),
});
}}
>
<Typography typographyType="body" align="center" as="span" color="inherit" weight="700">
Expand Down
Original file line number Diff line number Diff line change
@@ -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 { useDeleteBooking } from "features/booking/hooks/useDeleteBooking";
import useConfirmation from "features/confirmation/hooks/useConfirmation";
import { useTranslation } from "react-i18next";
Expand All @@ -10,6 +12,7 @@ const CancelBookingButton = () => {
const { t } = useTranslation(["booking"]);
const deleteBooking = useDeleteBooking();
const bookingValue = useRecoilValue(bookingAtom);
const { sendEvent } = useContext(AnalyticsContext);

const showConfirmation = useConfirmation({
type: "booking/delete",
Expand All @@ -19,7 +22,14 @@ const CancelBookingButton = () => {
},
});

const handleDelete = () => showConfirmation();
const handleDelete = () => {
showConfirmation();
sendEvent({
category: "navigation",
action: "Cancel Booking Button",
label: t("cancel-booking"),
});
};

if (bookingValue === undefined) return null;

Expand Down
Original file line number Diff line number Diff line change
@@ -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 { BOOKING_FORM_TYPES } from "models/service";
Expand All @@ -17,6 +19,7 @@ export const RescheduleBookingButton = () => {
const service = useRecoilValue(serviceAtom);
const [searchParams] = useSearchParams();
const urlSearchParams = Object.fromEntries(searchParams.entries());
const { sendEvent } = useContext(AnalyticsContext);

if (bookingValue === undefined || service?.viewConfig?.displayType === BOOKING_FORM_TYPES.PREORDER) return null;

Expand All @@ -33,6 +36,11 @@ export const RescheduleBookingButton = () => {
},
}),
);
sendEvent({
category: "navigation",
action: "Reschedule Booking Button",
label: t("reschedule"),
});
}}
>
<Typography typographyType="body" align="center" as="span" color="inherit" weight="700">
Expand Down
1 change: 1 addition & 0 deletions src/features/service/api/queries/models.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ export interface ServiceQueryResult {
theme: "DARK" | "LIGHT";
defaultLocale: string;
localTimeZone: string;
googleTagId: string | null;
};
title: string;
description: string;
Expand Down
1 change: 1 addition & 0 deletions src/features/service/api/queries/queries.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ export const GET_SERVICE = gql`
theme
defaultLocale
localTimeZone
googleTagId
}
title
description
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import React, { useCallback, useMemo } from "react";
import React, { useCallback, useContext, useMemo } from "react";
import { Button } from "components/Button";
import { Card } from "components/Card";
import { Typography } from "components/Typography";
import { Box } from "components/layout/Box";
import { Column } from "components/layout/Column";
import { AnalyticsContext } from "features/analytics/contexts/AnalyticsContext";
import { useBookDateRange } from "features/service/hooks/useBookDateRange";
import { useBookSlot } from "features/service/hooks/useBookSlot";
import { Form, Formik } from "formik";
Expand Down Expand Up @@ -110,6 +111,7 @@ const BookService = () => {
const [, setSelectedSlots] = useRecoilState(selectedSlots);
const slots = useRecoilValue(slotsAtom)!;
const locations = useRecoilValue(locationAtom);
const { sendEvent } = useContext(AnalyticsContext);

const isUploading = Object.values(uploadState).filter((item) => item.isLoading).length > 0;

Expand Down Expand Up @@ -205,6 +207,11 @@ const BookService = () => {
locations: locations ? [locations] : [],
},
}).then(() => setSelectedSlots([]));
sendEvent({
action: "book_slot",
category: "Book Service",
label: `book slot ${BOOKING_FORM_TYPES.DAYS}`,
});
} else if (
serviceType === BOOKING_FORM_TYPES.CALENDAR &&
selectedDateRangeValue.dateTimeFrom !== null &&
Expand All @@ -223,6 +230,11 @@ const BookService = () => {
locations: locations ? [locations] : [],
},
});
sendEvent({
action: "book_slot",
category: "Book Service",
label: `book slot ${BOOKING_FORM_TYPES.CALENDAR}`,
});
} else if (
(serviceType === BOOKING_FORM_TYPES.LIST || serviceType === BOOKING_FORM_TYPES.MULTILIST) &&
selectedSlotsValue.length
Expand All @@ -240,6 +252,11 @@ const BookService = () => {
locations: locations ? [locations] : [],
},
}).then(() => setSelectedSlots([]));
sendEvent({
action: "book_slot",
category: "Book Service",
label: `book slot ${BOOKING_FORM_TYPES.LIST}`,
});
} else if (serviceType === BOOKING_FORM_TYPES.PREORDER) {
bookDateRangeMutation({
variables: {
Expand All @@ -255,6 +272,11 @@ const BookService = () => {
locations: locations ? [locations] : [],
},
});
sendEvent({
action: "book_slot",
category: "Book Service",
label: `book slot ${BOOKING_FORM_TYPES.PREORDER}`,
});
}
};

Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import React from "react";
import React, { useContext } from "react";
import { Typography } from "components/Typography";
import { Row } from "components/layout/Row";
import { AnalyticsContext } from "features/analytics/contexts/AnalyticsContext";
import { useRecoilValue, useSetRecoilState } from "recoil";
import { hoursSystemAtom } from "state/atoms";
import styled, { css } from "styled-components";
Expand Down Expand Up @@ -31,11 +32,18 @@ const HoursSystemButton = styled(Typography)<{ isBold: boolean }>`
export const HoursSystem = () => {
const hoursSystem = useRecoilValue(hoursSystemAtom);
const setHoursSystem = useSetRecoilState(hoursSystemAtom);
const { sendEvent } = useContext(AnalyticsContext);

const handleHoursSystemChange = () => {
const newHoursSystem = hoursSystem === HOURS_SYSTEMS.h12 ? HOURS_SYSTEMS.h24 : HOURS_SYSTEMS.h12;
localStorage.setItem("HOURS_SYSTEM", newHoursSystem);
setHoursSystem(newHoursSystem);
sendEvent({
category: "Hours System",
action: "Change",
label: "Change Hours System",
value: newHoursSystem === HOURS_SYSTEMS.h12 ? 12 : 24,
});
};

return (
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import React, { useCallback, useMemo } from "react";
import React, { useCallback, useContext, useMemo } from "react";
import { Button } from "components/Button";
import { Card } from "components/Card";
import { Typography } from "components/Typography";
import { Box } from "components/layout/Box";
import { AnalyticsContext } from "features/analytics/contexts/AnalyticsContext";
import { useRescheduleBooking } from "features/service/hooks/useRescheduleBooking";
import { getPath, getServiceConfigByType } from "helpers/functions";
import { useIsEmbeddedPage } from "helpers/hooks/useIsEmbeddedPage";
Expand Down Expand Up @@ -58,6 +59,7 @@ const RescheduleService = () => {
const { PAGES } = useIsEmbeddedPage();
const [searchParams] = useSearchParams();
const urlSearchParams = Object.fromEntries(searchParams.entries());
const { sendEvent } = useContext(AnalyticsContext);

const serviceType = service?.viewConfig.displayType;

Expand Down Expand Up @@ -99,6 +101,11 @@ const RescheduleService = () => {
slots: selectedSlotsValue,
},
});
sendEvent({
category: "reschedule_slot",
action: "Reschedule Service",
label: "Reschedule",
});
} else if (isDateRangeType) {
if (!selectedSlotsValue.length || bookingValue?.bookingId === undefined) return;
rescheduleBookingMutation({
Expand All @@ -107,6 +114,11 @@ const RescheduleService = () => {
slots: selectedSlotsValue,
},
});
sendEvent({
category: "reschedule_slot",
action: "Reschedule Service",
label: "Reschedule",
});
} else if (isEventType) {
if (!selectedSlotsValue.length || bookingValue?.bookingId === undefined) return;
rescheduleBookingMutation({
Expand All @@ -115,6 +127,11 @@ const RescheduleService = () => {
slots: selectedSlotsValue,
},
});
sendEvent({
category: "reschedule_slot",
action: "Reschedule Service",
label: "Reschedule",
});
}
};

Expand Down
Loading

0 comments on commit 626aad8

Please sign in to comment.