feat(analitik): kapi_acildi + kapi_kapatildi olayları
Profil kapısının açılışı ve tamamlanmadan kapanışı doğrudan ölçülmüyordu;
"82 oturum kapıyı gördü" kod davranışından çıkarımdı (09-analitik-tur2).
İki yeni olay (adlar kalıcı sözleşme):
- kapi_acildi {tetik: arti|sonuc|sihirbaz|duzenle|diger}: depoda tek
açılış noktası (kapiyiAc). Her açıcı kendi tetik'ini yazar:
programEkleIste → arti, profilDuzenlemeyiAc → duzenle, profilToplamaAc →
isteğe bağlı 3. parametre, verilmezse hedeften (sonuc/sihirbaz), o da
yoksa diger. Depoya `tetik` alanı eklendi.
- kapi_kapatildi {adim: giris|sira|sihirbaz, tetik}: yalnız TAMAMLANMADAN
kapanışta; kapı bileşeninde tek noktadan (vazgec) — X, dışarı tıklama,
Esc ve "Vazgeç" butonları. Tamamlanan kapı profilTamamlandi ile depodan
kapanır, bu noktaya uğramaz.
Ham sıra/e-posta olaylara girmez. Davranış değişmez.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
"use client";
|
||||
|
||||
import { useState } from "react";
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
import { useRouter } from "next/navigation";
|
||||
import { ArrowRight, Trophy } from "lucide-react";
|
||||
import { useReducedMotion } from "motion/react";
|
||||
@@ -53,17 +53,38 @@ type Adim = "giris" | "sira" | "sihirbaz";
|
||||
const ADIM_SIRASI: Record<Adim, number> = { giris: 0, sira: 1, sihirbaz: 2 };
|
||||
|
||||
export function TercihProfiliKapisi() {
|
||||
const { acik, duzenle, dogrudanSira, bekleyen, bekleyenHedef, profil } =
|
||||
useProfilKapisi();
|
||||
const {
|
||||
acik,
|
||||
duzenle,
|
||||
dogrudanSira,
|
||||
tetik,
|
||||
bekleyen,
|
||||
bekleyenHedef,
|
||||
profil,
|
||||
} = useProfilKapisi();
|
||||
// Kapanış olayı için o an görünen adım; KapıIcerik her adım değişiminde
|
||||
// (ve mount'ta) bildirir. Render'ı etkilemediği için state değil ref.
|
||||
const adimRef = useRef<Adim>("giris");
|
||||
const oturumAnahtari = acik
|
||||
? `${duzenle ? "d" : "n"}-${bekleyen?.program.id ?? "yok"}-${profil?.sira ?? 0}-${profil?.tur ?? ""}`
|
||||
: "kapali";
|
||||
|
||||
// Tamamlanmadan kapanışın TEK noktası: X, dışarı tıklama, Esc (Dialog
|
||||
// onOpenChange) ve içerideki "Vazgeç" butonları buradan geçer.
|
||||
// Tamamlanan kapı buraya uğramaz: secimleriTamamla → profilTamamlandi
|
||||
// depoda `acik`'ı doğrudan kapatır; Radix kontrollü `open` prop'u
|
||||
// değişince onOpenChange çağırmaz → `kapi_kapatildi` atılmaz.
|
||||
function vazgec() {
|
||||
if (!acik) return;
|
||||
olay("kapi_kapatildi", { adim: adimRef.current, tetik });
|
||||
profilKapisiniKapat();
|
||||
}
|
||||
|
||||
return (
|
||||
<Dialog
|
||||
open={acik}
|
||||
onOpenChange={(v) => {
|
||||
if (!v) profilKapisiniKapat();
|
||||
if (!v) vazgec();
|
||||
}}
|
||||
>
|
||||
<DialogContent
|
||||
@@ -85,6 +106,10 @@ export function TercihProfiliKapisi() {
|
||||
bekleyen={bekleyen}
|
||||
hedef={bekleyenHedef}
|
||||
profil={profil}
|
||||
onAdimDegisti={(yeniAdim) => {
|
||||
adimRef.current = yeniAdim;
|
||||
}}
|
||||
onVazgec={vazgec}
|
||||
/>
|
||||
) : null}
|
||||
</DialogContent>
|
||||
@@ -98,21 +123,32 @@ function KapıIcerik({
|
||||
bekleyen,
|
||||
hedef,
|
||||
profil,
|
||||
onAdimDegisti,
|
||||
onVazgec,
|
||||
}: {
|
||||
duzenle: boolean;
|
||||
dogrudanSira: boolean;
|
||||
bekleyen: BekleyenProgram | null;
|
||||
hedef: BekleyenHedef | null;
|
||||
profil: TercihProfili | null;
|
||||
/** Görünen adımı üst bileşene bildirir (yalnız `kapi_kapatildi` ölçümü). */
|
||||
onAdimDegisti: (adim: Adim) => void;
|
||||
/** Tamamlamadan kapat — olayı üst bileşen atar, sonra kapıyı kapatır. */
|
||||
onVazgec: () => void;
|
||||
}) {
|
||||
const router = useRouter();
|
||||
const bilinenSira = bekleyen?.adaySira ?? profil?.sira ?? null;
|
||||
const bilinenTur = bekleyen?.adayTur ?? profil?.tur ?? "say";
|
||||
|
||||
// Düzenlemede ve navbar "Sıralamanı gir" girişinde doğrudan sıralama adımı
|
||||
const [adim, setAdim] = useState<Adim>(
|
||||
duzenle || dogrudanSira ? "sira" : "giris",
|
||||
);
|
||||
const ilkAdim: Adim = duzenle || dogrudanSira ? "sira" : "giris";
|
||||
const [adim, setAdim] = useState<Adim>(ilkAdim);
|
||||
// Her açılışta (key ile yeniden mount) üstteki adım kaydını başa sar;
|
||||
// önceki açılıştan kalan "sihirbaz" değeri yeni kapanışa sızmasın.
|
||||
useEffect(() => {
|
||||
onAdimDegisti(ilkAdim);
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps -- yalnız mount
|
||||
}, []);
|
||||
// Adım geçişleri sihirbazın kendi içindeki TransitionPanel kaymasıyla oynar
|
||||
const [yon, setYon] = useState(1);
|
||||
const [gecisBasladi, setGecisBasladi] = useState(false);
|
||||
@@ -140,6 +176,7 @@ function KapıIcerik({
|
||||
setYon(ADIM_SIRASI[yeniAdim] > ADIM_SIRASI[adim] ? 1 : -1);
|
||||
setGecisBasladi(true);
|
||||
setAdim(yeniAdim);
|
||||
onAdimDegisti(yeniAdim);
|
||||
}
|
||||
|
||||
async function facetYukle(siraDeger: number, turDeger: string) {
|
||||
@@ -287,7 +324,7 @@ function KapıIcerik({
|
||||
<Button
|
||||
type="button"
|
||||
variant="ghost"
|
||||
onClick={profilKapisiniKapat}
|
||||
onClick={onVazgec}
|
||||
className="cursor-pointer"
|
||||
>
|
||||
Vazgeç
|
||||
@@ -385,7 +422,7 @@ function KapıIcerik({
|
||||
onClick={() =>
|
||||
// Doğrudan sıralama adımıyla açıldıysa tanıtım adımı hiç
|
||||
// görülmedi; "Geri" yerine kapıyı kapatmak beklenen davranış.
|
||||
duzenle || dogrudanSira ? profilKapisiniKapat() : adimaGec("giris")
|
||||
duzenle || dogrudanSira ? onVazgec() : adimaGec("giris")
|
||||
}
|
||||
className="cursor-pointer"
|
||||
>
|
||||
|
||||
@@ -41,11 +41,19 @@ export type BekleyenProgram = {
|
||||
* girişsizse giriş CTA paneli; hero-form ile aynı desen). */
|
||||
export type BekleyenHedef = "sonuc" | "sihirbaz";
|
||||
|
||||
/** Kapıyı kimin açtığı — yalnız ölçüm içindir (`kapi_acildi` /
|
||||
* `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";
|
||||
|
||||
type KapıDurumu = {
|
||||
acik: boolean;
|
||||
duzenle: boolean;
|
||||
/** Tanıtım (giriş) adımını atla, doğrudan sıralama adımıyla aç. */
|
||||
dogrudanSira: boolean;
|
||||
/** Açık kapıyı kimin açtığı; kapalıyken "diger". */
|
||||
tetik: KapiTetik;
|
||||
bekleyen: BekleyenProgram | null;
|
||||
bekleyenHedef: BekleyenHedef | null;
|
||||
profil: TercihProfili | null;
|
||||
@@ -56,6 +64,7 @@ let durum: KapıDurumu = {
|
||||
acik: false,
|
||||
duzenle: false,
|
||||
dogrudanSira: false,
|
||||
tetik: "diger",
|
||||
bekleyen: null,
|
||||
bekleyenHedef: null,
|
||||
profil: null,
|
||||
@@ -107,6 +116,7 @@ const SSR_DURUM: KapıDurumu = {
|
||||
acik: false,
|
||||
duzenle: false,
|
||||
dogrudanSira: false,
|
||||
tetik: "diger",
|
||||
bekleyen: null,
|
||||
bekleyenHedef: null,
|
||||
profil: null,
|
||||
@@ -134,44 +144,63 @@ export function profilKapisiniKapat() {
|
||||
acik: false,
|
||||
duzenle: false,
|
||||
dogrudanSira: false,
|
||||
tetik: "diger",
|
||||
bekleyen: null,
|
||||
bekleyenHedef: null,
|
||||
};
|
||||
yayinla();
|
||||
}
|
||||
|
||||
/** Kapının tek açılış noktası: durumu yazar ve `kapi_acildi`'yi atar; her
|
||||
* açıcı kendi `tetik` değerini verir. (Tamamlanmadan kapanışın olayı
|
||||
* `kapi_kapatildi` kapı bileşeninden atılır — adımı yalnız o bilir.) */
|
||||
function kapiyiAc(
|
||||
yama: Pick<
|
||||
KapıDurumu,
|
||||
"duzenle" | "dogrudanSira" | "bekleyen" | "bekleyenHedef"
|
||||
>,
|
||||
tetik: KapiTetik,
|
||||
) {
|
||||
durum = { ...durum, acik: true, ...yama, tetik };
|
||||
yayinla();
|
||||
olay("kapi_acildi", { tetik });
|
||||
}
|
||||
|
||||
export function profilDuzenlemeyiAc() {
|
||||
if (!durum.profilYuklendi) yukle();
|
||||
durum = {
|
||||
...durum,
|
||||
acik: true,
|
||||
duzenle: true,
|
||||
dogrudanSira: false,
|
||||
bekleyen: null,
|
||||
bekleyenHedef: null,
|
||||
};
|
||||
yayinla();
|
||||
kapiyiAc(
|
||||
{
|
||||
duzenle: true,
|
||||
dogrudanSira: false,
|
||||
bekleyen: null,
|
||||
bekleyenHedef: null,
|
||||
},
|
||||
"duzenle",
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Profil yokken kapıyı açar; tamamlanınca `hedef`'e yönlendirilir.
|
||||
* Navbar, çekmece +, /sonuc boş hali ve listem gibi yerler bunu kullanır.
|
||||
* `dogrudanSira` tanıtım adımını atlar — kapı sıralama adımıyla açılır.
|
||||
* `tetik` yalnız ölçüm içindir; verilmezse hedeften türetilir ("sonuc" /
|
||||
* "sihirbaz"), hedef de yoksa "diger".
|
||||
*/
|
||||
export function profilToplamaAc(
|
||||
hedef: BekleyenHedef | null = null,
|
||||
dogrudanSira = false,
|
||||
tetik?: KapiTetik,
|
||||
) {
|
||||
if (!durum.profilYuklendi) yukle();
|
||||
durum = {
|
||||
...durum,
|
||||
acik: true,
|
||||
duzenle: false,
|
||||
dogrudanSira,
|
||||
bekleyen: null,
|
||||
bekleyenHedef: hedef,
|
||||
};
|
||||
yayinla();
|
||||
kapiyiAc(
|
||||
{
|
||||
duzenle: false,
|
||||
dogrudanSira,
|
||||
bekleyen: null,
|
||||
bekleyenHedef: hedef,
|
||||
},
|
||||
tetik ?? hedef ?? "diger",
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -210,15 +239,15 @@ export function programEkleIste(opts: {
|
||||
}
|
||||
|
||||
if (!tercihProfiliTamamlandiMi()) {
|
||||
durum = {
|
||||
...durum,
|
||||
acik: true,
|
||||
duzenle: false,
|
||||
dogrudanSira: false,
|
||||
bekleyen: { program, kaynak, adaySira, adayTur },
|
||||
bekleyenHedef: null,
|
||||
};
|
||||
yayinla();
|
||||
kapiyiAc(
|
||||
{
|
||||
duzenle: false,
|
||||
dogrudanSira: false,
|
||||
bekleyen: { program, kaynak, adaySira, adayTur },
|
||||
bekleyenHedef: null,
|
||||
},
|
||||
"arti",
|
||||
);
|
||||
return { eklendi: false, zatenVar: false, doldu: false, kapiAcildi: true };
|
||||
}
|
||||
|
||||
@@ -265,6 +294,7 @@ export function profilTamamlandi(
|
||||
acik: false,
|
||||
duzenle: false,
|
||||
dogrudanSira: false,
|
||||
tetik: "diger",
|
||||
bekleyen: null,
|
||||
bekleyenHedef: null,
|
||||
profil,
|
||||
|
||||
@@ -39,7 +39,12 @@ export type OlayAdi =
|
||||
| "sonuc_goruntulendi"
|
||||
| "paket_goruntulendi"
|
||||
| "liste_uretim_hata"
|
||||
| "giris_hata";
|
||||
| "giris_hata"
|
||||
// Profil kapısı (sıra + sihirbaz modalı). kapi_acildi {tetik}: her açılış.
|
||||
// kapi_kapatildi {adim, tetik}: yalnız TAMAMLANMADAN kapanış (X, dışarı
|
||||
// tıklama, Vazgeç); tamamlanan kapıda atılmaz.
|
||||
| "kapi_acildi"
|
||||
| "kapi_kapatildi";
|
||||
|
||||
export type Ozellikler = Record<string, string | number>;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user