123 lines
3.0 KiB
TypeScript
123 lines
3.0 KiB
TypeScript
"use client";
|
|
|
|
import { useEffect, useState } from "react";
|
|
import { Analytics } from "@vercel/analytics/react";
|
|
|
|
const CONSENT_COOKIE_NAME = "gdpr_consent";
|
|
|
|
/**
|
|
* GDPR-Compliant Analytics Wrapper
|
|
*
|
|
* This component wraps Vercel Analytics (or any analytics provider)
|
|
* and only loads it if the user has consented to analytics cookies.
|
|
*
|
|
* Usage in your layout.tsx:
|
|
*
|
|
* import { GDPRAnalytics } from "@/components/GDPRAnalytics";
|
|
*
|
|
* export default function RootLayout({ children }) {
|
|
* return (
|
|
* <html>
|
|
* <body>
|
|
* {children}
|
|
* <GDPRAnalytics />
|
|
* </body>
|
|
* </html>
|
|
* );
|
|
* }
|
|
*/
|
|
export function GDPRAnalytics() {
|
|
const [analyticsEnabled, setAnalyticsEnabled] = useState(false);
|
|
|
|
useEffect(() => {
|
|
const checkConsent = () => {
|
|
const savedConsent = localStorage.getItem(CONSENT_COOKIE_NAME);
|
|
if (savedConsent) {
|
|
try {
|
|
const parsed = JSON.parse(savedConsent);
|
|
setAnalyticsEnabled(parsed.analytics === true);
|
|
} catch {
|
|
setAnalyticsEnabled(false);
|
|
}
|
|
}
|
|
};
|
|
|
|
// Check on mount
|
|
checkConsent();
|
|
|
|
// Listen for consent changes
|
|
const handleStorageChange = (e: StorageEvent) => {
|
|
if (e.key === CONSENT_COOKIE_NAME) {
|
|
checkConsent();
|
|
}
|
|
};
|
|
|
|
window.addEventListener("storage", handleStorageChange);
|
|
return () => window.removeEventListener("storage", handleStorageChange);
|
|
}, []);
|
|
|
|
// Only render Analytics if user has consented
|
|
if (!analyticsEnabled) {
|
|
return null;
|
|
}
|
|
|
|
return <Analytics />;
|
|
}
|
|
|
|
/**
|
|
* Alternative: Google Analytics 4 GDPR wrapper
|
|
*
|
|
* If you use Google Analytics instead of Vercel Analytics,
|
|
* use this component.
|
|
*/
|
|
export function GDPRGoogleAnalytics({ measurementId }: { measurementId: string }) {
|
|
const [analyticsEnabled, setAnalyticsEnabled] = useState(false);
|
|
|
|
useEffect(() => {
|
|
const savedConsent = localStorage.getItem(CONSENT_COOKIE_NAME);
|
|
if (savedConsent) {
|
|
try {
|
|
const parsed = JSON.parse(savedConsent);
|
|
setAnalyticsEnabled(parsed.analytics === true);
|
|
} catch {
|
|
setAnalyticsEnabled(false);
|
|
}
|
|
}
|
|
}, []);
|
|
|
|
useEffect(() => {
|
|
if (!analyticsEnabled) return;
|
|
|
|
// Load Google Analytics script dynamically
|
|
const script = document.createElement("script");
|
|
script.src = `https://www.googletagmanager.com/gtag/js?id=${measurementId}`;
|
|
script.async = true;
|
|
document.head.appendChild(script);
|
|
|
|
// Initialize gtag
|
|
window.dataLayer = window.dataLayer || [];
|
|
function gtag(...args: unknown[]) {
|
|
window.dataLayer.push(args);
|
|
}
|
|
gtag("js", new Date());
|
|
gtag("config", measurementId, {
|
|
anonymize_ip: true, // GDPR requirement
|
|
cookie_flags: "SameSite=None;Secure",
|
|
});
|
|
|
|
return () => {
|
|
document.head.removeChild(script);
|
|
};
|
|
}, [analyticsEnabled, measurementId]);
|
|
|
|
return null;
|
|
}
|
|
|
|
// Type declaration for Google Analytics
|
|
declare global {
|
|
interface Window {
|
|
dataLayer: unknown[];
|
|
gtag?: (...args: unknown[]) => void;
|
|
}
|
|
}
|