Реализация cookie-баннера
Cookie-баннер информирует посетителей об использовании куков и получает согласие до их установки. GDPR требует: согласие добровольное, явное, гранулярное (по категориям), легко отзываемое.
Категории куков
| Категория | Обязательное согласие | Примеры |
|---|---|---|
| Необходимые | Нет | session, CSRF-токен, cookie-consent |
| Функциональные | Да | язык, тема, помнить меня |
| Аналитические | Да | Google Analytics, Yandex.Metrica |
| Маркетинговые | Да | Google Ads, Facebook Pixel |
React компонент
import { useState, useEffect } from 'react';
interface CookieConsent {
functional: boolean;
analytics: boolean;
marketing: boolean;
}
const CONSENT_KEY = 'cookie_consent_v2';
function useCookieConsent() {
const [consent, setConsent] = useState<CookieConsent | null>(null);
const [showBanner, setShowBanner] = useState(false);
useEffect(() => {
const saved = localStorage.getItem(CONSENT_KEY);
if (saved) {
const parsed = JSON.parse(saved);
setConsent(parsed);
applyConsent(parsed);
} else {
setShowBanner(true);
}
}, []);
const acceptAll = () => {
const all = { functional: true, analytics: true, marketing: true };
save(all);
};
const rejectAll = () => {
const none = { functional: false, analytics: false, marketing: false };
save(none);
};
const save = (c: CookieConsent) => {
localStorage.setItem(CONSENT_KEY, JSON.stringify({
...c,
updatedAt: new Date().toISOString(),
}));
setConsent(c);
setShowBanner(false);
applyConsent(c);
reportConsent(c);
};
return { consent, showBanner, acceptAll, rejectAll, save, openSettings: () => setShowBanner(true) };
}
function applyConsent(consent: CookieConsent) {
// Включить/выключить скрипты аналитики
if (consent.analytics) {
loadGoogleAnalytics();
loadYandexMetrica();
}
if (consent.marketing) {
loadFacebookPixel();
}
}
async function reportConsent(consent: CookieConsent) {
await fetch('/api/cookie-consent', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ consent, ip: undefined }), // IP определяет сервер
});
}
export function CookieBanner() {
const { showBanner, acceptAll, rejectAll, save } = useCookieConsent();
const [showDetails, setShowDetails] = useState(false);
const [selections, setSelections] = useState({ functional: false, analytics: false, marketing: false });
if (!showBanner) return null;
return (
<div className="cookie-banner" role="dialog" aria-label="Настройки cookies">
<div className="cookie-banner__content">
<h2>Мы используем cookies</h2>
<p>
Мы используем необходимые куки для работы сайта. С вашего согласия — аналитические и маркетинговые.{' '}
<a href="/cookie-policy">Подробнее</a>
</p>
{showDetails && (
<div className="cookie-categories">
<label className="cookie-category cookie-category--required">
<input type="checkbox" checked disabled />
<span>Необходимые</span>
<small>Всегда включены. Обеспечивают базовую работу сайта.</small>
</label>
{(['functional', 'analytics', 'marketing'] as const).map(cat => (
<label key={cat} className="cookie-category">
<input
type="checkbox"
checked={selections[cat]}
onChange={e => setSelections(s => ({ ...s, [cat]: e.target.checked }))}
/>
<span>{{ functional: 'Функциональные', analytics: 'Аналитические', marketing: 'Маркетинговые' }[cat]}</span>
</label>
))}
</div>
)}
<div className="cookie-banner__actions">
<button onClick={rejectAll} className="btn btn--outline">Отклонить всё</button>
<button onClick={() => setShowDetails(!showDetails)} className="btn btn--outline">Настроить</button>
{showDetails
? <button onClick={() => save(selections)} className="btn btn--primary">Сохранить</button>
: <button onClick={acceptAll} className="btn btn--primary">Принять всё</button>
}
</div>
</div>
</div>
);
}
.cookie-banner {
position: fixed;
bottom: 0;
left: 0;
right: 0;
background: white;
border-top: 1px solid #e5e7eb;
box-shadow: 0 -4px 24px rgba(0,0,0,0.08);
z-index: 9999;
padding: 1.5rem;
}
.cookie-banner__actions {
display: flex;
gap: 0.75rem;
margin-top: 1rem;
flex-wrap: wrap;
}
Сервер: фиксация согласия
class CookieConsentController extends Controller
{
public function store(Request $request): JsonResponse
{
$request->validate([
'consent.functional' => 'required|boolean',
'consent.analytics' => 'required|boolean',
'consent.marketing' => 'required|boolean',
]);
CookieConsent::create([
'user_id' => auth()->id(),
'session_id' => $request->session()->getId(),
'ip' => $request->ip(),
'user_agent' => $request->userAgent(),
'consent' => $request->consent,
'version' => config('cookies.policy_version', '2.0'),
'consented_at' => now(),
]);
return response()->json(['status' => 'saved']);
}
}
Подключение Google Analytics только с согласием
function loadGoogleAnalytics() {
if (document.getElementById('ga-script')) return; // уже загружен
const script = document.createElement('script');
script.id = 'ga-script';
script.src = `https://www.googletagmanager.com/gtag/js?id=${GA_ID}`;
script.async = true;
document.head.appendChild(script);
window.dataLayer = window.dataLayer || [];
function gtag(...args: unknown[]) { window.dataLayer.push(args); }
gtag('js', new Date());
gtag('config', GA_ID, { anonymize_ip: true });
}
Срок реализации
Cookie-баннер с тремя категориями, сохранением согласия в localStorage и API: 2–3 дня. С аудит-логом согласий в БД и GDPR-совместимым отзывом: 3–4 дня.







