fix: blog empty state + proje detay yarı boş meta tablosu

Claude vision ile localhost'ta yapılan tarama sonrası 2 sorun:

1) Blog sayfası — henüz yazı yoksa empty state küçük bir kart olup
   altında ~1000px beyaz alan kalıyordu, footer çok aşağıdaydı.

   Çözüm: Empty state için zengin bir layout:
   - Gradient hero ('Blog yazılarımızı hazırlıyoruz' + 3 CTA:
     keşif görüşmesi / WhatsApp / Telefon)
   - 'Bu arada hizmetlerimize göz atın' başlıkla 6 hizmet grid
   - 'Site analiz raporu' lead magnet kartı
   Yazı geldiğinde otomatik normal grid'e döner.

2) Proje detay sayfası — sağ üstte 2 sütunlu meta card sadece
   'Yıl: 2025' gösteriyordu (client_name/industry/duration boş),
   yarı boş görünüyor + sağ tarafta büyük boşluk.

   Çözüm:
   - meta.length >= 2 → eski 2x2 grid card
   - meta.length === 1 → inline pill strip altta
   - meta yok → grid tek sütuna döner (lg:grid-cols-[1.4fr_1fr]
     conditional)

Claude vision ile 10 sayfa screenshot alındı (/tmp/kovak-screenshots).
Diğer sayfalar (anasayfa, hizmet detay, sektör, iletişim, vs)
tasarım açısından temiz görünüyor.
This commit is contained in:
Ege Can Komur
2026-05-20 19:07:26 +03:00
parent fdfa556d42
commit f88b76546c
2 changed files with 228 additions and 59 deletions
+205 -57
View File
@@ -1,21 +1,179 @@
import Image from "next/image";
import Link from "next/link";
import type { Metadata } from "next";
import { ArrowRight, Calendar } from "lucide-react";
import {
ArrowRight,
Calendar,
Sparkles,
MessageCircle,
Phone,
FileText,
} from "lucide-react";
import { SectionTitle } from "@/components/section-title";
import { listPublishedPosts } from "@/lib/data";
import { Icon } from "@/components/icon";
import {
getSiteSettings,
listPublishedPosts,
listServices,
} from "@/lib/data";
import { buildMetadata } from "@/lib/seo";
import { siteConfig } from "@/lib/site-config";
export async function generateMetadata(): Promise<Metadata> {
return buildMetadata("/blog", {
title: "Blog",
description: "Yazılım, web tasarım, SEO ve dijital pazarlama üzerine yazılar.",
description:
"Yazılım, web tasarım, SEO ve dijital pazarlama üzerine yazılar.",
});
}
export default async function BlogIndex() {
const posts = await listPublishedPosts();
const [posts, services, settings] = await Promise.all([
listPublishedPosts(),
listServices(),
getSiteSettings(),
]);
const phoneRaw = settings?.contact_phone_raw ?? siteConfig.contact.phoneRaw;
const phone = settings?.contact_phone ?? siteConfig.contact.phone;
const wa = phoneRaw.replace(/[^\d]/g, "");
const waMessage = settings?.whatsapp_message ?? "";
const waHref = `https://wa.me/${wa}${
waMessage ? `?text=${encodeURIComponent(waMessage)}` : ""
}`;
// Empty state — site daha yeni, içerik yok
if (posts.length === 0) {
return (
<div className="mx-auto max-w-7xl px-6 py-20">
<SectionTitle
eyebrow="Blog"
title="Yazılım, tasarım ve büyüme üzerine"
description="Sektörden notlar, vaka çalışmaları ve teknik rehberler."
/>
{/* Coming soon hero */}
<div className="relative mt-14 overflow-hidden rounded-3xl bg-gradient-to-br from-[var(--navy)] via-[var(--sky-600)] to-[var(--sky)] p-12 text-center text-white shadow-xl shadow-[var(--navy)]/15">
<div
className="absolute inset-0 opacity-10"
style={{
backgroundImage:
"radial-gradient(circle at 1px 1px, white 1px, transparent 0)",
backgroundSize: "24px 24px",
}}
aria-hidden
/>
<div className="relative">
<span className="inline-flex items-center gap-2 rounded-full border border-white/30 bg-white/10 px-4 py-1.5 text-xs font-medium backdrop-blur">
<Sparkles className="size-3.5" />
Yakında
</span>
<h2 className="mt-5 text-3xl font-bold sm:text-4xl">
Blog yazılarımızı hazırlıyoruz
</h2>
<p className="mx-auto mt-4 max-w-xl text-base text-white/80">
Sektörden vaka çalışmaları, teknik rehberler ve sahada öğrendiklerimizi
kısa sürede burada paylaşmaya başlayacağız.
</p>
<div className="mt-8 flex flex-wrap items-center justify-center gap-3">
<Link
href="/iletisim"
className="inline-flex items-center gap-2 rounded-xl bg-white px-5 py-3 text-sm font-semibold text-[var(--navy)] transition hover:-translate-y-0.5 hover:bg-blue-50"
>
Ücretsiz keşif görüşmesi
<ArrowRight className="size-4" />
</Link>
<a
href={waHref}
target="_blank"
rel="noopener noreferrer"
className="inline-flex items-center gap-2 rounded-xl bg-[#25d366] px-5 py-3 text-sm font-semibold text-white transition hover:-translate-y-0.5 hover:bg-[#1ebe5d]"
>
<MessageCircle className="size-4" />
WhatsApp
</a>
<a
href={`tel:${phoneRaw}`}
className="inline-flex items-center gap-2 rounded-xl border border-white/30 bg-white/5 px-5 py-3 text-sm font-semibold text-white backdrop-blur transition hover:border-white/60"
>
<Phone className="size-4" />
{phone}
</a>
</div>
</div>
</div>
{/* Bu arada → Hizmetler grid */}
{services.length > 0 && (
<section className="mt-16">
<div className="flex items-end justify-between">
<SectionTitle
align="left"
eyebrow="Bu arada"
title="Hizmetlerimize göz atın"
description="Blog yazılarımızı beklerken sunduğumuz çözümleri keşfedin."
/>
<Link
href="/hizmetler"
className="hidden text-sm font-medium text-[var(--sky-600)] hover:text-[var(--navy)] sm:inline-flex"
>
Tümünü gör
</Link>
</div>
<div className="mt-10 grid gap-5 sm:grid-cols-2 lg:grid-cols-3">
{services.slice(0, 6).map((s) => (
<Link
key={s.slug}
href={`/hizmetler/${s.slug}`}
className="group relative overflow-hidden rounded-2xl border border-[var(--border)] bg-white p-6 transition-all duration-300 hover:-translate-y-1 hover:border-[var(--sky)]/40 hover:shadow-xl hover:shadow-[var(--navy)]/10"
>
<div className="flex size-12 items-center justify-center rounded-xl bg-gradient-to-br from-[var(--sky)] to-purple-500 text-white shadow-lg">
<Icon name={s.icon} className="size-5" />
</div>
<h3 className="mt-5 text-base font-bold text-[var(--navy)] group-hover:text-[var(--sky-600)]">
{s.title}
</h3>
<p className="mt-2 text-sm leading-relaxed text-[var(--muted)] line-clamp-2">
{s.description}
</p>
</Link>
))}
</div>
</section>
)}
{/* Lead magnet CTA */}
<section className="mt-16 rounded-2xl border border-dashed border-[var(--sky)]/40 bg-[var(--sky-50)]/40 p-8 sm:p-10">
<div className="flex flex-col items-start gap-6 sm:flex-row sm:items-center sm:justify-between">
<div>
<p className="text-xs font-semibold uppercase tracking-[0.18em] text-[var(--sky-600)]">
<FileText className="mr-1 inline size-3.5" />
Ücretsiz Rapor
</p>
<h3 className="mt-2 text-2xl font-bold text-[var(--navy)]">
Site analiz raporunuzu alın
</h3>
<p className="mt-2 max-w-xl text-sm text-[var(--muted)]">
Mevcut sitenizin SEO, hız, mobil ve dönüşüm performansını ücretsiz
değerlendirelim. 24 saat içinde detaylı rapor e-postanızda.
</p>
</div>
<Link
href="/site-analizi"
className="inline-flex items-center gap-2 rounded-xl bg-[var(--navy)] px-6 py-3 text-sm font-semibold text-white shadow-lg shadow-[var(--navy)]/20 transition hover:-translate-y-0.5 hover:bg-[var(--navy-700)]"
>
Ücretsiz raporumu istiyorum
<ArrowRight className="size-4" />
</Link>
</div>
</section>
</div>
);
}
// Normal grid — yazı varsa
return (
<div className="mx-auto max-w-7xl px-6 py-20">
<SectionTitle
@@ -24,60 +182,50 @@ export default async function BlogIndex() {
description="Sektörden notlar, vaka çalışmaları ve teknik rehberler."
/>
<div className="mt-14">
{posts.length === 0 ? (
<div className="rounded-2xl border border-dashed border-[var(--border)] bg-[var(--navy-50)]/40 p-12 text-center">
<p className="text-sm text-[var(--muted)]">
Henüz yayınlanmış yazı yok.
</p>
</div>
) : (
<div className="grid gap-8 md:grid-cols-2 lg:grid-cols-3">
{posts.map((p) => (
<article
key={p.$id}
className="group overflow-hidden rounded-2xl border border-[var(--border)] bg-white transition hover:shadow-lg"
>
<Link href={`/blog/${p.slug}`}>
<div className="relative aspect-video overflow-hidden bg-[var(--navy-50)]">
{p.cover_image ? (
<Image
src={p.cover_image}
alt={p.title}
fill
sizes="(min-width: 1024px) 33vw, (min-width: 768px) 50vw, 100vw"
className="object-cover transition group-hover:scale-105"
/>
) : (
<div className="flex h-full items-center justify-center text-3xl font-bold text-[var(--navy)]/30">
{p.title.charAt(0)}
</div>
)}
<div className="mt-14 grid gap-8 md:grid-cols-2 lg:grid-cols-3">
{posts.map((p) => (
<article
key={p.$id}
className="group overflow-hidden rounded-2xl border border-[var(--border)] bg-white transition hover:shadow-lg"
>
<Link href={`/blog/${p.slug}`}>
<div className="relative aspect-video overflow-hidden bg-[var(--navy-50)]">
{p.cover_image ? (
<Image
src={p.cover_image}
alt={p.title}
fill
sizes="(min-width: 1024px) 33vw, (min-width: 768px) 50vw, 100vw"
className="object-cover transition group-hover:scale-105"
/>
) : (
<div className="flex h-full items-center justify-center text-3xl font-bold text-[var(--navy)]/30">
{p.title.charAt(0)}
</div>
<div className="p-6">
<p className="flex items-center gap-1.5 text-xs text-[var(--muted)]">
<Calendar className="size-3.5" />
{p.published_at
? new Date(p.published_at).toLocaleDateString("tr-TR")
: "—"}
</p>
<h3 className="mt-2 text-lg font-semibold text-[var(--navy)] group-hover:text-[var(--sky-600)]">
{p.title}
</h3>
{p.excerpt && (
<p className="mt-2 text-sm leading-relaxed text-[var(--muted)] line-clamp-3">
{p.excerpt}
</p>
)}
<span className="mt-4 inline-flex items-center gap-1 text-sm font-medium text-[var(--sky-600)]">
Devamını oku <ArrowRight className="size-3.5" />
</span>
</div>
</Link>
</article>
))}
</div>
)}
)}
</div>
<div className="p-6">
<p className="flex items-center gap-1.5 text-xs text-[var(--muted)]">
<Calendar className="size-3.5" />
{p.published_at
? new Date(p.published_at).toLocaleDateString("tr-TR")
: "—"}
</p>
<h3 className="mt-2 text-lg font-semibold text-[var(--navy)] group-hover:text-[var(--sky-600)]">
{p.title}
</h3>
{p.excerpt && (
<p className="mt-2 text-sm leading-relaxed text-[var(--muted)] line-clamp-3">
{p.excerpt}
</p>
)}
<span className="mt-4 inline-flex items-center gap-1 text-sm font-medium text-[var(--sky-600)]">
Devamını oku <ArrowRight className="size-3.5" />
</span>
</div>
</Link>
</article>
))}
</div>
</div>
);
+23 -2
View File
@@ -83,7 +83,11 @@ export default async function ProjectDetailPage({
<ArrowLeft className="size-3.5" /> Tüm projeler
</Link>
<div className="mt-6 grid items-start gap-10 lg:grid-cols-[1.4fr_1fr]">
<div
className={`mt-6 grid items-start gap-10 ${
meta.length >= 2 ? "lg:grid-cols-[1.4fr_1fr]" : ""
}`}
>
<div>
{project.category && (
<span className="inline-flex rounded-full bg-[var(--sky-50)] px-3 py-1 text-xs font-medium text-[var(--sky-600)]">
@@ -128,7 +132,8 @@ export default async function ProjectDetailPage({
)}
</div>
{meta.length > 0 && (
{/* Meta tablo sadece 2+ alan dolu ise gösterilir — yarı boş card görünmesin */}
{meta.length >= 2 && (
<dl className="grid grid-cols-2 gap-4 rounded-2xl border border-[var(--border)] bg-white p-6">
{meta.map((m) => (
<div key={m.label}>
@@ -145,6 +150,22 @@ export default async function ProjectDetailPage({
)}
</div>
{/* Tek-iki meta varsa daha kompakt inline strip olarak gösterilir */}
{meta.length > 0 && meta.length < 2 && (
<div className="mt-6 flex flex-wrap gap-3">
{meta.map((m) => (
<span
key={m.label}
className="inline-flex items-center gap-1.5 rounded-full border border-[var(--border)] bg-white px-3 py-1.5 text-xs text-[var(--muted)]"
>
{m.icon}
<span className="font-medium text-[var(--navy)]">{m.label}:</span>{" "}
{m.value}
</span>
))}
</div>
)}
{project.image_url && (
<div className="relative mt-10 aspect-video overflow-hidden rounded-2xl">
<Image