118 lines
3.5 KiB
TypeScript
118 lines
3.5 KiB
TypeScript
import React, { useEffect, useRef, useState } from 'react';
|
|
import { useLocation } from 'react-router-dom';
|
|
import privacyService, {
|
|
PublicPrivacyConfig,
|
|
} from '../../services/api/privacyService';
|
|
import { useCookieConsent } from '../../contexts/CookieConsentContext';
|
|
|
|
declare global {
|
|
interface Window {
|
|
dataLayer: any[];
|
|
gtag: (...args: any[]) => void;
|
|
fbq: (...args: any[]) => void;
|
|
}
|
|
}
|
|
|
|
const AnalyticsLoader: React.FC = () => {
|
|
const location = useLocation();
|
|
const { consent } = useCookieConsent();
|
|
const [config, setConfig] = useState<PublicPrivacyConfig | null>(null);
|
|
const gaLoadedRef = useRef(false);
|
|
const fbLoadedRef = useRef(false);
|
|
|
|
// Load public privacy config once
|
|
useEffect(() => {
|
|
let mounted = true;
|
|
const loadConfig = async () => {
|
|
try {
|
|
const cfg = await privacyService.getPublicConfig();
|
|
if (!mounted) return;
|
|
setConfig(cfg);
|
|
} catch {
|
|
// Fail silently in production; analytics are non-critical
|
|
}
|
|
};
|
|
void loadConfig();
|
|
return () => {
|
|
mounted = false;
|
|
};
|
|
}, []);
|
|
|
|
// Load Google Analytics when allowed
|
|
useEffect(() => {
|
|
if (!config || !consent) return;
|
|
const measurementId = config.integrations.ga_measurement_id;
|
|
const analyticsAllowed =
|
|
config.policy.analytics_enabled && consent.categories.analytics;
|
|
if (!measurementId || !analyticsAllowed || gaLoadedRef.current) return;
|
|
|
|
// Inject GA script
|
|
const script = document.createElement('script');
|
|
script.async = true;
|
|
script.src = `https://www.googletagmanager.com/gtag/js?id=${encodeURIComponent(
|
|
measurementId
|
|
)}`;
|
|
document.head.appendChild(script);
|
|
|
|
window.dataLayer = window.dataLayer || [];
|
|
function gtag(...args: any[]) {
|
|
window.dataLayer.push(args);
|
|
}
|
|
window.gtag = gtag;
|
|
gtag('js', new Date());
|
|
gtag('config', measurementId, { anonymize_ip: true });
|
|
|
|
gaLoadedRef.current = true;
|
|
|
|
return () => {
|
|
// We don't remove GA script on unmount; typical SPA behaviour is to keep it.
|
|
};
|
|
}, [config, consent]);
|
|
|
|
// Track GA page views on route change
|
|
useEffect(() => {
|
|
if (!gaLoadedRef.current || !config?.integrations.ga_measurement_id) return;
|
|
if (typeof window.gtag !== 'function') return;
|
|
window.gtag('config', config.integrations.ga_measurement_id, {
|
|
page_path: location.pathname + location.search,
|
|
});
|
|
}, [location, config]);
|
|
|
|
// Load Meta Pixel when allowed
|
|
useEffect(() => {
|
|
if (!config || !consent) return;
|
|
const pixelId = config.integrations.fb_pixel_id;
|
|
const marketingAllowed =
|
|
config.policy.marketing_enabled && consent.categories.marketing;
|
|
if (!pixelId || !marketingAllowed || fbLoadedRef.current) return;
|
|
|
|
// Meta Pixel base code
|
|
!(function (f: any, b, e, v, n?, t?, s?) {
|
|
if (f.fbq) return;
|
|
n = f.fbq = function () {
|
|
(n.callMethod ? n.callMethod : n.queue.push).apply(n, arguments);
|
|
};
|
|
if (!f._fbq) f._fbq = n;
|
|
(n as any).push = n;
|
|
(n as any).loaded = true;
|
|
(n as any).version = '2.0';
|
|
(n as any).queue = [];
|
|
t = b.createElement(e);
|
|
t.async = true;
|
|
t.src = 'https://connect.facebook.net/en_US/fbevents.js';
|
|
s = b.getElementsByTagName(e)[0];
|
|
s.parentNode?.insertBefore(t, s);
|
|
})(window, document, 'script');
|
|
|
|
window.fbq('init', pixelId);
|
|
window.fbq('track', 'PageView');
|
|
fbLoadedRef.current = true;
|
|
}, [config, consent]);
|
|
|
|
return null;
|
|
};
|
|
|
|
export default AnalyticsLoader;
|
|
|
|
|