70ee518e1d
CookieBanner.tsx (equally-weighted Akzeptieren/Ablehnen, TTDSG) +
useConsent() cookie hook + TrackingScripts.tsx render active
tracking-codes rows only once their consentCategory is actually
accepted ('necessary' always renders). Wired into layout.tsx via the
new getTrackingCodes() fetcher. This is what makes the Phase 1
backend collection (tracking-codes) actually usable end to end.
Also reworks NotifyMeForm back to always-visible input+button (better
UX than a collapse-to-reveal step) — the resulting taller CTA is now
reserved on every card via NotifyMeFormReservedSpace, an invisible
twin rendered behind the real button, so an in-stock card's row
height matches an out-of-stock sibling's without the grid's
row-stretch pushing buttons out of alignment.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
77 lines
3.7 KiB
TypeScript
77 lines
3.7 KiB
TypeScript
"use client";
|
|
|
|
import Script from "next/script";
|
|
import { useConsent } from "../lib/useConsent";
|
|
import type { TrackingCode } from "../lib/payload";
|
|
|
|
// Renders one next/script per active tracking code, but only once consent
|
|
// actually allows it — `necessary` always renders (nothing in that
|
|
// category exists yet, but the field exists for future use, e.g. a
|
|
// consent-management/CMP script itself); `analytics`/`marketing` both gate
|
|
// on the same single useConsent() flag (see that hook's own comment on why
|
|
// there's no separate marketing toggle yet). Fetched server-side
|
|
// (getTrackingCodes(), passed in as a prop from layout.tsx) since this
|
|
// component itself is a client component and can't call that fetcher
|
|
// directly without losing the 60s ISR cache.
|
|
export function TrackingScripts({ codes }: { codes: TrackingCode[] }) {
|
|
const { consent, loaded } = useConsent();
|
|
if (!loaded) return null;
|
|
|
|
const allowed = codes.filter((code) => code.consentCategory === "necessary" || consent?.analytics === true);
|
|
|
|
return (
|
|
<>
|
|
{allowed.map((code) => {
|
|
if (code.provider === "google-analytics" && code.measurementId) {
|
|
return (
|
|
<div key={code.id}>
|
|
<Script src={`https://www.googletagmanager.com/gtag/js?id=${code.measurementId}`} strategy="afterInteractive" />
|
|
<Script id={`ga-init-${code.id}`} strategy="afterInteractive">
|
|
{`window.dataLayer = window.dataLayer || [];
|
|
function gtag(){dataLayer.push(arguments);}
|
|
gtag('js', new Date());
|
|
gtag('config', '${code.measurementId}');`}
|
|
</Script>
|
|
</div>
|
|
);
|
|
}
|
|
if (code.provider === "facebook-pixel" && code.pixelId) {
|
|
return (
|
|
<Script key={code.id} id={`fb-pixel-${code.id}`} strategy="afterInteractive">
|
|
{`!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');`}
|
|
</Script>
|
|
);
|
|
}
|
|
if (code.provider === "google-tag-manager" && code.containerId) {
|
|
return (
|
|
<Script key={code.id} id={`gtm-${code.id}`} strategy="afterInteractive">
|
|
{`(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}');`}
|
|
</Script>
|
|
);
|
|
}
|
|
// 'other' — the only provider whose script content is admin-
|
|
// supplied rather than a fixed loader snippet. Trusted deliberately
|
|
// (see TrackingCodes.ts's own admin.description) — only reachable
|
|
// by someone with backend admin access in the first place.
|
|
if (code.provider === "other" && code.customScript) {
|
|
return <Script key={code.id} id={`custom-${code.id}`} strategy="afterInteractive" dangerouslySetInnerHTML={{ __html: code.customScript }} />;
|
|
}
|
|
return null;
|
|
})}
|
|
</>
|
|
);
|
|
}
|