feat: anasayfa içeriği, iletişim ve sosyal medya yönetilebilir

Yeni site_settings tablosu (singleton, rowId='homepage'):
- Hero: badge, title, subtitle, 2 CTA (label+href), stats (JSON array)
- Section başlıkları: services/projects/testimonials eyebrow + title + description
- Alt CTA: title, description, button label+href
- Contact: phone (görünen + tel: ham), email, address, hafta içi/sonu saatleri
- Social: linkedin/instagram/twitter/facebook URL'leri
- Footer tagline

Mevcut hardcoded değerler seed edildi.

Admin:
- /admin/site sayfası eklendi (sidebar'a 'Site Ayarları' linki)
- Bölümlü tek form: Hero / Hizmetler / Projeler / Referanslar / Alt CTA / İletişim / Sosyal / Footer
- Stats için 'değer | etiket' satır formatı

Public bağlantılar:
- Hero component artık settings prop alıyor (fallback değerlerle)
- Anasayfa: tüm section başlıkları ve alt CTA settings'ten geliyor
- Header: telefon settings'ten
- Footer: tagline, adres, telefon, email, sosyal linkler settings'ten
  (sosyal link sadece dolu olanlar gösteriliyor)
- Footer'da hizmetler artık /hizmetler/[slug] detay sayfalarına bağlı
- İletişim sayfası: adres, telefon, email, saatler settings'ten

30 route üretiliyor.
This commit is contained in:
Ege Can Komur
2026-05-20 02:56:45 +03:00
parent c0da5ae8d3
commit 1444aa3995
11 changed files with 621 additions and 91 deletions
+55 -22
View File
@@ -1,8 +1,43 @@
import Image from "next/image";
import Link from "next/link";
import { ArrowRight, Sparkles } from "lucide-react";
import type { SiteSettingsRow, StatItem } from "@/lib/types";
const DEFAULT_STATS: StatItem[] = [
{ value: "50+", label: "Tamamlanan proje" },
{ value: "10+", label: "Yıllık deneyim" },
{ value: "24/7", label: "Teknik destek" },
];
function parseStats(items?: string[] | null): StatItem[] {
if (!items || items.length === 0) return DEFAULT_STATS;
const out: StatItem[] = [];
for (const raw of items) {
try {
const obj = JSON.parse(raw) as Partial<StatItem>;
if (obj.value && obj.label) out.push({ value: obj.value, label: obj.label });
} catch {
/* ignore */
}
}
return out.length > 0 ? out : DEFAULT_STATS;
}
export function Hero({ settings }: { settings?: SiteSettingsRow | null }) {
const badge = settings?.hero_badge ?? "Kocaeli'nin teknoloji ajansı";
const title =
settings?.hero_title ?? "Fikirden ürüne tek bir partner ile yola çıkın";
const subtitle =
settings?.hero_subtitle ??
"Web, mobil ve CRM çözümlerinde uçtan uca geliştirme. Markanıza özel tasarım, ölçeklenebilir altyapı ve uzun vadeli destek.";
const primaryLabel =
settings?.hero_cta_primary_label ?? "Proje görüşmesi başlat";
const primaryHref = settings?.hero_cta_primary_href ?? "/iletisim";
const secondaryLabel =
settings?.hero_cta_secondary_label ?? "Hizmetlerimizi inceleyin";
const secondaryHref = settings?.hero_cta_secondary_href ?? "/hizmetler";
const stats = parseStats(settings?.hero_stats);
export function Hero() {
return (
<section className="relative overflow-hidden">
<div className="absolute inset-0 hero-grid opacity-60" aria-hidden />
@@ -12,47 +47,45 @@ export function Hero() {
<div>
<span className="inline-flex items-center gap-2 rounded-full border border-[var(--sky)]/30 bg-[var(--sky-50)] px-3 py-1 text-xs font-medium text-[var(--sky-600)]">
<Sparkles className="size-3.5" />
Kocaeli'nin teknoloji ajansı
{badge}
</span>
<h1 className="mt-6 text-4xl font-bold leading-tight tracking-tight text-[var(--navy)] sm:text-5xl md:text-6xl">
Fikirden ürüne{" "}
<span className="gradient-text">tek bir partner</span> ile yola çıkın
{title}
</h1>
<p className="mt-6 max-w-xl text-lg leading-relaxed text-[var(--muted)]">
Web, mobil ve CRM çözümlerinde uçtan uca geliştirme. Markanıza
özel tasarım, ölçeklenebilir altyapı ve uzun vadeli destek.
{subtitle}
</p>
<div className="mt-8 flex flex-col gap-3 sm:flex-row">
<Link
href="/iletisim"
href={primaryHref}
className="inline-flex items-center justify-center gap-2 rounded-full bg-[var(--navy)] px-6 py-3 text-sm font-medium text-white transition hover:bg-[var(--navy-700)]"
>
Proje görüşmesi başlat
{primaryLabel}
<ArrowRight className="size-4" />
</Link>
<Link
href="/hizmetler"
href={secondaryHref}
className="inline-flex items-center justify-center gap-2 rounded-full border border-[var(--border)] bg-white px-6 py-3 text-sm font-medium text-[var(--navy)] transition hover:border-[var(--navy)]"
>
Hizmetlerimizi inceleyin
{secondaryLabel}
</Link>
</div>
<dl className="mt-12 grid max-w-md grid-cols-3 gap-6">
{[
{ value: "50+", label: "Tamamlanan proje" },
{ value: "10+", label: "Yıllık deneyim" },
{ value: "24/7", label: "Teknik destek" },
].map((stat) => (
<div key={stat.label}>
<dt className="text-2xl font-bold text-[var(--navy)]">{stat.value}</dt>
<dd className="mt-1 text-xs text-[var(--muted)]">{stat.label}</dd>
</div>
))}
</dl>
{stats.length > 0 && (
<dl className="mt-12 grid max-w-md grid-cols-3 gap-6">
{stats.map((stat) => (
<div key={stat.label}>
<dt className="text-2xl font-bold text-[var(--navy)]">
{stat.value}
</dt>
<dd className="mt-1 text-xs text-[var(--muted)]">{stat.label}</dd>
</div>
))}
</dl>
)}
</div>
<div className="relative flex justify-center">