Files
fotospiel-app/resources/js/components/analytics/MatomoTracker.tsx
Codex Agent 6290a3a448 Fix tenant event form package selector so it no longer renders empty-value options, handles loading/empty
states, and pulls data from the authenticated /api/v1/tenant/packages endpoint.
    (resources/js/admin/pages/EventFormPage.tsx, resources/js/admin/api.ts)
  - Harden tenant-admin auth flow: prevent PKCE state loss, scope out StrictMode double-processing, add SPA
    routes for /event-admin/login and /event-admin/logout, and tighten token/session clearing semantics (resources/js/admin/auth/{context,tokens}.tsx, resources/js/admin/pages/{AuthCallbackPage,LogoutPage}.tsx,
    resources/js/admin/router.tsx, routes/web.php)
2025-10-19 23:00:47 +02:00

119 lines
3.2 KiB
TypeScript

import { useEffect } from 'react';
import { usePage } from '@inertiajs/react';
import { useConsent } from '@/contexts/consent';
export type MatomoConfig = {
enabled: boolean;
url?: string;
siteId?: string;
};
declare global {
interface Window {
_paq?: any[];
}
}
interface MatomoTrackerProps {
config: MatomoConfig | undefined;
}
const MatomoTracker: React.FC<MatomoTrackerProps> = ({ config }) => {
const page = usePage();
const { hasConsent } = useConsent();
const scriptNonce = (page.props.security as { csp?: { scriptNonce?: string } } | undefined)?.csp?.scriptNonce;
const analyticsConsent = hasConsent('analytics');
useEffect(() => {
if (!config?.enabled || !config.url || !config.siteId || typeof window === 'undefined') {
return;
}
const base = config.url.replace(/\/$/, '');
const scriptSelector = `script[data-matomo="${base}"]`;
if (!analyticsConsent) {
const existing = document.querySelector<HTMLScriptElement>(scriptSelector);
existing?.remove();
if (window._paq) {
window._paq.length = 0;
}
delete (window as any).__matomoInitialized;
return;
}
window._paq = window._paq || [];
const { _paq } = window;
if (!(window as any).__matomoInitialized) {
_paq.push(['setTrackerUrl', `${base}/matomo.php`]);
_paq.push(['setSiteId', config.siteId]);
_paq.push(['disableCookies']);
_paq.push(['enableLinkTracking']);
if (!document.querySelector(scriptSelector)) {
const script = document.createElement('script');
script.async = true;
script.src = `${base}/matomo.js`;
script.dataset.matomo = base;
if (scriptNonce) {
script.setAttribute('nonce', scriptNonce);
} else if (typeof window !== 'undefined' && (window as any).__CSP_NONCE) {
script.setAttribute('nonce', (window as any).__CSP_NONCE);
} else {
const metaNonce = document
.querySelector('meta[name="csp-nonce"]')
?.getAttribute('content');
if (metaNonce) {
script.setAttribute('nonce', metaNonce);
}
}
document.body.appendChild(script);
}
(window as any).__matomoInitialized = true;
}
}, [config, analyticsConsent]);
useEffect(() => {
if (
!config?.enabled ||
!config.url ||
!config.siteId ||
typeof window === 'undefined' ||
!analyticsConsent
) {
return;
}
window._paq = window._paq || [];
const { _paq } = window;
const currentUrl =
typeof window !== 'undefined' ? `${window.location.origin}${page.url}` : page.url;
_paq.push(['setCustomUrl', currentUrl]);
if (typeof document !== 'undefined') {
_paq.push(['setDocumentTitle', document.title]);
}
_paq.push(['trackPageView']);
}, [config, analyticsConsent, page.url]);
if (!config?.enabled || !config.url || !config.siteId || !analyticsConsent) {
return null;
}
const base = config.url.replace(/\/$/, '');
const noscriptSrc = `${base}/matomo.php?idsite=${encodeURIComponent(config.siteId)}&rec=1`;
return (
<noscript>
<p>
<img src={noscriptSrc} style={{ border: 0 }} alt="" />
</p>
</noscript>
);
};
export default MatomoTracker;