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)
119 lines
3.2 KiB
TypeScript
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;
|