diff --git a/doc/gtag_setup.md b/doc/gtag_setup.md
index 38a8368..6ecd0aa 100644
--- a/doc/gtag_setup.md
+++ b/doc/gtag_setup.md
@@ -22,3 +22,18 @@ const App = () => {
);
};
```
+
+
+## Usage
+
+```js
+import {
+ EventTypes,
+ pushGaEvent,
+} from '@freshheads/analytics-essentials';
+
+pushGaEvent({
+ type: EventTypes.CLICK,
+ name: 'hero_button_click'
+});
+```
diff --git a/doc/mixpanel_setup.md b/doc/mixpanel_setup.md
new file mode 100644
index 0000000..56d5328
--- /dev/null
+++ b/doc/mixpanel_setup.md
@@ -0,0 +1,149 @@
+# Mixpanel setup
+
+## Introduction
+This implementation of mixpanel relies on a backend service to send the data to mixpanel. The goal of the frontend lib is to provide a typechecked event to help developers push events the same way.
+For the backend Freshheads created a php bundle, [FHMixpanelBundle](https://github.com/freshheads/FHMixpanelBundle) and a node library will also be created in the future.
+
+## Usage
+
+Add the MixpanelProvider to your app:
+
+```tsx
+import { MixpanelProvider } from '@freshheads/analytics-essentials';
+
+const App = () => {
+ return (
+
+
+
+ );
+};
+```
+
+`sendTrackEvent` is a function that sends the event to the backend. It should have the following signature:
+
+```tsx
+
+import { MixpanelEvent } from '@freshheads/analytics-essentials';
+import { executePostRequest } from '@/api/client';
+
+export const sendTrackEvent = async (data: MixpanelEvent) => {
+ return executePostRequest('_mixpanel/track', data);
+};
+```
+
+Then you can use the `useMixpanelContext` hook to send events:
+
+```tsx
+import { useMixpanelContext } from '@freshheads/analytics-essentials';
+
+const { trackEvent } = useMixpanelContext();
+
+trackEvent({
+ name: 'Add to cart',
+ data: {
+ product_name: product.title,
+ },
+});
+```
+
+### Context and overrides
+By default, each event will have a context property that contains more information about the page it was sent from.
+This is resolved from window.location because we have no access to the router state.
+
+```
+{
+ title: 'Product Page', // What page is the event triggered on
+ pathname: '/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
+}
+```
+
+You can override this context by providing a `context` property to the event.
+
+```tsx
+trackEvent({
+ name: 'Add to cart',
+ context: {
+ section: 'footer',
+ },
+});
+```
+
+Or by providing a default context to the MixpanelProvider:
+
+```tsx
+const router = useRouter();
+
+const defaultMixpanelEventContext = {
+ pathname: router.pathname,
+ route: router.route,
+ audience: 'Freelancer',
+};
+
+const App = () => {
+ return (
+
+
+
+ );
+};
+```
+
+### Page view events
+
+For page view events, you can use the `trackPageView` function.
+First create a component that will be used to track page views.
+The reason we don't make this part of the essential package is because we don't want to make any assumptions about the router you are using.
+Or you might want to send additional data with the page view event.
+
+```tsx
+export default function TrackPageView() {
+ const { trackPageView } = useMixpanelContext();
+ const router = useRouter();
+ const pathname = usePathname();
+
+ useEffect(() => {
+ trackPageView({
+ data: {
+ title: document.title,
+ pathname: pathname,
+ route: router.route,
+ },
+ });
+ }, [pathname]);
+
+ return null;
+}
+```
+
+Then add this component to your app:
+
+```tsx
+
+const App = () => {
+ return (
+
+
+ {children}
+
+ );
+};
+```
+
+### UTM tracking
+
+UTM tags are automatically added to the context of the event if they are present in the URL.
+They will be remembered for the duration of the session. Even if the user navigates to a different page, the UTM tags will be added to new events.
+
+## Mixpanel users
+
+TODO how to handle reset mixpanel user
+
+## Event naming conventions
+
+TODO - how to name events
+
+## Event types
+
+TODO - how to override the event types
diff --git a/doc/tagmanager_setup.md b/doc/tagmanager_setup.md
index d7e0002..8cf094e 100644
--- a/doc/tagmanager_setup.md
+++ b/doc/tagmanager_setup.md
@@ -17,3 +17,31 @@ const App = () => {
);
};
```
+
+## Usage
+
+```js
+import {
+ EventTypes,
+ pushDataLayerEvent,
+} from '@freshheads/analytics-essentials';
+
+pushDataLayerEvent({
+ type: EventTypes.CLICK,
+ name: 'hero_button_click',
+});
+```
+
+## Optional params
+
+```js
+pushDataLayerEvent({
+ type: EventTypes.CLICK,
+ name: 'hero_button_click',
+ context: {
+ // context is typed based on type
+ // you can always extend context with your custom properties
+ }
+}),
+
+```
diff --git a/lib/mixpanel/context.tsx b/lib/mixpanel/context.tsx
index 26ec2b2..5e8e63f 100644
--- a/lib/mixpanel/context.tsx
+++ b/lib/mixpanel/context.tsx
@@ -1,23 +1,26 @@
'use client';
import React, { createContext, useContext, useEffect } from 'react';
-import { MixpanelEvent as DTO } from './types';
+import { MixpanelEvent, MixpanelPageViewEvent } from './types';
import {
extractUtmParams,
isStandalonePWA,
writeUtmParamsToSessionStorage,
} from './utils.ts';
-interface MixpanelContextProps {
+interface MixpanelContextProps {
trackEvent: (event: DTO) => void;
+ trackPageView: (event: MixpanelPageViewEvent) => void;
}
-interface MixpanelProviderProps {
+interface MixpanelProviderProps {
children: React.ReactNode;
eventApiClient: (args: DTO) => Promise;
+ defaultEventContext?: DTO['context'];
}
-const MixpanelContext = createContext | null>(null);
+const MixpanelContext =
+ createContext | null>(null);
export function useMixpanelContext() {
const context = useContext(MixpanelContext);
@@ -32,8 +35,9 @@ export function useMixpanelContext() {
export function MixpanelProvider({
children,
eventApiClient,
-}: MixpanelProviderProps) {
- const trackEvent = (event: DTO) => {
+ defaultEventContext,
+}: MixpanelProviderProps) {
+ const trackEvent = (event: MixpanelEvent) => {
// only send events on the client
if (typeof window === 'undefined') {
return;
@@ -45,8 +49,27 @@ export function MixpanelProvider({
...event,
context: {
title: document.title,
- href: window.location.href,
- path: window.location.pathname,
+ pathname: window.location.pathname,
+ pwa: isStandalonePWA(),
+ ...defaultEventContext,
+ ...utmParams,
+ ...event.context,
+ },
+ }).catch((e) => console.error(e));
+ };
+
+ const trackPageView = (event: MixpanelPageViewEvent) => {
+ // only send events on the client
+ if (typeof window === 'undefined') {
+ return;
+ }
+
+ const utmParams = extractUtmParams(window.location.search);
+
+ eventApiClient({
+ ...event,
+ name: 'Page view',
+ context: {
pwa: isStandalonePWA(),
...utmParams,
...event.context,
@@ -62,6 +85,7 @@ export function MixpanelProvider({
{children}
diff --git a/lib/mixpanel/types.ts b/lib/mixpanel/types.ts
index cc3d59c..69640c1 100644
--- a/lib/mixpanel/types.ts
+++ b/lib/mixpanel/types.ts
@@ -4,14 +4,15 @@
* 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"
+ * name: 'Contact', // e.g. "Update profile", "Add to cart", "Purchase"
* 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
+ * pathname: '/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
+ * pwa: true, // Is the event triggered in a PWA
* 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
@@ -27,7 +28,7 @@ export type MixpanelEvent = {
name: string;
context?: {
title?: string;
- path?: string;
+ pathname?: string;
href?: string;
route?: string;
audience?: string;
@@ -38,8 +39,53 @@ export type MixpanelEvent = {
utm_campaign?: string;
utm_content?: string;
utm_term?: string;
+ [key: string]: unknown;
+ };
+ data?: {
+ [key: string]: unknown;
+ };
+};
+
+/**
+ * @description
+ * When sending a page view event to Mixpanel we will pass this event to our own backend that is used as a proxy for Mixpanel.
+ * It differs from the `MixpanelEvent` in that it has a fixed `name` and information about the page should be provided
+ *
+ * @example
+ * const event: MixpanelPageViewEvent = {
+ * data: {
+ * title: 'Product Page', // What page is the event triggered on
+ * pathname: '/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', // The audience that is viewing the page
+ * },
+ * context: { // This is optional and will be mostly provided by the MixpanelProvider
+ * pwa: true, // Is the event triggered in a PWA
+ * 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
+ * }
+ *}
+ */
+export type MixpanelPageViewEvent = {
+ context?: {
+ pwa?: boolean;
+ utm_source?: string;
+ utm_medium?: string;
+ utm_campaign?: string;
+ utm_content?: string;
+ utm_term?: string;
+ [key: string]: unknown;
};
data?: {
+ title?: string;
+ pathname: string;
+ href?: string;
+ route?: string;
+ audience?: string;
[key: string]: unknown;
};
};
diff --git a/package-lock.json b/package-lock.json
index 6aefb58..7d3ec5f 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -8,6 +8,9 @@
"name": "@freshheads/analytics-essentials",
"version": "0.0.5-2",
"license": "MIT",
+ "dependencies": {
+ "@freshheads/analytics-essentials": "^0.0.4"
+ },
"devDependencies": {
"@testing-library/react": "^14.2.1",
"@types/gtag.js": "^0.0.12",
@@ -858,6 +861,15 @@
"node": "^12.22.0 || ^14.17.0 || >=16.0.0"
}
},
+ "node_modules/@freshheads/analytics-essentials": {
+ "version": "0.0.4",
+ "resolved": "https://registry.npmjs.org/@freshheads/analytics-essentials/-/analytics-essentials-0.0.4.tgz",
+ "integrity": "sha512-oSH4pZcMC3X0nWgPUqcgp5xhWz3g++U8flcxBErYhfq1W18makx7r0g5TwaNSxFC57h1A4n4QDvto2sASQiulA==",
+ "peerDependencies": {
+ "next": ">=13",
+ "react": ">=18"
+ }
+ },
"node_modules/@humanwhocodes/config-array": {
"version": "0.11.14",
"resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.14.tgz",
@@ -1096,8 +1108,7 @@
"node_modules/@next/env": {
"version": "13.5.6",
"resolved": "https://registry.npmjs.org/@next/env/-/env-13.5.6.tgz",
- "integrity": "sha512-Yac/bV5sBGkkEXmAX5FWPS9Mmo2rthrOPRQQNfycJPkjUAUclomCPH7QFVCDQ4Mp2k2K1SSM6m0zrxYrOwtFQw==",
- "dev": true
+ "integrity": "sha512-Yac/bV5sBGkkEXmAX5FWPS9Mmo2rthrOPRQQNfycJPkjUAUclomCPH7QFVCDQ4Mp2k2K1SSM6m0zrxYrOwtFQw=="
},
"node_modules/@next/swc-darwin-arm64": {
"version": "13.5.6",
@@ -1106,7 +1117,6 @@
"cpu": [
"arm64"
],
- "dev": true,
"optional": true,
"os": [
"darwin"
@@ -1448,7 +1458,6 @@
"version": "0.5.2",
"resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.2.tgz",
"integrity": "sha512-E4KcWTpoLHqwPHLxidpOqQbcrZVgi0rsmmZXUle1jXmJfuIf/UWpczUJ7MZZ5tlxytgJXyp0w4PGkkeLiuIdZw==",
- "dev": true,
"dependencies": {
"tslib": "^2.4.0"
}
@@ -2347,7 +2356,6 @@
"version": "1.6.0",
"resolved": "https://registry.npmjs.org/busboy/-/busboy-1.6.0.tgz",
"integrity": "sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==",
- "dev": true,
"dependencies": {
"streamsearch": "^1.1.0"
},
@@ -2396,7 +2404,6 @@
"version": "1.0.30001596",
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001596.tgz",
"integrity": "sha512-zpkZ+kEr6We7w63ORkoJ2pOfBwBkY/bJrG/UZ90qNb45Isblu8wzDgevEOrRL1r9dWayHjYiiyCMEXPn4DweGQ==",
- "dev": true,
"funding": [
{
"type": "opencollective",
@@ -2459,8 +2466,7 @@
"node_modules/client-only": {
"version": "0.0.1",
"resolved": "https://registry.npmjs.org/client-only/-/client-only-0.0.1.tgz",
- "integrity": "sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==",
- "dev": true
+ "integrity": "sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA=="
},
"node_modules/color-convert": {
"version": "1.9.3",
@@ -3794,8 +3800,7 @@
"node_modules/glob-to-regexp": {
"version": "0.4.1",
"resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz",
- "integrity": "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==",
- "dev": true
+ "integrity": "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw=="
},
"node_modules/glob/node_modules/brace-expansion": {
"version": "2.0.1",
@@ -3865,8 +3870,7 @@
"node_modules/graceful-fs": {
"version": "4.2.11",
"resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz",
- "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==",
- "dev": true
+ "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ=="
},
"node_modules/graphemer": {
"version": "1.4.0",
@@ -4437,8 +4441,7 @@
"node_modules/js-tokens": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
- "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==",
- "dev": true
+ "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="
},
"node_modules/js-yaml": {
"version": "4.1.0",
@@ -4636,7 +4639,6 @@
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",
"integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==",
- "dev": true,
"dependencies": {
"js-tokens": "^3.0.0 || ^4.0.0"
},
@@ -4793,7 +4795,6 @@
"version": "3.3.7",
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz",
"integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==",
- "dev": true,
"funding": [
{
"type": "github",
@@ -4823,7 +4824,6 @@
"version": "13.5.6",
"resolved": "https://registry.npmjs.org/next/-/next-13.5.6.tgz",
"integrity": "sha512-Y2wTcTbO4WwEsVb4A8VSnOsG1I9ok+h74q0ZdxkwM3EODqrs4pasq7O0iUxbcS9VtWMicG7f3+HAj0r1+NtKSw==",
- "dev": true,
"dependencies": {
"@next/env": "13.5.6",
"@swc/helpers": "0.5.2",
@@ -8436,8 +8436,7 @@
"node_modules/picocolors": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz",
- "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==",
- "dev": true
+ "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ=="
},
"node_modules/picomatch": {
"version": "2.3.1",
@@ -8475,7 +8474,6 @@
"version": "8.4.31",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz",
"integrity": "sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==",
- "dev": true,
"funding": [
{
"type": "opencollective",
@@ -8603,7 +8601,6 @@
"version": "18.2.0",
"resolved": "https://registry.npmjs.org/react/-/react-18.2.0.tgz",
"integrity": "sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==",
- "dev": true,
"dependencies": {
"loose-envify": "^1.1.0"
},
@@ -8615,7 +8612,6 @@
"version": "18.2.0",
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.2.0.tgz",
"integrity": "sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g==",
- "dev": true,
"peer": true,
"dependencies": {
"loose-envify": "^1.1.0",
@@ -8808,7 +8804,6 @@
"version": "0.23.0",
"resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.0.tgz",
"integrity": "sha512-CtuThmgHNg7zIZWAXi3AsyIzA3n4xx7aNyjwC2VJldO2LMVDhFK+63xGqq6CsJH4rTAt6/M+N4GhZiDYPx9eUw==",
- "dev": true,
"peer": true,
"dependencies": {
"loose-envify": "^1.1.0"
@@ -8958,7 +8953,6 @@
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz",
"integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==",
- "dev": true,
"engines": {
"node": ">=0.10.0"
}
@@ -8997,7 +8991,6 @@
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-1.1.0.tgz",
"integrity": "sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==",
- "dev": true,
"engines": {
"node": ">=10.0.0"
}
@@ -9147,7 +9140,6 @@
"version": "5.1.1",
"resolved": "https://registry.npmjs.org/styled-jsx/-/styled-jsx-5.1.1.tgz",
"integrity": "sha512-pW7uC1l4mBZ8ugbiZrcIsiIvVx1UmTfw7UkC3Um2tmfUq9Bhk8IiyEIPl6F8agHgjzku6j0xQEZbfA5uSgSaCw==",
- "dev": true,
"dependencies": {
"client-only": "0.0.1"
},
@@ -9286,8 +9278,7 @@
"node_modules/tslib": {
"version": "2.6.2",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz",
- "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==",
- "dev": true
+ "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q=="
},
"node_modules/tsutils": {
"version": "3.21.0",
@@ -9993,7 +9984,6 @@
"version": "2.4.0",
"resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.0.tgz",
"integrity": "sha512-Lcvm7MGST/4fup+ifyKi2hjyIAwcdI4HRgtvTpIUxBRhB+RFtUh8XtDOxUfctVCnhVi+QQj49i91OyvzkJl6cg==",
- "dev": true,
"dependencies": {
"glob-to-regexp": "^0.4.1",
"graceful-fs": "^4.1.2"
diff --git a/package.json b/package.json
index afd741f..4d833cc 100644
--- a/package.json
+++ b/package.json
@@ -62,5 +62,8 @@
"next": {
"optional": true
}
+ },
+ "dependencies": {
+ "@freshheads/analytics-essentials": "^0.0.4"
}
}
diff --git a/readme.md b/readme.md
index 8207ca5..82e5179 100644
--- a/readme.md
+++ b/readme.md
@@ -8,52 +8,8 @@
npm i @freshheads/analytics-essentials
```
-### Setup Google Tag or Google Tagmanager
+### Usage by analytics provider
+- [Mixpanel Setup](doc/mixpanel_setup.md)
- [Gtag Setup](doc/gtag_setup.md)
- [Tagmanager Setup](doc/tagmanager_setup.md)
-
-
-## Usage
-
-### With gtag
-
-```js
-import {
- EventTypes,
- pushGaEvent,
-} from '@freshheads/analytics-essentials';
-
-pushGaEvent({
- type: EventTypes.CLICK,
- name: 'hero_button_click'
-});
-```
-
-### With Tag Manager
-
-```js
-import {
- EventTypes,
- pushDataLayerEvent,
-} from '@freshheads/analytics-essentials';
-
-pushDataLayerEvent({
- type: EventTypes.CLICK,
- name: 'hero_button_click',
-});
-```
-
-## Optional params
-
-```js
-pushDataLayerEvent({
- type: EventTypes.CLICK,
- name: 'hero_button_click',
- context: {
- // context is typed based on type
- // you can always extend context with your custom properties
- }
-}),
-
-```
diff --git a/test/mixpanel.test.tsx b/test/mixpanel.test.tsx
index cf098e1..024e9ac 100644
--- a/test/mixpanel.test.tsx
+++ b/test/mixpanel.test.tsx
@@ -5,8 +5,14 @@ import {
writeUtmParamsToSessionStorage,
} from '../lib/mixpanel/utils';
import { MixpanelProvider, useMixpanelContext } from '../lib/mixpanel/context';
-import { fireEvent, render, renderHook } from '@testing-library/react';
-import React from 'react';
+import {
+ fireEvent,
+ render,
+ renderHook,
+ RenderOptions,
+} from '@testing-library/react';
+import React, { useEffect } from 'react';
+import { MixpanelEvent } from '../lib/mixpanel';
describe('UTM tags', () => {
const urlContainingUTMParams = new URL(
@@ -43,12 +49,33 @@ describe('UTM tags', () => {
describe('MixpanelContext', () => {
const eventApiClient = vi.fn(() => Promise.resolve());
- const ContextWrapper = ({ children }: { children: React.ReactNode }) => (
-
+ const ContextWrapper = ({
+ children,
+ defaultEventContext,
+ }: {
+ children: React.ReactNode;
+ defaultEventContext: MixpanelEvent;
+ }) => (
+
{children}
);
+ const renderWithMixpanelProvider = (
+ ui: React.ReactElement,
+ options?: Omit
+ ) => {
+ return render(ui, {
+ wrapper: (props: any) => (
+
+ ),
+ ...options?.testingLibraryOptions,
+ });
+ };
+
function TrackEventTestingComponent() {
const { trackEvent } = useMixpanelContext();
return (
@@ -66,6 +93,22 @@ describe('MixpanelContext', () => {
);
}
+ function TrackPageView() {
+ const { trackPageView } = useMixpanelContext();
+
+ useEffect(() => {
+ trackPageView({
+ data: {
+ title: 'Example',
+ pathname: '/product/1',
+ route: '/product/:id',
+ },
+ });
+ }, []);
+
+ return null;
+ }
+
test('provides expected context with trackEvent function', () => {
const { result } = renderHook(() => useMixpanelContext(), {
wrapper: ContextWrapper,
@@ -73,12 +116,44 @@ describe('MixpanelContext', () => {
expect(result.current).toHaveProperty('trackEvent');
expect(typeof result.current.trackEvent).toBe('function');
+
+ expect(result.current).toHaveProperty('trackPageView');
+ expect(typeof result.current.trackPageView).toBe('function');
});
test('trackEvent sends correct data to api client', () => {
- const { getByText } = render(, {
- wrapper: ContextWrapper,
+ const { getByText } = renderWithMixpanelProvider(
+
+ );
+
+ fireEvent.click(getByText('button'));
+
+ expect(eventApiClient).toHaveBeenCalledWith({
+ name: 'event name',
+ context: {
+ title: 'Page title',
+ pathname: '/',
+ pwa: false,
+ },
+ data: {
+ productId: '123',
+ },
});
+ });
+
+ test('provider can extend the default context for event tracking', () => {
+ const defaultEventContext = {
+ href: 'https://example.com',
+ pathname: '/example',
+ audience: 'Consumer',
+ };
+
+ const { getByText } = renderWithMixpanelProvider(
+ ,
+ {
+ contextWrapperProps: { defaultEventContext },
+ }
+ );
fireEvent.click(getByText('button'));
@@ -86,13 +161,30 @@ describe('MixpanelContext', () => {
name: 'event name',
context: {
title: 'Page title',
- href: window.location.href,
- path: '/',
+ href: 'https://example.com',
+ pathname: '/example',
pwa: false,
+ audience: 'Consumer',
},
data: {
productId: '123',
},
});
});
+
+ test('trackPageView sends correct data to api client', () => {
+ renderWithMixpanelProvider();
+
+ expect(eventApiClient).toHaveBeenCalledWith({
+ name: 'Page view',
+ context: {
+ pwa: false,
+ },
+ data: {
+ title: 'Example',
+ pathname: '/product/1',
+ route: '/product/:id',
+ },
+ });
+ });
});