"use client"; import { useId, useState } from "react"; import Link from "next/link"; import { useRouter } from "next/navigation"; import { ArrowRight, ChartNoAxesCombined, Database, ListChecks, Trophy, X, } from "lucide-react"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { Drawer, DrawerClose, DrawerContent, DrawerDescription, DrawerTitle, } from "@/components/ui/drawer"; import { SectionEyebrow } from "@/components/pixel-decor"; import { useGorunumOlayi } from "@/components/use-gorunum-olayi"; import { SihirbazAdimlarLazy, preloadSihirbazAdimlar, } from "./sihirbaz-adimlar-lazy"; import { authClient } from "@/lib/auth-client"; import { olay, siraKovasi } from "@/lib/analitik"; import { cn } from "@/lib/utils"; import { onbellekliJsonGetir } from "@/lib/istek-cache"; import { useTercihProfili } from "@/features/liste/hooks/use-tercih-profili"; import type { SihirbazFacetleri } from "@/types/yokatlas"; import { sonucHref, type SihirbazSecimleri } from "@/features/sihirbaz/sihirbaz-sabitler"; import { tercihProfiliYaz } from "@/features/sihirbaz/sihirbaz-profil"; // lib/db sunucu tarafı; etiketler client bundle'a sızmasın diye burada. const PUAN_TURLERI_SECENEK = [ { deger: "say", etiket: "Sayısal" }, { deger: "ea", etiket: "Eşit Ağırlık" }, { deger: "soz", etiket: "Sözel" }, { deger: "dil", etiket: "Dil" }, { deger: "tyt", etiket: "TYT" }, ] as const; // Kanıt üçlüsü arama funnel kartıyla aynı dili konuşur (bkz. arama-funnel-karti) const KANITLAR = [ { ikon: ListChecks, etiket: "24 tercihlik plan" }, { ikon: ChartNoAxesCombined, etiket: "Risk analizi" }, { ikon: Database, etiket: "YÖK Atlas verisi" }, ] as const; /** * Programatik SEO sayfalarındaki funnel girişi. Statik/SSR çıktı her zaman * profilsiz hâldir (sayfalar prerender edilmeye devam eder); hydration * sonrası kayıtlı tercih profili bulunursa "sıralamanı gir" yerine doğrudan * sonuç sayfasına götüren CTA gösterilir — sıralamasını girmiş adaya bir * daha girmesini söylemeyiz. * * Profilsiz CTA adayı ana sayfaya göndermez: buton alttan bir çekmece açar, * sıralama girişi ve sihirbaz adayın bulunduğu sayfanın bağlamında akar. * Tasarım gerekçesi için bkz. docs/tasarim/cta-cekmece-tasarimi.md. * * `baslik` iki kartın (profilli/profilsiz) başlığını, `aciklama` yalnız * profilsiz karttaki paragrafı değiştirir — katalog sayfaları kartı üstündeki * tabloya bağlayan köprü cümlesini buradan verir. Başlık ve açıklamalar * yerleşme vaadi içermez: ürün sıralamaya yakın programları listeler. * * `varyant="kompakt"`: katalog sayfalarında başlığın hemen altına, tablonun * ÜSTÜNE konan tek satırlık sürüm. Sıralama alanı çekmecede değil doğrudan * sayfadadır (tek sayfada çıkan ziyaretçi alttaki karta hiç inmiyor); gönderince * aynı çekmece sihirbaz adımıyla açılır. Aynı sayfada alttaki kartla birlikte * durduğu için alan kimlikleri `useId` ile üretilir, analitik olayları da * `konum` ("ust" | "alt") özelliğiyle ayrışır — olay adları değişmez. */ export function CtaSiraForm({ baslik, aciklama, varyant = "kart", konum = "alt", }: { baslik?: string; aciklama?: string; varyant?: "kart" | "kompakt"; /** Yalnız ölçüm içindir: formun sayfadaki yeri (tablonun üstü / altı). */ konum?: "ust" | "alt"; }) { const profil = useTercihProfili(); const router = useRouter(); const kompakt = varyant === "kompakt"; // Aynı sayfada iki form (üst satır + alt kart) olabildiği için sabit id yok. const kimlik = useId(); const alanId = `${kimlik}-siralama`; const hataId = `${kimlik}-siralama-hata`; const baslikId = `${kimlik}-baslik`; const [acik, setAcik] = useState(false); const [adim, setAdim] = useState<"sira" | "sihirbaz">("sira"); const [siralama, setSiralama] = useState(""); const [tur, setTur] = useState("say"); const [sira, setSira] = useState(null); const [facetler, setFacetler] = useState(null); const [yukleniyor, setYukleniyor] = useState(false); const [hata, setHata] = useState(null); const [girisli, setGirisli] = useState(false); // Huni girişi ölçümü: ref yalnızca profilsiz daldaki karta bağlanır; kayıtlı // profilli ziyaretçi formu hiç görmediğinden sayılmaz. Sayfa ayrımı için // özellik gerekmez — Rybbit olayın pathname'ini zaten yazar. `konum` hem // özellik hem tekilleştirme ekidir: üstteki satır olayı atınca alttaki kartın // gösterimi yutulmasın (oturum başına konum başına en fazla 1). const formRef = useGorunumOlayi( "cta_form_goruntulendi", { konum }, konum, ); // Yazarken binlik ayracıyla biçimle (85000 → 85.000) function siralamaDegisti(ham: string) { setHata(null); const rakamlar = ham.replace(/\D/g, "").slice(0, 7); setSiralama(rakamlar ? Number(rakamlar).toLocaleString("tr-TR") : ""); } async function siraGonder(e: React.FormEvent) { e.preventDefault(); const value = Number(siralama.replace(/\./g, "")); if (!value || value < 1 || value > 4_000_000) { setHata("Geçerli bir başarı sıralaması gir (ör. 85.000)."); return; } setYukleniyor(true); try { // Oturum bilgisi facet isteğiyle PARALEL çözülür; sayfa statik kalır // (hero formundaki desenle aynı — bkz. hero-form.tsx). const [veri, oturum] = await Promise.all([ onbellekliJsonGetir<{ facetler: SihirbazFacetleri }>( `/api/facetler?sira=${value}&tur=${tur}`, ), authClient.getSession().catch(() => null), ]); setGirisli(Boolean(oturum?.data?.user)); setSira(value); setFacetler(veri.facetler); setAdim("sihirbaz"); // Kompakt satırda sıralama sayfada girilir; çekmece ancak şimdi, // doğrudan sihirbaz adımıyla açılır (kartta zaten açıktır). setAcik(true); olay("sira_girildi", { kaynak: "cta", konum, tur, sira_kovasi: siraKovasi(value), }); } catch { olay("sira_form_hata", { kaynak: "cta", konum }); setHata("Bir şeyler ters gitti, tekrar dener misin?"); } finally { setYukleniyor(false); } } // Seçimler tamam: sakla, çekmeceyi kapat ve /sonuc'a git. Girişli // kullanıcıda orada otomatik üretim başlar; girişsiz kullanıcı önce // ücretsiz tabloyu görür (değer önce — giriş duvarı sihirbazın hemen // arkasında değil). function secimleriTamamla(secimler: SihirbazSecimleri) { if (sira == null) return; const yeniProfil = { sira, tur, secimler }; tercihProfiliYaz(yeniProfil); setAcik(false); router.push(sonucHref(girisli ? { sihirbaz: "1", kaynak: "cta" } : { hazir: "1" })); } function acikDegisti(v: boolean) { setAcik(v); if (!v) { setHata(null); // Kompakt satırda çekmecenin sıra adımı yoktur; her gönderim adımı ve // facet'leri zaten baştan yazar (kapanış animasyonunda içerik boşalmasın). if (kompakt) return; // Yeniden açılışta sıfırdan başla; yazılan sıralama kaybolmasın. setAdim("sira"); setFacetler(null); } } // Sıra girişi: kartta çekmecenin ilk adımı, kompakt satırda sayfanın kendisi. // Kompakt sürüm masaüstünde (lg) tek satırdır: çipler · alan · buton. const siraFormu = (
{PUAN_TURLERI_SECENEK.map((t) => ( ))}
siralamaDegisti(e.target.value)} onFocus={preloadSihirbazAdimlar} aria-invalid={hata ? true : undefined} aria-describedby={hata ? hataId : undefined} className="h-12 bg-background pl-11 text-base" />
{hata ? ( ) : null}
); // Çekmece iki varyantta da aynıdır; kompakt satırda sıra adımı sayfada // olduğundan çekmece yalnız sihirbazı taşır. const cekmece = ( {/* Görünür başlıklar adıma göre değişir; erişilebilir ad sabit kalır. */} Sıralamana yakın programlar hangileri? YKS başarı sıralamanı gir; sıralamana uygun programları bu sayfadan ayrılmadan gör. Kapat
{!kompakt && adim === "sira" ? (
Saniyede gör

Sıralamana yakın programlar hangileri?

Puan türünü seç, başarı sıralamanı yaz; hayal, dengeli ve güvenli dilimlerindeki programları bu sayfadan ayrılmadan gör. Ücretsiz, üyelik gerekmez.

{siraFormu}
    {KANITLAR.map(({ ikon: Ikon, etiket }) => (
  • {etiket}
  • ))}
) : facetler && sira != null ? ( ) : null}
); if (kompakt) { // CLS: kayıtlı profil hydration SONRASI bulunur. Satır tablonun üstünde // olduğundan yüksekliği o anda değişmemeli — form yerinde (görünmez ve // inert) kalır, profilli kısayol aynı ızgara hücresine bindirilir; hücrenin // yüksekliğini her iki durumda da form belirler. return (

{baslik ?? "Sıralaman bu tabanların neresinde?"} {" "} Ücretsiz, üyelik gerekmez.

{siraFormu}
{profil ? (

Sıralaman kayıtlı ({profil.sira.toLocaleString("tr-TR")}. sıra).

Sıralamana uygun programları gör
) : null}
{cekmece}
); } if (profil) { return (

{baslik ?? "Sıralamana yakın programlar hangileri? Saniyede gör."}

Sıralaman kayıtlı ({profil.sira.toLocaleString("tr-TR")}. sıra) — gerçek YÖK Atlas verisiyle hayal, dengeli ve güvenli dilimlerindeki programların hazır.

Sıralamana uygun programları gör
); } return (

{baslik ?? "Sıralamana yakın programlar hangileri? Saniyede gör."}

{aciklama ?? "YKS başarı sıralamanı gir; gerçek YÖK Atlas verisiyle sıralamana uygun hayal, dengeli ve güvenli dilimlerindeki programları anında listele. Ücretsiz, üyelik gerekmez."}

{cekmece}
); }