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>
This commit is contained in:
Marco
2026-08-01 21:58:19 +00:00
parent 417bbd430e
commit ac6c3d6b71
6 changed files with 122 additions and 2 deletions
+44
View File
@@ -0,0 +1,44 @@
// Server Component — no "use client" needed. The consent gating itself
// happens entirely client-side inside Klaro (KlaroConsentManager.tsx),
// not via any React state here: Klaro's own contextual-consent DOM scan
// (renderContextualConsentNotices, runs as part of Klaro.render()) finds
// this iframe by its `data-name="google-maps"` attribute after mount,
// blanks its `src` and inserts its own "Karte laden?" placeholder in
// front of it until that service's consent is granted, then restores the
// real `src`. See klaroConfig.ts's own comment on the "google-maps"
// service this depends on — a `tracking-codes` row with that provider
// must exist and be `active`, or Klaro never gates (and never un-blanks)
// this iframe at all.
//
// `width`/`height` are real HTML attributes, not just the Tailwind aspect-
// ratio wrapper below — Klaro reads `element.width`/`element.height` (the
// DOM attributes) to size its own placeholder box before the real iframe
// has rendered, so both need to be present even though the wrapper's
// `aspect-[...]` class is what actually controls layout.
export function GoogleMapsEmbed({
src,
title,
className,
}: {
/** A full Google Maps embed URL (Google Maps → Teilen → Karte einbetten
* → "HTML kopieren" → the `src` attribute of that iframe), e.g.
* "https://www.google.com/maps/embed?pb=...". */
src: string;
title: string;
className?: string;
}) {
return (
<div className={`relative w-full aspect-[16/9] overflow-hidden rounded-md border border-border ${className ?? ""}`}>
<iframe
data-name="google-maps"
src={src}
title={title}
width={800}
height={450}
loading="lazy"
referrerPolicy="no-referrer-when-downgrade"
className="absolute inset-0 h-full w-full border-0"
/>
</div>
);
}
+24
View File
@@ -73,6 +73,30 @@ function KlaroTheme() {
.klaro .cm-btn:hover { opacity: 0.88; }
.klaro .cm-btn:active { transform: scale(0.97); }
.klaro .cm-btn:focus-visible { outline: 2px solid #f6a701 !important; outline-offset: 2px; }
/* The "Karte laden?" placeholder ContextualConsentNotice renders in
place of a gated embed (GoogleMapsEmbed.tsx) — fills that embed's
own aspect-ratio box, so this needs to look like a real card
slot, not a bare unstyled div floating inside it. */
.klaro.cm-as-context-notice {
height: 100% !important;
display: flex !important;
align-items: center !important;
justify-content: center !important;
}
.klaro .context-notice {
width: 100% !important;
height: 100% !important;
display: flex !important;
flex-direction: column !important;
align-items: center !important;
justify-content: center !important;
gap: 4px !important;
padding: 24px !important;
background: #fffdf8 !important;
text-align: center !important;
}
.klaro .context-notice p { text-align: center !important; max-width: 32ch; }
.klaro .context-notice .cm-buttons { display: flex !important; gap: 10px !important; margin-top: 6px !important; }
.klaro a.cm-link, .klaro .cookie-notice a, .klaro .cookie-modal a {
color: #a06b00 !important;
text-decoration: underline !important;
+21
View File
@@ -28,7 +28,16 @@ export type KlaroConfig = {
}[];
};
// "google-maps" is a fixed name, not id-based like every other provider —
// GoogleMapsEmbed.tsx has to hardcode a `data-name` matching this exact
// string for Klaro's own contextual-consent DOM scan
// (renderContextualConsentNotices in node_modules/klaro) to find it, and
// there's no per-row distinguishing ID the way GA/FB/GTM each have one
// (a Maps embed URL is per-page, not stored on this row at all — see
// TrackingCodes.ts's own field comment). Only ever create ONE
// google-maps row in the admin; a second one would collide on this name.
export function serviceName(code: TrackingCode): string {
if (code.provider === "google-maps") return "google-maps";
return `tracking-code-${code.id}`;
}
@@ -40,6 +49,8 @@ function providerTitle(code: TrackingCode): string {
return "Facebook Pixel";
case "google-tag-manager":
return "Google Tag Manager";
case "google-maps":
return "Google Maps";
default:
return "Sonstiges Tracking-Skript";
}
@@ -116,6 +127,7 @@ export function buildKlaroConfig(codes: TrackingCode[], onAccept: (code: Trackin
necessary: "Notwendig",
analytics: "Analyse",
marketing: "Marketing",
content: "Inhalte",
},
...serviceTranslations,
},
@@ -126,6 +138,15 @@ export function buildKlaroConfig(codes: TrackingCode[], onAccept: (code: Trackin
purposes: [code.consentCategory],
required: code.consentCategory === "necessary",
default: code.consentCategory === "necessary",
// For "google-maps", `onAccept` below is a no-op (see
// loadTrackingCode.ts) — an embed's src isn't a script this app
// injects, Klaro's OWN contextual-consent DOM scan
// (renderContextualConsentNotices, runs as part of Klaro.render())
// finds any `data-name="google-maps"` iframe on the page and
// blanks/restores its `src` itself, purely by service name +
// consent state. This service entry's only job is telling Klaro
// that name exists and which purpose gates it.
//
// A restock/page-reload shouldn't re-fire the loader for a service
// the visitor already accepted in an earlier session within this
// same page load — Klaro still calls `callback` once per accepted
+6
View File
@@ -23,6 +23,12 @@ function injectScript(id: string, src?: string, inlineJs?: string) {
// 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) {
+2 -2
View File
@@ -1181,8 +1181,8 @@ export async function getSeoSettings(): Promise<SeoSettings> {
export type TrackingCode = {
id: number;
provider: "google-analytics" | "facebook-pixel" | "google-tag-manager" | "other";
consentCategory: "necessary" | "analytics" | "marketing";
provider: "google-analytics" | "facebook-pixel" | "google-tag-manager" | "google-maps" | "other";
consentCategory: "necessary" | "analytics" | "marketing" | "content";
measurementId: string | null;
pixelId: string | null;
containerId: string | null;