ui(consent): persist cookie-banner choice in a cookie (365-day) + localStorage
Previously the banner only wrote bb_cookie_consent to localStorage. Many browsers wipe localStorage in strict-tracking-prevention modes, on "clear site data on close", and in some private-browsing-adjacent profiles — so the banner re-appeared on every visit even after the user clicked Accept or Decline. Now writes to BOTH a `bb_cookie_consent` cookie (path=/, max-age=365d, SameSite=Lax) and localStorage. Reads from cookie first, falls back to localStorage for backward compatibility with prior visitors. Banner stays hidden for 365 days after a decision is made. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -2,21 +2,41 @@
|
||||
import React, { useState, useEffect, useRef } from "react";
|
||||
import Link from "next/link";
|
||||
|
||||
const CONSENT_KEY = "bb_cookie_consent";
|
||||
const CONSENT_DAYS = 365;
|
||||
|
||||
function readConsent(): string | null {
|
||||
// Cookie first — survives most "clear localStorage on close" settings.
|
||||
if (typeof document !== "undefined") {
|
||||
const m = document.cookie.match(/(?:^|;\s*)bb_cookie_consent=([^;]+)/);
|
||||
if (m) return decodeURIComponent(m[1]);
|
||||
}
|
||||
try {
|
||||
return localStorage.getItem(CONSENT_KEY);
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
function writeConsent(value: "accepted" | "declined") {
|
||||
// Write to BOTH so we survive browsers that wipe one but not the other.
|
||||
try {
|
||||
const maxAge = CONSENT_DAYS * 24 * 60 * 60;
|
||||
document.cookie = `${CONSENT_KEY}=${encodeURIComponent(value)}; path=/; max-age=${maxAge}; SameSite=Lax`;
|
||||
} catch {}
|
||||
try {
|
||||
localStorage.setItem(CONSENT_KEY, value);
|
||||
} catch {}
|
||||
}
|
||||
|
||||
export default function CookieConsent() {
|
||||
const [visible, setVisible] = useState(false);
|
||||
const acceptRef = useRef<HTMLButtonElement>(null);
|
||||
|
||||
useEffect(() => {
|
||||
try {
|
||||
const consent = localStorage.getItem("bb_cookie_consent");
|
||||
if (!consent) {
|
||||
// Delay slightly so it doesn't flash on first paint
|
||||
const timer = setTimeout(() => setVisible(true), 1200);
|
||||
return () => clearTimeout(timer);
|
||||
}
|
||||
} catch {
|
||||
// localStorage unavailable (SSR or private mode)
|
||||
}
|
||||
if (readConsent()) return; // already decided — stay hidden
|
||||
const timer = setTimeout(() => setVisible(true), 1200);
|
||||
return () => clearTimeout(timer);
|
||||
}, []);
|
||||
|
||||
// Focus the accept button when banner appears (accessibility)
|
||||
@@ -27,16 +47,12 @@ export default function CookieConsent() {
|
||||
}, [visible]);
|
||||
|
||||
const handleAccept = () => {
|
||||
try {
|
||||
localStorage.setItem("bb_cookie_consent", "accepted");
|
||||
} catch {}
|
||||
writeConsent("accepted");
|
||||
setVisible(false);
|
||||
};
|
||||
|
||||
const handleDecline = () => {
|
||||
try {
|
||||
localStorage.setItem("bb_cookie_consent", "declined");
|
||||
} catch {}
|
||||
writeConsent("declined");
|
||||
setVisible(false);
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user