From 258fe341a3076c544d6fc2b1bfbcd410d5838cde Mon Sep 17 00:00:00 2001 From: Roman Onishchenko Date: Sat, 21 Mar 2026 10:12:46 +0100 Subject: [PATCH 1/2] add ga4 script --- index.html | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/index.html b/index.html index 0053b62..9860ac4 100644 --- a/index.html +++ b/index.html @@ -1,6 +1,18 @@ + + + + From fa4ffbc8ee446e4f728ee62e9d942734a25d3585 Mon Sep 17 00:00:00 2001 From: Roman Onishchenko Date: Sat, 21 Mar 2026 10:35:41 +0100 Subject: [PATCH 2/2] add custom events --- src/components/Footer/Footer.tsx | 11 ++++++++ src/components/Header/Header.tsx | 22 ++++++++++++++- src/components/Hero/Hero.tsx | 11 ++++++++ src/services/analytics-handlers.ts | 14 ++++++++++ src/services/analytics.ts | 43 ++++++++++++++++++++++++++++++ src/types/global.d.ts | 9 +++++++ 6 files changed, 109 insertions(+), 1 deletion(-) create mode 100644 src/services/analytics-handlers.ts create mode 100644 src/services/analytics.ts create mode 100644 src/types/global.d.ts 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; + } +}