Skip to content

Commit

Permalink
Merge pull request #85 from middlewarehq/feat/mixpanel-client-integra…
Browse files Browse the repository at this point in the history
…tion

feat: analytics event setup
  • Loading branch information
shivam-bit authored Dec 11, 2023
2 parents f8b09f6 + 36fbf1b commit 4629acd
Show file tree
Hide file tree
Showing 8 changed files with 188 additions and 6 deletions.
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
"jspdf": "^2.5.1",
"jszip": "^3.10.1",
"mixpanel": "^0.18.0",
"mixpanel-browser": "^2.48.1",
"next": "14.0.3",
"next-auth": "^4.24.5",
"pluralize": "^8.0.0",
Expand All @@ -45,6 +46,7 @@
"@types/archiver": "^6.0.2",
"@types/file-saver": "^2.0.7",
"@types/jest": "^29.5.11",
"@types/mixpanel-browser": "^2.47.5",
"@types/node": "^20",
"@types/pluralize": "^0.0.33",
"@types/ramda": "^0.29.9",
Expand Down
8 changes: 7 additions & 1 deletion src/components/SwiperCarousel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import 'swiper/css/navigation';
import { ShareButton } from './ShareButton';
import { UpdatedImageFile } from '@/types/images';
import { CardTypes, sequence } from '@/types/cards';
import { track } from '@/constants/events';

interface SwiperCarouselProps {
images: UpdatedImageFile[];
Expand All @@ -32,11 +33,13 @@ const SwiperCarousel: React.FC<SwiperCarouselProps> = ({
const handlePrev = useCallback(() => {
if (!sliderRef.current) return;
sliderRef.current?.swiper.slidePrev();
track('PREV_IMAGE_CLICKED');
}, []);

const handleNext = useCallback(() => {
if (!sliderRef.current) return;
sliderRef.current?.swiper.slideNext();
track('NEXT_IMAGE_CLICKED');
}, []);

return (
Expand Down Expand Up @@ -86,7 +89,10 @@ const SwiperCarousel: React.FC<SwiperCarouselProps> = ({
<SwiperSlide key={index} className="swiper-slide-img">
<ShareButton
className="share-active-image cursor-pointer"
callBack={() => singleImageSharingCallback({ images: image })}
callBack={() => {
singleImageSharingCallback({ images: image });
track('SINGLE_IMAGE_SHARE_CLICKED');
}}
/>
{index !== 0 && (
<IoIosArrowDropleftCircle
Expand Down
60 changes: 60 additions & 0 deletions src/components/TrackingConsent.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import { ALLOW_TRACKING_KEY } from '@/constants/events';
import { useCallback } from 'react';
import toast from 'react-hot-toast';
import { useLocalStorage } from 'usehooks-ts';

export const useTrackingConsent = () => {
const [trackingAllowed, setTrackingAllowed] = useLocalStorage<Boolean | null>(
ALLOW_TRACKING_KEY,
null
);

const updatedTrackingState = useCallback(
(allowed: boolean) => {
toast.dismiss(ALLOW_TRACKING_KEY);
setTrackingAllowed(allowed);
},
[setTrackingAllowed]
);

return useCallback(() => {
if (trackingAllowed || trackingAllowed !== null) return;
return toast.custom(
(t) => {
return (
<div
className={`${
t.visible ? 'animate-enter' : 'animate-leave'
} max-w-3xl w-full bg-white shadow-lg rounded-lg pointer-events-auto flex flex-col md:flex-row ring-1 ring-black ring-opacity-5 `}
style={{ backgroundColor: '#16161a' }}
>
<div className="flex-1 p-4">
This website uses analytics to ensure you get the best experience.
</div>
<div className="flex gap-5 items-center cursor-pointer p-2 justify-around">
<button
className="text-indigo-600 text-sm focus:outline-none hover:underline"
style={{ color: '#7f5af0' }}
onClick={() => updatedTrackingState(false)}
>
Decline
</button>
<button
className="bg-indigo-500 px-2 py-1 text-white rounded-md text-sm hover:bg-indigo-700 focus:outline-none"
style={{ backgroundColor: '#7f5af0' }}
onClick={() => updatedTrackingState(true)}
>
Allow Analytics
</button>
</div>
</div>
);
},
{
duration: 20000,
id: ALLOW_TRACKING_KEY,
position: 'bottom-center'
}
);
}, [trackingAllowed, updatedTrackingState]);
};
29 changes: 29 additions & 0 deletions src/constants/events.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import mixpanel from 'mixpanel-browser';

import { flattenObj } from '@/utils/datatype';
import { objectEnum } from '@/utils/enum';

enum TrackEventEnum {
WINDOW_FOCUS,
WINDOW_BLUR,
WINDOW_UNLOAD,
CREATE_UNWRAPPED_IMAGES_CLICKED,
UNWRAP_YOUR_YEAR_CLICKED,
SIGN_OUT_CLICKED,
ZIP_DOWNLOAD_CLICKED,
PDF_DOWNLOAD_CLICKED,
LINKEDIN_SHARE_CLICKED,
SINGLE_IMAGE_SHARE_CLICKED,
NEXT_IMAGE_CLICKED,
PREV_IMAGE_CLICKED
}

export const ALLOW_TRACKING_KEY = 'ALLOW_TRACKING';

export const TrackEvents = objectEnum(TrackEventEnum);

export const track = (ev: keyof typeof TrackEvents, props: any = {}) => {
const allowTracking = localStorage.getItem(ALLOW_TRACKING_KEY) === 'true';
if (!allowTracking) return;
mixpanel.track(ev, flattenObj(props));
};
42 changes: 42 additions & 0 deletions src/pages/_app.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,53 @@ import { Toaster } from 'react-hot-toast';
import '@/styles/swiper.css';
import { inter } from '@/styles/fonts';
import Head from 'next/head';
import { useRouter } from 'next/router';
import mixpanel from 'mixpanel-browser';
import { useEffect } from 'react';
import { track } from '@/constants/events';

export default function App({
Component,
pageProps: { session, ...pageProps }
}: AppProps) {
const router = useRouter();

useEffect(() => {
const isDev = process.env.NEXT_PUBLIC_APP_ENVIRONMENT === 'development';

mixpanel.init(process.env.NEXT_PUBLIC_MIXPANEL, {
debug: isDev,
api_host: isDev ? undefined : '/api/tunnel/mixpanel',
secure_cookie: true,
ignore_dnt: true
});

const handleRouteChange = (url: string) => {
mixpanel.track(router.pathname, {
url,
pathPattern: router.asPath,
environment: process.env.NEXT_PUBLIC_APP_ENVIRONMENT
});
};

if (router.isReady) handleRouteChange(router.asPath);

const onFocus = () => track('WINDOW_FOCUS');
const onBlur = () => track('WINDOW_BLUR');
const onUnload = () => track('WINDOW_UNLOAD');

window.addEventListener('focus', onFocus);
window.addEventListener('blur', onBlur);
window.addEventListener('beforeunload', onUnload);
router.events.on('routeChangeComplete', handleRouteChange);
return () => {
window.removeEventListener('focus', onFocus);
window.removeEventListener('blur', onBlur);
window.removeEventListener('beforeunload', onUnload);
router.events.off('routeChangeComplete', handleRouteChange);
};
}, [router.asPath, router.events, router.isReady, router.pathname]);

return (
<>
<Head>
Expand Down
24 changes: 22 additions & 2 deletions src/pages/index.tsx
Original file line number Diff line number Diff line change
@@ -1,21 +1,39 @@
import { major } from '@/styles/fonts';
import { signIn, signOut, useSession } from 'next-auth/react';
import { MouseScrollAnim } from '@/components/MouseScrollAnim/MouseScrollAnim';
import { useState } from 'react';
import { useEffect, useState } from 'react';

import LogoSvg from '@/assets/unwrapped-logo.svg';
import { useRouter } from 'next/router';
import { useTrackingConsent } from '@/components/TrackingConsent';
import { track } from '@/constants/events';

/**
* DISABLE_PUBLIC_ONLY_CONTRIBUTIONS
* Because this isn't implemented yet
*/
const DISABLE_PUBLIC_ONLY_CONTRIBUTIONS = true;
const TRACKING_CONSENT_BANNER_DELAY = 2000;

export default function Home() {
const [showPrivate, setShowPrivate] = useState(true);
const { status } = useSession();
const router = useRouter();
const [showPrivate, setShowPrivate] = useState(true);
const [showTrackingBanner, setShowTrackingBanner] = useState(false);
const showTrackingConsent = useTrackingConsent();

useEffect(() => {
if (!showTrackingBanner) return;
showTrackingConsent();
}, [showTrackingBanner, showTrackingConsent]);

useEffect(() => {
const timeoutId = setTimeout(
() => setShowTrackingBanner(true),
TRACKING_CONSENT_BANNER_DELAY
);
return () => clearTimeout(timeoutId);
}, []);

return (
<div className="justify-center w-full flex flex-col gap-4 box-border">
Expand Down Expand Up @@ -56,6 +74,7 @@ export default function Home() {
<button
className="bg-indigo-800 text-white px-4 py-2 rounded-md"
onClick={() => {
track('UNWRAP_YOUR_YEAR_CLICKED');
router.replace('/stats-unwrapped');
}}
>
Expand All @@ -64,6 +83,7 @@ export default function Home() {
<button
className="bg-gray-400 bg-opacity-10 text-white px-4 py-1 rounded-md text-xs"
onClick={() => {
track('SIGN_OUT_CLICKED');
signOut({ redirect: false });
}}
>
Expand Down
19 changes: 16 additions & 3 deletions src/pages/stats-unwrapped.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import Confetti from 'react-confetti';
import Link from 'next/link';
import toast from 'react-hot-toast';
import { UpdatedImageFile } from '@/types/images';
import { track } from '@/constants/events';

const LINKEDIN_URL = 'https://www.linkedin.com/';

Expand Down Expand Up @@ -60,17 +61,29 @@ export default function StatsUnwrapped() {
singleImageSharingCallback={downloadImage}
/>
<div className="flex gap-4 p-3 rounded-lg bg-indigo-900 bg-opacity-60 cursor-pointer">
<FaDownload size={36} onClick={() => downloadImage({ images })} />
<FaDownload
size={36}
onClick={() => {
downloadImage({ images });
track('ZIP_DOWNLOAD_CLICKED');
}}
/>
<Link href={LINKEDIN_URL} target="_blank">
<FaLinkedin
size={36}
onClick={() => downloadImagesAsPdf({ images })}
onClick={() => {
downloadImagesAsPdf({ images });
track('LINKEDIN_SHARE_CLICKED');
}}
/>
</Link>

<FaFilePdf
size={36}
onClick={() => downloadImagesAsPdf({ images })}
onClick={() => {
downloadImagesAsPdf({ images });
track('PDF_DOWNLOAD_CLICKED');
}}
/>
</div>
</div>
Expand Down
10 changes: 10 additions & 0 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -3134,6 +3134,11 @@
resolved "https://registry.yarnpkg.com/@types/minimist/-/minimist-1.2.5.tgz#ec10755e871497bcd83efe927e43ec46e8c0747e"
integrity sha512-hov8bUuiLiyFPGyFPE1lwWhmzYbirOXQNNo40+y3zow8aFVTeyn3VWL0VFFfdNddA8S4Vf0Tc062rzyNr7Paag==

"@types/mixpanel-browser@^2.47.5":
version "2.47.5"
resolved "https://registry.yarnpkg.com/@types/mixpanel-browser/-/mixpanel-browser-2.47.5.tgz#133dc80307884d6dc3277516a7d309be9ddc8bfd"
integrity sha512-qEKrYSN4nUCPLU39YCoF2vszON0KUkPox0mFlrvhdq+amE44CB25+rdfZV4M9n3pEggXuRH+fbdP9qxthvyFUw==

"@types/node@*", "@types/node@^20":
version "20.10.3"
resolved "https://registry.npmjs.org/@types/node/-/node-20.10.3.tgz"
Expand Down Expand Up @@ -11083,6 +11088,11 @@ mixin-deep@^1.2.0:
for-in "^1.0.2"
is-extendable "^1.0.1"

mixpanel-browser@^2.48.1:
version "2.48.1"
resolved "https://registry.yarnpkg.com/mixpanel-browser/-/mixpanel-browser-2.48.1.tgz#0fec03d87f57fe2e72c6a4b1df5924436840ece7"
integrity sha512-vXTuUzZMg+ht7sRqyjtc3dUDy/81Z/H6FLFgFkUZJqKFaAqcx1JSXmOdY/2kmsxCkUdy5JN5zW9m9TMCk+rxGQ==

mixpanel@^0.18.0:
version "0.18.0"
resolved "https://registry.yarnpkg.com/mixpanel/-/mixpanel-0.18.0.tgz#f010f2622902d0d4b434de238446ec8e5966ee32"
Expand Down

0 comments on commit 4629acd

Please sign in to comment.