feat(liste): ilk + profilsiz ziyaretçide anında eklesin [Bilal onayı gerekir]
PROTOTİP — Bilal telefonda bakıp karar verecek (CEO tur 2, karar #12). Katalogdan +'ya basan 82 profilsiz oturumun 77'si 5 ekranlık kapıdan dönüyordu (09-analitik-tur2, S6). Sıra tersine çevrildi: önce ver, sonra iste. - programEkleIste: profil yokken programı hemen ekler (sıra bilinmiyorsa risk null), kapıyı açmaz; dönüşe `profilsiz` eklendi. - Buton: program_eklendi {kaynak, yol: "dogrudan", profilli: evet|hayir}. Profilsiz eklemede sonner toast (sabit id, 8 sn): "Seçtiklerine eklendi" + "Sıralamamı gir →" aksiyonu → profilToplamaAc(null, true, "arti_toast") (tanıtım ekranı atlanır). KapiTetik'e "arti_toast". - Profil kapıdan girilince mevcut profilTamamlandi → riskleriYenile null riskleri doldurur (yeni kod yok). Kapı bileşeni, sihirbaz adımları, SEMA ve meraklisina-icerik değişmedi (+ kapısı şemada yok → senkron kuralı tetiklenmez). Geri almak için bu commit'i revert etmek yeterli; K1 ölçümü (gece/p5-kapi-olcum) bağımsız. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
@@ -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: (
|
||||
<span className="inline-flex items-center gap-1">
|
||||
Sıralamamı gir
|
||||
<ArrowRight className="size-4" aria-hidden />
|
||||
</span>
|
||||
),
|
||||
// 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={
|
||||
|
||||
@@ -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.
|
||||
|
||||
Reference in New Issue
Block a user