Files
Marco ac6c3d6b71 Add GoogleMapsEmbed.tsx — consent-gated Maps embed, prepared but unused
Uses Klaro's own built-in contextual-consent mechanism (not
loadTrackingCode.ts's script-injection path) — the iframe renders
with data-name="google-maps" and its real src; Klaro's DOM scan finds
it after mount and blanks/restores src based on that service's
consent state, showing its own placeholder (styled via
KlaroConsentManager.tsx's KlaroTheme) in the meantime.

No map embedded anywhere in the app yet — this is prep work only, per
explicit request ahead of an actual page needing one.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-01 21:58:19 +00:00

82 lines
3.3 KiB
TypeScript

import type { TrackingCode } from "./payload";
// Imperative <script> injection, not next/script — this fires from
// KlaroConsentManager.tsx's per-service `callback(consent)`, i.e. at an
// arbitrary point after mount (whenever a visitor actually accepts), not
// declaratively on every render. Idempotent via the DOM id check, since
// Klaro's `onlyOnce: true` (see klaroConfig.ts) already tries to guarantee
// a single callback-with-consent-true per page load, but this is a cheap
// second guard against ever injecting the same script twice.
function injectScript(id: string, src?: string, inlineJs?: string) {
if (document.getElementById(id)) return;
const script = document.createElement("script");
script.id = id;
script.async = true;
if (src) script.src = src;
else script.textContent = inlineJs ?? "";
document.head.appendChild(script);
}
// One loader per TrackingCodes.provider (google-analytics/facebook-pixel/
// google-tag-manager/other) — same fixed loader snippets per provider as
// the Payload backend's own admin.description promises, 'other' is the
// only one whose content is admin-supplied rather than a fixed snippet
// (trusted deliberately, see TrackingCodes.ts on the backend).
export function loadTrackingCode(code: TrackingCode): void {
// No-op — Google Maps isn't a script this app injects. Klaro's own
// contextual-consent DOM scan handles any `data-name="google-maps"`
// iframe (GoogleMapsEmbed.tsx) independently, purely off the service's
// consent state — see klaroConfig.ts's own comment on this service.
if (code.provider === "google-maps") return;
const baseId = `tracking-code-${code.id}`;
if (code.provider === "google-analytics" && code.measurementId) {
injectScript(`${baseId}-loader`, `https://www.googletagmanager.com/gtag/js?id=${code.measurementId}`);
injectScript(
`${baseId}-init`,
undefined,
`window.dataLayer = window.dataLayer || [];
function gtag(){dataLayer.push(arguments);}
gtag('js', new Date());
gtag('config', '${code.measurementId}');`,
);
return;
}
if (code.provider === "facebook-pixel" && code.pixelId) {
injectScript(
baseId,
undefined,
`!function(f,b,e,v,n,t,s)
{if(f.fbq)return;n=f.fbq=function(){n.callMethod?
n.callMethod.apply(n,arguments):n.queue.push(arguments)};
if(!f._fbq)f._fbq=n;n.push=n;n.loaded=!0;n.version='2.0';
n.queue=[];t=b.createElement(e);t.async=!0;
t.src=v;s=b.getElementsByTagName(e)[0];
s.parentNode.insertBefore(t,s)}(window, document,'script',
'https://connect.facebook.net/en_US/fbevents.js');
fbq('init', '${code.pixelId}');
fbq('track', 'PageView');`,
);
return;
}
if (code.provider === "google-tag-manager" && code.containerId) {
injectScript(
baseId,
undefined,
`(function(w,d,s,l,i){w[l]=w[l]||[];w[l].push({'gtm.start':
new Date().getTime(),event:'gtm.js'});var f=d.getElementsByTagName(s)[0],
j=d.createElement(s),dl=l!='dataLayer'?'&l='+l:'';j.async=true;j.src=
'https://www.googletagmanager.com/gtm.js?id='+i+dl;f.parentNode.insertBefore(j,f);
})(window,document,'script','dataLayer','${code.containerId}');`,
);
return;
}
if (code.provider === "other" && code.customScript) {
injectScript(baseId, undefined, code.customScript);
}
}