This commit is contained in:
Iliyan Angelov
2025-11-17 18:26:30 +02:00
parent 48353cde9c
commit 0c59fe1173
2535 changed files with 278997 additions and 2480 deletions

View File

@@ -112,15 +112,17 @@ const CookieSettingsPage: React.FC = () => {
}
return (
<div className="space-y-8">
<div className="space-y-10 pb-8 animate-fade-in">
{/* Header */}
<div className="flex flex-col sm:flex-row items-start sm:items-center justify-between gap-4">
<div className="space-y-1">
<div className="flex items-center gap-2">
<Shield className="w-6 h-6 text-amber-500" />
<div className="flex flex-col sm:flex-row items-start sm:items-center justify-between gap-6 pb-6 border-b border-gray-200/60">
<div className="space-y-3">
<div className="flex items-center gap-3">
<div className="p-2.5 rounded-lg bg-gradient-to-br from-[#d4af37]/10 to-[#d4af37]/5 border border-[#d4af37]/20 shadow-sm">
<Shield className="w-6 h-6 text-[#d4af37]" />
</div>
<h1 className="enterprise-section-title">Cookie & Privacy Controls</h1>
</div>
<p className="enterprise-section-subtitle max-w-2xl">
<p className="enterprise-section-subtitle max-w-2xl text-gray-600">
Define which cookie categories are allowed in the application. These
settings control which types of cookies your users can consent to.
</p>
@@ -130,51 +132,65 @@ const CookieSettingsPage: React.FC = () => {
type="button"
onClick={handleSave}
disabled={saving}
className="btn-enterprise-primary inline-flex items-center gap-2"
className="btn-enterprise-primary inline-flex items-center gap-2 whitespace-nowrap"
>
<Save className={`w-4 h-4 ${saving ? 'animate-pulse' : ''}`} />
{saving ? 'Saving...' : 'Save changes'}
{saving ? 'Saving...' : 'Save All Changes'}
</button>
</div>
{/* Info card */}
<div className="enterprise-card flex gap-4 p-4 sm:p-5">
<div className="mt-1">
<Info className="w-5 h-5 text-amber-500" />
<div className="enterprise-card flex gap-5 p-6 bg-gradient-to-br from-blue-50/50 to-indigo-50/30 border-blue-100/60">
<div className="mt-0.5 flex-shrink-0">
<div className="p-2.5 rounded-lg bg-gradient-to-br from-blue-500/10 to-indigo-500/10 border border-blue-200/40">
<Info className="w-5 h-5 text-blue-600" />
</div>
</div>
<div className="space-y-1 text-sm text-gray-700">
<p className="font-semibold text-gray-900">
How these settings affect the guest experience
</p>
<p>
<div className="space-y-2.5 flex-1">
<p className="font-semibold text-gray-900 text-base">
How these settings affect the guest experience
</p>
<p className="text-sm text-gray-700 leading-relaxed">
Disabling a category here prevents it from being offered to guests as
part of the cookie consent flow. For example, if marketing cookies are
disabled, the website should not load marketing pixels even if a guest
previously opted in.
</p>
{policyMeta?.updated_at && (
<p className="text-xs text-gray-500">
Last updated on{' '}
{new Date(policyMeta.updated_at).toLocaleString(undefined, {
dateStyle: 'medium',
timeStyle: 'short',
})}{' '}
{policyMeta.updated_by ? `by ${policyMeta.updated_by}` : ''}
</p>
<div className="pt-2 border-t border-gray-200/60">
<p className="text-xs text-gray-500 font-medium">
Last updated on{' '}
<span className="text-gray-700">
{new Date(policyMeta.updated_at).toLocaleString(undefined, {
dateStyle: 'medium',
timeStyle: 'short',
})}
</span>
{policyMeta.updated_by && (
<>
{' '}by <span className="text-gray-700 font-semibold">{policyMeta.updated_by}</span>
</>
)}
</p>
</div>
)}
</div>
</div>
{/* Toggles */}
<div className="grid grid-cols-1 md:grid-cols-3 gap-6">
<div className="enterprise-card p-5 space-y-3">
<div className="flex items-center justify-between gap-4">
<div>
<p className="font-semibold text-gray-900 flex items-center gap-2">
<SlidersHorizontal className="w-4 h-4 text-emerald-500" />
Analytics cookies
</p>
<p className="text-xs text-gray-500 mt-1">
<div className="enterprise-card p-6 space-y-4 group">
<div className="flex items-start justify-between gap-4">
<div className="flex-1 space-y-2">
<div className="flex items-center gap-2.5">
<div className="p-1.5 rounded-md bg-emerald-50 border border-emerald-100">
<SlidersHorizontal className="w-4 h-4 text-emerald-600" />
</div>
<p className="font-bold text-gray-900 text-base">
Analytics Cookies
</p>
</div>
<p className="text-sm text-gray-600 leading-relaxed">
Anonymous traffic and performance measurement (e.g. page views,
conversion funnels).
</p>
@@ -182,31 +198,39 @@ const CookieSettingsPage: React.FC = () => {
<button
type="button"
onClick={() => handleToggle('analytics_enabled')}
className={`relative inline-flex h-7 w-12 items-center rounded-full transition ${
policy.analytics_enabled ? 'bg-emerald-500' : 'bg-gray-300'
className={`relative inline-flex h-8 w-14 items-center rounded-full transition-all duration-300 shadow-lg ${
policy.analytics_enabled
? 'bg-gradient-to-r from-emerald-500 to-emerald-600 shadow-emerald-500/30'
: 'bg-gray-300 shadow-gray-300/20'
}`}
>
<span
className={`inline-block h-5 w-5 transform rounded-full bg-white shadow transition ${
policy.analytics_enabled ? 'translate-x-5' : 'translate-x-1'
className={`inline-block h-6 w-6 transform rounded-full bg-white shadow-lg transition-all duration-300 ${
policy.analytics_enabled ? 'translate-x-7' : 'translate-x-1'
}`}
/>
</button>
</div>
<p className="text-[11px] text-gray-500">
When disabled, analytics tracking scripts should not be executed,
regardless of user consent.
</p>
<div className="pt-3 border-t border-gray-100">
<p className="text-xs text-gray-500 leading-relaxed">
When disabled, analytics tracking scripts should not be executed,
regardless of user consent.
</p>
</div>
</div>
<div className="enterprise-card p-5 space-y-3">
<div className="flex items-center justify-between gap-4">
<div>
<p className="font-semibold text-gray-900 flex items-center gap-2">
<SlidersHorizontal className="w-4 h-4 text-pink-500" />
Marketing cookies
</p>
<p className="text-xs text-gray-500 mt-1">
<div className="enterprise-card p-6 space-y-4 group">
<div className="flex items-start justify-between gap-4">
<div className="flex-1 space-y-2">
<div className="flex items-center gap-2.5">
<div className="p-1.5 rounded-md bg-pink-50 border border-pink-100">
<SlidersHorizontal className="w-4 h-4 text-pink-600" />
</div>
<p className="font-bold text-gray-900 text-base">
Marketing Cookies
</p>
</div>
<p className="text-sm text-gray-600 leading-relaxed">
Personalised offers, remarketing campaigns, and external ad
networks.
</p>
@@ -214,97 +238,119 @@ const CookieSettingsPage: React.FC = () => {
<button
type="button"
onClick={() => handleToggle('marketing_enabled')}
className={`relative inline-flex h-7 w-12 items-center rounded-full transition ${
policy.marketing_enabled ? 'bg-pink-500' : 'bg-gray-300'
className={`relative inline-flex h-8 w-14 items-center rounded-full transition-all duration-300 shadow-lg ${
policy.marketing_enabled
? 'bg-gradient-to-r from-pink-500 to-pink-600 shadow-pink-500/30'
: 'bg-gray-300 shadow-gray-300/20'
}`}
>
<span
className={`inline-block h-5 w-5 transform rounded-full bg-white shadow transition ${
policy.marketing_enabled ? 'translate-x-5' : 'translate-x-1'
className={`inline-block h-6 w-6 transform rounded-full bg-white shadow-lg transition-all duration-300 ${
policy.marketing_enabled ? 'translate-x-7' : 'translate-x-1'
}`}
/>
</button>
</div>
<p className="text-[11px] text-gray-500">
When disabled, do not load any marketing pixels or share data with ad
platforms.
</p>
<div className="pt-3 border-t border-gray-100">
<p className="text-xs text-gray-500 leading-relaxed">
When disabled, do not load any marketing pixels or share data with ad
platforms.
</p>
</div>
</div>
<div className="enterprise-card p-5 space-y-3">
<div className="flex items-center justify-between gap-4">
<div>
<p className="font-semibold text-gray-900 flex items-center gap-2">
<SlidersHorizontal className="w-4 h-4 text-indigo-500" />
Preference cookies
</p>
<p className="text-xs text-gray-500 mt-1">
<div className="enterprise-card p-6 space-y-4 group">
<div className="flex items-start justify-between gap-4">
<div className="flex-1 space-y-2">
<div className="flex items-center gap-2.5">
<div className="p-1.5 rounded-md bg-indigo-50 border border-indigo-100">
<SlidersHorizontal className="w-4 h-4 text-indigo-600" />
</div>
<p className="font-bold text-gray-900 text-base">
Preference Cookies
</p>
</div>
<p className="text-sm text-gray-600 leading-relaxed">
Remember guest choices like language, currency, and layout.
</p>
</div>
<button
type="button"
onClick={() => handleToggle('preferences_enabled')}
className={`relative inline-flex h-7 w-12 items-center rounded-full transition ${
policy.preferences_enabled ? 'bg-indigo-500' : 'bg-gray-300'
className={`relative inline-flex h-8 w-14 items-center rounded-full transition-all duration-300 shadow-lg ${
policy.preferences_enabled
? 'bg-gradient-to-r from-indigo-500 to-indigo-600 shadow-indigo-500/30'
: 'bg-gray-300 shadow-gray-300/20'
}`}
>
<span
className={`inline-block h-5 w-5 transform rounded-full bg-white shadow transition ${
policy.preferences_enabled ? 'translate-x-5' : 'translate-x-1'
className={`inline-block h-6 w-6 transform rounded-full bg-white shadow-lg transition-all duration-300 ${
policy.preferences_enabled ? 'translate-x-7' : 'translate-x-1'
}`}
/>
</button>
</div>
<p className="text-[11px] text-gray-500">
When disabled, the application should avoid persisting non-essential
preferences client-side.
</p>
<div className="pt-3 border-t border-gray-100">
<p className="text-xs text-gray-500 leading-relaxed">
When disabled, the application should avoid persisting non-essential
preferences client-side.
</p>
</div>
</div>
</div>
{/* Integration IDs */}
<div className="enterprise-card p-5 space-y-4">
<div className="flex flex-col gap-3 md:flex-row md:items-center md:justify-between">
<div className="flex items-center gap-2">
<Globe className="w-5 h-5 text-blue-500" />
<div>
<p className="font-semibold text-gray-900">
Third-party integrations (IDs only)
<div className="enterprise-card p-6 space-y-6">
<div className="flex flex-col gap-4 md:flex-row md:items-start md:justify-between pb-4 border-b border-gray-200/60">
<div className="flex items-start gap-3">
<div className="p-2 rounded-lg bg-gradient-to-br from-blue-500/10 to-indigo-500/10 border border-blue-200/40 mt-0.5">
<Globe className="w-5 h-5 text-blue-600" />
</div>
<div className="space-y-1.5">
<p className="font-bold text-gray-900 text-lg">
Third-Party Integrations
</p>
<p className="text-xs text-gray-500">
<p className="text-sm text-gray-600 leading-relaxed max-w-xl">
Configure IDs for supported analytics and marketing platforms. The
application will only load these when both the policy and user consent
allow it.
</p>
</div>
</div>
<div className="flex flex-col items-start gap-2 md:items-end">
<div className="flex flex-col items-start gap-3 md:items-end md:min-w-[200px]">
{integrationMeta?.updated_at && (
<p className="text-[11px] text-gray-500">
Last changed{' '}
{new Date(integrationMeta.updated_at).toLocaleString(undefined, {
dateStyle: 'medium',
timeStyle: 'short',
})}{' '}
{integrationMeta.updated_by ? `by ${integrationMeta.updated_by}` : ''}
</p>
<div className="text-right">
<p className="text-xs text-gray-500 font-medium">
Last changed
</p>
<p className="text-xs text-gray-700 mt-0.5">
{new Date(integrationMeta.updated_at).toLocaleString(undefined, {
dateStyle: 'medium',
timeStyle: 'short',
})}
</p>
{integrationMeta.updated_by && (
<p className="text-xs text-gray-600 mt-0.5">
by <span className="font-semibold">{integrationMeta.updated_by}</span>
</p>
)}
</div>
)}
<button
type="button"
onClick={handleSaveIntegrations}
disabled={saving}
className="btn-enterprise-secondary inline-flex items-center gap-1.5 px-3 py-1.5 text-xs"
className="btn-enterprise-secondary inline-flex items-center gap-2 whitespace-nowrap"
>
<Save className="w-3.5 h-3.5" />
{saving ? 'Saving IDs...' : 'Save integration IDs'}
{saving ? 'Saving...' : 'Save Integration IDs'}
</button>
</div>
</div>
<div className="mt-4 grid grid-cols-1 md:grid-cols-2 gap-6">
<div className="space-y-2">
<label className="block text-sm font-medium text-gray-800">
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
<div className="space-y-3">
<label className="block text-sm font-semibold text-gray-900 tracking-wide">
Google Analytics 4 Measurement ID
</label>
<input
@@ -319,14 +365,14 @@ const CookieSettingsPage: React.FC = () => {
placeholder="G-XXXXXXXXXX"
className="enterprise-input text-sm"
/>
<p className="text-[11px] text-gray-500">
Example: <code className="font-mono">G-ABCDE12345</code>. This is used to
<p className="text-xs text-gray-500 leading-relaxed">
Example: <code className="font-mono text-gray-700 bg-gray-50 px-1.5 py-0.5 rounded border border-gray-200">G-ABCDE12345</code>. This is used to
load GA4 via gtag.js when analytics cookies are allowed.
</p>
</div>
<div className="space-y-2">
<label className="block text-sm font-medium text-gray-800">
<div className="space-y-3">
<label className="block text-sm font-semibold text-gray-900 tracking-wide">
Meta (Facebook) Pixel ID
</label>
<input
@@ -341,7 +387,7 @@ const CookieSettingsPage: React.FC = () => {
placeholder="123456789012345"
className="enterprise-input text-sm"
/>
<p className="text-[11px] text-gray-500">
<p className="text-xs text-gray-500 leading-relaxed">
Numeric ID from your Meta Pixel. The application will only fire pixel
events when marketing cookies are allowed.
</p>