219 lines
8.3 KiB
TypeScript
219 lines
8.3 KiB
TypeScript
'use client'
|
|
|
|
import React, { useState, useEffect } from 'react'
|
|
import { Cookie, Check, X } from 'lucide-react'
|
|
import { useConsentContext } from '@intecion/ipal-kit/client'
|
|
|
|
const CONSENT_CATEGORIES = ['necessary', 'functional', 'analytics', 'marketing'] as const
|
|
type ConsentCat = (typeof CONSENT_CATEGORIES)[number]
|
|
|
|
export function CustomCookieBanner() {
|
|
const { acceptAll, decided, rejectAll, savePreferences, state, texts } = useConsentContext()
|
|
const [showSettings, setShowSettings] = useState(false)
|
|
const [choices, setChoices] = useState(state)
|
|
|
|
useEffect(() => {
|
|
setChoices(state)
|
|
}, [state])
|
|
|
|
// Lock background page scroll while settings modal is open
|
|
useEffect(() => {
|
|
if (showSettings) {
|
|
const originalOverflow = document.body.style.overflow
|
|
document.body.style.overflow = 'hidden'
|
|
return () => {
|
|
document.body.style.overflow = originalOverflow
|
|
}
|
|
}
|
|
}, [showSettings])
|
|
|
|
if (decided) {
|
|
return null
|
|
}
|
|
|
|
const toggle = (cat: ConsentCat) => {
|
|
if (cat === 'necessary') return
|
|
setChoices((prev) => ({
|
|
...prev,
|
|
[cat]: !prev[cat],
|
|
}))
|
|
}
|
|
|
|
const handleSave = () => {
|
|
savePreferences(choices)
|
|
setShowSettings(false)
|
|
}
|
|
|
|
const handleAcceptAll = () => {
|
|
acceptAll()
|
|
setShowSettings(false)
|
|
}
|
|
|
|
const handleRejectAll = () => {
|
|
rejectAll()
|
|
setShowSettings(false)
|
|
}
|
|
|
|
return (
|
|
<>
|
|
{/* 1. Compact Bottom Banner */}
|
|
{!showSettings && (
|
|
<div
|
|
role="dialog"
|
|
aria-label="Cookie consent"
|
|
className="fixed bottom-0 inset-x-0 z-50 bg-dark text-white p-4 sm:p-6 border-t-4 border-primary shadow-2xl"
|
|
>
|
|
<div className="container mx-auto max-w-5xl">
|
|
<div className="flex flex-col lg:flex-row lg:items-center justify-between gap-5">
|
|
<div className="flex items-start gap-3.5">
|
|
<div className="p-2 bg-primary text-dark shrink-0 border border-dark">
|
|
<Cookie className="h-5 w-5" />
|
|
</div>
|
|
<div className="text-xs sm:text-sm text-[#e0e0e0] leading-relaxed">
|
|
<p>
|
|
{texts.message}{' '}
|
|
{texts.privacyLink && (
|
|
<a
|
|
href={texts.privacyLink.href}
|
|
className="text-primary underline hover:no-underline font-bold"
|
|
>
|
|
{texts.privacyLink.label}
|
|
</a>
|
|
)}
|
|
</p>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="flex flex-wrap items-center gap-2.5 shrink-0">
|
|
<button
|
|
type="button"
|
|
onClick={() => setShowSettings(true)}
|
|
className="bg-[#332d2e] hover:bg-[#423b3c] text-white border border-[#4a4244] px-4 py-2.5 text-xs font-bold uppercase tracking-wider transition-colors cursor-pointer"
|
|
>
|
|
{texts.buttons.settings}
|
|
</button>
|
|
<button
|
|
type="button"
|
|
onClick={handleRejectAll}
|
|
className="bg-[#332d2e] hover:bg-[#423b3c] text-white border border-[#4a4244] px-4 py-2.5 text-xs font-bold uppercase tracking-wider transition-colors cursor-pointer"
|
|
>
|
|
{texts.buttons.reject}
|
|
</button>
|
|
<button
|
|
type="button"
|
|
onClick={handleAcceptAll}
|
|
className="bg-primary hover:bg-primary-hover text-dark border-2 border-dark px-6 py-2.5 text-xs font-black uppercase tracking-widest transition-all cursor-pointer shadow-xs"
|
|
>
|
|
{texts.buttons.acceptAll}
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
)}
|
|
|
|
{/* 2. Modal Window for Cookie Settings */}
|
|
{showSettings && (
|
|
<div
|
|
className="fixed inset-0 z-50 flex items-center justify-center p-4 sm:p-6 bg-black/80 backdrop-blur-xs overflow-y-auto overscroll-contain"
|
|
onClick={(e) => {
|
|
if (e.target === e.currentTarget) setShowSettings(false)
|
|
}}
|
|
onWheel={(e) => e.stopPropagation()}
|
|
>
|
|
<div
|
|
role="dialog"
|
|
aria-modal="true"
|
|
aria-label={texts.settingsTitle}
|
|
className="w-full max-w-2xl bg-dark text-white p-6 sm:p-8 border-t-4 border-primary shadow-2xl my-auto max-h-[90vh] flex flex-col overscroll-contain"
|
|
onClick={(e) => e.stopPropagation()}
|
|
>
|
|
{/* Modal Header */}
|
|
<div className="flex items-center justify-between border-b border-[#363032] pb-4 mb-6 shrink-0">
|
|
<div className="flex items-center gap-3">
|
|
<div className="p-1.5 bg-primary text-dark">
|
|
<Cookie className="h-5 w-5" />
|
|
</div>
|
|
<h2 className="text-base sm:text-lg font-black uppercase tracking-wider text-white">
|
|
{texts.settingsTitle}
|
|
</h2>
|
|
</div>
|
|
<button
|
|
type="button"
|
|
onClick={() => setShowSettings(false)}
|
|
className="text-[#a0a0a0] hover:text-white p-1.5 cursor-pointer transition-colors"
|
|
aria-label="Zamknij"
|
|
>
|
|
<X className="h-5 w-5" />
|
|
</button>
|
|
</div>
|
|
|
|
{/* Scrollable Categories List */}
|
|
<div className="overflow-y-auto flex-1 pr-1 space-y-3.5 overscroll-contain">
|
|
{CONSENT_CATEGORIES.map((cat) => {
|
|
const isChecked = cat === 'necessary' ? true : !!choices[cat]
|
|
const isDisabled = cat === 'necessary'
|
|
const catData = texts.categories[cat]
|
|
|
|
if (!catData) return null
|
|
|
|
return (
|
|
<div
|
|
key={cat}
|
|
onClick={() => !isDisabled && toggle(cat)}
|
|
className={`flex items-start justify-between gap-4 p-4 border border-[#363032] bg-[#2d2728] ${
|
|
!isDisabled ? 'cursor-pointer hover:border-primary' : 'opacity-90'
|
|
} transition-colors`}
|
|
>
|
|
<div className="pr-4 select-none">
|
|
<div className="text-xs font-black uppercase tracking-wider text-primary">
|
|
{catData.title}
|
|
</div>
|
|
<p className="text-xs text-[#a0a0a0] mt-1 leading-relaxed">
|
|
{catData.description}
|
|
</p>
|
|
</div>
|
|
|
|
{/* Custom Checkbox */}
|
|
<div className="relative flex items-center justify-center mt-0.5 shrink-0">
|
|
<div
|
|
className={`w-5 h-5 border-2 border-dark transition-all flex items-center justify-center ${
|
|
isChecked ? 'bg-primary' : 'bg-white'
|
|
}`}
|
|
>
|
|
<Check
|
|
className={`w-3.5 h-3.5 text-dark stroke-[3] transition-opacity ${
|
|
isChecked ? 'opacity-100' : 'opacity-0'
|
|
}`}
|
|
/>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
)
|
|
})}
|
|
</div>
|
|
|
|
{/* Modal Footer Buttons */}
|
|
<div className="flex flex-wrap items-center justify-end gap-3 pt-6 mt-6 border-t border-[#363032] shrink-0">
|
|
<button
|
|
type="button"
|
|
onClick={() => setShowSettings(false)}
|
|
className="bg-[#332d2e] hover:bg-[#423b3c] text-white border border-[#4a4244] px-5 py-2.5 text-xs font-bold uppercase tracking-wider transition-colors cursor-pointer"
|
|
>
|
|
{texts.buttons.back}
|
|
</button>
|
|
<button
|
|
type="button"
|
|
onClick={handleSave}
|
|
className="bg-primary hover:bg-primary-hover text-dark border-2 border-dark px-6 py-2.5 text-xs font-black uppercase tracking-widest transition-all cursor-pointer shadow-xs"
|
|
>
|
|
{texts.buttons.save}
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
)}
|
|
</>
|
|
)
|
|
}
|