merge: origin/gece/p5-kapi-olcum (gece vardiyası 2026-09-21)
All checks were successful
Deploy / deploy (push) Successful in 8m3s
All checks were successful
Deploy / deploy (push) Successful in 8m3s
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
@@ -52,8 +52,11 @@ export function ProgramEkleButonu({
|
|||||||
toast.info(`Listen ${MANUEL_LISTE_MAX} tercihe ulaştı.`);
|
toast.info(`Listen ${MANUEL_LISTE_MAX} tercihe ulaştı.`);
|
||||||
} else if (sonuc.zatenVar) {
|
} else if (sonuc.zatenVar) {
|
||||||
toast.info("Bu program zaten seçtiklerinde.");
|
toast.info("Bu program zaten seçtiklerinde.");
|
||||||
} else {
|
} else if (sonuc.eklendi) {
|
||||||
olay("program_eklendi", { kaynak });
|
// 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" });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { useState } from "react";
|
import { useEffect, useRef, useState } from "react";
|
||||||
import { useRouter } from "next/navigation";
|
import { useRouter } from "next/navigation";
|
||||||
import { ArrowRight, Trophy } from "lucide-react";
|
import { ArrowRight, Trophy } from "lucide-react";
|
||||||
import { useReducedMotion } from "motion/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 };
|
const ADIM_SIRASI: Record<Adim, number> = { giris: 0, sira: 1, sihirbaz: 2 };
|
||||||
|
|
||||||
export function TercihProfiliKapisi() {
|
export function TercihProfiliKapisi() {
|
||||||
const { acik, duzenle, dogrudanSira, bekleyen, bekleyenHedef, profil } =
|
const {
|
||||||
useProfilKapisi();
|
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
|
const oturumAnahtari = acik
|
||||||
? `${duzenle ? "d" : "n"}-${bekleyen?.program.id ?? "yok"}-${profil?.sira ?? 0}-${profil?.tur ?? ""}`
|
? `${duzenle ? "d" : "n"}-${bekleyen?.program.id ?? "yok"}-${profil?.sira ?? 0}-${profil?.tur ?? ""}`
|
||||||
: "kapali";
|
: "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 (
|
return (
|
||||||
<Dialog
|
<Dialog
|
||||||
open={acik}
|
open={acik}
|
||||||
onOpenChange={(v) => {
|
onOpenChange={(v) => {
|
||||||
if (!v) profilKapisiniKapat();
|
if (!v) vazgec();
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<DialogContent
|
<DialogContent
|
||||||
@@ -85,6 +106,10 @@ export function TercihProfiliKapisi() {
|
|||||||
bekleyen={bekleyen}
|
bekleyen={bekleyen}
|
||||||
hedef={bekleyenHedef}
|
hedef={bekleyenHedef}
|
||||||
profil={profil}
|
profil={profil}
|
||||||
|
onAdimDegisti={(yeniAdim) => {
|
||||||
|
adimRef.current = yeniAdim;
|
||||||
|
}}
|
||||||
|
onVazgec={vazgec}
|
||||||
/>
|
/>
|
||||||
) : null}
|
) : null}
|
||||||
</DialogContent>
|
</DialogContent>
|
||||||
@@ -98,21 +123,32 @@ function KapıIcerik({
|
|||||||
bekleyen,
|
bekleyen,
|
||||||
hedef,
|
hedef,
|
||||||
profil,
|
profil,
|
||||||
|
onAdimDegisti,
|
||||||
|
onVazgec,
|
||||||
}: {
|
}: {
|
||||||
duzenle: boolean;
|
duzenle: boolean;
|
||||||
dogrudanSira: boolean;
|
dogrudanSira: boolean;
|
||||||
bekleyen: BekleyenProgram | null;
|
bekleyen: BekleyenProgram | null;
|
||||||
hedef: BekleyenHedef | null;
|
hedef: BekleyenHedef | null;
|
||||||
profil: TercihProfili | 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 router = useRouter();
|
||||||
const bilinenSira = bekleyen?.adaySira ?? profil?.sira ?? null;
|
const bilinenSira = bekleyen?.adaySira ?? profil?.sira ?? null;
|
||||||
const bilinenTur = bekleyen?.adayTur ?? profil?.tur ?? "say";
|
const bilinenTur = bekleyen?.adayTur ?? profil?.tur ?? "say";
|
||||||
|
|
||||||
// Düzenlemede ve navbar "Sıralamanı gir" girişinde doğrudan sıralama adımı
|
// Düzenlemede ve navbar "Sıralamanı gir" girişinde doğrudan sıralama adımı
|
||||||
const [adim, setAdim] = useState<Adim>(
|
const ilkAdim: Adim = duzenle || dogrudanSira ? "sira" : "giris";
|
||||||
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
|
// Adım geçişleri sihirbazın kendi içindeki TransitionPanel kaymasıyla oynar
|
||||||
const [yon, setYon] = useState(1);
|
const [yon, setYon] = useState(1);
|
||||||
const [gecisBasladi, setGecisBasladi] = useState(false);
|
const [gecisBasladi, setGecisBasladi] = useState(false);
|
||||||
@@ -140,6 +176,7 @@ function KapıIcerik({
|
|||||||
setYon(ADIM_SIRASI[yeniAdim] > ADIM_SIRASI[adim] ? 1 : -1);
|
setYon(ADIM_SIRASI[yeniAdim] > ADIM_SIRASI[adim] ? 1 : -1);
|
||||||
setGecisBasladi(true);
|
setGecisBasladi(true);
|
||||||
setAdim(yeniAdim);
|
setAdim(yeniAdim);
|
||||||
|
onAdimDegisti(yeniAdim);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function facetYukle(siraDeger: number, turDeger: string) {
|
async function facetYukle(siraDeger: number, turDeger: string) {
|
||||||
@@ -287,7 +324,7 @@ function KapıIcerik({
|
|||||||
<Button
|
<Button
|
||||||
type="button"
|
type="button"
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
onClick={profilKapisiniKapat}
|
onClick={onVazgec}
|
||||||
className="cursor-pointer"
|
className="cursor-pointer"
|
||||||
>
|
>
|
||||||
Vazgeç
|
Vazgeç
|
||||||
@@ -385,7 +422,7 @@ function KapıIcerik({
|
|||||||
onClick={() =>
|
onClick={() =>
|
||||||
// Doğrudan sıralama adımıyla açıldıysa tanıtım adımı hiç
|
// 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ış.
|
// görülmedi; "Geri" yerine kapıyı kapatmak beklenen davranış.
|
||||||
duzenle || dogrudanSira ? profilKapisiniKapat() : adimaGec("giris")
|
duzenle || dogrudanSira ? onVazgec() : adimaGec("giris")
|
||||||
}
|
}
|
||||||
className="cursor-pointer"
|
className="cursor-pointer"
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -5,6 +5,7 @@
|
|||||||
|
|
||||||
import { useSyncExternalStore } from "react";
|
import { useSyncExternalStore } from "react";
|
||||||
import type { Program } from "@/types/yokatlas";
|
import type { Program } from "@/types/yokatlas";
|
||||||
|
import { olay } from "@/lib/analitik";
|
||||||
import { PROFIL_DEGISTI_EVENT, sonucHref, type TercihProfili } from "@/features/sihirbaz/sihirbaz-sabitler";
|
import { PROFIL_DEGISTI_EVENT, sonucHref, type TercihProfili } from "@/features/sihirbaz/sihirbaz-sabitler";
|
||||||
import { tercihProfiliOku, tercihProfiliTamamlandiMi } from "@/features/sihirbaz/sihirbaz-profil";
|
import { tercihProfiliOku, tercihProfiliTamamlandiMi } from "@/features/sihirbaz/sihirbaz-profil";
|
||||||
import {
|
import {
|
||||||
@@ -40,11 +41,19 @@ export type BekleyenProgram = {
|
|||||||
* girişsizse giriş CTA paneli; hero-form ile aynı desen). */
|
* girişsizse giriş CTA paneli; hero-form ile aynı desen). */
|
||||||
export type BekleyenHedef = "sonuc" | "sihirbaz";
|
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 = {
|
type KapıDurumu = {
|
||||||
acik: boolean;
|
acik: boolean;
|
||||||
duzenle: boolean;
|
duzenle: boolean;
|
||||||
/** Tanıtım (giriş) adımını atla, doğrudan sıralama adımıyla aç. */
|
/** Tanıtım (giriş) adımını atla, doğrudan sıralama adımıyla aç. */
|
||||||
dogrudanSira: boolean;
|
dogrudanSira: boolean;
|
||||||
|
/** Açık kapıyı kimin açtığı; kapalıyken "diger". */
|
||||||
|
tetik: KapiTetik;
|
||||||
bekleyen: BekleyenProgram | null;
|
bekleyen: BekleyenProgram | null;
|
||||||
bekleyenHedef: BekleyenHedef | null;
|
bekleyenHedef: BekleyenHedef | null;
|
||||||
profil: TercihProfili | null;
|
profil: TercihProfili | null;
|
||||||
@@ -55,6 +64,7 @@ let durum: KapıDurumu = {
|
|||||||
acik: false,
|
acik: false,
|
||||||
duzenle: false,
|
duzenle: false,
|
||||||
dogrudanSira: false,
|
dogrudanSira: false,
|
||||||
|
tetik: "diger",
|
||||||
bekleyen: null,
|
bekleyen: null,
|
||||||
bekleyenHedef: null,
|
bekleyenHedef: null,
|
||||||
profil: null,
|
profil: null,
|
||||||
@@ -106,6 +116,7 @@ const SSR_DURUM: KapıDurumu = {
|
|||||||
acik: false,
|
acik: false,
|
||||||
duzenle: false,
|
duzenle: false,
|
||||||
dogrudanSira: false,
|
dogrudanSira: false,
|
||||||
|
tetik: "diger",
|
||||||
bekleyen: null,
|
bekleyen: null,
|
||||||
bekleyenHedef: null,
|
bekleyenHedef: null,
|
||||||
profil: null,
|
profil: null,
|
||||||
@@ -133,44 +144,63 @@ export function profilKapisiniKapat() {
|
|||||||
acik: false,
|
acik: false,
|
||||||
duzenle: false,
|
duzenle: false,
|
||||||
dogrudanSira: false,
|
dogrudanSira: false,
|
||||||
|
tetik: "diger",
|
||||||
bekleyen: null,
|
bekleyen: null,
|
||||||
bekleyenHedef: null,
|
bekleyenHedef: null,
|
||||||
};
|
};
|
||||||
yayinla();
|
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() {
|
export function profilDuzenlemeyiAc() {
|
||||||
if (!durum.profilYuklendi) yukle();
|
if (!durum.profilYuklendi) yukle();
|
||||||
durum = {
|
kapiyiAc(
|
||||||
...durum,
|
{
|
||||||
acik: true,
|
duzenle: true,
|
||||||
duzenle: true,
|
dogrudanSira: false,
|
||||||
dogrudanSira: false,
|
bekleyen: null,
|
||||||
bekleyen: null,
|
bekleyenHedef: null,
|
||||||
bekleyenHedef: null,
|
},
|
||||||
};
|
"duzenle",
|
||||||
yayinla();
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Profil yokken kapıyı açar; tamamlanınca `hedef`'e yönlendirilir.
|
* Profil yokken kapıyı açar; tamamlanınca `hedef`'e yönlendirilir.
|
||||||
* Navbar, çekmece +, /sonuc boş hali ve listem gibi yerler bunu kullanır.
|
* 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.
|
* `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(
|
export function profilToplamaAc(
|
||||||
hedef: BekleyenHedef | null = null,
|
hedef: BekleyenHedef | null = null,
|
||||||
dogrudanSira = false,
|
dogrudanSira = false,
|
||||||
|
tetik?: KapiTetik,
|
||||||
) {
|
) {
|
||||||
if (!durum.profilYuklendi) yukle();
|
if (!durum.profilYuklendi) yukle();
|
||||||
durum = {
|
kapiyiAc(
|
||||||
...durum,
|
{
|
||||||
acik: true,
|
duzenle: false,
|
||||||
duzenle: false,
|
dogrudanSira,
|
||||||
dogrudanSira,
|
bekleyen: null,
|
||||||
bekleyen: null,
|
bekleyenHedef: hedef,
|
||||||
bekleyenHedef: hedef,
|
},
|
||||||
};
|
tetik ?? hedef ?? "diger",
|
||||||
yayinla();
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -209,15 +239,15 @@ export function programEkleIste(opts: {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!tercihProfiliTamamlandiMi()) {
|
if (!tercihProfiliTamamlandiMi()) {
|
||||||
durum = {
|
kapiyiAc(
|
||||||
...durum,
|
{
|
||||||
acik: true,
|
duzenle: false,
|
||||||
duzenle: false,
|
dogrudanSira: false,
|
||||||
dogrudanSira: false,
|
bekleyen: { program, kaynak, adaySira, adayTur },
|
||||||
bekleyen: { program, kaynak, adaySira, adayTur },
|
bekleyenHedef: null,
|
||||||
bekleyenHedef: null,
|
},
|
||||||
};
|
"arti",
|
||||||
yayinla();
|
);
|
||||||
return { eklendi: false, zatenVar: false, doldu: false, kapiAcildi: true };
|
return { eklendi: false, zatenVar: false, doldu: false, kapiAcildi: true };
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -250,13 +280,21 @@ export function profilTamamlandi(
|
|||||||
const bekleyen = durum.bekleyen;
|
const bekleyen = durum.bekleyen;
|
||||||
const hedef = durum.bekleyenHedef;
|
const hedef = durum.bekleyenHedef;
|
||||||
if (bekleyen) {
|
if (bekleyen) {
|
||||||
ekle(programdanManuelTercih(bekleyen.program, profil.sira, bekleyen.kaynak));
|
const eklendi = ekle(
|
||||||
|
programdanManuelTercih(bekleyen.program, profil.sira, bekleyen.kaynak),
|
||||||
|
);
|
||||||
|
// Kapıdan geçen +: program ancak şimdi listeye girdi. Butondaki
|
||||||
|
// {yol: "dogrudan"} atışının eşi; liste doluysa/zaten varsa atılmaz.
|
||||||
|
if (eklendi) {
|
||||||
|
olay("program_eklendi", { kaynak: bekleyen.kaynak, yol: "kapi" });
|
||||||
|
}
|
||||||
}
|
}
|
||||||
durum = {
|
durum = {
|
||||||
...durum,
|
...durum,
|
||||||
acik: false,
|
acik: false,
|
||||||
duzenle: false,
|
duzenle: false,
|
||||||
dogrudanSira: false,
|
dogrudanSira: false,
|
||||||
|
tetik: "diger",
|
||||||
bekleyen: null,
|
bekleyen: null,
|
||||||
bekleyenHedef: null,
|
bekleyenHedef: null,
|
||||||
profil,
|
profil,
|
||||||
|
|||||||
@@ -20,6 +20,9 @@ export type OlayAdi =
|
|||||||
| "liste_olusturuldu"
|
| "liste_olusturuldu"
|
||||||
| "liste_guncellendi"
|
| "liste_guncellendi"
|
||||||
| "liste_revize"
|
| "liste_revize"
|
||||||
|
// Seri kırılması: bu düzeltmenin canlıya çıktığı tarihten önce = +
|
||||||
|
// tıklaması (kapı açılsa da atılıyordu), sonrası = gerçek ekleme
|
||||||
|
// ({yol: "dogrudan" | "kapi"}); seriler karşılaştırılamaz.
|
||||||
| "program_eklendi"
|
| "program_eklendi"
|
||||||
| "netler_goruntulendi"
|
| "netler_goruntulendi"
|
||||||
| "danisman_sorusu"
|
| "danisman_sorusu"
|
||||||
@@ -36,7 +39,12 @@ export type OlayAdi =
|
|||||||
| "sonuc_goruntulendi"
|
| "sonuc_goruntulendi"
|
||||||
| "paket_goruntulendi"
|
| "paket_goruntulendi"
|
||||||
| "liste_uretim_hata"
|
| "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>;
|
export type Ozellikler = Record<string, string | number>;
|
||||||
|
|
||||||
@@ -66,6 +74,10 @@ const DENEME_ARALIGI_MS = 500;
|
|||||||
const DENEME_SINIRI = 20;
|
const DENEME_SINIRI = 20;
|
||||||
|
|
||||||
let kuyruk: Array<[OlayAdi, Ozellikler | undefined]> = [];
|
let kuyruk: Array<[OlayAdi, Ozellikler | undefined]> = [];
|
||||||
|
// Script'ten önce gelen identify için tek "son değer" slotu: sert yüklemede
|
||||||
|
// (OAuth / e-posta linki dönüşü) RybbitIdentify effect'i de script'ten önce
|
||||||
|
// koşar. Kuyruk değil slot — yalnız en son kimlik anlamlıdır.
|
||||||
|
let bekleyenKimlik: [string, Record<string, string> | undefined] | null = null;
|
||||||
let zamanlayici: ReturnType<typeof setInterval> | null = null;
|
let zamanlayici: ReturnType<typeof setInterval> | null = null;
|
||||||
let denemeSayisi = 0;
|
let denemeSayisi = 0;
|
||||||
let kuyrukKapali = false;
|
let kuyrukKapali = false;
|
||||||
@@ -75,11 +87,21 @@ function zamanlayiciyiDurdur(): void {
|
|||||||
zamanlayici = null;
|
zamanlayici = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Bekleyen olayları geliş sırasıyla gönderir. */
|
/** Önce bekleyen kimliği uygular, sonra olayları geliş sırasıyla gönderir
|
||||||
|
* (kuyruktaki olaylar anonim profile değil tanımlı kullanıcıya yazılsın). */
|
||||||
function kuyruguBosalt(rybbit: RybbitApi): void {
|
function kuyruguBosalt(rybbit: RybbitApi): void {
|
||||||
const bekleyenler = kuyruk;
|
const bekleyenler = kuyruk;
|
||||||
|
const kimlik = bekleyenKimlik;
|
||||||
kuyruk = [];
|
kuyruk = [];
|
||||||
|
bekleyenKimlik = null;
|
||||||
zamanlayiciyiDurdur();
|
zamanlayiciyiDurdur();
|
||||||
|
if (kimlik) {
|
||||||
|
try {
|
||||||
|
rybbit.identify(kimlik[0], kimlik[1]);
|
||||||
|
} catch {
|
||||||
|
// yut
|
||||||
|
}
|
||||||
|
}
|
||||||
for (const [ad, ozellikler] of bekleyenler) {
|
for (const [ad, ozellikler] of bekleyenler) {
|
||||||
try {
|
try {
|
||||||
rybbit.event(ad, ozellikler);
|
rybbit.event(ad, ozellikler);
|
||||||
@@ -89,23 +111,29 @@ function kuyruguBosalt(rybbit: RybbitApi): void {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function kuyrugaEkle(ad: OlayAdi, ozellikler?: Ozellikler): void {
|
/** Script'i bekleyen yoklamayı başlatır (olay kuyruğu ve kimlik slotu ortak). */
|
||||||
if (kuyrukKapali || kuyruk.length >= KUYRUK_SINIRI) return;
|
function zamanlayiciyiBaslat(): void {
|
||||||
kuyruk.push([ad, ozellikler]);
|
|
||||||
if (zamanlayici !== null) return;
|
if (zamanlayici !== null) return;
|
||||||
zamanlayici = setInterval(() => {
|
zamanlayici = setInterval(() => {
|
||||||
denemeSayisi += 1;
|
denemeSayisi += 1;
|
||||||
if (window.rybbit) {
|
if (window.rybbit) {
|
||||||
kuyruguBosalt(window.rybbit);
|
kuyruguBosalt(window.rybbit);
|
||||||
} else if (denemeSayisi >= DENEME_SINIRI) {
|
} else if (denemeSayisi >= DENEME_SINIRI) {
|
||||||
// Script gelmedi: bekleyenleri at, sonraki olaylar eski no-op'a dönsün
|
// Script gelmedi: bekleyenleri at, sonraki çağrılar eski no-op'a dönsün
|
||||||
kuyruk = [];
|
kuyruk = [];
|
||||||
|
bekleyenKimlik = null;
|
||||||
kuyrukKapali = true;
|
kuyrukKapali = true;
|
||||||
zamanlayiciyiDurdur();
|
zamanlayiciyiDurdur();
|
||||||
}
|
}
|
||||||
}, DENEME_ARALIGI_MS);
|
}, DENEME_ARALIGI_MS);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function kuyrugaEkle(ad: OlayAdi, ozellikler?: Ozellikler): void {
|
||||||
|
if (kuyrukKapali || kuyruk.length >= KUYRUK_SINIRI) return;
|
||||||
|
kuyruk.push([ad, ozellikler]);
|
||||||
|
zamanlayiciyiBaslat();
|
||||||
|
}
|
||||||
|
|
||||||
/** Özel olay gönderir. Script henüz yoksa kısa süre kuyrukta bekletir. */
|
/** Özel olay gönderir. Script henüz yoksa kısa süre kuyrukta bekletir. */
|
||||||
export function olay(ad: OlayAdi, ozellikler?: Ozellikler): void {
|
export function olay(ad: OlayAdi, ozellikler?: Ozellikler): void {
|
||||||
if (typeof window === "undefined") return;
|
if (typeof window === "undefined") return;
|
||||||
@@ -114,8 +142,8 @@ export function olay(ad: OlayAdi, ozellikler?: Ozellikler): void {
|
|||||||
kuyrugaEkle(ad, ozellikler);
|
kuyrugaEkle(ad, ozellikler);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// Sıra bozulmasın: bekleyen varsa önce onlar gider
|
// Sıra bozulmasın: bekleyen (kimlik ya da olay) varsa önce onlar gider
|
||||||
if (kuyruk.length > 0) kuyruguBosalt(window.rybbit);
|
if (kuyruk.length > 0 || bekleyenKimlik) kuyruguBosalt(window.rybbit);
|
||||||
window.rybbit.event(ad, ozellikler);
|
window.rybbit.event(ad, ozellikler);
|
||||||
} catch {
|
} catch {
|
||||||
// Analitik hiçbir koşulda kullanıcı akışını bozmaz
|
// Analitik hiçbir koşulda kullanıcı akışını bozmaz
|
||||||
@@ -166,6 +194,8 @@ export function siraKovasi(sira: number): string {
|
|||||||
* Giriş yapan kullanıcıyı eşler. İlk çağrıda o cihazdaki önceki anonim
|
* Giriş yapan kullanıcıyı eşler. İlk çağrıda o cihazdaki önceki anonim
|
||||||
* olaylar geriye dönük olarak bu kullanıcıya bağlanır (sihirbazı doldurup
|
* olaylar geriye dönük olarak bu kullanıcıya bağlanır (sihirbazı doldurup
|
||||||
* sonra giriş yapan öğrencinin yolculuğu tek profilde birleşsin diye).
|
* sonra giriş yapan öğrencinin yolculuğu tek profilde birleşsin diye).
|
||||||
|
* Script henüz yoksa kimlik tek slotta bekler; kuyruk boşaltılırken
|
||||||
|
* olaylardan ÖNCE uygulanır.
|
||||||
*/
|
*/
|
||||||
export function kullaniciTanit(
|
export function kullaniciTanit(
|
||||||
kullaniciId: string,
|
kullaniciId: string,
|
||||||
@@ -173,15 +203,25 @@ export function kullaniciTanit(
|
|||||||
): void {
|
): void {
|
||||||
if (typeof window === "undefined") return;
|
if (typeof window === "undefined") return;
|
||||||
try {
|
try {
|
||||||
window.rybbit?.identify(kullaniciId, traits);
|
if (!window.rybbit) {
|
||||||
|
if (kuyrukKapali) return;
|
||||||
|
bekleyenKimlik = [kullaniciId, traits];
|
||||||
|
zamanlayiciyiBaslat();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// Doğrudan uygulanır; kuyrukta olay bekliyorsa sonraki boşaltmada bu
|
||||||
|
// kimlikle gider. Eski slot değeri geçersiz kalmasın diye silinir.
|
||||||
|
bekleyenKimlik = null;
|
||||||
|
window.rybbit.identify(kullaniciId, traits);
|
||||||
} catch {
|
} catch {
|
||||||
// yut
|
// yut
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Çıkışta kimliği temizler. */
|
/** Çıkışta kimliği temizler (bekleyen kimlik slotu dahil). */
|
||||||
export function kullaniciTemizle(): void {
|
export function kullaniciTemizle(): void {
|
||||||
if (typeof window === "undefined") return;
|
if (typeof window === "undefined") return;
|
||||||
|
bekleyenKimlik = null;
|
||||||
try {
|
try {
|
||||||
window.rybbit?.clearUserId();
|
window.rybbit?.clearUserId();
|
||||||
} catch {
|
} catch {
|
||||||
|
|||||||
Reference in New Issue
Block a user