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:
@@ -9,3 +9,8 @@ VITE_ALLOWED_HOSTS=localhost,spotlight.cam,.spotlight.cam
|
|||||||
# Cloudflare Turnstile (CAPTCHA)
|
# Cloudflare Turnstile (CAPTCHA)
|
||||||
# Get your keys from: https://dash.cloudflare.com/
|
# Get your keys from: https://dash.cloudflare.com/
|
||||||
VITE_TURNSTILE_SITE_KEY=your-site-key-here
|
VITE_TURNSTILE_SITE_KEY=your-site-key-here
|
||||||
|
|
||||||
|
# Google Analytics 4
|
||||||
|
# Format: G-XXXXXXXXXX
|
||||||
|
# Get your measurement ID from: https://analytics.google.com/
|
||||||
|
VITE_GA_MEASUREMENT_ID=
|
||||||
|
|||||||
143
docs/GOOGLE_ANALYTICS_SETUP.md
Normal file
143
docs/GOOGLE_ANALYTICS_SETUP.md
Normal file
@@ -0,0 +1,143 @@
|
|||||||
|
# Google Analytics Setup Guide
|
||||||
|
|
||||||
|
This document explains how to configure Google Analytics 4 (GA4) for spotlight.cam.
|
||||||
|
|
||||||
|
## Prerequisites
|
||||||
|
|
||||||
|
1. Google Analytics account
|
||||||
|
2. GA4 property created for spotlight.cam
|
||||||
|
|
||||||
|
## Setup Instructions
|
||||||
|
|
||||||
|
### 1. Get your Measurement ID
|
||||||
|
|
||||||
|
1. Go to [Google Analytics](https://analytics.google.com/)
|
||||||
|
2. Select your property
|
||||||
|
3. Go to **Admin** (bottom left)
|
||||||
|
4. Under **Property**, click **Data Streams**
|
||||||
|
5. Select your web stream (or create one)
|
||||||
|
6. Copy the **Measurement ID** (format: `G-XXXXXXXXXX`)
|
||||||
|
|
||||||
|
### 2. Configure Environment Variable
|
||||||
|
|
||||||
|
Add your Measurement ID to the `.env` file in the project root:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
VITE_GA_MEASUREMENT_ID=G-XXXXXXXXXX
|
||||||
|
```
|
||||||
|
|
||||||
|
Replace `G-XXXXXXXXXX` with your actual Measurement ID.
|
||||||
|
|
||||||
|
### 3. Restart the Application
|
||||||
|
|
||||||
|
For the changes to take effect:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Rebuild and restart the frontend container
|
||||||
|
docker compose up -d --build frontend
|
||||||
|
```
|
||||||
|
|
||||||
|
## How It Works
|
||||||
|
|
||||||
|
### Automatic Tracking
|
||||||
|
|
||||||
|
The application automatically tracks:
|
||||||
|
|
||||||
|
- **Page views**: Every route change is tracked
|
||||||
|
- **User consent**: GA only loads if user accepts cookies
|
||||||
|
- **Custom events**: See below for list of tracked events
|
||||||
|
|
||||||
|
### Privacy & GDPR Compliance
|
||||||
|
|
||||||
|
- GA script loads **only after** user accepts cookies
|
||||||
|
- If user declines cookies, no tracking occurs
|
||||||
|
- Consent is stored in `localStorage`
|
||||||
|
|
||||||
|
### Custom Events
|
||||||
|
|
||||||
|
The following custom events are tracked:
|
||||||
|
|
||||||
|
| Event | Description | Parameters |
|
||||||
|
|-------|-------------|------------|
|
||||||
|
| `login` | User logs in | `method` |
|
||||||
|
| `sign_up` | User registers | `method` |
|
||||||
|
| `match_request` | User sends match request | `event_slug` |
|
||||||
|
| `match_accepted` | User accepts match | `match_slug` |
|
||||||
|
| `webrtc_connection` | WebRTC connection status | `status` |
|
||||||
|
| `file_transfer` | File transfer via WebRTC | `file_size`, `success` |
|
||||||
|
| `event_join` | User joins event | `event_slug` |
|
||||||
|
| `recording_suggestion` | Recording suggestion action | `action` |
|
||||||
|
| `search` | User searches | `search_term` |
|
||||||
|
|
||||||
|
### Using Analytics in Code
|
||||||
|
|
||||||
|
Import and use the tracking functions:
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
import { trackEvent, trackLogin, trackMatchRequest } from './utils/analytics';
|
||||||
|
|
||||||
|
// Track custom event
|
||||||
|
trackEvent('button_click', { button_name: 'register' });
|
||||||
|
|
||||||
|
// Track login
|
||||||
|
trackLogin('email');
|
||||||
|
|
||||||
|
// Track match request
|
||||||
|
trackMatchRequest('event-slug-123');
|
||||||
|
```
|
||||||
|
|
||||||
|
## Debugging
|
||||||
|
|
||||||
|
### Check if GA is loaded
|
||||||
|
|
||||||
|
Open browser console and run:
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
console.log(window.gtag); // Should be a function
|
||||||
|
console.log(window.dataLayer); // Should be an array
|
||||||
|
```
|
||||||
|
|
||||||
|
### Check cookie consent
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
console.log(localStorage.getItem('cookieConsent')); // 'accepted' or 'declined'
|
||||||
|
```
|
||||||
|
|
||||||
|
### View GA events in real-time
|
||||||
|
|
||||||
|
1. Go to Google Analytics
|
||||||
|
2. Navigate to **Reports** → **Realtime**
|
||||||
|
3. You should see your page views and events as they happen
|
||||||
|
|
||||||
|
## Troubleshooting
|
||||||
|
|
||||||
|
### GA not loading
|
||||||
|
|
||||||
|
1. Check that `VITE_GA_MEASUREMENT_ID` is set in `.env`
|
||||||
|
2. Verify user accepted cookies (check browser localStorage)
|
||||||
|
3. Check browser console for errors
|
||||||
|
4. Ensure frontend container was rebuilt after adding env variable
|
||||||
|
|
||||||
|
### Events not showing up
|
||||||
|
|
||||||
|
1. Wait 24-48 hours for events to appear in standard reports
|
||||||
|
2. Use **Realtime** view for immediate feedback
|
||||||
|
3. Check that events are being sent: `console.log(window.dataLayer)`
|
||||||
|
|
||||||
|
### Ad blockers
|
||||||
|
|
||||||
|
Note: Users with ad blockers may block Google Analytics. This is expected behavior.
|
||||||
|
|
||||||
|
## Production Deployment
|
||||||
|
|
||||||
|
When deploying to production:
|
||||||
|
|
||||||
|
1. Set `VITE_GA_MEASUREMENT_ID` in production environment
|
||||||
|
2. Verify tracking works in production domain
|
||||||
|
3. Update GA4 property settings if domain changes
|
||||||
|
4. Configure data retention and user deletion settings in GA
|
||||||
|
|
||||||
|
## References
|
||||||
|
|
||||||
|
- [GA4 Documentation](https://developers.google.com/analytics/devguides/collection/ga4)
|
||||||
|
- [GDPR Compliance](https://support.google.com/analytics/answer/9019185)
|
||||||
@@ -1,6 +1,9 @@
|
|||||||
import { BrowserRouter, Routes, Route, Navigate } from 'react-router-dom';
|
import { BrowserRouter, Routes, Route, Navigate } from 'react-router-dom';
|
||||||
import { Toaster } from 'react-hot-toast';
|
import { Toaster } from 'react-hot-toast';
|
||||||
|
import { useEffect } from 'react';
|
||||||
import { AuthProvider, useAuth } from './contexts/AuthContext';
|
import { AuthProvider, useAuth } from './contexts/AuthContext';
|
||||||
|
import { initGA } from './utils/analytics';
|
||||||
|
import usePageTracking from './hooks/usePageTracking';
|
||||||
import HomePage from './pages/HomePage';
|
import HomePage from './pages/HomePage';
|
||||||
import LoginPage from './pages/LoginPage';
|
import LoginPage from './pages/LoginPage';
|
||||||
import RegisterPage from './pages/RegisterPage';
|
import RegisterPage from './pages/RegisterPage';
|
||||||
@@ -71,10 +74,30 @@ const PublicRoute = ({ children }) => {
|
|||||||
return 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() {
|
function App() {
|
||||||
return (
|
return (
|
||||||
<BrowserRouter>
|
<BrowserRouter>
|
||||||
<AuthProvider>
|
<AuthProvider>
|
||||||
|
<AnalyticsWrapper>
|
||||||
{/* PWA Install Prompt */}
|
{/* PWA Install Prompt */}
|
||||||
<InstallPWA />
|
<InstallPWA />
|
||||||
|
|
||||||
@@ -246,6 +269,7 @@ function App() {
|
|||||||
{/* 404 Not Found - Catch all unmatched routes */}
|
{/* 404 Not Found - Catch all unmatched routes */}
|
||||||
<Route path="*" element={<NotFoundPage />} />
|
<Route path="*" element={<NotFoundPage />} />
|
||||||
</Routes>
|
</Routes>
|
||||||
|
</AnalyticsWrapper>
|
||||||
</AuthProvider>
|
</AuthProvider>
|
||||||
</BrowserRouter>
|
</BrowserRouter>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { useState, useEffect } from 'react';
|
import { useState, useEffect } from 'react';
|
||||||
import { Cookie, X } from 'lucide-react';
|
import { Cookie, X } from 'lucide-react';
|
||||||
|
import { initGA } from '../../utils/analytics';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* GDPR/RODO compliant cookie consent banner
|
* GDPR/RODO compliant cookie consent banner
|
||||||
@@ -20,6 +21,12 @@ const CookieConsent = () => {
|
|||||||
const handleAccept = () => {
|
const handleAccept = () => {
|
||||||
localStorage.setItem('cookieConsent', 'accepted');
|
localStorage.setItem('cookieConsent', 'accepted');
|
||||||
setIsVisible(false);
|
setIsVisible(false);
|
||||||
|
|
||||||
|
// Initialize Google Analytics after consent
|
||||||
|
const measurementId = import.meta.env.VITE_GA_MEASUREMENT_ID;
|
||||||
|
if (measurementId) {
|
||||||
|
setTimeout(() => initGA(measurementId), 500);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleDecline = () => {
|
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