Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: mixpanel context vars + utm tags #27

Merged
merged 6 commits into from
Mar 12, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 0 additions & 42 deletions lib/components/MixpanelContext.tsx

This file was deleted.

1 change: 0 additions & 1 deletion lib/components/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,2 @@
export { LoadGA4 } from './LoadGA4';
export { LoadTagManager } from './LoadTagManager';
export { MixpanelProvider, useMixpanelContext } from './MixpanelContext';
1 change: 1 addition & 0 deletions lib/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,4 @@ export * from './tagManager';
export * from './GA4';
export * from './constants';
export * from './components';
export * from './mixpanel';
70 changes: 70 additions & 0 deletions lib/mixpanel/context.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
'use client';

import React, { createContext, useContext, useEffect } from 'react';
import { MixpanelEvent as DTO } from './types';
import {
extractUtmParams,
isStandalonePWA,
writeUtmParamsToSessionStorage,
} from './utils.ts';

interface MixpanelContextProps<DTO> {
trackEvent: (event: DTO) => void;
}

interface MixpanelProviderProps<DTO> {
children: React.ReactNode;
eventApiClient: (args: DTO) => Promise<unknown>;
}

const MixpanelContext = createContext<MixpanelContextProps<DTO> | null>(null);

export function useMixpanelContext() {
const context = useContext(MixpanelContext);

if (!context) {
throw new Error('<MixpanelProvider /> not found');
}

return context;
}

export function MixpanelProvider({
children,
eventApiClient,
}: MixpanelProviderProps<DTO>) {
const trackEvent = (event: DTO) => {
// only send events on the client
if (typeof window === 'undefined') {
return;
}

const utmParams = extractUtmParams(window.location.search);

eventApiClient({
...event,
context: {
title: document.title,
href: window.location.href,
path: window.location.pathname,
pwa: isStandalonePWA(),
...utmParams,
...event.context,
},
}).catch((e) => console.error(e));
};

useEffect(() => {
writeUtmParamsToSessionStorage(window.location.search);
}, []);

return (
<MixpanelContext.Provider
value={{
trackEvent,
}}
>
{children}
</MixpanelContext.Provider>
);
}
2 changes: 2 additions & 0 deletions lib/mixpanel/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export { MixpanelProvider, useMixpanelContext } from './context';
export * from './types';
45 changes: 45 additions & 0 deletions lib/mixpanel/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
/**
* @description
* Mixpanel event we will pass this event to our own backend that is used as a proxy for Mixpanel. Almost everything is optional. The only required field is `name`.
* the other properties are suggestions for a "normal" event.
* @example
* const event: MixpanelEvent = {
* name: 'Contact', // e.g. "Update profile", "Add to cart", "Purchase", "Page view"
* context: { // Give some context to the event. Where is it triggered and by who
* title: 'Product Page', // What page is the event triggered on
* path: '/product/123', // Make sure there aren't any personal info in the path
* href: 'https://www.example.com/product/123', // Make sure there aren't any personal info in the href
* route: '/product/:id',
* audience: 'Freelancer', // Who is triggering this event e.g. a role or "new user"
* section: 'footer', // What section is the event triggered in
* utm_source: 'Facebook', // track the source where traffic is coming from, including a website or advertiser
* utm_medium: 'advertising', // track the advertising medium, including email and banner ads
* utm_campaign: 'Black friday', // track the campaign name associated with the traffic
* utm_content: 'cta button', //track the specific link within in an ad that a user clicked
* utm_term: 'tv sale', // track keywords associated with campaigns
* },
* data: { // Any other properties that you want to add to the event
* product_id: '123',
* }
* }
*/
export type MixpanelEvent = {
name: string;
context?: {
title?: string;
path?: string;
href?: string;
route?: string;
audience?: string;
section?: string;
pwa?: boolean;
utm_source?: string;
utm_medium?: string;
utm_campaign?: string;
utm_content?: string;
utm_term?: string;
};
data?: {
[key: string]: unknown;
};
};
49 changes: 49 additions & 0 deletions lib/mixpanel/utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
export const isStandalonePWA = () =>
typeof window !== 'undefined'
? window.matchMedia('(display-mode: standalone)').matches
: false;

export const extractUtmParams = (paramsString: string) => {
const searchParams = new URLSearchParams(paramsString);

return {
utm_source:
searchParams.get('utm_source') ||
sessionStorage.getItem('utm_source') ||
undefined,
utm_medium:
searchParams.get('utm_medium') ||
sessionStorage.getItem('utm_medium') ||
undefined,
utm_campaign:
searchParams.get('utm_campaign') ||
sessionStorage.getItem('utm_campaign') ||
undefined,
utm_content:
searchParams.get('utm_content') ||
sessionStorage.getItem('utm_content') ||
undefined,
utm_term:
searchParams.get('utm_term') ||
sessionStorage.getItem('utm_term') ||
undefined,
};
};

export const writeUtmParamsToSessionStorage = (paramsString: string) => {
const searchParams = new URLSearchParams(paramsString);

const utmSourceKeys = [
'utm_source',
'utm_medium',
'utm_campaign',
'utm_content',
'utm_term',
];

utmSourceKeys.forEach((key) => {
if (searchParams.has(key)) {
sessionStorage.setItem(key, <string>searchParams.get(key));
}
});
};
49 changes: 0 additions & 49 deletions lib/types/trackEventsPropsType.ts
Original file line number Diff line number Diff line change
Expand Up @@ -73,52 +73,3 @@ export type Event =
| SubmitEvent
| CookieSettingsChangedEvent
| GenericEventType;

/**
* @description
* Mixpanel event we will pass this event to our own backend that is used as a proxy for Mixpanel. Almost everything is optional. The only required field is `event`.
* the other properties are suggestions for a "normal" event.
* @example
* const event: MixpanelEvent = {
* event: 'Contact', // e.g. "Update profile", "Add to cart", "Purchase", "Page view"
* properties: { // All the properties that can be used by Mixpanel. Everything is optional. These are suggestions for a "normal" event
* context: { // Give some context to the event. Where is it triggered and by who
* page_title: 'Product Page', // What page is the event triggered on
* page_path: '/product/123', // Make sure there aren't any PII in the path
* page_route: '/product/:id',
* page_href: 'https://www.example.com/product/123', // Make sure there aren't any PII in the href
* audience: 'Freelancer', // Who is triggering this event e.g. a role or "new user"
* section: 'footer', // What section is the event triggered in
* },
* groups: { // To what group does the person triggering the event belong
* 'company': ['tesco', 'sainsburys'],
* 'plan': ['paid', 'premium']
* },
* }
*/
export type MixpanelEvent = {
/** e.g. "Update profile", "Add to cart", "Purchase", "Page view" */
event: string;
/** All the properties that can be used by Mixpanel. Everything is optional. These are suggestions for a "normal" event */
properties: {
/** Give some context to the event. Where is it triggered and by who */
context: {
/** What page is the event triggered on e.g. Product Page */
page_title?: string;
/** What is the path of the page e.g. /product/123 <- Make sure there aren't any PII in the path */
page_path?: string;
/** What is the route of the page e.g. /product/:id */
page_route?: string;
/** What is the href of the page e.g. https://www.example.com/product/123 <- Make sure there aren't any PII in the href */
page_href?: string;
/** Who is triggering this event e.g. a role or "new user" */
audience?: string;
/** What section is the event triggered in e.g. footer or main menu */
section?: string;
};
/** To what group does the person triggering the event belong e.g. { 'company': ['tesco', 'sainsburys'], 'plan': ['paid', 'premium'] } */
groups?: { [key: string]: (string | number)[] };
/** Any other properties that you want to add to the event */
[key: string]: string | number | object | undefined;
};
};
Loading
Loading