70 lines
2.3 KiB
TypeScript
70 lines
2.3 KiB
TypeScript
|
|
'use client';
|
||
|
|
|
||
|
|
import Link from 'next/link';
|
||
|
|
import { useEffect, useState } from 'react';
|
||
|
|
|
||
|
|
const STORAGE_KEY = 'bmm-cookie-ack-v1';
|
||
|
|
|
||
|
|
/**
|
||
|
|
* BMM uses only strictly-necessary cookies (session + OAuth CSRF state). Under
|
||
|
|
* Swiss DSG and GDPR strictly-necessary cookies do not require opt-in consent,
|
||
|
|
* only clear disclosure — this banner satisfies the disclosure obligation
|
||
|
|
* without dark-pattern cookie walls or false-choice "Reject all" UIs.
|
||
|
|
*/
|
||
|
|
export function CookieBanner() {
|
||
|
|
const [show, setShow] = useState(false);
|
||
|
|
|
||
|
|
useEffect(() => {
|
||
|
|
try {
|
||
|
|
const ack = window.localStorage.getItem(STORAGE_KEY);
|
||
|
|
if (!ack) setShow(true);
|
||
|
|
} catch {
|
||
|
|
// localStorage blocked (private mode etc.) — show banner anyway, it's
|
||
|
|
// dismissable via a single click and never persists if storage fails.
|
||
|
|
setShow(true);
|
||
|
|
}
|
||
|
|
}, []);
|
||
|
|
|
||
|
|
function acknowledge() {
|
||
|
|
try {
|
||
|
|
window.localStorage.setItem(STORAGE_KEY, new Date().toISOString());
|
||
|
|
} catch {
|
||
|
|
/* ignore — re-shown on next visit */
|
||
|
|
}
|
||
|
|
setShow(false);
|
||
|
|
}
|
||
|
|
|
||
|
|
if (!show) return null;
|
||
|
|
|
||
|
|
return (
|
||
|
|
<div
|
||
|
|
role="dialog"
|
||
|
|
aria-label="Cookie notice"
|
||
|
|
className="fixed inset-x-0 bottom-0 z-50 border-t border-[--color-border] backdrop-blur-md"
|
||
|
|
style={{
|
||
|
|
backgroundColor: 'color-mix(in oklab, var(--color-bg-elevated) 92%, transparent)',
|
||
|
|
paddingBottom: 'max(env(safe-area-inset-bottom), 0.75rem)',
|
||
|
|
}}
|
||
|
|
>
|
||
|
|
<div className="mx-auto flex max-w-6xl flex-col gap-3 px-5 pt-3 sm:flex-row sm:items-center sm:gap-4 sm:px-6">
|
||
|
|
<p className="flex-1 text-[12.5px] leading-relaxed text-[--color-fg-muted]">
|
||
|
|
We use strictly-necessary cookies for login (session token) and CSRF
|
||
|
|
protection. No tracking, no analytics, no third-party cookies on this
|
||
|
|
domain. Details:{' '}
|
||
|
|
<Link href="/privacy" className="text-[--color-accent] hover:underline">
|
||
|
|
privacy policy
|
||
|
|
</Link>
|
||
|
|
.
|
||
|
|
</p>
|
||
|
|
<button
|
||
|
|
type="button"
|
||
|
|
onClick={acknowledge}
|
||
|
|
className="inline-flex h-9 shrink-0 items-center justify-center rounded-md bg-[--color-accent] px-4 text-[13px] font-medium text-white transition-colors hover:bg-[#5557e8]"
|
||
|
|
>
|
||
|
|
OK
|
||
|
|
</button>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
);
|
||
|
|
}
|