Skip to content

Commit

Permalink
Features/google analytics (#471)
Browse files Browse the repository at this point in the history
* added google analystics
tracks user events across the site (organisation registed, user logged in etc)

* implementation of GA, app icons for PWA
  • Loading branch information
jasondicker authored Feb 8, 2024
1 parent ff45ef1 commit a828ec2
Show file tree
Hide file tree
Showing 36 changed files with 443 additions and 48 deletions.
3 changes: 3 additions & 0 deletions src/web/.env.example
Original file line number Diff line number Diff line change
Expand Up @@ -28,3 +28,6 @@ NEXT_PUBLIC_API_BASE_URL=http://localhost:5000/api/v3
NEXT_PUBLIC_ENVIRONMENT=local
NEXT_PUBLIC_GOOGLE_MAPS_API_KEY=secret
NEXT_PUBLIC_COOKIE_DOMAIN=localhost

# Google Analytics
NEXT_PUBLIC_GA_MEASUREMENT_ID=
1 change: 1 addition & 0 deletions src/web/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ ARG NEXT_PUBLIC_GOOGLE_MAPS_API_KEY
ENV NEXT_PUBLIC_GOOGLE_MAPS_API_KEY=${NEXT_PUBLIC_GOOGLE_MAPS_API_KEY}
ARG NEXT_PUBLIC_COOKIE_DOMAIN
ENV NEXT_PUBLIC_COOKIE_DOMAIN=${NEXT_PUBLIC_COOKIE_DOMAIN}
ARG NEXT_PUBLIC_GA_MEASUREMENT_ID=${NEXT_PUBLIC_GA_MEASUREMENT_ID}
ARG SENTRY_AUTH_TOKEN
ENV SENTRY_AUTH_TOKEN=${SENTRY_AUTH_TOKEN}

Expand Down
1 change: 1 addition & 0 deletions src/web/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
"react": "^18.2.0",
"react-datepicker": "^4.23.0",
"react-dom": "^18.2.0",
"react-ga4": "^2.1.0",
"react-hook-form": "^7.48.2",
"react-icons": "^4.12.0",
"react-modal": "^3.16.1",
Expand Down
Binary file removed src/web/public/icon-192x192.png
Binary file not shown.
Binary file removed src/web/public/icon-256x256.png
Binary file not shown.
Binary file removed src/web/public/icon-384x384.png
Binary file not shown.
Binary file removed src/web/public/icon-512x512.png
Binary file not shown.
8 changes: 4 additions & 4 deletions src/web/public/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,22 +9,22 @@
"description": "The Yoma platform enables you to build and transform your future by unlocking your hidden potential. Make a difference, earn rewards and build your CV by taking part in our impact challenges.",
"icons": [
{
"src": "/icon-192x192.png",
"src": "yoma-icon-192.png",
"sizes": "192x192",
"type": "image/png"
},
{
"src": "/icon-256x256.png",
"src": "yoma-icon-256.png",
"sizes": "256x256",
"type": "image/png"
},
{
"src": "/icon-384x384.png",
"src": "yoma-icon-384.png",
"sizes": "384x384",
"type": "image/png"
},
{
"src": "/icon-512x512.png",
"src": "yoma-icon-512.png",
"sizes": "512x512",
"type": "image/png"
}
Expand Down
11 changes: 11 additions & 0 deletions src/web/public/scripts/lang-config.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,14 @@ window.__GOOGLE_TRANSLATION_CONFIG__ = {
],
defaultLanguage: "en",
};

// window.__GOOGLE_TRANSLATION_CONFIG__ = {
// ...__GOOGLE_TRANSLATION_CONFIG__,
// apiKey: "YOUR_API_KEY_HERE",
// languages: [
// { title: "English", name: "en" },
// { title: "Español", name: "es" },
// { title: "Français", name: "fr" },
// ],
// defaultLanguage: "en",
// };
Binary file added src/web/public/yoma-icon-192.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added src/web/public/yoma-icon-256.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added src/web/public/yoma-icon-384.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added src/web/public/yoma-icon-512.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
66 changes: 44 additions & 22 deletions src/web/src/components/Global.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,13 @@ import { useRouter } from "next/router";
import { useCallback, useEffect, useState } from "react";
import { getOrganisationById } from "~/api/services/organisations";
import { getUserProfile, patchYoIDOnboarding } from "~/api/services/user";
import { ROLE_ADMIN, ROLE_ORG_ADMIN } from "~/lib/constants";
import {
GA_ACTION_USER_LOGIN_AFTER,
GA_ACTION_USER_YOIDONBOARDINGCONFIRMED,
GA_CATEGORY_USER,
ROLE_ADMIN,
ROLE_ORG_ADMIN,
} from "~/lib/constants";
import {
RoleView,
activeNavigationRoleViewAtom,
Expand All @@ -20,6 +26,7 @@ import iconBell from "public/images/icon-bell.webp";
import Image from "next/image";
import { toast } from "react-toastify";
import { ApiErrors } from "./Status/ApiErrors";
import { trackGAEvent } from "~/lib/google-analytics";

// * GLOBAL APP CONCERNS
// * needs to be done here as jotai atoms are not available in _app.tsx
Expand All @@ -45,25 +52,28 @@ export const Global: React.FC = () => {

// 🔔 USER PROFILE
useEffect(() => {
if (!userProfile) {
getUserProfile()
.then((res) => {
setUserProfile(res);

// show onboarding dialog if not onboarded
if (!res.yoIDOnboarded) {
setOnboardingDialogVisible(true);
}
})
.catch((e) => console.error(e));
}
}, [
router,
session,
userProfile,
setUserProfile,
setOnboardingDialogVisible,
]);
// skip if not logged in or userProfile atom already set (atomWithStorage)
if (!session || userProfile) return;

getUserProfile()
.then((res) => {
// update atom
setUserProfile(res);

// 📊 GOOGLE ANALYTICS: track event
trackGAEvent(
GA_CATEGORY_USER,
GA_ACTION_USER_LOGIN_AFTER,
"User logged in",
);

// show onboarding dialog if not onboarded
if (!res.yoIDOnboarded) {
setOnboardingDialogVisible(true);
}
})
.catch((e) => console.error(e));
}, [session, userProfile, setUserProfile, setOnboardingDialogVisible]);

// 🔔 SMALL DISPLAY
// track the screen size for responsive elements
Expand Down Expand Up @@ -151,13 +161,25 @@ export const Global: React.FC = () => {
currentOrganisationIdValue,
]);

const onClickOk = useCallback(async () => {
// 🔔 CLICK HANDLER: ONBOARDING DIALOG CONFIRMATION
const onClickYoIDOnboardingConfirm = useCallback(async () => {
try {
toast.dismiss();

// update API
const userProfile = await patchYoIDOnboarding();

// 📊 GOOGLE ANALYTICS: track event
trackGAEvent(
GA_CATEGORY_USER,
GA_ACTION_USER_YOIDONBOARDINGCONFIRMED,
`User confirmed YoID onboarding message at ${new Date().toISOString()}`,
);

// update ATOM
setUserProfile(userProfile);

// hide popup
setOnboardingDialogVisible(false);
} catch (error) {
console.error(error);
Expand Down Expand Up @@ -205,7 +227,7 @@ export const Global: React.FC = () => {
type="button"
className="btn rounded-full bg-green normal-case text-white hover:bg-green-dark md:w-[250px]"
//className="btn rounded-full border-purple bg-white normal-case text-purple md:w-[300px]"
onClick={onClickOk}
onClick={onClickYoIDOnboardingConfirm}
>
<IoMdThumbsUp className="h-5 w-5 text-white" />

Expand Down
19 changes: 19 additions & 0 deletions src/web/src/components/GoogleAnalytics.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
declare global {
interface Window {
GA_INITIALIZED: boolean;
}
}

import { useEffect } from "react";
import initializeGA from "~/lib/google-analytics";

export const GoogleAnalytics: React.FC = () => {
useEffect(() => {
if (!window.GA_INITIALIZED) {
initializeGA();
window.GA_INITIALIZED = true;
}
}, []);

return null; // Replace 'void' with 'null' or add your desired JSX here
};
8 changes: 8 additions & 0 deletions src/web/src/components/NavBar/LanguageSwitcher.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,11 @@ import { destroyCookie, parseCookies, setCookie } from "nookies";
import { IoMdGlobe } from "react-icons/io";
import { useSetAtom } from "jotai";
import { currentLanguageAtom } from "~/lib/store";
import {
GA_ACTION_USER_LANGUAGE_CHANGE,
GA_CATEGORY_USER,
} from "~/lib/constants";
import { trackGAEvent } from "~/lib/google-analytics";

// The following cookie name is important because it's Google-predefined for the translation engine purpose
const COOKIE_NAME = "googtrans";
Expand Down Expand Up @@ -80,6 +85,9 @@ const LanguageSwitcher = () => {
domain: process.env.NEXT_PUBLIC_COOKIE_DOMAIN,
});

// 📊 GOOGLE ANALYTICS: track event
trackGAEvent(GA_CATEGORY_USER, GA_ACTION_USER_LANGUAGE_CHANGE, lang);

window.location.reload();
};

Expand Down
9 changes: 9 additions & 0 deletions src/web/src/components/NavBar/SignInButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ import { useAtomValue } from "jotai";
import { signIn } from "next-auth/react";
import React, { useCallback, useState } from "react";
import { IoMdFingerPrint } from "react-icons/io";
import { GA_ACTION_USER_LOGIN_BEFORE, GA_CATEGORY_USER } from "~/lib/constants";
import { trackGAEvent } from "~/lib/google-analytics";
import { currentLanguageAtom } from "~/lib/store";
import { fetchClientEnv } from "~/lib/utils";

Expand All @@ -14,6 +16,13 @@ export const SignInButton: React.FC<{ className?: string }> = ({
const handleLogin = useCallback(async () => {
setIsButtonLoading(true);

// 📊 GOOGLE ANALYTICS: track event
trackGAEvent(
GA_CATEGORY_USER,
GA_ACTION_USER_LOGIN_BEFORE,
"User Logging In. Redirected to External Authentication Provider",
);

// eslint-disable-next-line @typescript-eslint/no-floating-promises
signIn(
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
Expand Down
23 changes: 18 additions & 5 deletions src/web/src/components/NavBar/UserMenu.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { useAtomValue } from "jotai";
import { useAtomValue, useSetAtom } from "jotai";
import { signOut, useSession } from "next-auth/react";
import Image from "next/image";
import Link from "next/link";
import { useState } from "react";
import { useCallback, useState } from "react";
import {
IoMdAdd,
IoMdCard,
Expand All @@ -15,7 +15,12 @@ import {
} from "react-icons/io";
import ReactModal from "react-modal";
import { type OrganizationInfo } from "~/api/models/user";
import { ROLE_ADMIN } from "~/lib/constants";
import {
GA_ACTION_USER_LOGOUT,
GA_CATEGORY_USER,
ROLE_ADMIN,
} from "~/lib/constants";
import { trackGAEvent } from "~/lib/google-analytics";
import { shimmer, toBase64 } from "~/lib/image";
import {
RoleView,
Expand All @@ -27,18 +32,26 @@ import {
export const UserMenu: React.FC = () => {
const [userMenuVisible, setUserMenuVisible] = useState(false);
const userProfile = useAtomValue(userProfileAtom);
const setUserProfile = useSetAtom(userProfileAtom);
const activeRoleView = useAtomValue(activeNavigationRoleViewAtom);
const currentOrganisationLogo = useAtomValue(currentOrganisationLogoAtom);
const { data: session } = useSession();
const isAdmin = session?.user?.roles.includes(ROLE_ADMIN);

const handleLogout = () => {
const handleLogout = useCallback(() => {
setUserMenuVisible(false);

// update atom
setUserProfile(null);

// 📊 GOOGLE ANALYTICS: track event
trackGAEvent(GA_CATEGORY_USER, GA_ACTION_USER_LOGOUT, "User logged out");

// signout from keycloak
signOut({
callbackUrl: `${window.location.origin}/`,
}); // eslint-disable-line @typescript-eslint/no-floating-promises
};
}, [setUserProfile]);

const renderOrganisationMenuItem = (organisation: OrganizationInfo) => {
return (
Expand Down
2 changes: 2 additions & 0 deletions src/web/src/env.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ export const env = createEnv({
NEXT_PUBLIC_ENVIRONMENT: z.string(),
NEXT_PUBLIC_GOOGLE_MAPS_API_KEY: z.string(),
NEXT_PUBLIC_COOKIE_DOMAIN: z.string(),
NEXT_PUBLIC_GA_MEASUREMENT_ID: z.string().optional(),
},

/**
Expand All @@ -53,6 +54,7 @@ export const env = createEnv({
NEXT_PUBLIC_GOOGLE_MAPS_API_KEY:
process.env.NEXT_PUBLIC_GOOGLE_MAPS_API_KEY,
NEXT_PUBLIC_COOKIE_DOMAIN: process.env.NEXT_PUBLIC_COOKIE_DOMAIN,
NEXT_PUBLIC_GA_MEASUREMENT_ID: process.env.NEXT_PUBLIC_GA_MEASUREMENT_ID,
},
/**
* Run `build` or `dev` with `SKIP_ENV_VALIDATION` to skip env validation.
Expand Down
Loading

0 comments on commit a828ec2

Please sign in to comment.