Files
kolaytercih/src/app/sonuc/page.tsx
bilalgursen e9126650c3 fix(sonuc): harita tablonun altına alınır — hidrasyon kayması kapanır
Liste store'unun sunucu snapshot'ı her zaman boş liste döner
(liste-store.ts:86), kayıtlı manuel liste ancak hidrasyondan sonra
okunur. Harita sayfanın tepesindeyken kayıtlı listesi olan dönüş
ziyaretçisinde ~368 px yüksekliğinde sonradan beliriyor ve tadımlık ile
tabloyu aşağı itiyordu; main'in ilk çocuğu olduğu için impact fraction
~1, CLS eşiğinin çok üstü. Silinen placeholder bunu yeri sunucuda
rezerve ederek engelliyordu.

Harita tablonun altına alındı: sonradan belirdiğinde yalnızca sihirbaz
ile dipnotu iter, ikisi de katlanın altında. Bilgi mimarisi olarak da
doğru sıra — harita, tablodan kurulan listenin görselleştirmesi.

Yeni sıra: tadımlık → tablo → harita → sihirbaz → dipnot.
Sıraya bağlı stiller yeni sıraya göre doğrulandı: tadımlığın
first:mt-0'ı artık her durumda geçerli (üstünde kardeş kalmadı), harita
kendi mt-16'sını alıyor (tablo section'ı ve sihirbaz kartlarıyla aynı
ritim — eskiden üst boşluğu main'in py-12'sinden geliyordu).

Bulgu: 36-tasarimci-g1-ve-navbar.md B1.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-22 23:20:34 +03:00

156 lines
6.5 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 { Suspense } from "react";
import type { Metadata } from "next";
import { cookies } from "next/headers";
import { PUAN_TURLERI, type PuanTuruKey } from "@/types/yokatlas";
import {
cerezdenTercihProfili,
PROFIL_CEREZ_ADI,
} from "@/features/sihirbaz/sihirbaz-sabitler";
import {
SonucProgramTablosu,
SonucProgramTablosuSkeleton,
} from "@/features/liste/components/sonuc-program-tablosu";
import { ManuelHarita } from "@/features/liste/components/manuel-harita";
import { TadimlikBolumu } from "@/features/rapor/components/tadimlik-bolumu";
import { SonucSihirbazi } from "@/features/sihirbaz/components/sihirbaz-bolumu";
import { SiraGerekli } from "@/features/sihirbaz/components/sira-gerekli";
import { SiteFooter } from "@/components/site-footer";
import { Skeleton } from "@/components/ui/skeleton";
export const metadata: Metadata = {
title: "Tercih Listeni Kur",
// İçerik profil çerezine göre kişisel (eski linklerde param uzayı sonsuz):
// indexlenmez ama link değeri follow ile geri akar. robots.txt'te
// ENGELLEME — noindex'in görülebilmesi için sayfa taranabilir kalmalı.
robots: { index: false, follow: true },
};
export default function SonucPage({
searchParams,
}: {
searchParams: Promise<{
sira?: string;
tur?: string;
sihirbaz?: string;
kaynak?: string;
hazir?: string;
il?: string | string[];
tip?: string;
}>;
}) {
return (
// Tablo değişken boylu olduğundan altındaki tadımlık, sihirbaz, dipnot ve
// footer aynı sınırda gruplu — bugünkü tek await'li akışla birebir aynı
// anda beliriyorlar, ara durumda zıplama yok.
<Suspense
fallback={
<main className="mx-auto w-full max-w-6xl px-4 py-10">
<Skeleton className="h-6 w-40 rounded-full" />
<Skeleton className="mt-4 h-9 w-3/4 max-w-xl" />
<Skeleton className="mt-3 h-5 w-2/3 max-w-lg" />
<Skeleton className="mt-8 h-36 w-full rounded-2xl" />
<SonucProgramTablosuSkeleton />
</main>
}
>
{Promise.all([searchParams, cookies()]).then(([params, cerezler]) => {
// Profil (sıra/tür/il/tip) normalde PROFIL_CEREZ_ADI çerezinden gelir;
// URL param taşımaz (bkz. sonucHref). Query'de sira varsa eski
// paylaşılan/yer imli link demektir — açık URL niyeti çerezi ezer,
// eski linkler çalışmayı sürdürür.
const eskiLinkSira = Number.parseInt(params.sira ?? "", 10);
const eskiLink = Number.isFinite(eskiLinkSira);
const profil = eskiLink
? null
: cerezdenTercihProfili(cerezler.get(PROFIL_CEREZ_ADI)?.value);
const sira = eskiLink ? eskiLinkSira : (profil?.sira ?? Number.NaN);
const turHam = eskiLink ? params.tur : profil?.tur;
const turKey: PuanTuruKey =
turHam && turHam in PUAN_TURLERI ? (turHam as PuanTuruKey) : "say";
// Tablo filtreleri: il en fazla 5 tekrarsız, tip devlet|vakif
// allowlist'li (çerez tarafı tercihProfiliDogrula'dan zaten geçmiş
// olsa da iki kaynak aynı süzgeçten akar).
const hamIller = eskiLink
? Array.isArray(params.il)
? params.il
: params.il
? [params.il]
: []
: (profil?.secimler.iller ?? []);
const iller = [
...new Set(hamIller.filter((i) => i.trim().length > 0)),
].slice(0, 5);
const tipHam = eskiLink ? params.tip : profil?.secimler.universiteTipi;
const uniturGrubu =
tipHam === "devlet" || tipHam === "vakif" ? tipHam : undefined;
if (!Number.isFinite(sira) || sira < 1 || sira > 4_000_000) {
return (
<>
<SiraGerekli />
<SiteFooter />
</>
);
}
return (
<div className="min-h-screen bg-slate-50 text-slate-900">
<main className="mx-auto max-w-4xl px-4 py-12">
{/* Sayfa hiyerarşisi: tadımlık → ham tablo → manuel listenin
haritası. Tadımlık, ödeme öncesi gösterebildiğimiz TEK gerçek
önizleme olduğu için ilk ekranda durur.
Harita neden TABLONUN ALTINDA: liste store'unun sunucu
snapshot'ı her zaman boş liste döner (liste-store.ts), kayıtlı
liste ancak hidrasyondan sonra okunur. Harita yukarıda olsaydı
dönüş ziyaretçisinde sayfa açıldıktan saniyeler sonra ~368 px
yüksekliğinde belirip tadımlığı ve tabloyu aşağı itecekti
(impact fraction ~1, CLS eşiğinin çok üstü). Aşağıda belirince
yalnızca sihirbaz ile dipnotu iter, ikisi de katlanın altında.
Bilgi mimarisi olarak da doğru sıra: harita, tablodan kurulan
listenin görselleştirmesidir. Liste boşken hiç render edilmez
(bkz. manuel-harita.tsx); store'a abone olduğu için tablodan
"+" ile eklenenler buraya canlı yansır. */}
{/* Girişsiz tadımlık: gerçek rapor satırı formatında tek örnek.
Havuzda içerik yoksa bileşen hiç render edilmez. */}
<TadimlikBolumu sira={sira} tur={turKey} />
{/* Yapay Zeka'dan bağımsız ham tablo — sayfanın ana gövdesi;
puan türü navbar'daki sıralama rozetinden değiştirilir.
Sihirbaz seçimleri (il, devlet/vakıf) profil çerezinden
tabloya uygulanır. */}
<SonucProgramTablosu
sira={sira}
tur={turKey}
iller={iller}
uniturGrubu={uniturGrubu}
/>
<ManuelHarita />
{/* Sihirbaz akışı (CTA + modal) — üretim ve liste /listem'de
yaşar, burada yalnızca funnel kartları render edilir */}
<SonucSihirbazi
sira={sira}
tur={turKey}
otomatikAc={params.sihirbaz === "1"}
kaynak={params.kaynak}
secimlerHazir={params.hazir === "1"}
/>
<p className="mt-10 text-center text-xs leading-relaxed text-slate-500">
Veriler resmî YÖK Atlas kaynağından derlenmiştir. KolayTercih
bir karar destek aracıdır; yerleşme garantisi vermez, tercih
sorumluluğu adaya aittir.
</p>
</main>
<SiteFooter />
</div>
);
})}
</Suspense>
);
}