feat(kvkk): çerez politikası, ölçümü kapatma ve ilk ziyaret bilgi bandı
All checks were successful
Deploy / deploy (push) Successful in 6m27s
All checks were successful
Deploy / deploy (push) Successful in 6m27s
- /cerez-politikasi: çerez aydınlatma metni (ad, amaç, süre, taraf), Rybbit ölçümünün kapsamı ve hukuki dayanağı (KVKK md. 5/2-f, Çerez Rehberi Temmuz 2025 §5.9) - OlcumTercihi: Rybbit'i tarayıcıda kapatan düğme (disable-rybbit) - CerezBilgi: ilk ziyarette bir kez görünen bilgilendirme (onay penceresi değil; rıza gerektiren çerez yok) - gizlilik: analitik bölümü kodla eşitlendi (e-posta/ad gönderilmiyor) - alt bilgi ve sitemap'e sayfa eklendi - docs: hukuki analiz ve meşru menfaat denge testi Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
80
src/components/cerez-bilgi.tsx
Normal file
80
src/components/cerez-bilgi.tsx
Normal file
@@ -0,0 +1,80 @@
|
||||
"use client";
|
||||
|
||||
// İlk ziyarette bir kez görünen çerez bilgilendirmesi. Onay penceresi DEĞİL:
|
||||
// sitede açık rıza gerektiren çerez yok (zorunlu çerezler + kendi
|
||||
// sunucumuzdaki ölçüm; bkz. /cerez-politikasi). KVKK Çerez Rehberi (Temmuz
|
||||
// 2025) §9, veri işleme şartından bağımsız olarak siteye girişte
|
||||
// bilgilendirme bekliyor; bu bant onu karşılar. Kabul/ret düğmesi yoktur,
|
||||
// çünkü sorulan bir rıza yoktur — ölçümü kapatma anahtarı politika
|
||||
// sayfasındadır. Kapatma kalıcıdır (localStorage).
|
||||
|
||||
import { useEffect, useState } from "react";
|
||||
import Link from "next/link";
|
||||
import { usePathname } from "next/navigation";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { ViewportPortal } from "@/components/viewport-portal";
|
||||
import { kromGizliMi } from "@/components/header-gate";
|
||||
|
||||
const GORULDU_KEY = "kolaytercih.cerez-bilgi";
|
||||
|
||||
export function CerezBilgi() {
|
||||
const pathname = usePathname();
|
||||
|
||||
// SSR/ilk boyada gizli başla; durum mount'ta okunur.
|
||||
const [goruldu, setGoruldu] = useState(true);
|
||||
useEffect(() => {
|
||||
try {
|
||||
// eslint-disable-next-line react-hooks/set-state-in-effect
|
||||
setGoruldu(Boolean(localStorage.getItem(GORULDU_KEY)));
|
||||
} catch {
|
||||
// Depolama kapalıysa "görüldü" saklanamaz; her sayfada çıkmasın.
|
||||
setGoruldu(true);
|
||||
}
|
||||
}, []);
|
||||
|
||||
if (goruldu) return null;
|
||||
// Giriş/ödeme gibi kromsuz akışlarda ve politikanın kendisinde gösterilmez.
|
||||
if (pathname && kromGizliMi(pathname)) return null;
|
||||
if (pathname === "/cerez-politikasi") return null;
|
||||
|
||||
function kapat() {
|
||||
setGoruldu(true);
|
||||
try {
|
||||
localStorage.setItem(GORULDU_KEY, "1");
|
||||
} catch {}
|
||||
}
|
||||
|
||||
return (
|
||||
<ViewportPortal>
|
||||
{/* z-45: KaydetBanner'ın (z-40) üstünde, modalların (z-50) altında */}
|
||||
<div className="pointer-events-none fixed inset-x-0 bottom-4 z-45 px-4 print:hidden">
|
||||
<section
|
||||
aria-label="Çerez bilgilendirmesi"
|
||||
className="pointer-events-auto mx-auto flex max-w-xl flex-wrap items-center gap-3 rounded-2xl border border-slate-200 bg-card p-4 shadow-xl sm:flex-nowrap sm:gap-4"
|
||||
>
|
||||
<p className="min-w-0 flex-1 text-sm leading-snug text-slate-700">
|
||||
Bu sitede reklam ya da izleme çerezi yok. Yalnız oturumun ve listen
|
||||
için gerekli çerezleri ve kendi sunucumuzda çalışan çerezsiz
|
||||
ziyaret ölçümünü kullanıyoruz.{" "}
|
||||
<Link
|
||||
href="/cerez-politikasi"
|
||||
onClick={kapat}
|
||||
className="font-semibold text-primary underline"
|
||||
>
|
||||
Ayrıntılar ve ölçümü kapatma
|
||||
</Link>
|
||||
</p>
|
||||
<Button
|
||||
type="button"
|
||||
variant="outline"
|
||||
size="lg"
|
||||
onClick={kapat}
|
||||
className="w-full cursor-pointer px-6 transition-[background-color,border-color,color,transform] duration-150 hover:border-orange-500 hover:bg-orange-50/60 hover:text-orange-500 active:scale-[0.97] sm:w-auto"
|
||||
>
|
||||
Tamam
|
||||
</Button>
|
||||
</section>
|
||||
</div>
|
||||
</ViewportPortal>
|
||||
);
|
||||
}
|
||||
72
src/components/olcum-tercihi.tsx
Normal file
72
src/components/olcum-tercihi.tsx
Normal file
@@ -0,0 +1,72 @@
|
||||
"use client";
|
||||
|
||||
// Kullanım ölçümünü (Rybbit) bu tarayıcıda kapatan/açan anahtar — KVKK
|
||||
// md. 11 itiraz hakkının arayüzü, /cerez-politikasi sayfasında durur.
|
||||
//
|
||||
// Rybbit betiği yüklenirken localStorage'da `disable-rybbit` anahtarını
|
||||
// görürse hiçbir şey göndermez (betiğin kendi vazgeçme kapısı). Betik
|
||||
// yüklendikten sonra durdurulamadığı için tercih değişince sayfa yenilenir.
|
||||
|
||||
import { useEffect, useState } from "react";
|
||||
import { Button } from "@/components/ui/button";
|
||||
|
||||
const KAPALI_KEY = "disable-rybbit";
|
||||
// Rybbit'in bu tarayıcıya yazdığı kimlikler; ölçüm kapanınca silinir.
|
||||
const RYBBIT_KIMLIK_KEYLERI = ["rybbit-user-id", "rybbit-visitor-id"];
|
||||
|
||||
export function OlcumTercihi() {
|
||||
// null = henüz okunmadı (SSR/ilk boya)
|
||||
const [kapali, setKapali] = useState<boolean | null>(null);
|
||||
const [hata, setHata] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
try {
|
||||
// eslint-disable-next-line react-hooks/set-state-in-effect
|
||||
setKapali(localStorage.getItem(KAPALI_KEY) !== null);
|
||||
} catch {
|
||||
setKapali(false);
|
||||
}
|
||||
}, []);
|
||||
|
||||
function degistir() {
|
||||
try {
|
||||
if (kapali) {
|
||||
localStorage.removeItem(KAPALI_KEY);
|
||||
} else {
|
||||
localStorage.setItem(KAPALI_KEY, "1");
|
||||
for (const k of RYBBIT_KIMLIK_KEYLERI) localStorage.removeItem(k);
|
||||
}
|
||||
} catch {
|
||||
setHata(true);
|
||||
return;
|
||||
}
|
||||
window.location.reload();
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="mt-3 flex flex-wrap items-center gap-3 rounded-2xl border border-slate-200 bg-card p-4 sm:flex-nowrap sm:gap-4">
|
||||
<div className="min-w-0 flex-1">
|
||||
<p className="font-heading text-base font-bold text-slate-900">
|
||||
{kapali ? "Ölçüm bu tarayıcıda kapalı" : "Ölçüm bu tarayıcıda açık"}
|
||||
</p>
|
||||
<p className="text-sm leading-snug text-slate-500" role="status">
|
||||
{hata
|
||||
? "Tarayıcın bu tercihi saklamaya izin vermiyor."
|
||||
: kapali
|
||||
? "Ziyaretlerin sayılmıyor. Site aynı şekilde çalışır."
|
||||
: "Kapatırsan ziyaretlerin sayılmaz; site aynı şekilde çalışır."}
|
||||
</p>
|
||||
</div>
|
||||
<Button
|
||||
type="button"
|
||||
variant="outline"
|
||||
size="lg"
|
||||
disabled={kapali === null}
|
||||
onClick={degistir}
|
||||
className="w-full cursor-pointer px-6 transition-[background-color,border-color,color,transform] duration-150 hover:border-orange-500 hover:bg-orange-50/60 hover:text-orange-500 active:scale-[0.97] sm:w-auto"
|
||||
>
|
||||
{kapali ? "Ölçümü aç" : "Ölçümü kapat"}
|
||||
</Button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -180,6 +180,12 @@ export function SiteFooter() {
|
||||
>
|
||||
Gizlilik & KVKK
|
||||
</Link>
|
||||
<Link
|
||||
href="/cerez-politikasi"
|
||||
className="hover:text-slate-700 hover:underline"
|
||||
>
|
||||
Çerez politikası
|
||||
</Link>
|
||||
<Link
|
||||
href="/kosullar"
|
||||
className="hover:text-slate-700 hover:underline"
|
||||
|
||||
Reference in New Issue
Block a user