209 lines
9.4 KiB
TypeScript
209 lines
9.4 KiB
TypeScript
import React, { useEffect, useState } from 'react';
|
|
import { useCookieConsent } from '../contexts/CookieConsentContext';
|
|
import { Link } from 'react-router-dom';
|
|
|
|
const CookieConsentBanner: React.FC = () => {
|
|
const { consent, isLoading, hasDecided, updateConsent } = useCookieConsent();
|
|
const [showDetails, setShowDetails] = useState(false);
|
|
const [analyticsChecked, setAnalyticsChecked] = useState(false);
|
|
const [marketingChecked, setMarketingChecked] = useState(false);
|
|
const [preferencesChecked, setPreferencesChecked] = useState(false);
|
|
|
|
useEffect(() => {
|
|
if (consent) {
|
|
setAnalyticsChecked(consent.categories.analytics);
|
|
setMarketingChecked(consent.categories.marketing);
|
|
setPreferencesChecked(consent.categories.preferences);
|
|
}
|
|
}, [consent]);
|
|
|
|
useEffect(() => {
|
|
const handleOpenPreferences = () => {
|
|
setShowDetails(true);
|
|
};
|
|
|
|
window.addEventListener('open-cookie-preferences', handleOpenPreferences);
|
|
return () => {
|
|
window.removeEventListener(
|
|
'open-cookie-preferences',
|
|
handleOpenPreferences
|
|
);
|
|
};
|
|
}, []);
|
|
|
|
if (isLoading || hasDecided) {
|
|
return null;
|
|
}
|
|
|
|
const handleAcceptAll = async () => {
|
|
await updateConsent({
|
|
analytics: true,
|
|
marketing: true,
|
|
preferences: true,
|
|
});
|
|
};
|
|
|
|
const handleRejectNonEssential = async () => {
|
|
await updateConsent({
|
|
analytics: false,
|
|
marketing: false,
|
|
preferences: false,
|
|
});
|
|
};
|
|
|
|
const handleSaveSelection = async () => {
|
|
await updateConsent({
|
|
analytics: analyticsChecked,
|
|
marketing: marketingChecked,
|
|
preferences: preferencesChecked,
|
|
});
|
|
};
|
|
|
|
return (
|
|
<div className="pointer-events-none fixed inset-x-0 bottom-0 z-40 flex justify-center px-4 pb-4 sm:px-6 sm:pb-6">
|
|
<div className="pointer-events-auto relative w-full max-w-4xl overflow-hidden rounded-2xl bg-gradient-to-r from-black/85 via-zinc-900/90 to-black/85 p-[1px] shadow-[0_24px_60px_rgba(0,0,0,0.8)]">
|
|
{}
|
|
<div className="absolute inset-0 rounded-2xl border border-[#d4af37]/40" />
|
|
|
|
{}
|
|
<div className="pointer-events-none absolute -inset-8 bg-[radial-gradient(circle_at_top,_rgba(212,175,55,0.18),_transparent_55%),radial-gradient(circle_at_bottom,_rgba(0,0,0,0.8),_transparent_60%)] opacity-80" />
|
|
|
|
<div className="relative flex flex-col gap-4 bg-gradient-to-br from-zinc-950/80 via-zinc-900/90 to-black/90 px-4 py-4 sm:px-6 sm:py-5 lg:px-8 lg:py-6 sm:flex-row sm:items-start sm:justify-between">
|
|
{}
|
|
<div className="space-y-3 sm:max-w-xl">
|
|
<div className="inline-flex items-center gap-2 rounded-full bg-black/60 px-3 py-1 text-[11px] font-medium uppercase tracking-[0.16em] text-[#d4af37]/90 ring-1 ring-[#d4af37]/30">
|
|
<span className="h-1.5 w-1.5 rounded-full bg-[#d4af37]" />
|
|
Privacy Suite
|
|
</div>
|
|
|
|
<div className="space-y-1.5">
|
|
<h2 className="text-lg font-semibold tracking-wide text-white sm:text-xl">
|
|
A tailored privacy experience
|
|
</h2>
|
|
<p className="text-xs leading-relaxed text-zinc-300 sm:text-sm">
|
|
We use cookies to ensure a seamless booking journey, enhance performance,
|
|
and offer curated experiences. Choose a level of personalization that
|
|
matches your comfort.
|
|
</p>
|
|
</div>
|
|
|
|
<div className="flex flex-wrap items-center gap-3">
|
|
<button
|
|
type="button"
|
|
className="text-[11px] font-semibold uppercase tracking-[0.16em] text-[#d4af37] underline underline-offset-4 hover:text-[#f6e7b4]"
|
|
onClick={() => setShowDetails((prev) => !prev)}
|
|
>
|
|
{showDetails ? 'Hide detailed preferences' : 'Fine-tune preferences'}
|
|
</button>
|
|
<Link
|
|
to="/gdpr"
|
|
className="text-[11px] font-semibold uppercase tracking-[0.16em] text-[#d4af37] underline underline-offset-4 hover:text-[#f6e7b4]"
|
|
>
|
|
Data Privacy (GDPR)
|
|
</Link>
|
|
</div>
|
|
|
|
{showDetails && (
|
|
<div className="mt-1.5 space-y-3 rounded-xl bg-black/40 p-3 ring-1 ring-zinc-800/80 backdrop-blur-md sm:p-4">
|
|
<div className="flex flex-col gap-2 text-xs text-zinc-200 sm:text-[13px]">
|
|
<div className="flex items-start gap-3">
|
|
<div className="mt-0.5 h-4 w-4 rounded border border-[#d4af37]/50 bg-[#d4af37]/20" />
|
|
<div>
|
|
<p className="font-semibold text-zinc-50">Strictly necessary</p>
|
|
<p className="text-[11px] text-zinc-400">
|
|
Essential for security, authentication, and core booking flows.
|
|
These are always enabled.
|
|
</p>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="flex items-start gap-3">
|
|
<input
|
|
id="cookie-analytics"
|
|
type="checkbox"
|
|
className="mt-0.5 h-4 w-4 rounded border-zinc-500 bg-black/40 text-[#d4af37] focus:ring-[#d4af37]"
|
|
checked={analyticsChecked}
|
|
onChange={(e) => setAnalyticsChecked(e.target.checked)}
|
|
/>
|
|
<label htmlFor="cookie-analytics" className="cursor-pointer">
|
|
<p className="font-semibold text-zinc-50">Analytics</p>
|
|
<p className="text-[11px] text-zinc-400">
|
|
Anonymous insights that help us refine performance and guest
|
|
experience throughout the site.
|
|
</p>
|
|
</label>
|
|
</div>
|
|
|
|
<div className="flex items-start gap-3">
|
|
<input
|
|
id="cookie-marketing"
|
|
type="checkbox"
|
|
className="mt-0.5 h-4 w-4 rounded border-zinc-500 bg-black/40 text-[#d4af37] focus:ring-[#d4af37]"
|
|
checked={marketingChecked}
|
|
onChange={(e) => setMarketingChecked(e.target.checked)}
|
|
/>
|
|
<label htmlFor="cookie-marketing" className="cursor-pointer">
|
|
<p className="font-semibold text-zinc-50">Tailored offers</p>
|
|
<p className="text-[11px] text-zinc-400">
|
|
Allow us to present bespoke promotions and experiences aligned
|
|
with your interests.
|
|
</p>
|
|
</label>
|
|
</div>
|
|
|
|
<div className="flex items-start gap-3">
|
|
<input
|
|
id="cookie-preferences"
|
|
type="checkbox"
|
|
className="mt-0.5 h-4 w-4 rounded border-zinc-500 bg-black/40 text-[#d4af37] focus:ring-[#d4af37]"
|
|
checked={preferencesChecked}
|
|
onChange={(e) => setPreferencesChecked(e.target.checked)}
|
|
/>
|
|
<label htmlFor="cookie-preferences" className="cursor-pointer">
|
|
<p className="font-semibold text-zinc-50">Comfort preferences</p>
|
|
<p className="text-[11px] text-zinc-400">
|
|
Remember your choices such as language, currency, and layout for
|
|
a smoother return visit.
|
|
</p>
|
|
</label>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
)}
|
|
</div>
|
|
|
|
{}
|
|
<div className="mt-2 flex flex-col gap-2 sm:mt-0 sm:w-56">
|
|
<button
|
|
type="button"
|
|
className="inline-flex w-full items-center justify-center rounded-full bg-gradient-to-r from-[#d4af37] via-[#f2cf74] to-[#d4af37] px-4 py-2.5 text-xs font-semibold uppercase tracking-[0.16em] text-black shadow-[0_10px_30px_rgba(0,0,0,0.6)] transition hover:from-[#f8e4a6] hover:via-[#ffe6a3] hover:to-[#f2cf74] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[#d4af37]/70"
|
|
onClick={handleAcceptAll}
|
|
>
|
|
Accept all & continue
|
|
</button>
|
|
<button
|
|
type="button"
|
|
className="inline-flex w-full items-center justify-center rounded-full border border-zinc-600/80 bg-black/40 px-4 py-2.5 text-xs font-semibold uppercase tracking-[0.16em] text-zinc-100 shadow-[0_10px_25px_rgba(0,0,0,0.65)] transition hover:border-zinc-400 hover:bg-black/60 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-zinc-500/70"
|
|
onClick={handleRejectNonEssential}
|
|
>
|
|
Essential only
|
|
</button>
|
|
{showDetails && (
|
|
<button
|
|
type="button"
|
|
className="inline-flex w-full items-center justify-center rounded-full border border-zinc-700 bg-zinc-900/80 px-4 py-2.5 text-xs font-semibold uppercase tracking-[0.16em] text-zinc-100 shadow-[0_8px_22px_rgba(0,0,0,0.6)] transition hover:border-[#d4af37]/60 hover:text-[#f5e9c6] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[#d4af37]/70"
|
|
onClick={handleSaveSelection}
|
|
>
|
|
Save my selection
|
|
</button>
|
|
)}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
);
|
|
};
|
|
|
|
export default CookieConsentBanner;
|
|
|