Skip to content

Commit 6768e43

Browse files
authored
add reusable analytics tracking (#16)
1 parent f32d8c9 commit 6768e43

File tree

15 files changed

+209
-88
lines changed

15 files changed

+209
-88
lines changed

src/App.tsx

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
1+
import { useEffect } from 'react';
12
import { Suspense } from 'react';
23
import { useRoutes } from 'react-router-dom';
34
import { Box, CircularProgress } from '@mui/material';
5+
import { trackScrollDepth, trackTimeOnPage } from './services/analytics';
46
import { routes } from './app/router';
57

68
const Loader = () => {
@@ -14,5 +16,15 @@ const Loader = () => {
1416
export default function App() {
1517
const element = useRoutes(routes);
1618

19+
useEffect(() => {
20+
const cleanupScroll = trackScrollDepth();
21+
const cleanupTime = trackTimeOnPage(30);
22+
23+
return () => {
24+
cleanupScroll();
25+
cleanupTime();
26+
};
27+
}, []);
28+
1729
return <Suspense fallback={<Loader />}>{element}</Suspense>;
1830
}

src/components/Footer/Footer.tsx

Lines changed: 15 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { Box, Container, Link, Stack, Typography } from '@mui/material';
2-
import { createTrackedLinkHandler } from '../../services/analytics-handlers.ts';
2+
import { createTrackedLinkHandler } from '../../services/analytics';
33

44
export const Footer = () => {
55
return (
@@ -28,10 +28,13 @@ export const Footer = () => {
2828
target="_blank"
2929
rel="noreferrer"
3030
underline="hover"
31-
onClick={createTrackedLinkHandler('npm', {
32-
location: 'footer',
33-
url: 'https://github.com/dfsyncjs/dfsync',
34-
label: 'npm',
31+
onClick={createTrackedLinkHandler({
32+
params: {
33+
cta_name: 'npm',
34+
location: 'footer',
35+
label: 'npm',
36+
link_url: 'https://www.npmjs.com/package/@dfsync/client',
37+
},
3538
})}
3639
>
3740
npm
@@ -41,10 +44,13 @@ export const Footer = () => {
4144
target="_blank"
4245
rel="noreferrer"
4346
underline="hover"
44-
onClick={createTrackedLinkHandler('github', {
45-
location: 'footer',
46-
url: 'https://github.com/dfsyncjs/dfsync',
47-
label: 'GitHub',
47+
onClick={createTrackedLinkHandler({
48+
params: {
49+
cta_name: 'github',
50+
location: 'footer',
51+
label: 'GitHub',
52+
link_url: 'https://github.com/dfsyncjs/dfsync',
53+
},
4854
})}
4955
>
5056
GitHub

src/components/Header/Header.tsx

Lines changed: 22 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { AppBar, Box, Button, Container, Toolbar } from '@mui/material';
33
import { Link as RouterLink } from 'react-router-dom';
44
import { Brand } from '../Brand/Brand';
55
import { ThemeToggle } from '../ThemeToggle/ThemeToggle';
6-
import { createTrackedLinkHandler } from '../../services/analytics-handlers.ts';
6+
import { createTrackedLinkHandler } from '../../services/analytics';
77

88
export const Header = () => {
99
return (
@@ -25,10 +25,13 @@ export const Header = () => {
2525
component={RouterLink}
2626
to="/docs"
2727
color="inherit"
28-
onClick={createTrackedLinkHandler('docs', {
29-
location: 'header',
30-
url: 'https://github.com/dfsyncjs/dfsync',
31-
label: 'Docs',
28+
onClick={createTrackedLinkHandler({
29+
params: {
30+
cta_name: 'docs',
31+
location: 'header',
32+
label: 'Docs',
33+
link_url: '/docs',
34+
},
3235
})}
3336
>
3437
Docs
@@ -38,10 +41,13 @@ export const Header = () => {
3841
href="https://www.npmjs.com/package/@dfsync/client"
3942
target="_blank"
4043
rel="noreferrer"
41-
onClick={createTrackedLinkHandler('npm', {
42-
location: 'header',
43-
url: 'https://github.com/dfsyncjs/dfsync',
44-
label: 'npm',
44+
onClick={createTrackedLinkHandler({
45+
params: {
46+
cta_name: 'npm',
47+
location: 'header',
48+
label: 'npm',
49+
link_url: 'https://www.npmjs.com/package/@dfsync/client',
50+
},
4551
})}
4652
>
4753
npm
@@ -52,10 +58,13 @@ export const Header = () => {
5258
target="_blank"
5359
rel="noreferrer"
5460
startIcon={<GitHubIcon />}
55-
onClick={createTrackedLinkHandler('github', {
56-
location: 'header',
57-
url: 'https://github.com/dfsyncjs/dfsync',
58-
label: 'GitHub',
61+
onClick={createTrackedLinkHandler({
62+
params: {
63+
cta_name: 'github',
64+
location: 'header',
65+
label: 'GitHub',
66+
link_url: 'https://github.com/dfsyncjs/dfsync',
67+
},
5968
})}
6069
>
6170
GitHub

src/components/Hero/Hero.tsx

Lines changed: 15 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import { Box, Button, Chip, Container, Stack, Typography } from '@mui/material';
44
import { Link as RouterLink } from 'react-router-dom';
55
import { InstallCommand } from '../InstallCommand/InstallCommand';
66
import { ProjectBadges } from '../ProjectBadges/ProjectBadges.tsx';
7-
import { createTrackedLinkHandler } from '../../services/analytics-handlers.ts';
7+
import { createTrackedLinkHandler } from '../../services/analytics';
88

99
export const Hero = () => {
1010
return (
@@ -75,10 +75,13 @@ export const Hero = () => {
7575
target="_blank"
7676
rel="noreferrer"
7777
endIcon={<OpenInNewIcon />}
78-
onClick={createTrackedLinkHandler('npm', {
79-
location: 'hero',
80-
url: 'https://github.com/dfsyncjs/dfsync',
81-
label: 'View on npm',
78+
onClick={createTrackedLinkHandler({
79+
params: {
80+
cta_name: 'npm',
81+
location: 'hero',
82+
label: 'View on npm',
83+
link_url: 'https://www.npmjs.com/package/@dfsync/client',
84+
},
8285
})}
8386
>
8487
View on npm
@@ -90,10 +93,13 @@ export const Hero = () => {
9093
size="medium"
9194
to="/docs"
9295
startIcon={<ListIcon />}
93-
onClick={createTrackedLinkHandler('docs', {
94-
location: 'hero',
95-
url: 'https://github.com/dfsyncjs/dfsync',
96-
label: 'Documentation',
96+
onClick={createTrackedLinkHandler({
97+
params: {
98+
cta_name: 'docs',
99+
location: 'hero',
100+
label: 'Documentation',
101+
link_url: '/docs',
102+
},
97103
})}
98104
>
99105
Documentation

src/components/InstallCommand/InstallCommand.tsx

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { useState } from 'react';
22
import ContentCopyIcon from '@mui/icons-material/ContentCopy';
33
import CheckIcon from '@mui/icons-material/Check';
44
import { IconButton, Stack, Typography } from '@mui/material';
5+
import { Analytics } from '../../services/analytics';
56

67
export const InstallCommand = () => {
78
const [copied, setCopied] = useState(false);
@@ -12,6 +13,11 @@ export const InstallCommand = () => {
1213
await navigator.clipboard.writeText(command);
1314
setCopied(true);
1415
setTimeout(() => setCopied(false), 2000);
16+
17+
Analytics.track('npm_install_copy', {
18+
location: 'hero',
19+
command: command,
20+
});
1521
};
1622

1723
return (

src/main.tsx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,12 @@
11
import React from 'react';
22
import ReactDOM from 'react-dom/client';
33
import { HashRouter } from 'react-router-dom';
4+
import { Analytics, GA4Provider } from './services/analytics';
45
import App from './App';
56
import { AppProviders } from './app/AppProviders';
67

8+
Analytics.init(new GA4Provider());
9+
710
ReactDOM.createRoot(document.getElementById('root')!).render(
811
<React.StrictMode>
912
<AppProviders>

src/services/analytics-handlers.ts

Lines changed: 0 additions & 14 deletions
This file was deleted.

src/services/analytics.ts

Lines changed: 0 additions & 43 deletions
This file was deleted.
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
import type { AnalyticsEvent, AnalyticsEventParams, AnalyticsProvider } from './types';
2+
3+
export class Analytics {
4+
private static provider: AnalyticsProvider | null = null;
5+
6+
static init(provider: AnalyticsProvider): void {
7+
this.provider = provider;
8+
}
9+
10+
static track(name: string, params?: AnalyticsEventParams): void {
11+
if (!this.provider) {
12+
return;
13+
}
14+
15+
const event: AnalyticsEvent = {
16+
name,
17+
params,
18+
};
19+
20+
this.provider.track(event);
21+
}
22+
23+
static trackCta(params: {
24+
ctaName: string;
25+
location?: string;
26+
linkUrl?: string;
27+
label?: string;
28+
}): void {
29+
this.track('cta_click', {
30+
cta_name: params.ctaName,
31+
location: params.location,
32+
link_url: params.linkUrl,
33+
label: params.label,
34+
});
35+
}
36+
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import { Analytics } from './analytics';
2+
import type { AnalyticsEventParams } from './types';
3+
4+
type TrackedLinkHandlerOptions = {
5+
eventName?: string;
6+
params?: AnalyticsEventParams;
7+
};
8+
9+
export function createTrackedLinkHandler(options: TrackedLinkHandlerOptions = {}) {
10+
const { eventName = 'cta_click', params } = options;
11+
12+
return () => {
13+
Analytics.track(eventName, params);
14+
};
15+
}

0 commit comments

Comments
 (0)