diff --git a/index.html b/index.html index 0053b62..9860ac4 100644 --- a/index.html +++ b/index.html @@ -1,6 +1,18 @@ + + + + diff --git a/src/components/Footer/Footer.tsx b/src/components/Footer/Footer.tsx index 329fd05..0b52cf3 100644 --- a/src/components/Footer/Footer.tsx +++ b/src/components/Footer/Footer.tsx @@ -1,4 +1,5 @@ import { Box, Container, Link, Stack, Typography } from '@mui/material'; +import { createTrackedLinkHandler } from '../../services/analytics-handlers.ts'; export const Footer = () => { return ( @@ -27,6 +28,11 @@ export const Footer = () => { target="_blank" rel="noreferrer" underline="hover" + onClick={createTrackedLinkHandler('npm', { + location: 'footer', + url: 'https://github.com/dfsyncjs/dfsync', + label: 'npm', + })} > npm @@ -35,6 +41,11 @@ export const Footer = () => { target="_blank" rel="noreferrer" underline="hover" + onClick={createTrackedLinkHandler('github', { + location: 'footer', + url: 'https://github.com/dfsyncjs/dfsync', + label: 'GitHub', + })} > GitHub diff --git a/src/components/Header/Header.tsx b/src/components/Header/Header.tsx index 302ed4e..f829ae0 100644 --- a/src/components/Header/Header.tsx +++ b/src/components/Header/Header.tsx @@ -3,6 +3,7 @@ import { AppBar, Box, Button, Container, Toolbar } from '@mui/material'; import { Link as RouterLink } from 'react-router-dom'; import { Brand } from '../Brand/Brand'; import { ThemeToggle } from '../ThemeToggle/ThemeToggle'; +import { createTrackedLinkHandler } from '../../services/analytics-handlers.ts'; export const Header = () => { return ( @@ -20,7 +21,16 @@ export const Header = () => { - @@ -37,6 +52,11 @@ export const Header = () => { target="_blank" rel="noreferrer" startIcon={} + onClick={createTrackedLinkHandler('github', { + location: 'header', + url: 'https://github.com/dfsyncjs/dfsync', + label: 'GitHub', + })} > GitHub diff --git a/src/components/Hero/Hero.tsx b/src/components/Hero/Hero.tsx index 103deb1..4b4b1b8 100644 --- a/src/components/Hero/Hero.tsx +++ b/src/components/Hero/Hero.tsx @@ -4,6 +4,7 @@ import { Box, Button, Chip, Container, Stack, Typography } from '@mui/material'; import { Link as RouterLink } from 'react-router-dom'; import { InstallCommand } from '../InstallCommand/InstallCommand'; import { ProjectBadges } from '../ProjectBadges/ProjectBadges.tsx'; +import { createTrackedLinkHandler } from '../../services/analytics-handlers.ts'; export const Hero = () => { return ( @@ -74,6 +75,11 @@ export const Hero = () => { target="_blank" rel="noreferrer" endIcon={} + onClick={createTrackedLinkHandler('npm', { + location: 'hero', + url: 'https://github.com/dfsyncjs/dfsync', + label: 'View on npm', + })} > View on npm @@ -84,6 +90,11 @@ export const Hero = () => { size="medium" to="/docs" startIcon={} + onClick={createTrackedLinkHandler('docs', { + location: 'hero', + url: 'https://github.com/dfsyncjs/dfsync', + label: 'Documentation', + })} > Documentation diff --git a/src/services/analytics-handlers.ts b/src/services/analytics-handlers.ts new file mode 100644 index 0000000..3be3b2c --- /dev/null +++ b/src/services/analytics-handlers.ts @@ -0,0 +1,14 @@ +import { Analytics } from './analytics'; + +export function createTrackedLinkHandler( + ctaName: string, + options: { + location?: string; + url?: string; + label?: string; + }, +) { + return () => { + Analytics.trackCta(ctaName, options); + }; +} diff --git a/src/services/analytics.ts b/src/services/analytics.ts new file mode 100644 index 0000000..3152f4f --- /dev/null +++ b/src/services/analytics.ts @@ -0,0 +1,43 @@ +type AnalyticsEventName = 'cta_click' | 'page_view' | 'custom_event'; + +type CtaName = 'github' | 'npm' | 'docs'; + +type TrackEventParams = Record; + +export class Analytics { + private static isEnabled(): boolean { + return typeof window !== 'undefined' && typeof window.gtag === 'function'; + } + + static track(eventName: AnalyticsEventName | string, params?: TrackEventParams): void { + if (!this.isEnabled()) return; + + window.gtag!('event', eventName, params ?? {}); + } + + static trackCta( + ctaName: CtaName | string, + params?: { + location?: string; + url?: string; + label?: string; + }, + ): void { + this.track('cta_click', { + cta_name: ctaName, + location: params?.location, + link_url: params?.url, + label: params?.label, + }); + } + + static trackPageView(path: string, title?: string): void { + if (!this.isEnabled()) return; + + window.gtag!('event', 'page_view', { + page_path: path, + page_title: title, + page_location: typeof window !== 'undefined' ? window.location.href : undefined, + }); + } +} diff --git a/src/types/global.d.ts b/src/types/global.d.ts new file mode 100644 index 0000000..8a29751 --- /dev/null +++ b/src/types/global.d.ts @@ -0,0 +1,9 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ + +export {}; + +declare global { + interface Window { + gtag?: (...args: any[]) => void; + } +}