refactor: sihirbaz feature'ı — lib/sihirbaz bölündü, bileşenler taşındı (Faz 6)
- lib/sihirbaz.ts ikiye ayrıldı: sihirbaz-sabitler.ts (şema + doğrulayıcılar + event/storage sabitleri + sonucHref/secimOzeti, izomorfik) ve sihirbaz-profil.ts (localStorage oku/yaz/sil); 25 tüketicinin importu sembol bazında bölünerek güncellendi, localStorage anahtar/event değerleri birebir aynı - sihirbaz-adimlar(+lazy), hero-form, cta-sira-form, il-secim-haritasi ve /sonuc'un sihirbaz-modal/sihirbaz-cagri-karti/sira-gerekli bileşenleri features/sihirbaz/components/ altına taşındı - sihirbaz-queries.ts ve async SihirbazBolumu sarmalayıcısı /sonuc sayfa dönüşümüyle birlikte Faz 9'da gelecek (o zamana dek ölü kod olurlardı) - Davranış değişikliği yok Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -13,7 +13,7 @@ import { sohbetAkisi } from "@/lib/ai/cagri";
|
||||
import { listeOzetiCikar } from "@/lib/ai/rapor";
|
||||
import type { RaporSonuc } from "@/features/rapor/types/rapor";
|
||||
import type { RaporParams } from "@/features/rapor/types/rapor";
|
||||
import { secimOzeti } from "@/lib/sihirbaz";
|
||||
import { secimOzeti } from "@/features/sihirbaz/sihirbaz-sabitler";
|
||||
import { PUAN_TURLERI } from "@/types/yokatlas";
|
||||
import { ACIK_SATIR } from "@/lib/rapor-maske";
|
||||
|
||||
|
||||
@@ -13,12 +13,8 @@ import { Button } from "@/components/ui/button";
|
||||
import { Skeleton } from "@/components/ui/skeleton";
|
||||
import { listeOlustur } from "@/app/sonuc/actions";
|
||||
import { olay, siraKovasi } from "@/lib/analitik";
|
||||
import {
|
||||
sonucHref,
|
||||
tercihProfiliOku,
|
||||
tercihProfiliSil,
|
||||
type TercihProfili,
|
||||
} from "@/lib/sihirbaz";
|
||||
import { sonucHref, type TercihProfili } from "@/features/sihirbaz/sihirbaz-sabitler";
|
||||
import { tercihProfiliOku, tercihProfiliSil } from "@/features/sihirbaz/sihirbaz-profil";
|
||||
|
||||
const URETIM_MESAJLARI = [
|
||||
"Sıralamana uygun programlar taranıyor…",
|
||||
|
||||
@@ -5,7 +5,7 @@ import { ArrowRight } from "lucide-react";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { PagePixelDivider } from "@/components/pixel-decor";
|
||||
import { useTercihProfili } from "@/components/manuel-liste/profil-kapisi-store";
|
||||
import { sonucHref } from "@/lib/sihirbaz";
|
||||
import { sonucHref } from "@/features/sihirbaz/sihirbaz-sabitler";
|
||||
|
||||
/**
|
||||
* /listem boş hâlinin metni + CTA'sı: sıralamasını zaten girmiş adaya
|
||||
|
||||
@@ -4,7 +4,7 @@ import Link from "next/link";
|
||||
import { ArrowRight } from "lucide-react";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { useTercihProfili } from "@/components/manuel-liste/profil-kapisi-store";
|
||||
import { sonucHref } from "@/lib/sihirbaz";
|
||||
import { sonucHref } from "@/features/sihirbaz/sihirbaz-sabitler";
|
||||
|
||||
/**
|
||||
* Ödeme başarılı ama henüz Yapay Zeka listesi yokken gösterilen CTA: kayıtlı tercih
|
||||
|
||||
@@ -18,7 +18,7 @@ import { Suspense } from "react";
|
||||
import { ComparisonCell } from "@/features/pazarlama/components/landing-parcalari";
|
||||
import { HeroBaslik } from "@/features/pazarlama/components/hero-baslik";
|
||||
import { KapanisCta } from "@/features/pazarlama/components/hero-focus-button";
|
||||
import { HeroForm } from "@/components/hero-form";
|
||||
import { HeroForm } from "@/features/sihirbaz/components/hero-form";
|
||||
import { Parallax } from "@/components/parallax";
|
||||
import { Reveal } from "@/features/pazarlama/components/reveal";
|
||||
import { SosyalKanitBandi } from "@/features/pazarlama/components/sosyal-kanit";
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { Suspense } from "react";
|
||||
import type { Metadata } from "next";
|
||||
import { JsonLd, breadcrumbJsonLd } from "@/lib/seo";
|
||||
import { CtaSiraForm } from "@/components/cta-sira-form";
|
||||
import { CtaSiraForm } from "@/features/sihirbaz/components/cta-sira-form";
|
||||
import { SiteFooter } from "@/components/site-footer";
|
||||
import { PagePixelDivider } from "@/components/pixel-decor";
|
||||
import {
|
||||
|
||||
@@ -21,7 +21,7 @@ import type {
|
||||
RaporParams,
|
||||
RaporSonuc,
|
||||
} from "@/features/rapor/types/rapor";
|
||||
import { sihirbazDogrula, type SihirbazSecimleri } from "@/lib/sihirbaz";
|
||||
import { sihirbazDogrula, type SihirbazSecimleri } from "@/features/sihirbaz/sihirbaz-sabitler";
|
||||
import { raporMaskele } from "@/lib/rapor-maske";
|
||||
|
||||
|
||||
|
||||
@@ -4,12 +4,12 @@ import { PUAN_TURLERI, type PuanTuruKey } from "@/types/yokatlas";
|
||||
import { getSession } from "@/lib/session";
|
||||
import { kullanicininRaporu } from "@/lib/rapor-kaydi";
|
||||
import { tadimlikSec } from "@/lib/tadimlik-havuzu";
|
||||
import { SihirbazBolumu } from "./sihirbaz-cagri-karti";
|
||||
import { SihirbazBolumu } from "@/features/sihirbaz/components/sihirbaz-cagri-karti";
|
||||
import { TadimlikSatiri } from "./tadimlik-satiri";
|
||||
import { ProgramTablosu } from "@/components/program-tablosu";
|
||||
import { ManuelHarita } from "@/components/manuel-liste/manuel-harita";
|
||||
import { SiteFooter } from "@/components/site-footer";
|
||||
import { SiraGerekli } from "./sira-gerekli";
|
||||
import { SiraGerekli } from "@/features/sihirbaz/components/sira-gerekli";
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: "Tercih Listeni Kur",
|
||||
|
||||
@@ -1,344 +0,0 @@
|
||||
"use client";
|
||||
|
||||
import { useEffect, useRef, useState, useSyncExternalStore } from "react";
|
||||
import Link from "next/link";
|
||||
import { useRouter } from "next/navigation";
|
||||
import { ArrowRight } from "lucide-react";
|
||||
import { animate } from "motion";
|
||||
import { useReducedMotion } from "motion/react";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { PixelField } from "@/components/pixel-decor";
|
||||
import { useGorunumOlayi } from "@/components/use-gorunum-olayi";
|
||||
import { olay } from "@/lib/analitik";
|
||||
import type { SihirbazFacetleri } from "@/types/yokatlas";
|
||||
import {
|
||||
SIHIRBAZ_AC_EVENT,
|
||||
SIHIRBAZ_STORAGE_KEY,
|
||||
sihirbazDogrula,
|
||||
tercihProfiliYaz,
|
||||
type SihirbazSecimleri,
|
||||
} from "@/lib/sihirbaz";
|
||||
import { SihirbazModal } from "./sihirbaz-modal";
|
||||
|
||||
// Üretim /listem'de koşar (bkz. listem/liste-uretici.tsx); sihirbaz
|
||||
// tamamlanınca seçimler storage'a yazılıp kullanıcı buraya yönlendirilir.
|
||||
const LISTEM_URET_HREF = "/listem?uret=1";
|
||||
|
||||
// SSR'da false, client'ta true — storage'a ancak mount sonrası dokunuruz
|
||||
const bosAbone = () => () => {};
|
||||
const useMonteEdildi = () =>
|
||||
useSyncExternalStore(
|
||||
bosAbone,
|
||||
() => true,
|
||||
() => false,
|
||||
);
|
||||
|
||||
type BekleyenSecim = {
|
||||
secimler: SihirbazSecimleri;
|
||||
eslesir: boolean; // storage'daki sıra/tur bu sayfayla aynı mı
|
||||
};
|
||||
|
||||
/**
|
||||
* Sonuç sayfasında sihirbaz akışını yöneten client orkestratör:
|
||||
* - Sayfa ortasındaki büyük CTA (rapor yokken)
|
||||
* - Adımları toplayan modal
|
||||
* Liste bu sayfada üretilmez ve gösterilmez: sihirbaz tamamlanınca seçimler
|
||||
* storage'a yazılır ve kullanıcı /listem'e yönlendirilir; üretim animasyonu,
|
||||
* kredi/hata durumları ve listenin kendisi orada yaşar. Girişsiz kullanıcı
|
||||
* hero'dan seçimleri tamamlanmış gelirse önce ücretsiz tabloyu görür; giriş
|
||||
* CTA'sı "hazır seçimlerin kaydedilmesi" olarak panelde sunulur, girişten
|
||||
* dönüş doğrudan /listem'e düşer ve üretim orada başlar.
|
||||
*/
|
||||
export function SihirbazBolumu({
|
||||
sira,
|
||||
tur,
|
||||
facetler,
|
||||
girisliMi,
|
||||
raporVar,
|
||||
otomatikAc,
|
||||
secimlerHazir,
|
||||
}: {
|
||||
sira: number;
|
||||
tur: string;
|
||||
facetler: SihirbazFacetleri;
|
||||
girisliMi: boolean;
|
||||
/** Kullanıcının kayıtlı bir Yapay Zeka listesi var mı (listenin kendisi /listem'de) */
|
||||
raporVar: boolean;
|
||||
/** ?sihirbaz=1 ile gelindiyse modalı otomatik aç */
|
||||
otomatikAc?: boolean;
|
||||
/** ?hazir=1 (hero sihirbazından girişsiz geliş): seçimler storage'da,
|
||||
panel giriş CTA'sına döner. URL'den gelir ki SSR'da doğru panel
|
||||
render edilsin. */
|
||||
secimlerHazir?: boolean;
|
||||
}) {
|
||||
const router = useRouter();
|
||||
const monteEdildi = useMonteEdildi();
|
||||
const hareketiAzalt = useReducedMotion();
|
||||
|
||||
const [elleAcik, setElleAcik] = useState(false);
|
||||
const [elleKapandi, setElleKapandi] = useState(false);
|
||||
const [bekleyen, setBekleyen] = useState<BekleyenSecim | null>(null);
|
||||
// ?sihirbaz=1 fallback modalı storage okunmadan açılmasın: hero'dan gelen
|
||||
// kayıtlı seçimler varken modal bir anlığına açılıp kapanıyordu.
|
||||
const [depoOkundu, setDepoOkundu] = useState(false);
|
||||
|
||||
const otomatikBasladi = useRef(false);
|
||||
// Funnel CTA'ları (banner olayı dahil) tıklama anındaki bekleyen seçimi
|
||||
// görsün diye ref'te tutulur; olay dinleyicisi bir kez bağlanır.
|
||||
const bekleyenRef = useRef<BekleyenSecim | null>(null);
|
||||
|
||||
// Mount sonrası storage'dan bekleyen seçimleri oku
|
||||
useEffect(() => {
|
||||
try {
|
||||
const ham = localStorage.getItem(SIHIRBAZ_STORAGE_KEY);
|
||||
let yeniBekleyen: BekleyenSecim | null = null;
|
||||
if (ham) {
|
||||
const veri = JSON.parse(ham) as {
|
||||
sira?: number;
|
||||
tur?: string;
|
||||
secimler?: unknown;
|
||||
};
|
||||
const secimler = sihirbazDogrula(veri.secimler);
|
||||
if (secimler) {
|
||||
yeniBekleyen = {
|
||||
secimler,
|
||||
eslesir: veri.sira === sira && veri.tur === tur,
|
||||
};
|
||||
}
|
||||
}
|
||||
if (yeniBekleyen) {
|
||||
// eslint-disable-next-line react-hooks/set-state-in-effect
|
||||
setBekleyen(yeniBekleyen);
|
||||
bekleyenRef.current = yeniBekleyen;
|
||||
}
|
||||
} catch {}
|
||||
setDepoOkundu(true);
|
||||
}, [sira, tur]);
|
||||
|
||||
// Arama modalındaki funnel kartının CTA'sı sihirbazı bu olayla başlatır
|
||||
useEffect(() => {
|
||||
const ac = () => sihirbaziBaslat();
|
||||
window.addEventListener(SIHIRBAZ_AC_EVENT, ac);
|
||||
return () => window.removeEventListener(SIHIRBAZ_AC_EVENT, ac);
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
|
||||
async function modalCikisiniOynat() {
|
||||
if (hareketiAzalt) return;
|
||||
|
||||
const modal = document.querySelector<HTMLElement>("[data-sihirbaz-modal]");
|
||||
if (!modal) return;
|
||||
|
||||
const mevcutTransform = getComputedStyle(modal).transform;
|
||||
const hedefTransform =
|
||||
mevcutTransform === "none"
|
||||
? "translate3d(0, 48px, 0)"
|
||||
: new DOMMatrixReadOnly(mevcutTransform).translate(0, 48).toString();
|
||||
const perde = document.querySelector<HTMLElement>(
|
||||
'[data-slot="dialog-overlay"]',
|
||||
);
|
||||
|
||||
const modalAnimasyonu = animate(
|
||||
modal,
|
||||
{
|
||||
opacity: 0,
|
||||
transform: hedefTransform,
|
||||
},
|
||||
{
|
||||
duration: 0.24,
|
||||
ease: [0.4, 0, 0.7, 0.2],
|
||||
},
|
||||
);
|
||||
|
||||
if (perde) {
|
||||
await Promise.all([
|
||||
modalAnimasyonu,
|
||||
animate(perde, { opacity: 0 }, { duration: 0.2, ease: "easeOut" }),
|
||||
]);
|
||||
return;
|
||||
}
|
||||
|
||||
await modalAnimasyonu;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sihirbaz tamamlandı: seçimleri storage'a yaz ve üretimin koştuğu
|
||||
* /listem'e geç. Girişsiz kullanıcı önce girişe uğrar; callback doğrudan
|
||||
* /listem'e düştüğünden üretim dönüşte orada otomatik başlar.
|
||||
*/
|
||||
async function ilerle(secimler: SihirbazSecimleri) {
|
||||
tercihProfiliYaz({ sira, tur, secimler });
|
||||
await modalCikisiniOynat();
|
||||
setElleAcik(false);
|
||||
if (!girisliMi) {
|
||||
router.push(`/giris?callback=${encodeURIComponent(LISTEM_URET_HREF)}`);
|
||||
return;
|
||||
}
|
||||
router.push(LISTEM_URET_HREF);
|
||||
}
|
||||
|
||||
/**
|
||||
* Funnel CTA'larının (banner, büyük kart) ortak girişi:
|
||||
* adımlar zaten tamamlanıp kaydedildiyse modal açılmaz — girişli
|
||||
* kullanıcı doğrudan /listem'e gider, girişsiz kullanıcı girişe gider.
|
||||
* Kayıtlı seçim yoksa (ya da başka sıra/türe aitse) modal açılır.
|
||||
*/
|
||||
function sihirbaziBaslat() {
|
||||
const hazir = bekleyenRef.current;
|
||||
if (hazir?.eslesir) {
|
||||
void ilerle(hazir.secimler);
|
||||
return;
|
||||
}
|
||||
setElleAcik(true);
|
||||
setElleKapandi(false);
|
||||
}
|
||||
|
||||
// Giriş dönüşü (?sihirbaz=1) storage'da eşleşen seçimlerle geldiyse modal
|
||||
// açılmaz: seçimler hazır, üretim /listem'de başlasın (bir kez).
|
||||
const otomatikGonder = Boolean(
|
||||
otomatikAc && bekleyen?.eslesir && girisliMi && !raporVar,
|
||||
);
|
||||
useEffect(() => {
|
||||
if (otomatikGonder && !otomatikBasladi.current) {
|
||||
otomatikBasladi.current = true;
|
||||
// replace: geri tuşu bu sayfaya dönünce tekrar /listem'e sekmesin
|
||||
router.replace(LISTEM_URET_HREF);
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [otomatikGonder]);
|
||||
|
||||
// ?sihirbaz=1: otomatik yönlendirme devreye girmediyse (seçim kaybolduysa/
|
||||
// eşleşmiyorsa) modalı aç ki kullanıcı adımları tamamlayabilsin. Karar
|
||||
// storage okunmadan verilmez: kayıtlı seçimlerle gelen kullanıcıya modal
|
||||
// hiç gösterilmez, /listem'e geçilir.
|
||||
const acik =
|
||||
!elleKapandi &&
|
||||
(elleAcik ||
|
||||
Boolean(otomatikAc && depoOkundu && !otomatikGonder && !raporVar));
|
||||
|
||||
function acikDegisti(v: boolean) {
|
||||
if (v) {
|
||||
setElleAcik(true);
|
||||
setElleKapandi(false);
|
||||
} else {
|
||||
setElleKapandi(true);
|
||||
setElleAcik(false);
|
||||
}
|
||||
}
|
||||
|
||||
// Değer önce: sihirbazı bitirmiş ama girişsiz kullanıcı ücretsiz tabloyu
|
||||
// zaten görüyor; giriş burada bariyer değil, hazır seçimlerin (ödülün)
|
||||
// kaydedilmesi olarak sunulur. Girişten dönüşte üretim /listem'de başlar.
|
||||
// Sinyal URL'den (?hazir=1) gelir ki SSR'da doğru panel render edilsin.
|
||||
const girisBekliyor = Boolean(!girisliMi && secimlerHazir && !raporVar);
|
||||
const girisUrl = `/giris?callback=${encodeURIComponent(LISTEM_URET_HREF)}`;
|
||||
// Kapı 1 ölçümü: panel yalnızca girisBekliyor dalında render edilir; ref
|
||||
// başka dalda bağlanmadığından observer da yalnızca o durumda kurulur.
|
||||
const hazirPanelRef = useGorunumOlayi<HTMLElement>("hazir_panel_goruntulendi");
|
||||
|
||||
return (
|
||||
<>
|
||||
{/* Yapay Zeka çıktısı (üretim animasyonu dahil) bu sayfada render
|
||||
edilmez; panel yalnızca funnel CTA'larını gösterir, üretim ve
|
||||
listenin kendisi /listem'de yaşar. */}
|
||||
{raporVar ? (
|
||||
<section className="mt-16 rounded-2xl border border-slate-200 bg-white p-8 text-center sm:p-12">
|
||||
<h2 className="font-heading text-2xl font-bold sm:text-3xl">
|
||||
24 tercihlik listen hazır
|
||||
</h2>
|
||||
<p className="mx-auto mt-3 max-w-xl text-slate-600">
|
||||
Yapay zekânın kurduğu risk analizli listen Listem sayfasında
|
||||
seni bekliyor.
|
||||
</p>
|
||||
<Button
|
||||
asChild
|
||||
size="lg"
|
||||
className="mt-6 cursor-pointer bg-orange-500 text-white transition-colors duration-200 hover:bg-orange-600"
|
||||
>
|
||||
<Link href="/listem">Listeni gör</Link>
|
||||
</Button>
|
||||
</section>
|
||||
) : girisBekliyor ? (
|
||||
<section
|
||||
ref={hazirPanelRef}
|
||||
className="relative isolate mt-16 min-w-0 overflow-hidden rounded-2xl bg-primary px-5 py-8 text-center text-white sm:p-12"
|
||||
>
|
||||
<div
|
||||
aria-hidden
|
||||
className="pointer-events-none absolute inset-x-0 -inset-y-10 -z-10 text-white"
|
||||
>
|
||||
<PixelField seed={13} />
|
||||
</div>
|
||||
<h2 className="relative font-heading text-2xl font-bold sm:text-3xl">
|
||||
Seçimlerin hazır — Yapay Zeka listen bir adım uzakta
|
||||
</h2>
|
||||
<p className="relative mx-auto mt-3 max-w-xl text-white/90">
|
||||
Yukarıdaki tablo her zaman ücretsiz. 5 deneme kredinle 24
|
||||
tercihlik Yapay Zeka listeni de kurabilirsin: ücretsiz giriş yap,
|
||||
seçimlerin kayıtlı — listen otomatik kurulsun (üretim 3
|
||||
kredi, kalan 2 kredinle danışmana soru sorarsın).
|
||||
</p>
|
||||
<Button
|
||||
asChild
|
||||
size="lg"
|
||||
className="relative mt-6 h-auto min-h-12 w-full cursor-pointer whitespace-normal bg-orange-500 px-4 py-3 text-white transition-colors duration-200 hover:bg-orange-600 sm:w-auto sm:px-8"
|
||||
>
|
||||
<Link
|
||||
href={girisUrl}
|
||||
onClick={() =>
|
||||
olay("giris_cta_tiklandi", { kaynak: "hazir_panel" })
|
||||
}
|
||||
>
|
||||
5 deneme kredinle Yapay Zeka listeni kur — giriş yap
|
||||
<ArrowRight className="size-4" aria-hidden />
|
||||
</Link>
|
||||
</Button>
|
||||
<p className="relative mt-3 text-xs text-white/70">
|
||||
Kredi kartı gerekmez · Google ya da e-posta ile 30 saniyede
|
||||
</p>
|
||||
</section>
|
||||
) : (
|
||||
<section className="relative isolate mt-16 min-w-0 overflow-hidden rounded-2xl bg-primary px-5 py-8 text-center text-white sm:p-12">
|
||||
<div
|
||||
aria-hidden
|
||||
className="pointer-events-none absolute inset-x-0 -inset-y-10 -z-10 text-white"
|
||||
>
|
||||
<PixelField seed={19} />
|
||||
</div>
|
||||
<h2 className="relative font-heading text-2xl font-bold sm:text-3xl">
|
||||
24 tercihi tek tek elemek yerine, yapay zekâ kursun
|
||||
</h2>
|
||||
<p className="relative mx-auto mt-3 max-w-xl text-white/90">
|
||||
Tablodan elle eklemek her zaman serbest. Ama üç adımda ilgi
|
||||
alanlarını ve şehirlerini söylersen, yapay zekâ hayal, dengeli
|
||||
ve garanti dilimlerine dağıtılmış 24 tercihi — her satırı
|
||||
yerleşme riski ve gerekçesiyle — senin yerine dizer.
|
||||
</p>
|
||||
<Button
|
||||
size="lg"
|
||||
onClick={sihirbaziBaslat}
|
||||
className="relative mt-6 h-auto min-h-12 w-full cursor-pointer whitespace-normal bg-orange-500 px-4 py-3 text-white transition-colors duration-200 hover:bg-orange-600 sm:w-auto sm:px-8"
|
||||
>
|
||||
Yapay Zeka listemi kur
|
||||
<ArrowRight className="size-4" aria-hidden />
|
||||
</Button>
|
||||
</section>
|
||||
)}
|
||||
|
||||
{monteEdildi ? (
|
||||
<SihirbazModal
|
||||
acik={acik}
|
||||
onAcikDegisti={acikDegisti}
|
||||
sira={sira}
|
||||
tur={tur}
|
||||
facetler={facetler}
|
||||
baslangicSecimler={bekleyen?.eslesir ? bekleyen.secimler : null}
|
||||
sonButonEtiketi={
|
||||
girisliMi ? "Yapay Zeka listemi kur (3 kredi)" : "Giriş yap ve listeni gör"
|
||||
}
|
||||
onTamamla={ilerle}
|
||||
/>
|
||||
) : null}
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -1,58 +0,0 @@
|
||||
"use client";
|
||||
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogDescription,
|
||||
DialogTitle,
|
||||
} from "@/components/ui/dialog";
|
||||
import { SihirbazAdimlarLazy } from "@/components/sihirbaz-adimlar-lazy";
|
||||
import type { SihirbazFacetleri } from "@/types/yokatlas";
|
||||
import type { SihirbazSecimleri } from "@/lib/sihirbaz";
|
||||
|
||||
/**
|
||||
* Sihirbaz modalı artık yalnızca adımları toplar; üretim, önizleme ve liste
|
||||
* /listem'de yaşar (bkz. listem/liste-uretici.tsx). Akış yönetimi
|
||||
* sihirbaz-cagri-karti.tsx'teki SihirbazBolumu'ndadır.
|
||||
*/
|
||||
export function SihirbazModal({
|
||||
acik,
|
||||
onAcikDegisti,
|
||||
sira,
|
||||
tur,
|
||||
facetler,
|
||||
baslangicSecimler,
|
||||
sonButonEtiketi,
|
||||
onTamamla,
|
||||
}: {
|
||||
acik: boolean;
|
||||
onAcikDegisti: (acik: boolean) => void;
|
||||
sira: number;
|
||||
tur: string;
|
||||
facetler: SihirbazFacetleri;
|
||||
baslangicSecimler?: SihirbazSecimleri | null;
|
||||
sonButonEtiketi: string;
|
||||
onTamamla: (secimler: SihirbazSecimleri) => void;
|
||||
}) {
|
||||
return (
|
||||
<Dialog open={acik} onOpenChange={onAcikDegisti}>
|
||||
<DialogContent
|
||||
data-sihirbaz-modal
|
||||
className="max-h-[92dvh] gap-0 overflow-y-auto p-6 sm:max-w-3xl"
|
||||
>
|
||||
<DialogTitle className="sr-only">Tercih listesi sihirbazı</DialogTitle>
|
||||
<DialogDescription className="sr-only">
|
||||
Sıralamana uygun 24 tercihlik listeni oluştur.
|
||||
</DialogDescription>
|
||||
<SihirbazAdimlarLazy
|
||||
sira={sira}
|
||||
tur={tur}
|
||||
facetler={facetler}
|
||||
baslangicSecimler={baslangicSecimler}
|
||||
sonButonEtiketi={sonButonEtiketi}
|
||||
onTamamla={onTamamla}
|
||||
/>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
@@ -1,70 +0,0 @@
|
||||
"use client";
|
||||
|
||||
// /sonuc?sira= yokken: kayıtlı profil varsa oraya taşı; yoksa kapıyı aç.
|
||||
// Sıra bilgisi localStorage'daki TercihProfili ile tüm uygulamada ortak.
|
||||
|
||||
import { useEffect, useState } from "react";
|
||||
import { useRouter } from "next/navigation";
|
||||
import Link from "next/link";
|
||||
import { ArrowLeft, Trophy } from "lucide-react";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { PagePixelDivider } from "@/components/pixel-decor";
|
||||
import { sonucHref } from "@/lib/sihirbaz";
|
||||
import {
|
||||
sonucaGitIste,
|
||||
useProfilKapisi,
|
||||
} from "@/components/manuel-liste/profil-kapisi-store";
|
||||
|
||||
export function SiraGerekli() {
|
||||
const router = useRouter();
|
||||
const { profil, profilYuklendi } = useProfilKapisi();
|
||||
const [yonleniyor, setYonleniyor] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
if (!profilYuklendi) return;
|
||||
if (profil) {
|
||||
setYonleniyor(true);
|
||||
router.replace(sonucHref(profil));
|
||||
}
|
||||
}, [profil, profilYuklendi, router]);
|
||||
|
||||
if (!profilYuklendi || yonleniyor || profil) {
|
||||
return (
|
||||
<main className="mx-auto flex max-w-2xl flex-col items-center px-4 py-24 text-center">
|
||||
<p className="text-sm text-slate-500">Sıralaman kontrol ediliyor…</p>
|
||||
</main>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<main className="mx-auto flex max-w-2xl flex-col items-center px-4 py-24 text-center">
|
||||
<h1 className="font-heading text-2xl font-bold">
|
||||
Geçerli bir sıralama gerekli
|
||||
</h1>
|
||||
<p className="mt-3 text-slate-600">
|
||||
Listeni kurabilmek için YKS başarı sıralamanı gir. Kaydettiğin sıra tüm
|
||||
uygulamada kullanılır.
|
||||
</p>
|
||||
<PagePixelDivider seed={59} className="mx-auto mt-6" />
|
||||
<div className="mt-6 flex flex-wrap items-center justify-center gap-3">
|
||||
<Button
|
||||
type="button"
|
||||
onClick={() => {
|
||||
const { href } = sonucaGitIste();
|
||||
if (href) router.push(href);
|
||||
}}
|
||||
className="cursor-pointer bg-orange-500 text-white hover:bg-orange-600"
|
||||
>
|
||||
<Trophy className="size-4" aria-hidden />
|
||||
Sıralamamı gir
|
||||
</Button>
|
||||
<Button asChild variant="outline" className="cursor-pointer">
|
||||
<Link href="/">
|
||||
<ArrowLeft className="size-4" aria-hidden />
|
||||
Ana sayfaya dön
|
||||
</Link>
|
||||
</Button>
|
||||
</div>
|
||||
</main>
|
||||
);
|
||||
}
|
||||
@@ -25,11 +25,8 @@ import { useGorunumOlayi } from "@/components/use-gorunum-olayi";
|
||||
import { olay, siraKovasi } from "@/lib/analitik";
|
||||
import { dilimdenRisk, riskHesapla } from "@/lib/risk";
|
||||
import { turkishSlugify } from "@/lib/slug";
|
||||
import {
|
||||
PROFIL_DEGISTI_EVENT,
|
||||
sonucHref,
|
||||
tercihProfiliOku,
|
||||
} from "@/lib/sihirbaz";
|
||||
import { PROFIL_DEGISTI_EVENT, sonucHref } from "@/features/sihirbaz/sihirbaz-sabitler";
|
||||
import { tercihProfiliOku } from "@/features/sihirbaz/sihirbaz-profil";
|
||||
import type { TadimlikSatir } from "@/lib/tadimlik-havuzu";
|
||||
|
||||
const GENEL = "genel";
|
||||
|
||||
Reference in New Issue
Block a user