Files
kovakyazilim/components/header.tsx
T
egecankomur d49c9aa225 feat: SEO altyapısı + admin editör/favicon/menü düzeltmeleri
Admin & site:
- @tailwindcss/typography ekle → editör ve yayın içeriği prose stilleriyle düzgün render
- Favicon: logo.png'den kare app/icon.png + apple-icon.png, varsayılan favicon.ico kaldırıldı
- SEO keyword: seo_settings.default_keywords + seo_pages.keywords + buildMetadata birleştirme
- Menü düzeni admin'den yönetilebilir (site_settings.nav_items, /admin/menu, header & mobile-menu refactor)

SEO:
- app/sitemap.ts (statik + blog/hizmet/çözüm/proje/sektör dinamik)
- app/robots.ts (sitemap ref + /admin,/api disallow)
- app/llms.txt/route.ts (AI/LLM rehberi)
- BlogPosting/Service/FAQ/Article JSON-LD wire (json-ld bileşenleri bağlandı)
- buildMetadata: blog/proje OG görseli + type article + keywords birleştirme düzeltmesi
- blog tags → keyword
2026-06-04 07:15:18 +03:00

189 lines
7.7 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import Image from "next/image";
import Link from "next/link";
import { ChevronDown, Phone } from "lucide-react";
import { getSiteSettings, listServices } from "@/lib/data";
import { siteConfig } from "@/lib/site-config";
import { resolveNavItems } from "@/lib/nav";
import type { ServiceRow } from "@/lib/types";
import { HeaderScrollEffect } from "@/components/header-scroll";
import { MobileMenu } from "@/components/mobile-menu";
export async function Header() {
const [settings, services] = await Promise.all([
getSiteSettings(),
listServices(),
]);
const phone = settings?.contact_phone ?? siteConfig.contact.phone;
const phoneRaw = settings?.contact_phone_raw ?? siteConfig.contact.phoneRaw;
// Mega menu groups
const webServices = services.filter((s) =>
["web-tasarim", "e-ticaret", "mobil-uygulama", "yazilim-gelistirme", "crm-sistemleri"].includes(s.slug),
);
const marketingServices = services.filter((s) =>
["seo-dijital-pazarlama", "sosyal-medya-yonetimi", "dijital-reklam"].includes(s.slug),
);
// Admin'den düzenlenebilir üst menü düzeni
const navItems = resolveNavItems(settings?.nav_items).filter((i) => i.visible);
return (
<>
<HeaderScrollEffect />
<div className="sticky top-0 z-50 w-full" id="floating-header-wrap">
<div id="header-pill-wrap" className="transition-all duration-300 ease-out">
<header
id="site-header"
className="mx-auto w-full border-b border-gray-100 bg-white/95 backdrop-blur-lg transition-all duration-300 ease-out"
>
<nav
id="header-nav-bar"
className="flex h-14 items-center justify-between px-6 transition-all duration-300 ease-out lg:grid lg:h-16 lg:grid-cols-[1fr_auto_1fr] lg:px-8"
>
{/* Col 1 — Logo */}
<Link href="/" className="flex items-center gap-2.5">
<Image
src="/logo.png"
alt={siteConfig.name}
width={40}
height={40}
priority
className="h-9 w-9 object-contain"
/>
<span className="hidden text-sm font-semibold tracking-tight text-[var(--navy)] sm:block">
{siteConfig.name}
</span>
</Link>
{/* Col 2 — Desktop nav (sıra admin'den yönetilir) */}
<div className="hidden items-center gap-0.5 lg:flex">
{navItems.map((item) =>
item.mega ? (
<ServicesMegaMenu
key={item.key}
label={item.label}
webServices={webServices}
marketingServices={marketingServices}
/>
) : (
<Link
key={item.key}
href={item.href}
className="inline-flex h-9 items-center justify-center whitespace-nowrap rounded-lg px-3.5 text-sm font-medium text-gray-600 transition-colors hover:bg-gray-100 hover:text-gray-900"
>
{item.label}
</Link>
),
)}
</div>
{/* Col 3 — CTA */}
<div className="flex items-center justify-end gap-2">
{/* Phone — full mode (XL) */}
<a
href={`tel:${phoneRaw}`}
data-pill-hide="true"
className="hidden h-9 items-center gap-1.5 rounded-lg px-3 text-sm font-medium text-gray-600 transition-colors hover:bg-gray-100 hover:text-gray-900 xl:inline-flex"
aria-label={phone}
>
<Phone className="size-3.5" />
<span>{phone}</span>
</a>
{/* "Ara" — pill mode'da görünür, kompakt */}
<a
href={`tel:${phoneRaw}`}
data-pill-show="true"
className="hidden h-9 items-center gap-1.5 rounded-lg border border-gray-200 px-3 text-sm font-medium text-gray-700 transition-colors hover:border-[var(--navy)] hover:text-[var(--navy)]"
aria-label={`${phone} - Ara`}
style={{ display: "none" }}
>
<Phone className="size-3.5" />
<span>Ara</span>
</a>
<Link
href="/iletisim"
className="hidden h-9 items-center justify-center whitespace-nowrap rounded-lg bg-[var(--navy)] px-4 text-sm font-semibold text-white shadow-sm transition-colors hover:bg-[var(--navy-700)] sm:inline-flex"
>
Ücretsiz Teklif
</Link>
{/* Mobil menü (hamburger) — sadece < lg */}
<MobileMenu
navItems={navItems}
services={services.map((s) => ({ slug: s.slug, title: s.title }))}
phone={phone}
phoneRaw={phoneRaw}
/>
</div>
</nav>
</header>
</div>
</div>
</>
);
}
function ServicesMegaMenu({
label,
webServices,
marketingServices,
}: {
label: string;
webServices: ServiceRow[];
marketingServices: ServiceRow[];
}) {
return (
<div className="group relative">
<button
type="button"
className="inline-flex h-9 items-center justify-center gap-1 whitespace-nowrap rounded-lg px-3.5 text-sm font-medium text-gray-600 transition-colors hover:bg-gray-100 hover:text-gray-900"
>
{label}
<ChevronDown className="size-3 transition-transform duration-200 group-hover:rotate-180" />
</button>
<div className="pointer-events-none invisible absolute left-1/2 top-full z-50 w-[480px] -translate-x-1/2 pt-2 opacity-0 transition-all duration-150 ease-out group-hover:pointer-events-auto group-hover:visible group-hover:opacity-100">
<div className="translate-y-1 rounded-2xl border border-gray-100 bg-white p-4 shadow-xl transition-transform duration-150 group-hover:translate-y-0">
<div className="grid grid-cols-2 gap-x-3">
<div>
<p className="px-3 pb-1.5 text-[10px] font-semibold uppercase tracking-wider text-gray-400">
Web & Yazılım
</p>
{webServices.map((s) => (
<Link
key={s.slug}
href={`/hizmetler/${s.slug}`}
className="flex items-center gap-3 rounded-xl px-3 py-2.5 text-sm text-gray-700 transition-colors hover:bg-blue-50 hover:text-[var(--navy)]"
>
{s.title}
</Link>
))}
</div>
<div>
<p className="px-3 pb-1.5 text-[10px] font-semibold uppercase tracking-wider text-gray-400">
Dijital Pazarlama
</p>
{marketingServices.map((s) => (
<Link
key={s.slug}
href={`/hizmetler/${s.slug}`}
className="flex items-center gap-3 rounded-xl px-3 py-2.5 text-sm text-gray-700 transition-colors hover:bg-blue-50 hover:text-[var(--navy)]"
>
{s.title}
</Link>
))}
</div>
</div>
<div className="mt-3 border-t border-gray-100 pt-3">
<Link
href="/hizmetler"
className="block rounded-xl px-3 py-2 text-center text-xs font-semibold text-[var(--navy)] hover:bg-blue-50"
>
Tüm hizmetleri gör
</Link>
</div>
</div>
</div>
</div>
);
}