Ship tracking-codes Phase 2: cookie consent banner + gated script injection

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>
This commit is contained in:
Marco
2026-08-01 20:03:11 +00:00
parent c6b12e9d60
commit 70ee518e1d
8 changed files with 327 additions and 75 deletions
+41 -30
View File
@@ -4,7 +4,7 @@ import { useEffect, useRef, useState } from "react";
import Image from "next/image";
import { addToCart, useCart } from "../lib/cart";
import { useCartFly } from "./CartFly";
import { NotifyMeForm } from "./NotifyMeForm";
import { NotifyMeForm, NotifyMeFormReservedSpace } from "./NotifyMeForm";
// Exported so consumers like RelatedProducts.tsx can delay their own
// follow-up UI changes (e.g. swapping out this exact card) until after
@@ -113,35 +113,46 @@ export function AddToCartInlineButton({
))}
</select>
)}
{currentlyOutOfStock ? (
// Replaces the button slot entirely rather than stacking below a
// disabled "Ausverkauft" button — this component's collapsed idle
// state is a single button, same height as the "In den Warenkorb"
// button it replaces, so an out-of-stock card doesn't end up taller
// than its in-stock siblings and stretch their own buttons down
// (plain CSS Grid rows stretch to the tallest card — see
// ProductGrid.tsx's own flex-1-spacer comment on why equal card
// height matters here).
<NotifyMeForm productId={numericId} variantName={variants.length > 0 ? (selectedVariant ?? "") : ""} />
) : (
<button
ref={buttonRef}
type="button"
onClick={handleClick}
disabled={disabled}
className={`${base} ${stateClasses}`}
>
<span
className={
"text-body-sm transition-colors " +
(disabled ? "text-text-muted" : added ? "font-semibold text-success" : "text-text-primary")
}
>
{limitReached ? "Maximale Menge im Warenkorb" : added ? "Hinzugefügt ✓" : label}
</span>
<Image alt="" src="/icon-cart-outline.png" width={32} height={30} className="h-[1.875rem] w-8 object-contain" />
</button>
)}
{/* grid + [grid-area:1/1] stack — NotifyMeFormReservedSpace (an
invisible, non-interactive twin of NotifyMeForm's markup) always
contributes its height here, even for an in-stock card that
never shows the real form. Without it, only out-of-stock cards
would be tall enough to need the input+button, and plain CSS
Grid's row-stretch (ProductGrid.tsx has no explicit row height)
would push every sibling card's button down to match whichever
card in the row happens to be out of stock. */}
<div className="relative grid w-full">
<div className="invisible pointer-events-none [grid-area:1/1]">
<NotifyMeFormReservedSpace />
</div>
{/* self-start, not the stretch default — the button's TOP edge is
what must align across cards (ProductGrid.tsx's flex-1 spacer
pins this whole block to a card's bottom already); the extra
reserved height below a single button just stays blank. */}
<div className="[grid-area:1/1] self-start">
{currentlyOutOfStock ? (
<NotifyMeForm productId={numericId} variantName={variants.length > 0 ? (selectedVariant ?? "") : ""} />
) : (
<button
ref={buttonRef}
type="button"
onClick={handleClick}
disabled={disabled}
className={`${base} ${stateClasses}`}
>
<span
className={
"text-body-sm transition-colors " +
(disabled ? "text-text-muted" : added ? "font-semibold text-success" : "text-text-primary")
}
>
{limitReached ? "Maximale Menge im Warenkorb" : added ? "Hinzugefügt ✓" : label}
</span>
<Image alt="" src="/icon-cart-outline.png" width={32} height={30} className="h-[1.875rem] w-8 object-contain" />
</button>
)}
</div>
</div>
</div>
);
}