feat: KVKK/GDPR uyumlu cookie consent + Google Consent Mode v2

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).
This commit is contained in:
Ege Can Komur
2026-05-20 03:27:02 +03:00
parent 2373eccb17
commit 304a344955
7 changed files with 569 additions and 2 deletions
+52
View File
@@ -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 (
<>
<Script id="consent-default" strategy="beforeInteractive">
{`
window.dataLayer = window.dataLayer || [];
function gtag(){dataLayer.push(arguments);}
window.gtag = gtag;
gtag('consent', 'default', {
ad_storage: 'denied',
ad_user_data: 'denied',
ad_personalization: 'denied',
analytics_storage: 'denied',
functionality_storage: 'granted',
personalization_storage: 'denied',
security_storage: 'granted',
wait_for_update: 500,
});
`}
</Script>
{gtmId && (
<>
<Script id="gtm-script" strategy="afterInteractive">
{`
(function(w,d,s,l,i){w[l]=w[l]||[];w[l].push({'gtm.start': new Date().getTime(),event:'gtm.js'});
var f=d.getElementsByTagName(s)[0],j=d.createElement(s),dl=l!='dataLayer'?'&l='+l:'';
j.async=true;j.src='https://www.googletagmanager.com/gtm.js?id='+i+dl;f.parentNode.insertBefore(j,f);
})(window,document,'script','dataLayer','${gtmId}');
`}
</Script>
<noscript>
<iframe
src={`https://www.googletagmanager.com/ns.html?id=${gtmId}`}
height="0"
width="0"
style={{ display: "none", visibility: "hidden" }}
/>
</noscript>
</>
)}
</>
);
}