Skip to content

Commit

Permalink
updates
Browse files Browse the repository at this point in the history
  • Loading branch information
OchiengPaul442 committed Feb 24, 2025
1 parent 34e6867 commit 6474187
Show file tree
Hide file tree
Showing 2 changed files with 65 additions and 69 deletions.
10 changes: 4 additions & 6 deletions website2/src/app/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import localFont from 'next/font/local';
import { ReactNode, Suspense } from 'react';

import EngagementDialog from '@/components/dialogs/EngagementDialog';
import { GoogleAnalytics } from '@/components/GoogleAnalytics';
import GoogleAnalytics from '@/components/GoogleAnalytics';
import Loading from '@/components/loading';
import { ErrorBoundary } from '@/components/ui';
import { ReduxDataProvider } from '@/context/ReduxDataProvider';
Expand Down Expand Up @@ -38,11 +38,6 @@ export default async function RootLayout({

return (
<html lang="en" className={interFont.variable}>
<head>
{GA_MEASUREMENT_ID && (
<GoogleAnalytics measurementId={GA_MEASUREMENT_ID} />
)}
</head>
<body>
<ErrorBoundary>
<ReduxDataProvider>
Expand All @@ -58,6 +53,9 @@ export default async function RootLayout({
</Suspense>
</ReduxDataProvider>
</ErrorBoundary>

{/* Initialize & Track Google Analytics */}
<GoogleAnalytics measurementId={GA_MEASUREMENT_ID} />
</body>
</html>
);
Expand Down
124 changes: 61 additions & 63 deletions website2/src/components/GoogleAnalytics.tsx
Original file line number Diff line number Diff line change
@@ -1,96 +1,94 @@
'use client';

import { usePathname, useSearchParams } from 'next/navigation';
import Script from 'next/script';
import { useEffect } from 'react';

declare global {
interface Window {
gtag: (
option: string,
gaTrackingId: string,
options: Record<string, unknown>,
) => void;
dataLayer: Record<string, unknown>[];
dataLayer?: Record<string, any>[];
gtag?: (...args: any[]) => void;
}
}

interface GoogleAnalyticsProps {
measurementId: string;
measurementId?: string;
}

// Validate measurement ID format
const isValidMeasurementId = (id: string): boolean => {
return /^G-[A-Z0-9]+$/.test(id);
};

export function GoogleAnalytics({ measurementId }: GoogleAnalyticsProps) {
/**
* Single component to initialize Google Analytics and
* track page views on route changes using the Next.js App Router.
*/
export default function GoogleAnalytics({
measurementId,
}: GoogleAnalyticsProps) {
const pathname = usePathname();
const searchParams = useSearchParams();

useEffect(() => {
if (!measurementId || !isValidMeasurementId(measurementId)) return;
if (
typeof window === 'undefined' ||
!measurementId ||
typeof window.gtag === 'undefined'
) {
return;
}

const url = pathname + searchParams.toString();
// Construct page path with query strings (if any)
const pagePath = searchParams.toString()
? `${pathname}?${searchParams.toString()}`
: pathname;

window.gtag?.('config', measurementId, {
page_path: url,
window.gtag('config', measurementId, {
page_path: pagePath,
});
}, [pathname, searchParams, measurementId]);
}, [measurementId, pathname, searchParams]);

if (!measurementId || !isValidMeasurementId(measurementId)) {
console.warn('Invalid or missing Google Analytics Measurement ID');
if (!measurementId) {
// If you don't provide a measurement ID, no GA scripts will load
return null;
}

return (
<>
<script
async
{/* Load the gtag script AFTER the page is interactive */}
<Script
src={`https://www.googletagmanager.com/gtag/js?id=${measurementId}`}
strategy="afterInteractive"
/>
<script
dangerouslySetInnerHTML={{
__html: `
window.dataLayer = window.dataLayer || [];
function gtag(){dataLayer.push(arguments);}
gtag('js', new Date());
gtag('config', '${measurementId}', {
page_path: window.location.pathname,
});
`,
}}
/>
<Script id="ga-init" strategy="afterInteractive">
{`
window.dataLayer = window.dataLayer || [];
function gtag(){ dataLayer.push(arguments); }
gtag('js', new Date());
gtag('config', '${measurementId}', {
page_path: window.location.pathname,
});
`}
</Script>
</>
);
}

// Safe event tracking utility with input validation
export const trackEvent = (
action: string,
category: string,
label: string,
value?: number,
) => {
// Validate inputs
if (!action || typeof action !== 'string') return;
if (!category || typeof category !== 'string') return;
if (!label || typeof label !== 'string') return;
if (value !== undefined && typeof value !== 'number') return;

// Sanitize inputs
const sanitizedAction = action.slice(0, 100);
const sanitizedCategory = category.slice(0, 100);
const sanitizedLabel = label.slice(0, 100);

if (typeof window.gtag !== 'undefined') {
try {
window.gtag('event', sanitizedAction, {
event_category: sanitizedCategory,
event_label: sanitizedLabel,
value: value,
});
} catch (error) {
console.error('Error tracking event:', error);
}
/**
* Example helper function to track custom GA events.
*/
export function trackEvent({
action,
category,
label,
value,
}: {
action: string;
category: string;
label: string;
value?: number;
}) {
if (typeof window !== 'undefined' && typeof window.gtag !== 'undefined') {
window.gtag('event', action, {
event_category: category,
event_label: label,
value,
});
}
};
}

0 comments on commit 6474187

Please sign in to comment.