"use client"; // ============================================================================= // CookieBanner // ============================================================================= // // A small, non-blocking cookie / consent banner shown on first visit. // Two buttons: ACK (accept) / RST (reject) — network-engineer humour. // // Behaviour: // - On first load, if no consent decision is stored, the banner appears. // - "ACK" stores `novarix-cookie-consent = "ack"` in localStorage and the // banner closes. The site then behaves as before (intro animation // remembers it has played using sessionStorage). // - "RST" stores `novarix-cookie-consent = "rst"` in localStorage and the // banner closes. We also clear the intro-seen flag so we don't keep any // non-consent storage around. // - The footer "Cookie preferences" link dispatches a window event that // re-opens this banner so users can change their mind. // // All visible text comes from `/content.ts` -> site.cookies. // ============================================================================= import Link from "next/link"; import { useEffect, useState } from "react"; import { site } from "@/content"; const CONSENT_KEY = "novarix-cookie-consent"; const INTRO_SEEN_KEY = "novarix-intro-seen"; const REOPEN_EVENT = "novarix:open-cookie-banner"; type Consent = "unknown" | "ack" | "rst"; export default function CookieBanner() { // Start hidden on the server and on first client paint to avoid a flash. const [visible, setVisible] = useState(false); // Read the stored consent on mount and decide whether to show the banner. useEffect(() => { try { const stored = window.localStorage.getItem(CONSENT_KEY) as Consent | null; if (stored !== "ack" && stored !== "rst") { // No decision yet — show the banner. We delay slightly so the page // settles in first; feels less aggressive than appearing instantly. const t = window.setTimeout(() => setVisible(true), 600); return () => window.clearTimeout(t); } } catch { // localStorage unavailable (private mode, blocked, etc.) — show the // banner so the user is still told. Sync-once-on-mount is a legitimate // use of setState in an effect. // eslint-disable-next-line react-hooks/set-state-in-effect setVisible(true); } }, []); // Listen for the "re-open" event from the footer "Cookie preferences" link. useEffect(() => { const handler = () => setVisible(true); window.addEventListener(REOPEN_EVENT, handler); return () => window.removeEventListener(REOPEN_EVENT, handler); }, []); function decide(choice: "ack" | "rst") { try { window.localStorage.setItem(CONSENT_KEY, choice); if (choice === "rst") { // Honour the rejection by clearing any non-consent storage. window.sessionStorage.removeItem(INTRO_SEEN_KEY); } } catch { /* storage unavailable — nothing to do */ } setVisible(false); } if (!visible) return null; return (
{site.cookies.message}{" "} {site.cookies.policyLabel} →
{/* Buttons */}