304a344955
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).
127 lines
4.5 KiB
TypeScript
127 lines
4.5 KiB
TypeScript
import Image from "next/image";
|
||
import Link from "next/link";
|
||
import { Mail, MapPin, Phone } from "lucide-react";
|
||
import { LinkedinIcon, InstagramIcon, TwitterIcon } from "@/components/social-icons";
|
||
import { getSiteSettings, listServices } from "@/lib/data";
|
||
import { siteConfig } from "@/lib/site-config";
|
||
|
||
export async function Footer() {
|
||
const [settings, services] = await Promise.all([
|
||
getSiteSettings(),
|
||
listServices(),
|
||
]);
|
||
|
||
const tagline = settings?.footer_tagline ?? siteConfig.tagline;
|
||
const address = settings?.contact_address ?? siteConfig.contact.address;
|
||
const phone = settings?.contact_phone ?? siteConfig.contact.phone;
|
||
const phoneRaw = settings?.contact_phone_raw ?? siteConfig.contact.phoneRaw;
|
||
const email = settings?.contact_email ?? siteConfig.contact.email;
|
||
|
||
const socials = [
|
||
{ url: settings?.social_linkedin, label: "LinkedIn", Icon: LinkedinIcon },
|
||
{ url: settings?.social_instagram, label: "Instagram", Icon: InstagramIcon },
|
||
{ url: settings?.social_twitter, label: "Twitter / X", Icon: TwitterIcon },
|
||
].filter((s) => s.url);
|
||
|
||
const footerServices =
|
||
services.length > 0
|
||
? services.slice(0, 5)
|
||
: siteConfig.fallbackServices.slice(0, 5);
|
||
|
||
return (
|
||
<footer className="border-t border-[var(--border)] bg-[var(--navy)] text-white">
|
||
<div className="mx-auto grid max-w-7xl gap-10 px-6 py-14 md:grid-cols-4">
|
||
<div>
|
||
<div className="flex items-center gap-3">
|
||
<Image
|
||
src="/logo.png"
|
||
alt={siteConfig.name}
|
||
width={48}
|
||
height={48}
|
||
className="brightness-0 invert"
|
||
/>
|
||
<span className="text-lg font-semibold">{siteConfig.name}</span>
|
||
</div>
|
||
<p className="mt-4 text-sm leading-relaxed text-white/70">{tagline}</p>
|
||
</div>
|
||
|
||
<div>
|
||
<h3 className="text-sm font-semibold uppercase tracking-wider text-white/80">
|
||
Hizmetler
|
||
</h3>
|
||
<ul className="mt-4 space-y-2 text-sm text-white/70">
|
||
{footerServices.map((s) => (
|
||
<li key={s.slug}>
|
||
<Link href={`/hizmetler/${s.slug}`} className="hover:text-white">
|
||
{s.title}
|
||
</Link>
|
||
</li>
|
||
))}
|
||
</ul>
|
||
</div>
|
||
|
||
<div>
|
||
<h3 className="text-sm font-semibold uppercase tracking-wider text-white/80">
|
||
İletişim
|
||
</h3>
|
||
<ul className="mt-4 space-y-3 text-sm text-white/70">
|
||
<li className="flex items-start gap-2">
|
||
<MapPin className="mt-0.5 size-4 shrink-0" />
|
||
<span>{address}</span>
|
||
</li>
|
||
<li className="flex items-center gap-2">
|
||
<Phone className="size-4 shrink-0" />
|
||
<a href={`tel:${phoneRaw}`} className="hover:text-white">
|
||
{phone}
|
||
</a>
|
||
</li>
|
||
<li className="flex items-center gap-2">
|
||
<Mail className="size-4 shrink-0" />
|
||
<a href={`mailto:${email}`} className="hover:text-white">
|
||
{email}
|
||
</a>
|
||
</li>
|
||
</ul>
|
||
</div>
|
||
|
||
{socials.length > 0 && (
|
||
<div>
|
||
<h3 className="text-sm font-semibold uppercase tracking-wider text-white/80">
|
||
Sosyal Medya
|
||
</h3>
|
||
<div className="mt-4 flex gap-3">
|
||
{socials.map(({ url, label, Icon }) => (
|
||
<a
|
||
key={label}
|
||
href={url as string}
|
||
target="_blank"
|
||
rel="noopener noreferrer"
|
||
aria-label={label}
|
||
className="flex size-9 items-center justify-center rounded-full bg-white/10 transition hover:bg-white/20"
|
||
>
|
||
<Icon className="size-4" />
|
||
</a>
|
||
))}
|
||
</div>
|
||
</div>
|
||
)}
|
||
</div>
|
||
|
||
<div className="border-t border-white/10">
|
||
<div className="mx-auto flex max-w-7xl flex-col items-center justify-between gap-2 px-6 py-5 text-xs text-white/60 md:flex-row">
|
||
<p>
|
||
© {new Date().getFullYear()} {siteConfig.name}. Tüm hakları saklıdır.
|
||
</p>
|
||
<div className="flex flex-wrap items-center gap-3">
|
||
<Link href="/cerez-politikasi" className="hover:text-white">
|
||
Çerez Politikası
|
||
</Link>
|
||
<span>•</span>
|
||
<p>Kocaeli, Türkiye</p>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</footer>
|
||
);
|
||
}
|