feat(olcum): sihirbazın 1. adımı ve kaynaksız /paket linkleri ölçülür

(a) `sihirbaz_adim{adim:1}` artık sihirbaz modalı açıldığında atılıyor.
Bugüne kadar olay yalnız `adimaGit`in ileri geçişinde atılıyordu; modal
zaten 1. adımda açıldığı için 1. adım hiç "geçilmiyor", dolayısıyla hiç
sayılmıyordu ve huninin paydası `sira_girildi`den türetilmek zorunda
kalıyordu. Mount effect'i + `olayBirKez` ile tam bir kez düşüyor:
1→2→1→2 gezinmesi bileşeni unmount etmediği için tekrar tetiklemiyor,
sessionStorage tekilleştirmesi de aynı sekmede ikinci açılışı sayıyor.

SERİ KIRILMASI: bu değişikliğin canlıya çıktığı tarihten ÖNCEKİ
`sihirbaz_adim` serisinin paydası farklıdır — o dönemde `adim:1` hiç
atılmadı, seri yalnız 2 ve 3'ten oluşuyor. Öncesi ve sonrası
karşılaştırılamaz; huni okumalarında kırılma tarihi dipnot edilmeli.
Aynı desen `program_eklendi` için 21 Eylül'de uygulanmıştı.

(b) Kaynaksız 10 `/paket` linkine tekil `?kaynak=` eklendi; sitedeki 14
`/paket` linkinin 14'ünde artık kaynak var. Değerler mevcut şemayla
(`kilit`, `kredi_bitti`, `nav`) aynı dilde: fiyat_paket, fiyat_kredi,
kosullar, tercih_degisti, tercih_degisti_yetersiz, danisman_rozet,
danisman_kredi_bitti, danisman_kredi_az, odeme_geri, odeme_hata.

`paket-gorunum-olayi.tsx`'teki `KAYNAKLAR` allowlist'i de genişletildi:
listede olmayan her değer `"diger"`e düştüğü için, genişletilmeseydi 10
yeni kaynağın tamamı tek kovada toplanır ve ölçüm deliği kapanmazdı
(bağımlılık kapanışı — dosya şartnamenin listesinde yoktu, raporda
sapma olarak yazıldı).

Görsel değişiklik yok; yeni olay adı ya da yeni ölçüm yüzeyi eklenmedi.
Doğrulama: `next typegen` + `tsc --noEmit` + `lint` temiz.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
bilalgursen
2026-09-23 00:17:21 +03:00
parent 9bd448bd16
commit 26893ce7cf
9 changed files with 125 additions and 13 deletions

View File

@@ -42,7 +42,7 @@ export function OdemeUstSerit() {
</span>
</div>
<Link
href="/paket"
href="/paket?kaynak=odeme_geri"
className="inline-flex shrink-0 items-center gap-1.5 text-sm text-slate-500 transition-colors hover:text-slate-700"
>
<ArrowLeft className="size-4" aria-hidden />

View File

@@ -138,7 +138,7 @@ export async function OdemeSonucKarti({ siparis }: { siparis?: string }) {
</p>
<PagePixelDivider seed={83} className="mx-auto mt-5" />
<Button asChild variant="outline" className="mt-6 h-11 cursor-pointer">
<Link href="/paket">Tekrar dene</Link>
<Link href="/paket?kaynak=odeme_hata">Tekrar dene</Link>
</Button>
</div>
);

View File

@@ -11,7 +11,32 @@ import { useEffect } from "react";
import { olayBirKez } from "@/lib/analitik";
// Serbest metin analitiğe sızmasın: bilinmeyen değerler "diger"e düşer.
const KAYNAKLAR = ["kilit", "kredi_bitti", "nav"] as const;
// Yeni bir /paket linki eklerken değerini önce buraya yaz, sonra href'e koy —
// listede olmayan her kaynak "diger"e düşer ve yüzey görünmez kalır.
const KAYNAKLAR = [
// Kilitli liste satırı / kilit kartı (`listem-govde`)
"kilit",
// Üretimde kredi yetmedi (`liste-uretici`)
"kredi_bitti",
// Üst menü (`user-nav`)
"nav",
// Ana sayfa fiyat bölümü: paket kartı ve kredi (topup) kartı
"fiyat_paket",
"fiyat_kredi",
// Koşullar metnindeki "paket sayfasında" linki
"kosullar",
// "Tercihlerin değişmiş" modalı: aksiyon satırı / kredi-yetersiz satırı
"tercih_degisti",
"tercih_degisti_yetersiz",
// Danışman sohbeti: başlıktaki kredi rozeti, kredi bitti bandı, son krediler
// uyarısı
"danisman_rozet",
"danisman_kredi_bitti",
"danisman_kredi_az",
// Ödeme akışı: checkout şeridindeki geri linki, başarısız sonuç kartı
"odeme_geri",
"odeme_hata",
] as const;
export function PaketGorunumOlayi() {
useEffect(() => {

View File

@@ -348,7 +348,7 @@ export function SohbetClient({
</button>
) : null}
<Link
href="/paket"
href="/paket?kaynak=danisman_rozet"
className="inline-flex items-center gap-1.5 rounded-full border border-amber-200 bg-amber-50 px-2.5 py-1 text-xs font-semibold text-amber-700 transition-colors hover:bg-amber-100"
title="Kredilerin — yüklemek için tıkla"
>
@@ -535,7 +535,7 @@ export function SohbetClient({
size="sm"
className="cursor-pointer bg-orange-500 text-white hover:bg-orange-600"
>
<Link href="/paket">Kredi yükle</Link>
<Link href="/paket?kaynak=danisman_kredi_bitti">Kredi yükle</Link>
</Button>
</div>
) : (
@@ -543,7 +543,7 @@ export function SohbetClient({
{kredi <= 3 ? (
<p className="mb-2 px-1 text-xs text-amber-700">
Son {kredi} kredin — bitse de listen ve sohbetin kaybolmaz;{" "}
<Link href="/paket" className="underline hover:text-amber-800">
<Link href="/paket?kaynak=danisman_kredi_az" className="underline hover:text-amber-800">
kredi yükleyip
</Link>{" "}
kaldığın yerden devam edersin.

View File

@@ -280,7 +280,7 @@ export function TercihDegistiModali({
asChild
className="cursor-pointer bg-orange-500 text-white transition-[background-color,transform] duration-200 active:scale-[0.97] hover:bg-orange-600"
>
<Link href="/paket">
<Link href="/paket?kaynak=tercih_degisti">
{hasPaket
? "Kredi yükle ve güncelle"
: "Paketi aktive et"}
@@ -332,7 +332,7 @@ function KrediYetersiz({
asChild
className="cursor-pointer bg-orange-500 text-white transition-[background-color,transform] duration-200 active:scale-[0.97] hover:bg-orange-600"
>
<Link href="/paket">
<Link href="/paket?kaynak=tercih_degisti_yetersiz">
{hasPaket ? "Kredi yükle" : "Paketi aktive et"}
<ArrowRight className="size-4" aria-hidden />
</Link>

View File

@@ -18,7 +18,7 @@ import { Button } from "@/components/ui/button";
import { IlSecimHaritasi } from "./il-secim-haritasi";
import { normalizeIlAdi } from "@/lib/harita";
import { onbellekliJsonGetir } from "@/lib/istek-cache";
import { olay } from "@/lib/analitik";
import { olay, olayBirKez } from "@/lib/analitik";
import { trBaslikDuzeni } from "@/lib/slug";
import type { SihirbazFacetleri } from "@/types/yokatlas";
import { ONCELIKLER, UNIVERSITE_TIPI_ETIKET, type SihirbazSecimleri, type UniversiteTipi } from "@/features/sihirbaz/sihirbaz-sabitler";
@@ -141,6 +141,20 @@ export function SihirbazAdimlar({
() => canliFacetler.iller.filter((i) => HARITA_ILLERI.has(i.il)),
[canliFacetler.iller],
);
// Huninin paydası: 1. adım hiçbir zaman "geçilmediği" için `adimaGit`ten
// atılamıyordu (orası yalnız ileri geçişleri sayar) ve `sihirbaz_adim{1}`
// hiç düşmüyordu — payda `sira_girildi`den türetilmek zorunda kalıyordu.
// Modal kapanınca bileşen unmount olduğu için mount = "1. adım görüldü";
// 1→2→1→2 gidip gelmek unmount etmediğinden tekrar tetiklenmez.
// `olayBirKez` oturum başına da tekilleştirir: aynı sekmede sihirbazı
// ikinci kez açmak paydayı şişirmez (bkz. lib/analitik.ts).
//
// SERİ KIRILMASI: bu satırın canlıya çıktığı tarihten önce `sihirbaz_adim`
// serisinde `adim:1` hiç yok; öncesi/sonrası karşılaştırılamaz.
useEffect(() => {
olayBirKez("sihirbaz_adim", { adim: 1 }, "adim1");
}, []);
const [facetYukleniyor, setFacetYukleniyor] = useState(false);
useEffect(() => {
if (adim === 0) return;
@@ -201,7 +215,8 @@ export function SihirbazAdimlar({
setYon(yeniAdim > adim ? 1 : -1);
setGecisBasladi(true);
setAdim(yeniAdim);
// Yalnızca ileri geçişler huni adımı sayılır; geri dönüşler tekrar saymasın
// Yalnızca ileri geçişler huni adımı sayılır; geri dönüşler tekrar saymasın.
// `adim:1` buradan hiç geçmez — onu yukarıdaki mount effect'i atar.
if (yeniAdim > adim) olay("sihirbaz_adim", { adim: yeniAdim + 1 });
}