Cookie Consent 4
Cookie ConsentModal

Code
relume-cookie4.tsx
"use client";
import { useState, useEffect } from "react";
import { AnimatePresence, motion } from "motion/react";
import { Button } from "@/components/ui/button";
import { Checkbox } from "@/components/ui/checkbox";
import { Dialog, DialogContent, DialogTrigger } from "@/components/ui/dialog";
import { Label } from "@/components/ui/label";
import { Close, Cookie } from "relume-icons";
type CookieConsent = {
version: string;
essential: true;
marketing: boolean;
personalization: boolean;
analytics: boolean;
timestamp: number;
};
type CookieBannerProps = {
visible: boolean;
onAccept: () => void;
onDecline: () => void;
onPreferences: () => void;
onClose: () => void;
};
const CONSENT_VERSION = "1.0";
const getDefaultConsent = (): CookieConsent => ({
version: CONSENT_VERSION,
essential: true,
marketing: false,
personalization: false,
analytics: false,
timestamp: Date.now(),
});
const validateConsent = (consent: unknown): consent is CookieConsent => {
if (typeof consent !== "object" || consent === null) return false;
const requiredKeys = [
"version",
"essential",
"marketing",
"personalization",
"analytics",
"timestamp",
];
return (
requiredKeys.every((key) => key in consent) &&
typeof (consent as CookieConsent).version === "string" &&
typeof (consent as CookieConsent).essential === "boolean" &&
typeof (consent as CookieConsent).marketing === "boolean" &&
typeof (consent as CookieConsent).personalization === "boolean" &&
typeof (consent as CookieConsent).analytics === "boolean" &&
typeof (consent as CookieConsent).timestamp === "number"
);
};
export const Cookie4 = () => {
const [dialogOpen, setDialogOpen] = useState(false);
const [bannerVisible, setBannerVisible] = useState(false);
const [showPreferencesButton, setShowPreferencesButton] = useState(false);
const [consent, setConsent] = useState<CookieConsent>(getDefaultConsent());
const [hasConsentBeenSet, setHasConsentBeenSet] = useState(false);
const [isBannerDismissed, setIsBannerDismissed] = useState(false);
const applyCookieSettings = async (newConsent: CookieConsent): Promise<boolean> => {
try {
// Apply essential cookies (always enabled)
// Add your essential cookies logic here
// Apply optional cookies based on consent
if (newConsent.marketing) {
// Enable marketing cookies
// Add your marketing cookies logic here
}
if (newConsent.analytics) {
// Enable analytics cookies
// Add your analytics cookies logic here
}
if (newConsent.personalization) {
// Enable personalization cookies
// Add your personalization cookies logic here
}
return true;
} catch (error) {
console.error("Error applying cookie settings:", error);
return false;
}
};
const handleConsentUpdate = async (newConsent: CookieConsent) => {
const success = await applyCookieSettings(newConsent);
if (success) {
try {
localStorage.setItem("cookieConsent", JSON.stringify(newConsent));
setConsent(newConsent);
setBannerVisible(false);
setShowPreferencesButton(true);
setHasConsentBeenSet(true);
} catch (error) {
console.error("Error saving cookie consent:", error);
}
} else {
console.error("Failed to apply cookie settings");
}
};
useEffect(() => {
setDialogOpen(false);
try {
const savedConsent = localStorage.getItem("cookieConsent");
if (savedConsent) {
const parsed = JSON.parse(savedConsent);
if (validateConsent(parsed)) {
if (parsed.version !== CONSENT_VERSION) {
setConsent(getDefaultConsent());
setBannerVisible(true);
setHasConsentBeenSet(false);
return;
}
setConsent(parsed);
setBannerVisible(false);
setShowPreferencesButton(true);
setHasConsentBeenSet(true);
} else {
throw new Error("Invalid consent structure");
}
} else {
setConsent(getDefaultConsent());
setBannerVisible(true);
}
} catch (error) {
console.error("Error loading cookie consent:", error);
localStorage.removeItem("cookieConsent");
setConsent(getDefaultConsent());
setBannerVisible(true);
}
}, []);
const handleSubmit = async (event: React.FormEvent<HTMLFormElement>) => {
event.preventDefault();
const newConsent = {
...consent,
timestamp: Date.now(),
};
await handleConsentUpdate(newConsent);
setDialogOpen(false);
};
const handleAcceptAll = async () => {
const newConsent: CookieConsent = {
...getDefaultConsent(),
marketing: true,
personalization: true,
analytics: true,
timestamp: Date.now(),
};
await handleConsentUpdate(newConsent);
setDialogOpen(false);
};
const handleRejectAll = async () => {
const newConsent: CookieConsent = {
...getDefaultConsent(),
timestamp: Date.now(),
};
await handleConsentUpdate(newConsent);
setDialogOpen(false);
};
const handleDialogOpenChange = (open: boolean) => {
setDialogOpen(open);
if (!open && !hasConsentBeenSet) {
setBannerVisible(!isBannerDismissed);
setShowPreferencesButton(isBannerDismissed);
}
};
const handlePreferences = () => {
setBannerVisible(false);
setDialogOpen(true);
};
const handleCloseBanner = () => {
setBannerVisible(false);
setIsBannerDismissed(true);
setShowPreferencesButton(true);
};
return (
<section>
<Dialog open={dialogOpen} onOpenChange={handleDialogOpenChange}>
<AnimatePresence>
{showPreferencesButton && (
<motion.div
initial={{ opacity: 0, y: 50 }}
animate={{ opacity: 1, y: 0 }}
exit={{ opacity: 0, y: 50 }}
transition={{ type: "spring", stiffness: 100, damping: 20 }}
className="fixed bottom-4 left-4 md:bottom-8 md:left-8"
>
<DialogTrigger asChild>
<Button
variant="link"
size="link"
iconLeft={<Cookie className="size-6 text-scheme-text" />}
>
Cookie preferences
</Button>
</DialogTrigger>
</motion.div>
)}
</AnimatePresence>
<DialogContent
closeIconPosition="inside"
overlayClassName="bg-neutral-darkest/60"
className="h-fit max-h-[70vh] w-[90%] max-w-lg overflow-scroll bg-white p-8 sm:w-[95%] md:p-10 lg:w-full lg:p-12"
>
<form onSubmit={handleSubmit}>
<h2 className="mb-6 text-center text-h4 font-bold">Privacy Preferences</h2>
<div className="grid grid-cols-1 items-start gap-y-6 py-6">
<div className="flex items-center justify-between">
<Label className="text-h6 font-bold">Essential</Label>
<p className="font-semibold">Required</p>
</div>
<div className="h-px w-full bg-scheme-border" />
<div className="flex items-center justify-between">
<Label className="text-h6 font-bold">Marketing cookies</Label>
<Checkbox
id="marketing"
checked={consent.marketing}
onCheckedChange={(checked: boolean) =>
setConsent((prev) => ({ ...prev, marketing: checked }))
}
/>
</div>
<div className="h-px w-full bg-scheme-border" />
<div className="flex items-center justify-between">
<Label className="text-h6 font-bold">Personalization cookies</Label>
<Checkbox
id="personalization"
checked={consent.personalization}
onCheckedChange={(checked: boolean) =>
setConsent((prev) => ({ ...prev, personalization: checked }))
}
/>
</div>
<div className="h-px w-full bg-scheme-border" />
<div className="flex items-center justify-between">
<Label className="text-h6 font-bold">Analytics cookies</Label>
<Checkbox
id="analytics"
checked={consent.analytics}
onCheckedChange={(checked: boolean) =>
setConsent((prev) => ({ ...prev, analytics: checked }))
}
/>
</div>
</div>
<div className="h-px w-full bg-scheme-border" />
<div className="mt-8 flex flex-col gap-4">
<div className="flex flex-wrap gap-4 md:flex-nowrap">
<Button onClick={handleRejectAll} variant="secondary" className="w-full">
Reject all cookies
</Button>
<Button onClick={handleAcceptAll} className="w-full">
Accept all cookies
</Button>
</div>
<Button type="submit" className="w-full">
Save preferences
</Button>
</div>
</form>
</DialogContent>
</Dialog>
<CookieBanner
visible={bannerVisible}
onAccept={handleAcceptAll}
onDecline={handleRejectAll}
onPreferences={handlePreferences}
onClose={handleCloseBanner}
/>
</section>
);
};
const CookieBanner = ({
visible,
onAccept,
onDecline,
onPreferences,
onClose,
}: CookieBannerProps) => {
if (!visible) return null;
return (
<AnimatePresence>
{visible && (
<motion.div
initial={{ opacity: 0, y: 50 }}
animate={{ opacity: 1, y: 0 }}
exit={{ opacity: 0, y: 50 }}
transition={{ type: "spring", stiffness: 100, damping: 20, duration: 0.3 }}
className="absolute right-8 bottom-8 z-[998] flex w-[calc(100%-4rem)] max-w-md items-start justify-between gap-4 border border-scheme-border bg-scheme-background px-6 py-8 md:pr-12 md:pl-8"
>
<div className="flex flex-col flex-wrap gap-9 md:gap-8 lg:flex-nowrap">
<p className="pr-4">
By clicking "Accept all cookies", you agree to the storing of cookies on your device
to enhance site navigation, analyze site usage, and assist in our marketing efforts.
View our{" "}
<a href="#" className="underline">
Privacy Policy
</a>{" "}
for more information.
</p>
<div className="grid grid-cols-1 items-center justify-center gap-4">
<div className="grid w-full grid-cols-1 gap-4 md:grid-cols-2">
<Button onClick={onDecline}>Reject all cookies</Button>
<Button onClick={onAccept}>Accept all cookies</Button>
</div>
<Button variant="secondary" onClick={onPreferences} className="w-full">
Manage preferences
</Button>
</div>
</div>
<Button variant="link" size="link" onClick={onClose} className="absolute top-2 right-2">
<Close className="size-6" />
</Button>
</motion.div>
)}
</AnimatePresence>
);
};npm i @radix-ui/react-checkbox @radix-ui/react-dialog @radix-ui/react-label @radix-ui/react-radio-group @radix-ui/react-select @radix-ui/react-slot @radix-ui/react-switch class-variance-authority@0.7.1 clsx@^2.1.1 motion@^12.15.0 relume-icons@1.3.0 tailwind-merge@^2.2.2


