diff --git a/src/features/liste/components/program-ekle-butonu.tsx b/src/features/liste/components/program-ekle-butonu.tsx index 6a1a30d..95613cc 100644 --- a/src/features/liste/components/program-ekle-butonu.tsx +++ b/src/features/liste/components/program-ekle-butonu.tsx @@ -1,6 +1,6 @@ "use client"; -import { Check, Plus } from "lucide-react"; +import { ArrowRight, Check, Plus } from "lucide-react"; import { toast } from "sonner"; import type { Program } from "@/types/yokatlas"; import { olay } from "@/lib/analitik"; @@ -10,7 +10,10 @@ import { useListedeMi, type ManuelKaynak, } from "../liste-store"; -import { programEkleIste } from "../hooks/use-tercih-profili"; +import { + profilToplamaAc, + programEkleIste, +} from "../hooks/use-tercih-profili"; import { preloadTercihProfiliKapisi } from "./tercih-profili-kapisi-lazy"; type ProgramOzet = Pick< @@ -25,6 +28,8 @@ type ProgramOzet = Pick< | "sira2024" >; +const PROFILSIZ_EKLEME_TOAST_ID = "profilsiz-ekleme"; + export function ProgramEkleButonu({ program, kaynak = "sonuc", @@ -53,10 +58,32 @@ export function ProgramEkleButonu({ } else if (sonuc.zatenVar) { toast.info("Bu program zaten seçtiklerinde."); } else if (sonuc.eklendi) { - // Yalnız gerçek eklemede: profilsiz + kapıyı açar (kapiAcildi) ve - // program o an listeye girmez — o yolun olayı kapı tamamlanınca - // profilTamamlandi içinden {yol: "kapi"} ile atılır. - olay("program_eklendi", { kaynak, yol: "dogrudan" }); + olay("program_eklendi", { + kaynak, + yol: "dogrudan", + profilli: sonuc.profilsiz ? "hayir" : "evet", + }); + if (sonuc.profilsiz) { + // Önce ver, sonra iste: program eklendi; sıralama sorusu değer + // karşılığında gelir. Sabit id → art arda +'larda toast yığılmaz, + // aynı toast tazelenir. + toast.success("Seçtiklerine eklendi", { + id: PROFILSIZ_EKLEME_TOAST_ID, + duration: 8000, + description: + "Sıralamanı girersen bu programın risk seviyesini de gösteririz.", + action: { + label: ( + + Sıralamamı gir + + + ), + // Tanıtım ekranını atla, doğrudan sıralama adımı + onClick: () => profilToplamaAc(null, true, "arti_toast"), + }, + }); + } } } @@ -65,7 +92,8 @@ export function ProgramEkleButonu({ type="button" disabled={listede || doldu} onClick={handleClick} - // Profilsiz kullanıcıda + kapıyı açar — chunk'ı niyet anında ısıt + // Profilsiz eklemenin toast aksiyonu kapıyı açar — chunk'ı niyet + // anında ısıt onPointerEnter={preloadTercihProfiliKapisi} onFocus={preloadTercihProfiliKapisi} title={ diff --git a/src/features/liste/hooks/use-tercih-profili.ts b/src/features/liste/hooks/use-tercih-profili.ts index 64de752..f76fb90 100644 --- a/src/features/liste/hooks/use-tercih-profili.ts +++ b/src/features/liste/hooks/use-tercih-profili.ts @@ -1,6 +1,7 @@ "use client"; -// Global "+" kapısı: profil yokken bekleyen programı tutar, modalı açar. +// Global profil kapısı deposu. + profil yokken de programı hemen ekler +// (prototip); kapı, ekleme toast'ındaki aksiyondan ve diğer CTA'lardan açılır. // Layout'ta bir kez monte edilen TercihProfiliKapisi bu store'a abone olur. import { useSyncExternalStore } from "react"; @@ -45,7 +46,15 @@ export type BekleyenHedef = "sonuc" | "sihirbaz"; * `kapi_kapatildi` olaylarının `tetik` özelliği), davranışı etkilemez. * "arti": + butonu · "sonuc": sonuca gitmek isteyen CTA'lar · * "sihirbaz": Yapay Zeka niyetli CTA'lar · "duzenle": profil düzenleme. */ -export type KapiTetik = "arti" | "sonuc" | "sihirbaz" | "duzenle" | "diger"; +export type KapiTetik = + | "arti" + // Profilsiz + artık anında ekliyor; kapı, ekleme toast'ındaki + // "Sıralamamı gir" aksiyonundan açılır ("arti" yeni akışta atılmaz). + | "arti_toast" + | "sonuc" + | "sihirbaz" + | "duzenle" + | "diger"; type KapıDurumu = { acik: boolean; @@ -230,32 +239,35 @@ export function programEkleIste(opts: { eklendi: boolean; zatenVar: boolean; doldu: boolean; + /** Artık hep false: + kapıyı açmıyor (çağıranlar kırılmasın diye duruyor). */ kapiAcildi: boolean; + /** Program profil (sıralama) olmadan eklendi → risk henüz hesaplanamadı. */ + profilsiz: boolean; } { - const { program, kaynak = "sonuc", adaySira, adayTur } = opts; + const { program, kaynak = "sonuc", adaySira } = opts; if (listedeMi(program.id)) { - return { eklendi: false, zatenVar: true, doldu: false, kapiAcildi: false }; + return { + eklendi: false, + zatenVar: true, + doldu: false, + kapiAcildi: false, + profilsiz: false, + }; } - if (!tercihProfiliTamamlandiMi()) { - kapiyiAc( - { - duzenle: false, - dogrudanSira: false, - bekleyen: { program, kaynak, adaySira, adayTur }, - bekleyenHedef: null, - }, - "arti", - ); - return { eklendi: false, zatenVar: false, doldu: false, kapiAcildi: true }; - } + // [PROTOTİP] Önce ver, sonra iste: profil yokken de program HEMEN listeye + // girer (sıra bilinmiyorsa risk null), kapı açılmaz. Sıralama sorusu + // butondaki toast aksiyonuyla gelir; profil girilince profilTamamlandi → + // riskleriYenile tüm listedeki null riskleri doldurur. Eski davranış + // (bekleyen program + kapı) için bu commit'i geri almak yeterli. + const profilsiz = !tercihProfiliTamamlandiMi(); - if (!durum.profilYuklendi) { + if (!profilsiz && !durum.profilYuklendi) { profiliTazele(); } - const profil = durum.profil ?? tercihProfiliOku(); + const profil = profilsiz ? null : (durum.profil ?? tercihProfiliOku()); const tercih = programdanManuelTercih( program, adaySira ?? profil?.sira ?? null, @@ -263,9 +275,21 @@ export function programEkleIste(opts: { ); const ok = ekle(tercih); if (!ok) { - return { eklendi: false, zatenVar: false, doldu: true, kapiAcildi: false }; + return { + eklendi: false, + zatenVar: false, + doldu: true, + kapiAcildi: false, + profilsiz, + }; } - return { eklendi: true, zatenVar: false, doldu: false, kapiAcildi: false }; + return { + eklendi: true, + zatenVar: false, + doldu: false, + kapiAcildi: false, + profilsiz, + }; } /** Profil yazıldıktan sonra; bekleyen hedef varsa yönlendirme href'i döner.