Files
kolaytercih/src/features/katalog/components/il-icerik.tsx
bilalgursen 78ad84922a
All checks were successful
Deploy / deploy (push) Successful in 6m23s
feat(tema): açık / koyu / sistem teması
- TemaSaglayici (next-themes) + alt bilgide TemaSecici
- globals.css: .dark bloğunda palet token'ları çevrilir; bileşenlere
  dark: sınıfı yazılmaz
- yüzeyler bg-white → bg-card, koyu haplar text-slate-50, SVG'lerde
  sabit renk yerine değişken
- DESIGN.md §2.3 + tasarim-denetim kuralları (yuzey-beyaz,
  koyu-hap-beyaz-yazi, dark-sinif), AGENTS.md karanlık mod bölümü
- docs: koyu tema denetimi raporu

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-05 10:20:02 +03:00

229 lines
8.3 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 type { Metadata } from "next";
import Link from "next/link";
import { notFound } from "next/navigation";
import { Building2, ChevronRight, Landmark, MapPin } from "lucide-react";
import {
getAllIller,
getAllIlSlugs,
getIlBySlug,
type UniOzet,
} from "../katalog-queries";
import { CtaSiraForm } from "@/features/sihirbaz/components/cta-sira-form";
import { PagePixelDivider } from "@/components/pixel-decor";
import { Skeleton } from "@/components/ui/skeleton";
import {
JsonLd,
breadcrumbJsonLd,
sayfaMetadata,
ustSegmentOgGorseli,
} from "@/lib/seo";
import { BASLIK_YILI, SON_YIL, TERCIH_YILI } from "@/lib/veri-yillari";
import { NavBaslik } from "./nav-baslik";
import { SehirCipleri, oneCikanIller } from "./sehir-cipleri";
import { SehirPikselHaritasi } from "./sehir-piksel-haritasi";
import { SehirYuzeyi } from "./sehir-yuzeyi";
import { UniSatirlari } from "./uni-satirlari";
const sayi = (n: number) => n.toLocaleString("tr-TR");
export function ilStaticSlugs(): string[] {
return getAllIlSlugs();
}
export function ilSayfaMetadata(slug: string): Metadata {
const il = getIlBySlug(slug);
if (!il) return {};
return sayfaMetadata({
title: `${il.ad} Üniversiteleri Taban Puanları ve Sıralamaları ${BASLIK_YILI}`,
description: `${il.ad}'daki ${il.uniler.length} üniversitenin ${SON_YIL} YKS yerleştirme verileri: ${sayi(il.programSayisi)} programın taban puanları, başarı sıralamaları ve kontenjanları. ${TERCIH_YILI} tercih rehberi.`,
path: `/universiteler/${slug}`,
openGraph: {
images: ustSegmentOgGorseli(
"/universiteler",
"Üniversitelerin taban puanları ve sıralamaları — KolayTercih",
),
},
});
}
function UniGrubu({
baslik,
uniler,
buIl,
}: {
baslik: string;
uniler: UniOzet[];
buIl: string;
}) {
if (uniler.length === 0) return null;
return (
<section className="mt-10 text-center lg:text-left">
<h2 className="font-heading text-2xl font-bold text-slate-900">
{baslik}
<span className="ml-2 text-sm font-normal text-slate-400">
{sayi(uniler.length)}
</span>
</h2>
<UniSatirlari uniler={uniler} gizliIl={buIl} />
</section>
);
}
/**
* /universiteler/[il]: bir şehirdeki üniversiteler, devlet / vakıf / diğer
* öbekleriyle. Düzen katalog detay sayfalarıyla aynı (DESIGN.md §5.2):
* yol satırı, başlık, kimlik hapları ve özet lg altında ortalı; listeler
* her zaman sola yaslı.
*/
export function IlIcerik({ slug }: { slug: string }) {
const il = getIlBySlug(slug);
if (!il) notFound();
const devlet: UniOzet[] = [];
const vakif: UniOzet[] = [];
const diger: UniOzet[] = [];
for (const u of il.uniler) {
if (u.unitur === "DEVLET") devlet.push(u);
else if (u.unitur === "VAKIF" || u.unitur === "VAKIF MYO") vakif.push(u);
else diger.push(u);
}
const ozetParcalar = [
devlet.length > 0 ? `${sayi(devlet.length)} devlet` : null,
vakif.length > 0 ? `${sayi(vakif.length)} vakıf` : null,
diger.length > 0 ? `${sayi(diger.length)} KKTC/yurt dışı` : null,
].filter(Boolean);
const ctaBaslik = `Sıralaman ${il.ad} üniversitelerinin neresinde?`;
const digerSehirler = oneCikanIller(getAllIller());
return (
<main className="mx-auto w-full max-w-5xl px-4 py-12 sm:py-16">
<JsonLd
data={breadcrumbJsonLd([
{ name: "Ana Sayfa", path: "/" },
{ name: "Üniversiteler", path: "/universiteler" },
{ name: il.ad, path: `/universiteler/${slug}` },
])}
/>
<nav
aria-label="İçerik yolu"
className="flex flex-wrap items-center justify-center gap-1.5 text-sm text-slate-500 lg:justify-start"
>
<Link
href="/"
className="transition-colors hover:text-orange-500 hover:underline"
>
Ana Sayfa
</Link>
<ChevronRight className="size-3.5 shrink-0" aria-hidden />
<Link
href="/universiteler"
className="transition-colors hover:text-orange-500 hover:underline"
>
Üniversiteler
</Link>
<ChevronRight className="size-3.5 shrink-0" aria-hidden />
<span className="text-slate-700">{il.ad}</span>
</nav>
<NavBaslik baslikId="il-baslik" ad={`${il.ad} üniversiteleri`} />
{/* Başlık yüzeyi: beyaz kart, arka planı kaplayan dot-matrix şehir
haritası (DESIGN.md §4.17). lg altında metin ortalı, harita altta. */}
<SehirYuzeyi
harita={
<SehirPikselHaritasi
il={il.il}
ad={il.ad}
doldur
odakHiza={{ lg: { x: 0.74, y: 0.5 }, dar: { x: 0.5, y: 0.78 } }}
/>
}
metin={
<div className="text-center lg:text-left">
<h1
id="il-baslik"
className="font-heading text-3xl font-bold leading-tight text-slate-900 sm:text-5xl"
>
{il.ad} Üniversiteleri Taban Puanları {BASLIK_YILI}
</h1>
{/* Kimlik hapları (DESIGN.md §4.12): il + üniversite sayısı */}
<p className="mt-5 flex flex-wrap items-center justify-center gap-2 lg:justify-start">
<span className="inline-flex h-10 items-center gap-2 rounded-full border border-slate-200 bg-card px-4 text-sm font-semibold text-slate-800">
<MapPin className="size-4 text-primary" aria-hidden />
{il.ad}
</span>
<span className="inline-flex h-10 items-center gap-2 rounded-full border border-slate-200 bg-card px-4 text-sm font-semibold text-slate-800">
{devlet.length >= vakif.length ? (
<Landmark className="size-4 text-primary" aria-hidden />
) : (
<Building2 className="size-4 text-primary" aria-hidden />
)}
{sayi(il.uniler.length)} üniversite
</span>
</p>
<p className="mx-auto mt-6 max-w-2xl text-base leading-relaxed text-slate-600 sm:text-lg lg:mx-0">
{il.ad}&apos;da {SON_YIL} YKS yerleştirmesinde öğrenci alan{" "}
<strong>{sayi(il.uniler.length)} üniversite</strong>
{ozetParcalar.length > 1 ? ` (${ozetParcalar.join(", ")})` : ""}{" "}
ve toplam <strong>{sayi(il.programSayisi)} program</strong> var.
Üniversiteye tıklayınca programlarının taban puanlarını, başarı
sıralamalarını ve kontenjanlarını fakülte fakülte görürsün.
Veriler YÖK Atlas {SON_YIL} yerleştirme sonuçlarına dayanır;{" "}
{TERCIH_YILI} tercihleri için rehber niteliğindedir.
</p>
</div>
}
/>
<div className="mt-8">
<CtaSiraForm varyant="kompakt" konum="ust" baslik={ctaBaslik} />
</div>
<PagePixelDivider seed={71} className="mx-auto mt-8 lg:mx-0" />
<UniGrubu baslik="Devlet Üniversiteleri" uniler={devlet} buIl={il.il} />
<UniGrubu baslik="Vakıf Üniversiteleri" uniler={vakif} buIl={il.il} />
<UniGrubu baslik="KKTC ve Yurt Dışı" uniler={diger} buIl={il.il} />
<section className="mt-12 text-center lg:text-left" aria-labelledby="diger-sehirler">
<h2
id="diger-sehirler"
className="font-heading text-2xl font-bold text-slate-900"
>
Diğer şehirler
</h2>
<SehirCipleri
iller={digerSehirler}
aktifSlug={slug}
tumuHref="/universiteler#tum-sehirler"
className="mt-4"
/>
</section>
<div className="mt-12">
<CtaSiraForm
baslik={ctaBaslik}
aciklama={`YKS başarı sıralamanı gir; ${il.ad} dahil tüm şehirlerde sıralamana uygun hayal, dengeli ve güvenli dilimlerindeki programları anında listele. Ücretsiz, üyelik gerekmez.`}
/>
</div>
</main>
);
}
export function IlIcerikSkeleton() {
return (
<main className="mx-auto w-full max-w-5xl px-4 py-12 sm:py-16">
<Skeleton className="mx-auto h-4 w-64 lg:mx-0" />
<Skeleton className="mt-6 h-72 w-full rounded-3xl" />
<div className="mt-10 space-y-4">
<Skeleton className="h-8 w-72" />
{Array.from({ length: 6 }, (_, i) => (
<Skeleton key={i} className="h-9 w-full max-w-2xl" />
))}
</div>
</main>
);
}