feat: update blog posts and improve cookie consent management
This commit is contained in:
@@ -0,0 +1,185 @@
|
||||
'use client';
|
||||
|
||||
import { useState, useEffect } from 'react';
|
||||
import Link from 'next/link';
|
||||
import { useConsentStore } from '@/lib/consent-store';
|
||||
|
||||
interface CookiePreferences {
|
||||
necessary: boolean;
|
||||
analytics: boolean;
|
||||
marketing: boolean;
|
||||
advertising: boolean;
|
||||
}
|
||||
|
||||
export default function CookieBanner() {
|
||||
const {
|
||||
isInitialized,
|
||||
isOpen,
|
||||
analytics,
|
||||
marketing,
|
||||
advertising,
|
||||
setConsent,
|
||||
acceptAll: storeAcceptAll,
|
||||
rejectOptional: storeRejectOptional
|
||||
} = useConsentStore();
|
||||
|
||||
const [showDetails, setShowDetails] = useState(false);
|
||||
const [prefs, setPrefs] = useState<CookiePreferences>({
|
||||
necessary: true,
|
||||
analytics: true,
|
||||
marketing: true,
|
||||
advertising: true,
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
useConsentStore.getState().initialize();
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (isOpen) {
|
||||
setPrefs({
|
||||
necessary: true,
|
||||
analytics: analytics,
|
||||
marketing: marketing,
|
||||
advertising: advertising,
|
||||
});
|
||||
setShowDetails(false);
|
||||
}
|
||||
}, [isOpen, analytics, marketing, advertising]);
|
||||
|
||||
const saveSelected = () => {
|
||||
setConsent(prefs.analytics, prefs.marketing, prefs.advertising);
|
||||
};
|
||||
|
||||
if (!isInitialized || !isOpen) return null;
|
||||
|
||||
return (
|
||||
<div className="fixed bottom-0 left-0 right-0 z-[100] p-4 sm:p-6">
|
||||
<div className="max-w-4xl mx-auto bg-white rounded-2xl shadow-2xl border border-border overflow-hidden">
|
||||
{/* Główny pasek */}
|
||||
<div className="p-5 sm:p-6">
|
||||
<div className="flex items-start gap-3 mb-4">
|
||||
<span className="text-2xl shrink-0">🍪</span>
|
||||
<div>
|
||||
<h2 className="text-base font-extrabold text-primary font-heading mb-1">
|
||||
Ta strona używa plików cookie
|
||||
</h2>
|
||||
<p className="text-sm text-text-muted leading-relaxed">
|
||||
Używamy plików cookie, aby zapewnić prawidłowe działanie strony, analizować ruch
|
||||
i dostosowywać treści. Możesz wybrać które kategorie akceptujesz.{' '}
|
||||
<Link href="/polityka-prywatnosci" className="text-accent underline hover:no-underline">
|
||||
Polityka prywatności
|
||||
</Link>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Szczegóły */}
|
||||
{showDetails && (
|
||||
<div className="flex flex-col gap-3 mb-5 p-4 bg-slate-50 rounded-xl">
|
||||
{/* Niezbędne */}
|
||||
<div className="flex items-start justify-between gap-4">
|
||||
<div>
|
||||
<p className="text-sm font-semibold text-primary">Niezbędne</p>
|
||||
<p className="text-xs text-text-muted mt-0.5">
|
||||
Wymagane do prawidłowego działania strony. Nie można wyłączyć.
|
||||
</p>
|
||||
</div>
|
||||
<div className="shrink-0">
|
||||
<div className="w-11 h-6 bg-accent rounded-full relative cursor-not-allowed opacity-60">
|
||||
<div className="absolute right-1 top-1 size-4 bg-white rounded-full" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Analityczne */}
|
||||
<div className="flex items-start justify-between gap-4 border-t border-border pt-3">
|
||||
<div>
|
||||
<p className="text-sm font-semibold text-primary">Analityczne</p>
|
||||
<p className="text-xs text-text-muted mt-0.5">
|
||||
Google Analytics — pomagają nam zrozumieć jak użytkownicy korzystają ze strony. Dane są anonimowe.
|
||||
</p>
|
||||
</div>
|
||||
<button
|
||||
onClick={() => setPrefs(p => ({ ...p, analytics: !p.analytics }))}
|
||||
className="shrink-0"
|
||||
>
|
||||
<div className={`w-11 h-6 rounded-full relative transition-colors ${prefs.analytics ? 'bg-accent' : 'bg-slate-300'}`}>
|
||||
<div className={`absolute top-1 size-4 bg-white rounded-full transition-all ${prefs.analytics ? 'right-1' : 'left-1'}`} />
|
||||
</div>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* Marketingowe */}
|
||||
<div className="flex items-start justify-between gap-4 border-t border-border pt-3">
|
||||
<div>
|
||||
<p className="text-sm font-semibold text-primary">Marketingowe</p>
|
||||
<p className="text-xs text-text-muted mt-0.5">
|
||||
Google Ads, Facebook Ads — pozwalają mierzyć skuteczność kampanii i docierać do odpowiednich odbiorców.
|
||||
</p>
|
||||
</div>
|
||||
<button
|
||||
onClick={() => setPrefs(p => ({ ...p, marketing: !p.marketing }))}
|
||||
className="shrink-0"
|
||||
>
|
||||
<div className={`w-11 h-6 rounded-full relative transition-colors ${prefs.marketing ? 'bg-accent' : 'bg-slate-300'}`}>
|
||||
<div className={`absolute top-1 size-4 bg-white rounded-full transition-all ${prefs.marketing ? 'right-1' : 'left-1'}`} />
|
||||
</div>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* Reklamowe */}
|
||||
<div className="flex items-start justify-between gap-4 border-t border-border pt-3">
|
||||
<div>
|
||||
<p className="text-sm font-semibold text-primary">Reklamowe</p>
|
||||
<p className="text-xs text-text-muted mt-0.5">
|
||||
Meta Pixel, Google Ads — umożliwiają wyświetlanie spersonalizowanych reklam na platformach takich jak Facebook, Instagram i Google.
|
||||
</p>
|
||||
</div>
|
||||
<button
|
||||
onClick={() => setPrefs(p => ({ ...p, advertising: !p.advertising }))}
|
||||
className="shrink-0"
|
||||
>
|
||||
<div className={`w-11 h-6 rounded-full relative transition-colors ${prefs.advertising ? 'bg-accent' : 'bg-slate-300'}`}>
|
||||
<div className={`absolute top-1 size-4 bg-white rounded-full transition-all ${prefs.advertising ? 'right-1' : 'left-1'}`} />
|
||||
</div>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Przyciski */}
|
||||
<div className="flex flex-col sm:flex-row items-stretch sm:items-center gap-2">
|
||||
<button
|
||||
onClick={storeAcceptAll}
|
||||
className="flex-1 sm:flex-none px-5 py-2.5 bg-accent text-white font-bold rounded-xl text-sm hover:bg-accent/90 transition-colors"
|
||||
>
|
||||
Akceptuj wszystkie
|
||||
</button>
|
||||
<button
|
||||
onClick={storeRejectOptional}
|
||||
className="flex-1 sm:flex-none px-5 py-2.5 bg-slate-100 text-primary font-bold rounded-xl text-sm hover:bg-slate-200 transition-colors"
|
||||
>
|
||||
Odrzuć opcjonalne
|
||||
</button>
|
||||
{showDetails ? (
|
||||
<button
|
||||
onClick={saveSelected}
|
||||
className="flex-1 sm:flex-none px-5 py-2.5 bg-white border border-border text-primary font-bold rounded-xl text-sm hover:border-accent transition-colors"
|
||||
>
|
||||
Zapisz wybór
|
||||
</button>
|
||||
) : (
|
||||
<button
|
||||
onClick={() => setShowDetails(true)}
|
||||
className="flex-1 sm:flex-none px-5 py-2.5 bg-white border border-border text-primary font-bold rounded-xl text-sm hover:border-accent transition-colors"
|
||||
>
|
||||
Dostosuj ustawienia
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -1,206 +0,0 @@
|
||||
"use client";
|
||||
|
||||
import { useState } from "react";
|
||||
import Link from "next/link";
|
||||
import { Cookie, X, Check, Settings2, Info } from "lucide-react";
|
||||
import { useConsentStore } from "@/lib/consent-store";
|
||||
|
||||
export function CookieBanner() {
|
||||
const {
|
||||
isInitialized,
|
||||
isOpen,
|
||||
analytics,
|
||||
marketing,
|
||||
setConsent,
|
||||
setAnalyticsConsent,
|
||||
acceptAll,
|
||||
rejectOptional,
|
||||
closeBanner
|
||||
} = useConsentStore();
|
||||
|
||||
const [showSettings, setShowSettings] = useState(false);
|
||||
const [localAnalytics, setLocalAnalytics] = useState(true);
|
||||
const [localMarketing, setLocalMarketing] = useState(true);
|
||||
|
||||
if (!isInitialized || !isOpen) return null;
|
||||
|
||||
const handleSaveSettings = () => {
|
||||
setConsent(localAnalytics, localMarketing);
|
||||
setAnalyticsConsent(localAnalytics);
|
||||
setShowSettings(false);
|
||||
};
|
||||
|
||||
const handleAcceptAll = () => {
|
||||
acceptAll();
|
||||
setAnalyticsConsent(true);
|
||||
};
|
||||
|
||||
const handleRejectOptional = () => {
|
||||
rejectOptional();
|
||||
setAnalyticsConsent(false);
|
||||
};
|
||||
|
||||
const handleOpenSettings = () => {
|
||||
const hasConsent = localStorage.getItem('thermcool_cookie_consent_v1');
|
||||
setLocalAnalytics(hasConsent ? analytics : true);
|
||||
setLocalMarketing(hasConsent ? marketing : true);
|
||||
setShowSettings(true);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="fixed bottom-0 left-0 right-0 z-50 p-4 md:p-6 pb-safe flex flex-col items-center pointer-events-none">
|
||||
<div className="relative pointer-events-auto max-w-5xl w-full bg-slate-900 border border-slate-700 shadow-2xl rounded-2xl overflow-hidden animate-in slide-in-from-bottom-10 fade-in duration-500">
|
||||
|
||||
{/* WIDOK GŁÓWNY BANERA */}
|
||||
{!showSettings && (
|
||||
<div className="p-5 md:p-8">
|
||||
<div className="flex flex-col md:flex-row items-start gap-6">
|
||||
<div className="bg-slate-800 p-3 rounded-full shrink-0 text-white hidden md:block border border-slate-700 shadow-sm">
|
||||
<Cookie className="w-8 h-8" />
|
||||
</div>
|
||||
|
||||
<div className="flex-grow">
|
||||
<h3 className="text-white font-bold text-lg md:text-xl mb-2 flex items-center gap-2">
|
||||
<Cookie className="w-5 h-5 md:hidden text-white" />
|
||||
Szanujemy Twoją prywatność
|
||||
</h3>
|
||||
<p className="text-slate-300 text-sm md:text-base leading-relaxed mb-4">
|
||||
Używamy plików cookies i podobnych technologii, by zapewnić prawidłowe działanie serwisu, personalizować treści oraz analizować ruch.
|
||||
Możesz zaakceptować wszystkie ciasteczka, odrzucić opcjonalne lub dostosować zgody w ustawieniach.
|
||||
Szczegóły znajdziesz w naszej{" "}
|
||||
<Link href="/polityka-prywatnosci" className="text-accent hover:text-white underline underline-offset-2">
|
||||
Polityce Prywatności
|
||||
</Link>.
|
||||
</p>
|
||||
|
||||
<div className="flex flex-col sm:flex-row flex-wrap items-center gap-3 mt-6">
|
||||
<button
|
||||
onClick={handleAcceptAll}
|
||||
className="btn btn-primary text-white flex-grow sm:flex-grow-0 border-none shadow-md"
|
||||
>
|
||||
<Check className="w-4 h-4 mr-1 text-white" /> Akceptuj wszystkie
|
||||
</button>
|
||||
<button
|
||||
onClick={handleRejectOptional}
|
||||
className="btn btn-outline border-slate-600 text-slate-200 hover:bg-slate-800 hover:text-white hover:border-slate-500 flex-grow sm:flex-grow-0"
|
||||
>
|
||||
<X className="w-4 h-4 mr-1 text-slate-300" /> Tylko niezbędne
|
||||
</button>
|
||||
<div className="flex-grow hidden sm:block"></div>
|
||||
<button
|
||||
onClick={handleOpenSettings}
|
||||
className="btn btn-ghost text-slate-300 hover:text-white hover:bg-slate-800 flex-grow sm:flex-grow-0 group"
|
||||
>
|
||||
<Settings2 className="w-4 h-4 mr-1 text-slate-300 group-hover:text-white" /> Ustawienia
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Przycisk zamknięcia wymusza "Tylko niezbędne" dla bezpieczeństwa */}
|
||||
<button
|
||||
onClick={handleRejectOptional}
|
||||
className="absolute top-4 right-4 text-slate-500 hover:text-white p-1"
|
||||
aria-label="Zamknij"
|
||||
>
|
||||
<X className="w-5 h-5" />
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* WIDOK ZARZĄDZANIA (MODAL) */}
|
||||
{showSettings && (
|
||||
<div className="flex flex-col h-full max-h-[85vh]">
|
||||
<div className="p-5 border-b border-slate-700 flex justify-between items-center bg-slate-800/50">
|
||||
<h3 className="text-white font-bold text-lg flex items-center gap-2">
|
||||
<Settings2 className="w-5 h-5 text-slate-300" />
|
||||
Ustawienia plików cookies
|
||||
</h3>
|
||||
<button
|
||||
onClick={() => setShowSettings(false)}
|
||||
className="text-slate-400 hover:text-white"
|
||||
>
|
||||
<X className="w-5 h-5" />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div className="p-5 overflow-y-auto space-y-6">
|
||||
{/* Niezbędne */}
|
||||
<div className="flex gap-4 items-start bg-slate-800/30 p-4 rounded-xl border border-slate-700/50">
|
||||
<div className="pt-1">
|
||||
<input type="checkbox" className="toggle toggle-primary" checked disabled />
|
||||
</div>
|
||||
<div>
|
||||
<h4 className="text-white font-semibold mb-1 flex items-center gap-2">
|
||||
Niezbędne <span className="text-[10px] uppercase bg-slate-700 px-2 py-0.5 rounded text-slate-300">Zawsze aktywne</span>
|
||||
</h4>
|
||||
<p className="text-slate-400 text-sm">
|
||||
Te pliki cookies są wymagane do podstawowego działania strony (np. nawigacja, sesja koszyka, zapisywanie tych preferencji). Nie można ich wyłączyć.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Analityczne */}
|
||||
<div className="flex gap-4 items-start p-2">
|
||||
<div className="pt-1">
|
||||
<input
|
||||
type="checkbox"
|
||||
className="toggle toggle-primary bg-slate-600 border-slate-500 [--tglbg:theme(colors.slate.200)] hover:bg-slate-500"
|
||||
checked={localAnalytics}
|
||||
onChange={(e) => setLocalAnalytics(e.target.checked)}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<h4 className="text-white font-semibold mb-1">Analityczne (Google Analytics 4)</h4>
|
||||
<p className="text-slate-400 text-sm">
|
||||
Pozwalają nam badać statystyki ruchu na stronie, rozumieć jak Użytkownicy z niej korzystają i na tej podstawie optymalizować jej działanie.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Marketingowe */}
|
||||
<div className="flex gap-4 items-start p-2">
|
||||
<div className="pt-1">
|
||||
<input
|
||||
type="checkbox"
|
||||
className="toggle toggle-primary bg-slate-600 border-slate-500 [--tglbg:theme(colors.slate.200)] hover:bg-slate-500"
|
||||
checked={localMarketing}
|
||||
onChange={(e) => setLocalMarketing(e.target.checked)}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<h4 className="text-white font-semibold mb-1">Marketingowe (Google Tag Manager)</h4>
|
||||
<p className="text-slate-400 text-sm">
|
||||
Pozwalają nam dopasowywać treści reklamowe do Twoich zainteresowań w sieci (np. w systemach Google Ads, Facebook Ads) oraz mierzyć skuteczność kampanii.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="p-5 border-t border-slate-700 bg-slate-800/50 flex flex-col-reverse sm:flex-row justify-end gap-3 items-center">
|
||||
<button
|
||||
onClick={handleRejectOptional}
|
||||
className="btn btn-ghost text-slate-400 hover:text-white w-full sm:w-auto"
|
||||
>
|
||||
Odrzuć wszystkie
|
||||
</button>
|
||||
<button
|
||||
onClick={handleAcceptAll}
|
||||
className="btn btn-outline border-slate-600 text-white hover:bg-slate-700 w-full sm:w-auto"
|
||||
>
|
||||
Akceptuj wszystkie
|
||||
</button>
|
||||
<button
|
||||
onClick={handleSaveSettings}
|
||||
className="btn btn-primary text-white w-full sm:w-auto"
|
||||
>
|
||||
Zapisz ustawienia
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
'use client';
|
||||
|
||||
import { Cookie } from 'lucide-react';
|
||||
import { useConsentStore } from '@/lib/consent-store';
|
||||
|
||||
export function CookieSettingsFloatingIcon() {
|
||||
const { isInitialized, isOpen, openBanner } = useConsentStore();
|
||||
|
||||
// Only show if initialized (user has made a decision or default exists) and the banner is closed
|
||||
if (!isInitialized || isOpen) return null;
|
||||
|
||||
return (
|
||||
<button
|
||||
onClick={openBanner}
|
||||
className="fixed bottom-24 sm:bottom-6 left-4 sm:left-6 z-[90] flex size-12 items-center justify-center rounded-full bg-white text-primary shadow-xl border border-border hover:bg-slate-50 transition-all duration-300 group focus:outline-none focus:ring-2 focus:ring-accent focus:ring-offset-2"
|
||||
aria-label="Ustawienia ciasteczek"
|
||||
title="Zarządzaj plikami cookies"
|
||||
>
|
||||
<Cookie className="size-5 text-slate-500 group-hover:text-accent transition-colors" />
|
||||
</button>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user