feat(analytics): integrate Google Analytics 4 with GDPR compliance
Prepared the application for Google Analytics 4 tracking with full GDPR/RODO compliance. GA only loads after user explicitly accepts cookies. Features: - Automatic page view tracking on route changes - Custom event tracking for key user actions - Privacy-first: GA loads only after cookie consent - Easy configuration via environment variable - Comprehensive tracking utilities for common events Implementation: - Created analytics.js with GA initialization and event tracking functions - Created usePageTracking hook for automatic page view tracking - Integrated GA into App.jsx with AnalyticsWrapper component - Updated CookieConsent to initialize GA after user consent - Added VITE_GA_MEASUREMENT_ID to .env.example Custom events tracked: - login, sign_up (authentication) - match_request, match_accepted (matching) - webrtc_connection, file_transfer (WebRTC) - event_join, recording_suggestion (events/recording) - search (search functionality) Setup: 1. Add VITE_GA_MEASUREMENT_ID=G-XXXXXXXXXX to .env 2. Restart frontend container 3. GA will auto-load after user accepts cookies Documentation: - Created comprehensive setup guide in docs/GOOGLE_ANALYTICS_SETUP.md - Includes troubleshooting, debugging tips, and usage examples
This commit is contained in:
@@ -1,6 +1,9 @@
|
||||
import { BrowserRouter, Routes, Route, Navigate } from 'react-router-dom';
|
||||
import { Toaster } from 'react-hot-toast';
|
||||
import { useEffect } from 'react';
|
||||
import { AuthProvider, useAuth } from './contexts/AuthContext';
|
||||
import { initGA } from './utils/analytics';
|
||||
import usePageTracking from './hooks/usePageTracking';
|
||||
import HomePage from './pages/HomePage';
|
||||
import LoginPage from './pages/LoginPage';
|
||||
import RegisterPage from './pages/RegisterPage';
|
||||
@@ -71,15 +74,35 @@ const PublicRoute = ({ children }) => {
|
||||
return children;
|
||||
};
|
||||
|
||||
// Analytics wrapper component
|
||||
const AnalyticsWrapper = ({ children }) => {
|
||||
usePageTracking(); // Track page views on route changes
|
||||
|
||||
useEffect(() => {
|
||||
// Initialize Google Analytics when user consents to cookies
|
||||
const measurementId = import.meta.env.VITE_GA_MEASUREMENT_ID;
|
||||
if (measurementId) {
|
||||
// Wait a bit for cookie consent banner to be processed
|
||||
const timer = setTimeout(() => {
|
||||
initGA(measurementId);
|
||||
}, 1500);
|
||||
return () => clearTimeout(timer);
|
||||
}
|
||||
}, []);
|
||||
|
||||
return children;
|
||||
};
|
||||
|
||||
function App() {
|
||||
return (
|
||||
<BrowserRouter>
|
||||
<AuthProvider>
|
||||
{/* PWA Install Prompt */}
|
||||
<InstallPWA />
|
||||
<AnalyticsWrapper>
|
||||
{/* PWA Install Prompt */}
|
||||
<InstallPWA />
|
||||
|
||||
{/* Cookie Consent Banner */}
|
||||
<CookieConsent />
|
||||
{/* Cookie Consent Banner */}
|
||||
<CookieConsent />
|
||||
|
||||
{/* Toast Notifications */}
|
||||
<Toaster
|
||||
@@ -246,6 +269,7 @@ function App() {
|
||||
{/* 404 Not Found - Catch all unmatched routes */}
|
||||
<Route path="*" element={<NotFoundPage />} />
|
||||
</Routes>
|
||||
</AnalyticsWrapper>
|
||||
</AuthProvider>
|
||||
</BrowserRouter>
|
||||
);
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { useState, useEffect } from 'react';
|
||||
import { Cookie, X } from 'lucide-react';
|
||||
import { initGA } from '../../utils/analytics';
|
||||
|
||||
/**
|
||||
* GDPR/RODO compliant cookie consent banner
|
||||
@@ -20,6 +21,12 @@ const CookieConsent = () => {
|
||||
const handleAccept = () => {
|
||||
localStorage.setItem('cookieConsent', 'accepted');
|
||||
setIsVisible(false);
|
||||
|
||||
// Initialize Google Analytics after consent
|
||||
const measurementId = import.meta.env.VITE_GA_MEASUREMENT_ID;
|
||||
if (measurementId) {
|
||||
setTimeout(() => initGA(measurementId), 500);
|
||||
}
|
||||
};
|
||||
|
||||
const handleDecline = () => {
|
||||
|
||||
18
frontend/src/hooks/usePageTracking.js
Normal file
18
frontend/src/hooks/usePageTracking.js
Normal file
@@ -0,0 +1,18 @@
|
||||
import { useEffect } from 'react';
|
||||
import { useLocation } from 'react-router-dom';
|
||||
import { trackPageView } from '../utils/analytics';
|
||||
|
||||
/**
|
||||
* Hook to automatically track page views on route changes
|
||||
* Usage: Add usePageTracking() to App.jsx
|
||||
*/
|
||||
const usePageTracking = () => {
|
||||
const location = useLocation();
|
||||
|
||||
useEffect(() => {
|
||||
// Track page view on route change
|
||||
trackPageView(location.pathname + location.search, document.title);
|
||||
}, [location]);
|
||||
};
|
||||
|
||||
export default usePageTracking;
|
||||
112
frontend/src/utils/analytics.js
Normal file
112
frontend/src/utils/analytics.js
Normal file
@@ -0,0 +1,112 @@
|
||||
/**
|
||||
* Google Analytics utilities
|
||||
* Handles GA4 tracking events and page views
|
||||
*/
|
||||
|
||||
// Initialize Google Analytics
|
||||
export const initGA = (measurementId) => {
|
||||
if (!measurementId) {
|
||||
console.warn('Google Analytics: No measurement ID provided');
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if user has consented to cookies
|
||||
const cookieConsent = localStorage.getItem('cookieConsent');
|
||||
if (cookieConsent !== 'accepted') {
|
||||
console.log('Google Analytics: Disabled - user has not consented to cookies');
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if gtag already exists
|
||||
if (window.gtag) {
|
||||
console.log('Google Analytics: Already initialized');
|
||||
return;
|
||||
}
|
||||
|
||||
// Load GA script
|
||||
const script = document.createElement('script');
|
||||
script.async = true;
|
||||
script.src = `https://www.googletagmanager.com/gtag/js?id=${measurementId}`;
|
||||
document.head.appendChild(script);
|
||||
|
||||
// Initialize gtag
|
||||
window.dataLayer = window.dataLayer || [];
|
||||
window.gtag = function() {
|
||||
window.dataLayer.push(arguments);
|
||||
};
|
||||
window.gtag('js', new Date());
|
||||
window.gtag('config', measurementId, {
|
||||
send_page_view: false, // We'll handle page views manually
|
||||
});
|
||||
|
||||
console.log('Google Analytics: Initialized with ID', measurementId);
|
||||
};
|
||||
|
||||
// Track page view
|
||||
export const trackPageView = (path, title) => {
|
||||
if (!window.gtag) {
|
||||
return;
|
||||
}
|
||||
|
||||
window.gtag('event', 'page_view', {
|
||||
page_path: path,
|
||||
page_title: title,
|
||||
});
|
||||
};
|
||||
|
||||
// Track custom event
|
||||
export const trackEvent = (eventName, eventParams = {}) => {
|
||||
if (!window.gtag) {
|
||||
return;
|
||||
}
|
||||
|
||||
window.gtag('event', eventName, eventParams);
|
||||
};
|
||||
|
||||
// Track user login
|
||||
export const trackLogin = (method = 'email') => {
|
||||
trackEvent('login', { method });
|
||||
};
|
||||
|
||||
// Track user registration
|
||||
export const trackSignUp = (method = 'email') => {
|
||||
trackEvent('sign_up', { method });
|
||||
};
|
||||
|
||||
// Track search
|
||||
export const trackSearch = (searchTerm) => {
|
||||
trackEvent('search', { search_term: searchTerm });
|
||||
};
|
||||
|
||||
// Track match request
|
||||
export const trackMatchRequest = (eventSlug) => {
|
||||
trackEvent('match_request', { event_slug: eventSlug });
|
||||
};
|
||||
|
||||
// Track match accepted
|
||||
export const trackMatchAccepted = (matchSlug) => {
|
||||
trackEvent('match_accepted', { match_slug: matchSlug });
|
||||
};
|
||||
|
||||
// Track WebRTC connection
|
||||
export const trackWebRTCConnection = (status) => {
|
||||
trackEvent('webrtc_connection', { status });
|
||||
};
|
||||
|
||||
// Track file transfer
|
||||
export const trackFileTransfer = (fileSize, success) => {
|
||||
trackEvent('file_transfer', {
|
||||
file_size: fileSize,
|
||||
success: success ? 'yes' : 'no',
|
||||
});
|
||||
};
|
||||
|
||||
// Track event join
|
||||
export const trackEventJoin = (eventSlug) => {
|
||||
trackEvent('event_join', { event_slug: eventSlug });
|
||||
};
|
||||
|
||||
// Track recording suggestion
|
||||
export const trackRecordingSuggestion = (action) => {
|
||||
trackEvent('recording_suggestion', { action });
|
||||
};
|
||||
Reference in New Issue
Block a user