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";
|
"use client";
|
||||||
|
|
||||||
import { Check, Plus } from "lucide-react";
|
import { ArrowRight, Check, Plus } from "lucide-react";
|
||||||
import { toast } from "sonner";
|
import { toast } from "sonner";
|
||||||
import type { Program } from "@/types/yokatlas";
|
import type { Program } from "@/types/yokatlas";
|
||||||
import { olay } from "@/lib/analitik";
|
import { olay } from "@/lib/analitik";
|
||||||
@@ -10,7 +10,10 @@ import {
|
|||||||
useListedeMi,
|
useListedeMi,
|
||||||
type ManuelKaynak,
|
type ManuelKaynak,
|
||||||
} from "../liste-store";
|
} 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";
|
import { preloadTercihProfiliKapisi } from "./tercih-profili-kapisi-lazy";
|
||||||
|
|
||||||
type ProgramOzet = Pick<
|
type ProgramOzet = Pick<
|
||||||
@@ -25,6 +28,8 @@ type ProgramOzet = Pick<
|
|||||||
| "sira2024"
|
| "sira2024"
|
||||||
>;
|
>;
|
||||||
|
|
||||||
|
const PROFILSIZ_EKLEME_TOAST_ID = "profilsiz-ekleme";
|
||||||
|
|
||||||
export function ProgramEkleButonu({
|
export function ProgramEkleButonu({
|
||||||
program,
|
program,
|
||||||
kaynak = "sonuc",
|
kaynak = "sonuc",
|
||||||
@@ -53,10 +58,32 @@ export function ProgramEkleButonu({
|
|||||||
} else if (sonuc.zatenVar) {
|
} else if (sonuc.zatenVar) {
|
||||||
toast.info("Bu program zaten seçtiklerinde.");
|
toast.info("Bu program zaten seçtiklerinde.");
|
||||||
} else if (sonuc.eklendi) {
|
} else if (sonuc.eklendi) {
|
||||||
// Yalnız gerçek eklemede: profilsiz + kapıyı açar (kapiAcildi) ve
|
olay("program_eklendi", {
|
||||||
// program o an listeye girmez — o yolun olayı kapı tamamlanınca
|
kaynak,
|
||||||
// profilTamamlandi içinden {yol: "kapi"} ile atılır.
|
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"
|
type="button"
|
||||||
disabled={listede || doldu}
|
disabled={listede || doldu}
|
||||||
onClick={handleClick}
|
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}
|
onPointerEnter={preloadTercihProfiliKapisi}
|
||||||
onFocus={preloadTercihProfiliKapisi}
|
onFocus={preloadTercihProfiliKapisi}
|
||||||
title={
|
title={
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
"use client";
|
"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.
|
// Layout'ta bir kez monte edilen TercihProfiliKapisi bu store'a abone olur.
|
||||||
|
|
||||||
import { useSyncExternalStore } from "react";
|
import { useSyncExternalStore } from "react";
|
||||||
@@ -45,7 +46,15 @@ export type BekleyenHedef = "sonuc" | "sihirbaz";
|
|||||||
* `kapi_kapatildi` olaylarının `tetik` özelliği), davranışı etkilemez.
|
* `kapi_kapatildi` olaylarının `tetik` özelliği), davranışı etkilemez.
|
||||||
* "arti": + butonu · "sonuc": sonuca gitmek isteyen CTA'lar ·
|
* "arti": + butonu · "sonuc": sonuca gitmek isteyen CTA'lar ·
|
||||||
* "sihirbaz": Yapay Zeka niyetli CTA'lar · "duzenle": profil düzenleme. */
|
* "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 = {
|
type KapıDurumu = {
|
||||||
acik: boolean;
|
acik: boolean;
|
||||||
@@ -230,32 +239,35 @@ export function programEkleIste(opts: {
|
|||||||
eklendi: boolean;
|
eklendi: boolean;
|
||||||
zatenVar: boolean;
|
zatenVar: boolean;
|
||||||
doldu: boolean;
|
doldu: boolean;
|
||||||
|
/** Artık hep false: + kapıyı açmıyor (çağıranlar kırılmasın diye duruyor). */
|
||||||
kapiAcildi: boolean;
|
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)) {
|
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()) {
|
// [PROTOTİP] Önce ver, sonra iste: profil yokken de program HEMEN listeye
|
||||||
kapiyiAc(
|
// girer (sıra bilinmiyorsa risk null), kapı açılmaz. Sıralama sorusu
|
||||||
{
|
// butondaki toast aksiyonuyla gelir; profil girilince profilTamamlandi →
|
||||||
duzenle: false,
|
// riskleriYenile tüm listedeki null riskleri doldurur. Eski davranış
|
||||||
dogrudanSira: false,
|
// (bekleyen program + kapı) için bu commit'i geri almak yeterli.
|
||||||
bekleyen: { program, kaynak, adaySira, adayTur },
|
const profilsiz = !tercihProfiliTamamlandiMi();
|
||||||
bekleyenHedef: null,
|
|
||||||
},
|
|
||||||
"arti",
|
|
||||||
);
|
|
||||||
return { eklendi: false, zatenVar: false, doldu: false, kapiAcildi: true };
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!durum.profilYuklendi) {
|
if (!profilsiz && !durum.profilYuklendi) {
|
||||||
profiliTazele();
|
profiliTazele();
|
||||||
}
|
}
|
||||||
|
|
||||||
const profil = durum.profil ?? tercihProfiliOku();
|
const profil = profilsiz ? null : (durum.profil ?? tercihProfiliOku());
|
||||||
const tercih = programdanManuelTercih(
|
const tercih = programdanManuelTercih(
|
||||||
program,
|
program,
|
||||||
adaySira ?? profil?.sira ?? null,
|
adaySira ?? profil?.sira ?? null,
|
||||||
@@ -263,9 +275,21 @@ export function programEkleIste(opts: {
|
|||||||
);
|
);
|
||||||
const ok = ekle(tercih);
|
const ok = ekle(tercih);
|
||||||
if (!ok) {
|
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.
|
/** Profil yazıldıktan sonra; bekleyen hedef varsa yönlendirme href'i döner.
|
||||||
|
|||||||
Reference in New Issue
Block a user