web-next: banner de consentimiento de cookies + estado de la API (Zaraz)
- Banner real `uw-cookie-content` (Rechazar/Personalizar/Aceptar todas) fijo
abajo y site-wide (montado en el layout, componente CookieBanner). Aparece
en la primera visita, guarda la decisión en la cookie uw_cookie_consent (1
año) y se sincroniza con Cloudflare Zaraz si está presente
(zaraz.consent.setAll / .modal). Estilos en globals.css.
- «Personalizar» abre el modal granular de Zaraz (o /cookies si no hay Zaraz).
- window.UWCookies {showBanner, revokeConsent, acceptAll, rejectAll}.
- Página /cookies: panel «Tu estado de consentimiento» (ConsentStatus) que
muestra los datos EN VIVO de la API de Zaraz (zaraz.consent.getAll()) por
propósito; si Zaraz no está, cae a la preferencia guardada por categoría.
Verificado con navegador: el banner aparece, «Aceptar todas» lo oculta y
escribe la cookie, y el panel de /cookies refleja el estado.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1,6 +1,6 @@
|
|||||||
import type { Metadata } from 'next'
|
import type { Metadata } from 'next'
|
||||||
import { Link } from '@/i18n/navigation'
|
import { Link } from '@/i18n/navigation'
|
||||||
import { CookieConsentButtons } from '@/components/CookieConsent'
|
import { CookieConsentButtons, ConsentStatus } from '@/components/CookieConsent'
|
||||||
|
|
||||||
export const metadata: Metadata = {
|
export const metadata: Metadata = {
|
||||||
title: 'Declaración de Cookies',
|
title: 'Declaración de Cookies',
|
||||||
@@ -163,6 +163,7 @@ export default function CookiesPage() {
|
|||||||
<span>Tu estado de consentimiento</span>
|
<span>Tu estado de consentimiento</span>
|
||||||
<p>Puedes cambiar o retirar tu consentimiento en cualquier momento. Al contactarnos respecto a su consentimiento, por favor, indique la fecha de su consentimiento.</p>
|
<p>Puedes cambiar o retirar tu consentimiento en cualquier momento. Al contactarnos respecto a su consentimiento, por favor, indique la fecha de su consentimiento.</p>
|
||||||
|
|
||||||
|
<ConsentStatus />
|
||||||
<CookieConsentButtons />
|
<CookieConsentButtons />
|
||||||
<br />
|
<br />
|
||||||
<hr />
|
<hr />
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import { Header } from '@/components/Header'
|
|||||||
import { Video } from '@/components/Video'
|
import { Video } from '@/components/Video'
|
||||||
import { Social } from '@/components/Social'
|
import { Social } from '@/components/Social'
|
||||||
import { Footer } from '@/components/Footer'
|
import { Footer } from '@/components/Footer'
|
||||||
|
import { CookieBanner } from '@/components/CookieConsent'
|
||||||
import '../globals.css'
|
import '../globals.css'
|
||||||
|
|
||||||
export function generateStaticParams() {
|
export function generateStaticParams() {
|
||||||
@@ -73,6 +74,7 @@ export default async function LocaleLayout({
|
|||||||
{children}
|
{children}
|
||||||
<Social />
|
<Social />
|
||||||
<Footer />
|
<Footer />
|
||||||
|
<CookieBanner />
|
||||||
</NextIntlClientProvider>
|
</NextIntlClientProvider>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@@ -197,6 +197,108 @@ textarea:focus {
|
|||||||
color: #fff;
|
color: #fff;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Banner de consentimiento de cookies (fijo abajo). */
|
||||||
|
.uw-cookie-content {
|
||||||
|
position: fixed;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
z-index: 9999;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
gap: 20px;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
padding: 16px 24px;
|
||||||
|
background: #140d08;
|
||||||
|
border-top: 2px solid #4a3320;
|
||||||
|
box-shadow: 0 -4px 24px rgba(0, 0, 0, 0.55);
|
||||||
|
}
|
||||||
|
.uw-cookie-text {
|
||||||
|
flex: 1 1 420px;
|
||||||
|
}
|
||||||
|
.uw-cookie-text h3 {
|
||||||
|
color: #d79602;
|
||||||
|
margin-bottom: 6px;
|
||||||
|
}
|
||||||
|
.uw-cookie-text p {
|
||||||
|
color: #b1997f;
|
||||||
|
font-size: 14px;
|
||||||
|
line-height: 20px;
|
||||||
|
}
|
||||||
|
.uw-cookie-link {
|
||||||
|
display: inline-block;
|
||||||
|
margin-top: 6px;
|
||||||
|
color: #2471a9;
|
||||||
|
}
|
||||||
|
.uw-cookie-actions {
|
||||||
|
display: flex;
|
||||||
|
gap: 10px;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
.uw-cookie-btn {
|
||||||
|
border: 2px solid #352e2b;
|
||||||
|
background: hsla(0, 0%, 100%, 0.05);
|
||||||
|
color: #ebdec2;
|
||||||
|
padding: 9px 16px;
|
||||||
|
cursor: pointer;
|
||||||
|
text-transform: uppercase;
|
||||||
|
font-size: 14px;
|
||||||
|
transition: 0.3s;
|
||||||
|
}
|
||||||
|
.uw-cookie-btn:hover {
|
||||||
|
background: hsla(0, 0%, 100%, 0.1);
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
.uw-cookie-btn-accept {
|
||||||
|
border-color: #b17c02;
|
||||||
|
color: #f0b93f;
|
||||||
|
}
|
||||||
|
.uw-cookie-btn-accept:hover {
|
||||||
|
background: hsla(40, 90%, 45%, 0.15);
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
.uw-cookie-btn-reject {
|
||||||
|
border-color: #5c2b2b;
|
||||||
|
color: #d98c8c;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Panel «Tu estado de consentimiento» en /cookies (datos de la API de Zaraz). */
|
||||||
|
.uw-consent-status {
|
||||||
|
border: 1px solid #352e2b;
|
||||||
|
padding: 12px 14px;
|
||||||
|
margin: 12px 0;
|
||||||
|
}
|
||||||
|
.uw-consent-status .uw-consent-source {
|
||||||
|
font-size: 13px;
|
||||||
|
color: #7e8d09;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
}
|
||||||
|
.uw-consent-status ul {
|
||||||
|
list-style: none;
|
||||||
|
padding: 0;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
.uw-consent-status li {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
gap: 12px;
|
||||||
|
padding: 4px 0;
|
||||||
|
border-bottom: 1px solid #241812;
|
||||||
|
}
|
||||||
|
.uw-consent-status li:last-child {
|
||||||
|
border-bottom: 0;
|
||||||
|
}
|
||||||
|
.uw-consent-on {
|
||||||
|
color: #6aa84f;
|
||||||
|
}
|
||||||
|
.uw-consent-off {
|
||||||
|
color: #cc5b5b;
|
||||||
|
}
|
||||||
|
.uw-consent-always {
|
||||||
|
color: #b1997f;
|
||||||
|
}
|
||||||
|
|
||||||
/* Colores de clase de WoW (nombre de personaje) */
|
/* Colores de clase de WoW (nombre de personaje) */
|
||||||
.class-warrior { color: #c79c6e; }
|
.class-warrior { color: #c79c6e; }
|
||||||
.class-paladin { color: #f58cba; }
|
.class-paladin { color: #f58cba; }
|
||||||
|
|||||||
@@ -1,50 +1,148 @@
|
|||||||
'use client'
|
'use client'
|
||||||
|
|
||||||
import { useEffect } from 'react'
|
import { useEffect, useState } from 'react'
|
||||||
|
import { Link } from '@/i18n/navigation'
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
// API de consentimiento de Cloudflare Zaraz (se inyecta cuando el dominio pasa
|
// API de consentimiento de Cloudflare Zaraz (se inyecta cuando el dominio pasa
|
||||||
// por el proxy de Cloudflare con Zaraz Consent activado).
|
// por el proxy de Cloudflare con Zaraz Consent activado).
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
interface ZarazConsent {
|
interface ZarazConsent {
|
||||||
modal?: boolean
|
modal?: boolean
|
||||||
APIReady?: boolean
|
APIReady?: boolean
|
||||||
setAll?: (status: boolean) => void
|
setAll?: (status: boolean) => void
|
||||||
getAll?: () => Record<string, boolean>
|
getAll?: () => Record<string, boolean>
|
||||||
|
sendQueuedEvents?: () => void
|
||||||
}
|
}
|
||||||
declare global {
|
declare global {
|
||||||
interface Window {
|
interface Window {
|
||||||
zaraz?: { consent?: ZarazConsent }
|
zaraz?: { consent?: ZarazConsent }
|
||||||
UWCookies?: { showBanner: () => void; revokeConsent: () => void }
|
UWCookies?: {
|
||||||
|
showBanner: () => void
|
||||||
|
revokeConsent: () => void
|
||||||
|
acceptAll: () => void
|
||||||
|
rejectAll: () => void
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Abre el modal de consentimiento de Zaraz. */
|
// Categorías de consentimiento (necesarias siempre activas).
|
||||||
function showBanner() {
|
type Category = { key: string; label: string; always?: boolean }
|
||||||
const c = typeof window !== 'undefined' ? window.zaraz?.consent : undefined
|
export const CONSENT_CATEGORIES: readonly Category[] = [
|
||||||
|
{ key: 'necessary', label: 'Necesarias', always: true },
|
||||||
|
{ key: 'preferences', label: 'Preferencias' },
|
||||||
|
{ key: 'analytics', label: 'Analíticas' },
|
||||||
|
{ key: 'marketing', label: 'Marketing' },
|
||||||
|
]
|
||||||
|
|
||||||
|
type Consent = Record<string, boolean>
|
||||||
|
const CONSENT_COOKIE = 'uw_cookie_consent'
|
||||||
|
const CONSENT_EVENT = 'uw:consentchange'
|
||||||
|
const SHOW_EVENT = 'uw:showbanner'
|
||||||
|
|
||||||
|
function zaraz(): ZarazConsent | undefined {
|
||||||
|
return typeof window !== 'undefined' ? window.zaraz?.consent : undefined
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Lee la preferencia guardada en la cookie uw_cookie_consent. */
|
||||||
|
function readConsent(): Consent | null {
|
||||||
|
if (typeof document === 'undefined') return null
|
||||||
|
const m = document.cookie.match(/(?:^|; )uw_cookie_consent=([^;]*)/)
|
||||||
|
if (!m) return null
|
||||||
|
try {
|
||||||
|
return JSON.parse(decodeURIComponent(m[1])) as Consent
|
||||||
|
} catch {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function writeConsent(c: Consent) {
|
||||||
|
const maxAge = 60 * 60 * 24 * 365 // 1 año
|
||||||
|
document.cookie = `${CONSENT_COOKIE}=${encodeURIComponent(JSON.stringify(c))}; path=/; max-age=${maxAge}; SameSite=Lax`
|
||||||
|
// Sincroniza con Cloudflare Zaraz si está presente (control granular real lo
|
||||||
|
// gestiona el panel de Zaraz; aquí aplicamos aceptar/rechazar todo).
|
||||||
|
const nonNecessary = CONSENT_CATEGORIES.some((cat) => !cat.always && c[cat.key])
|
||||||
|
zaraz()?.setAll?.(nonNecessary)
|
||||||
|
zaraz()?.sendQueuedEvents?.()
|
||||||
|
window.dispatchEvent(new CustomEvent(CONSENT_EVENT))
|
||||||
|
}
|
||||||
|
|
||||||
|
function allFlags(value: boolean): Consent {
|
||||||
|
const c: Consent = {}
|
||||||
|
for (const cat of CONSENT_CATEGORIES) c[cat.key] = cat.always ? true : value
|
||||||
|
return c
|
||||||
|
}
|
||||||
|
|
||||||
|
function acceptAll() {
|
||||||
|
writeConsent(allFlags(true))
|
||||||
|
}
|
||||||
|
function rejectAll() {
|
||||||
|
writeConsent(allFlags(false))
|
||||||
|
}
|
||||||
|
/** Abre el modal granular de Zaraz; si no está, lleva a la página de cookies. */
|
||||||
|
function customize() {
|
||||||
|
const c = zaraz()
|
||||||
if (c) c.modal = true
|
if (c) c.modal = true
|
||||||
else console.warn('[UWCookies] Zaraz no disponible (el sitio debe pasar por Cloudflare con Zaraz Consent activado).')
|
else if (typeof window !== 'undefined') window.location.href = '/cookies'
|
||||||
|
}
|
||||||
|
function showBanner() {
|
||||||
|
window.dispatchEvent(new CustomEvent(SHOW_EVENT))
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Retira el consentimiento (pone todos los propósitos a false) y reabre el modal. */
|
|
||||||
function revokeConsent() {
|
function revokeConsent() {
|
||||||
const c = typeof window !== 'undefined' ? window.zaraz?.consent : undefined
|
// Borra la cookie, retira en Zaraz y reabre el banner.
|
||||||
if (c?.setAll) {
|
document.cookie = `${CONSENT_COOKIE}=; path=/; max-age=0; SameSite=Lax`
|
||||||
c.setAll(false)
|
zaraz()?.setAll?.(false)
|
||||||
c.modal = true
|
window.dispatchEvent(new CustomEvent(CONSENT_EVENT))
|
||||||
} else {
|
showBanner()
|
||||||
console.warn('[UWCookies] Zaraz no disponible; no se puede retirar el consentimiento.')
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
// ---------------------------------------------------------------------------
|
||||||
* Botones de consentimiento de la página /cookies. Cablean el sistema de
|
// Banner de consentimiento (fijo abajo, en todo el sitio).
|
||||||
* Cloudflare Zaraz y exponen window.UWCookies (showBanner/revokeConsent) para
|
// ---------------------------------------------------------------------------
|
||||||
* el resto del sitio.
|
export function CookieBanner() {
|
||||||
*/
|
const [visible, setVisible] = useState(false)
|
||||||
export function CookieConsentButtons() {
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
window.UWCookies = { showBanner, revokeConsent }
|
window.UWCookies = { showBanner, revokeConsent, acceptAll, rejectAll }
|
||||||
|
// Aparece si aún no hay decisión guardada.
|
||||||
|
if (!readConsent()) setVisible(true)
|
||||||
|
const onShow = () => setVisible(true)
|
||||||
|
const onChange = () => {
|
||||||
|
if (readConsent()) setVisible(false)
|
||||||
|
}
|
||||||
|
window.addEventListener(SHOW_EVENT, onShow)
|
||||||
|
window.addEventListener(CONSENT_EVENT, onChange)
|
||||||
|
return () => {
|
||||||
|
window.removeEventListener(SHOW_EVENT, onShow)
|
||||||
|
window.removeEventListener(CONSENT_EVENT, onChange)
|
||||||
|
}
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
|
if (!visible) return null
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="uw-cookie-content" role="dialog" aria-label="Consentimiento de cookies">
|
||||||
|
<div className="uw-cookie-text">
|
||||||
|
<h3>Utilizamos cookies</h3>
|
||||||
|
<p>Este sitio utiliza cookies propias y de terceros para garantizar el funcionamiento del sitio, analizar el tráfico y ofrecerte publicidad personalizada. Consulta nuestra política de cookies para más información.</p>
|
||||||
|
<Link href="/cookies" className="uw-cookie-link">Política de Cookies</Link>
|
||||||
|
</div>
|
||||||
|
<div className="uw-cookie-actions">
|
||||||
|
<button type="button" className="uw-cookie-btn uw-cookie-btn-reject" data-action="reject" onClick={() => { rejectAll(); setVisible(false) }}>Rechazar</button>
|
||||||
|
<button type="button" className="uw-cookie-btn uw-cookie-btn-customize" data-action="customize" onClick={customize}>Personalizar</button>
|
||||||
|
<button type="button" className="uw-cookie-btn uw-cookie-btn-accept" data-action="accept" onClick={() => { acceptAll(); setVisible(false) }}>Aceptar todas</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// Botones «Cambiar / Retirar consentimiento» de la página /cookies.
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
export function CookieConsentButtons() {
|
||||||
|
useEffect(() => {
|
||||||
|
window.UWCookies = { showBanner, revokeConsent, acceptAll, rejectAll }
|
||||||
|
}, [])
|
||||||
return (
|
return (
|
||||||
<div className="uw-consent-buttons">
|
<div className="uw-consent-buttons">
|
||||||
<button type="button" onClick={showBanner} className="btn-uw btn-uw-form">
|
<button type="button" onClick={showBanner} className="btn-uw btn-uw-form">
|
||||||
@@ -56,3 +154,75 @@ export function CookieConsentButtons() {
|
|||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// Panel «Tu estado de consentimiento»: muestra los datos de la API de Zaraz
|
||||||
|
// (o, si Zaraz no está, la preferencia guardada en la cookie uw_cookie_consent).
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
export function ConsentStatus() {
|
||||||
|
const [state, setState] = useState<{ source: string; values: Consent | null }>({
|
||||||
|
source: 'cookie',
|
||||||
|
values: null,
|
||||||
|
})
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
function refresh() {
|
||||||
|
const c = zaraz()
|
||||||
|
if (c?.APIReady && c.getAll) {
|
||||||
|
setState({ source: 'zaraz', values: c.getAll() })
|
||||||
|
} else {
|
||||||
|
setState({ source: 'cookie', values: readConsent() })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
refresh()
|
||||||
|
window.addEventListener(CONSENT_EVENT, refresh)
|
||||||
|
window.addEventListener('zarazConsentAPIReady', refresh)
|
||||||
|
window.addEventListener('zarazConsentChoicesUpdated', refresh)
|
||||||
|
return () => {
|
||||||
|
window.removeEventListener(CONSENT_EVENT, refresh)
|
||||||
|
window.removeEventListener('zarazConsentAPIReady', refresh)
|
||||||
|
window.removeEventListener('zarazConsentChoicesUpdated', refresh)
|
||||||
|
}
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
const zarazData = state.source === 'zaraz' && state.values
|
||||||
|
const values = state.values
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="uw-consent-status">
|
||||||
|
<p className="uw-consent-source">
|
||||||
|
{zarazData
|
||||||
|
? 'Estado actual (Cloudflare Zaraz Consent API):'
|
||||||
|
: values
|
||||||
|
? 'Estado actual (preferencia guardada):'
|
||||||
|
: 'Cloudflare Zaraz no está disponible en este dominio; aún no hay preferencia guardada.'}
|
||||||
|
</p>
|
||||||
|
<ul>
|
||||||
|
{zarazData ? (
|
||||||
|
// Datos en vivo de la API de Zaraz: cada propósito y su estado.
|
||||||
|
Object.entries(values as Consent).map(([purpose, granted]) => (
|
||||||
|
<li key={purpose}>
|
||||||
|
<span>{purpose}</span>
|
||||||
|
<span className={granted ? 'uw-consent-on' : 'uw-consent-off'}>
|
||||||
|
{granted ? 'Aceptada' : 'Rechazada'}
|
||||||
|
</span>
|
||||||
|
</li>
|
||||||
|
))
|
||||||
|
) : (
|
||||||
|
// Categorías locales (cookie uw_cookie_consent).
|
||||||
|
CONSENT_CATEGORIES.map((cat) => {
|
||||||
|
const on = cat.always || Boolean(values?.[cat.key])
|
||||||
|
return (
|
||||||
|
<li key={cat.key}>
|
||||||
|
<span>{cat.label}</span>
|
||||||
|
<span className={cat.always ? 'uw-consent-always' : on ? 'uw-consent-on' : 'uw-consent-off'}>
|
||||||
|
{cat.always ? 'Siempre activas' : values ? (on ? 'Aceptada' : 'Rechazada') : '—'}
|
||||||
|
</span>
|
||||||
|
</li>
|
||||||
|
)
|
||||||
|
})
|
||||||
|
)}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user