From 304a3449557fd77797068e6a1b676d2f58c2c692 Mon Sep 17 00:00:00 2001 From: Ege Can Komur Date: Wed, 20 May 2026 03:27:02 +0300 Subject: [PATCH] feat: KVKK/GDPR uyumlu cookie consent + Google Consent Mode v2 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Yeni component'ler: - CookieBanner (sağ alt banner + tam ekran ayarlar modal) - 'Tümünü kabul', 'Tümünü reddet', 'Ayarları yönet' - 4 kategori toggle: Zorunlu / Analitik / Reklam / Tercih - Zorunlu kategori her zaman açık (KVKK) - localStorage + cookie persistence (12 ay TTL) - Versionlı (CONSENT_VERSION=1) — şema değişince yeniden sorma - window.openCookieSettings() global helper (footer/policy sayfasından çağrılabilir) - ConsentInit (Google Consent Mode v2 default deny) - beforeInteractive Script ile gtag default deny yüklenir - User onayladığında banner gtag('consent','update', ...) çağırır - seo_settings.gtm_id doluysa GTM injection (asenkron) - CookieSettingsButton (politika sayfasında 'ayarları değiştir') Yeni sayfa: - /cerez-politikasi — KVKK uyumlu çerez politikası metni - 4 kategori detaylı açıklama + örnek çerez isimleri - KVKK Madde 11 kapsamındaki kullanıcı hakları - İletişim bilgileri site_settings'ten Layout entegrasyonu: - app/layout.tsx — ConsentInit head'e, CookieBanner body sonuna - Footer'a 'Çerez Politikası' linki Consent flag mapping (Consent Mode v2): - Zorunlu → functionality_storage + security_storage (her zaman granted) - Analitik → analytics_storage - Reklam → ad_storage + ad_user_data + ad_personalization - Tercih → personalization_storage Önemli: Default state 'denied' — kullanıcı seçim yapmadan hiçbir analytics/ads çerezi tetiklenmez. Google Ads Consent Mode v2 uyumlu. 31 route, public sayfalar static (1m revalidate). --- app/(site)/cerez-politikasi/page.tsx | 138 +++++++++++ app/layout.tsx | 17 +- components/consent-init.tsx | 52 +++++ components/cookie-banner.tsx | 321 ++++++++++++++++++++++++++ components/cookie-settings-button.tsx | 16 ++ components/footer.tsx | 8 +- lib/consent-types.ts | 19 ++ 7 files changed, 569 insertions(+), 2 deletions(-) create mode 100644 app/(site)/cerez-politikasi/page.tsx create mode 100644 components/consent-init.tsx create mode 100644 components/cookie-banner.tsx create mode 100644 components/cookie-settings-button.tsx create mode 100644 lib/consent-types.ts diff --git a/app/(site)/cerez-politikasi/page.tsx b/app/(site)/cerez-politikasi/page.tsx new file mode 100644 index 0000000..863aa2f --- /dev/null +++ b/app/(site)/cerez-politikasi/page.tsx @@ -0,0 +1,138 @@ +import type { Metadata } from "next"; +import { SectionTitle } from "@/components/section-title"; +import { CookieSettingsButton } from "@/components/cookie-settings-button"; +import { getSiteSettings } from "@/lib/data"; +import { siteConfig } from "@/lib/site-config"; +import { buildMetadata } from "@/lib/seo"; + +export async function generateMetadata(): Promise { + return buildMetadata("/cerez-politikasi", { + title: "Çerez Politikası", + description: + "Kovak Yazılım çerez politikası — KVKK ve GDPR uyumlu çerez kullanımı.", + }); +} + +const CATEGORIES = [ + { + title: "Zorunlu çerezler", + description: + "Web sitesinin temel işlevleri için gereklidir. Oturum yönetimi, güvenlik, form gönderimi gibi temel işlemler bu çerezler olmadan çalışamaz. Devre dışı bırakılamaz.", + examples: ["kovak_session", "kovak_consent_v1"], + }, + { + title: "Analitik çerezler", + description: + "Web sitesi ziyaretçilerinin sitenizi nasıl kullandığını anlamamıza yardımcı olur. Toplanan veriler anonimdir ve istatistiksel amaçlıdır.", + examples: ["_ga", "_gid", "_gat"], + }, + { + title: "Reklam çerezleri", + description: + "Sizinle ilgili olabilecek reklamların gösterilmesi ve reklam kampanyalarının performansının ölçülmesi amacıyla kullanılır.", + examples: ["_gcl_au", "_fbp", "IDE", "ads/ga-audiences"], + }, + { + title: "Tercih çerezleri", + description: + "Dil, bölge veya görünüm tercihlerinizi hatırlamak için kullanılır.", + examples: ["NID", "lang", "theme"], + }, +]; + +export default async function CookiePolicyPage() { + const s = await getSiteSettings(); + const email = s?.contact_email ?? siteConfig.contact.email; + const phone = s?.contact_phone ?? siteConfig.contact.phone; + + return ( +
+ + +
+

+ Çerez tercihlerinizi istediğiniz zaman değiştirebilirsiniz. +

+ +
+ +
+

Çerez Nedir?

+

+ Çerezler (cookies), ziyaret ettiğiniz web siteleri tarafından + tarayıcınız aracılığıyla cihazınıza kaydedilen küçük metin + dosyalarıdır. Bu dosyalar sayesinde site, sizi sonraki ziyaretlerde + tanır ve deneyiminizi kişiselleştirir. +

+ +

Hangi Çerezleri Kullanıyoruz?

+

+ Sitemizde 4 farklı kategoride çerez kullanılmaktadır. Zorunlu çerezler + dışındakileri çerez ayarları üzerinden devre dışı bırakabilirsiniz. +

+ + {CATEGORIES.map((c) => ( +
+

+ {c.title} +

+

+ {c.description} +

+

+ Örnek çerezler:{" "} + {c.examples.map((e, i) => ( + + + {e} + + {i < c.examples.length - 1 && ", "} + + ))} +

+
+ ))} + +

Çerezleri Nasıl Devre Dışı Bırakabilirim?

+

+ Tarayıcı ayarlarınızdan tüm çerezleri silebilir veya engelleyebilirsiniz. + Ancak zorunlu çerezler devre dışı bırakıldığında sitemizin bazı bölümleri + çalışmayabilir. Tercihlerinizi yönetmek için sayfanın altındaki "Çerez + ayarları" butonunu kullanabilirsiniz. +

+ +

KVKK Kapsamında Haklarınız

+

+ 6698 sayılı Kişisel Verilerin Korunması Kanunu (KVKK) kapsamında + aşağıdaki haklara sahipsiniz: +

+
    +
  • Kişisel verilerinizin işlenip işlenmediğini öğrenme
  • +
  • İşlenmişse buna ilişkin bilgi talep etme
  • +
  • İşlenme amacını ve amacına uygun kullanılıp kullanılmadığını öğrenme
  • +
  • Eksik veya yanlış işlenmiş verilerin düzeltilmesini isteme
  • +
  • Silinmesini veya yok edilmesini isteme
  • +
  • Kanuna aykırı işleme sonucu zarara uğramışsanız tazminat talep etme
  • +
+ +

İletişim

+

+ Çerez politikamız veya kişisel verilerinizin işlenmesi hakkında soru + veya talepleriniz için bizimle iletişime geçebilirsiniz: +

+
    +
  • + E-posta: {email} +
  • +
  • Telefon: {phone}
  • +
  • Adres: {s?.contact_address ?? siteConfig.contact.address}
  • +
+
+
+ ); +} diff --git a/app/layout.tsx b/app/layout.tsx index 83479fc..6356b33 100644 --- a/app/layout.tsx +++ b/app/layout.tsx @@ -2,6 +2,9 @@ import type { Metadata } from "next"; import { Geist, Geist_Mono } from "next/font/google"; import "./globals.css"; import { siteConfig } from "@/lib/site-config"; +import { ConsentInit } from "@/components/consent-init"; +import { CookieBanner } from "@/components/cookie-banner"; +import { getSeoSettings } from "@/lib/data"; const geistSans = Geist({ variable: "--font-geist-sans", @@ -29,16 +32,28 @@ export const metadata: Metadata = { icons: { icon: "/logo.png" }, }; -export default function RootLayout({ +export default async function RootLayout({ children, }: Readonly<{ children: React.ReactNode }>) { + let gtmId: string | null = null; + try { + const seo = await getSeoSettings(); + gtmId = seo?.gtm_id ?? null; + } catch { + gtmId = null; + } + return ( + + + {children} + ); diff --git a/components/consent-init.tsx b/components/consent-init.tsx new file mode 100644 index 0000000..d750b17 --- /dev/null +++ b/components/consent-init.tsx @@ -0,0 +1,52 @@ +import Script from "next/script"; + +/** + * Google Consent Mode v2 — defaults set to "denied" before any tag loads. + * After the user makes a choice, CookieBanner calls gtag('consent','update', ...). + * + * GTM/GA inject sadece site_settings.gtm_id doluysa yapılır. + */ +export function ConsentInit({ gtmId }: { gtmId?: string | null }) { + return ( + <> + + + {gtmId && ( + <> + +