diff --git a/src/app/paket/layout.tsx b/src/app/paket/layout.tsx new file mode 100644 index 0000000..7620f3e --- /dev/null +++ b/src/app/paket/layout.tsx @@ -0,0 +1,16 @@ +import { PaketGorunumOlayi } from "@/features/odeme/components/paket-gorunum-olayi"; + +// Yalnızca ölçüm için: sayfanın kendisine dokunmadan paket_goruntulendi +// olayını bağlar (bkz. paket-gorunum-olayi.tsx). +export default function PaketLayout({ + children, +}: { + children: React.ReactNode; +}) { + return ( + <> + {children} + + + ); +} diff --git a/src/features/kullanici/components/giris-form.tsx b/src/features/kullanici/components/giris-form.tsx index 2a14fe6..1510ece 100644 --- a/src/features/kullanici/components/giris-form.tsx +++ b/src/features/kullanici/components/giris-form.tsx @@ -59,6 +59,7 @@ export function GirisForm({ callbackURL }: { callbackURL: string }) { callbackURL, }); if (error) { + olay("giris_hata", { yontem: "google", status: error.status ?? 0 }); toast.error("Google ile giriş başarısız. Tekrar dener misin?"); setBusy(null); } @@ -81,6 +82,7 @@ export function GirisForm({ callbackURL }: { callbackURL: string }) { }); setBusy(null); if (error) { + olay("giris_hata", { yontem: "eposta", status: error.status ?? 0 }); toast.error("Bağlantı gönderilemedi. Tekrar dener misin?"); return; } diff --git a/src/features/kullanici/components/user-nav.tsx b/src/features/kullanici/components/user-nav.tsx index 5b43f6a..9a45188 100644 --- a/src/features/kullanici/components/user-nav.tsx +++ b/src/features/kullanici/components/user-nav.tsx @@ -48,7 +48,7 @@ export async function UserNav() { diff --git a/src/features/liste/components/tercih-profili-kapisi.tsx b/src/features/liste/components/tercih-profili-kapisi.tsx index d9ca13a..7085430 100644 --- a/src/features/liste/components/tercih-profili-kapisi.tsx +++ b/src/features/liste/components/tercih-profili-kapisi.tsx @@ -163,6 +163,7 @@ function KapıIcerik({ sira_kovasi: siraKovasi(siraDeger), }); } catch { + olay("sira_form_hata", { kaynak: duzenle ? "duzenle" : "kapi" }); setHata("Bir şeyler ters gitti, tekrar dener misin?"); } finally { setYukleniyor(false); diff --git a/src/features/odeme/components/paket-gorunum-olayi.tsx b/src/features/odeme/components/paket-gorunum-olayi.tsx new file mode 100644 index 0000000..8d9a172 --- /dev/null +++ b/src/features/odeme/components/paket-gorunum-olayi.tsx @@ -0,0 +1,25 @@ +"use client"; + +// /paket görüntülenmesini ve ziyaretçiyi oraya getiren yüzeyi ölçer. +// +// Neden ayrı bileşen + layout: /paket sayfası sunucu bileşeni ve statik +// kalmalı. `useSearchParams` kullanılmaz — cacheComponents açıkken Suspense +// sınırı/CSR bailout gerektirir; parametre yalnızca ölçüm için lazım, mount +// sonrası window.location'dan okumak yeter. + +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; + +export function PaketGorunumOlayi() { + useEffect(() => { + const ham = new URLSearchParams(window.location.search).get("kaynak"); + const kaynak = KAYNAKLAR.find((k) => k === ham) ?? "diger"; + // Oturum başına kaynak başına 1 kez: yenileme sayıyı şişirmesin + olayBirKez("paket_goruntulendi", { kaynak }, kaynak); + }, []); + + return null; +} diff --git a/src/features/rapor/components/liste-uretici.tsx b/src/features/rapor/components/liste-uretici.tsx index b5f36dc..c355e0b 100644 --- a/src/features/rapor/components/liste-uretici.tsx +++ b/src/features/rapor/components/liste-uretici.tsx @@ -84,9 +84,11 @@ export function ListeUretici({ } else if (sonuc.code === "AUTH") { router.push(`/giris?callback=${encodeURIComponent("/listem?uret=1")}`); } else { + olay("liste_uretim_hata", { kod: sonuc.code }); setDurum({ ad: "hata", mesaj: sonuc.error }); } } catch { + olay("liste_uretim_hata", { kod: "ISTISNA" }); setDurum({ ad: "hata", mesaj: "Beklenmeyen bir hata oluştu, tekrar dener misin?", @@ -131,7 +133,7 @@ export function ListeUretici({ size="lg" className="mt-6 cursor-pointer bg-orange-500 text-white transition-colors duration-200 hover:bg-orange-600" > - + {hasPaket ? "+30 kredi yükle — 129 TL" : "Paketi aktive et — 299 TL"} diff --git a/src/features/rapor/components/listem-govde.tsx b/src/features/rapor/components/listem-govde.tsx index 026a86c..28feb08 100644 --- a/src/features/rapor/components/listem-govde.tsx +++ b/src/features/rapor/components/listem-govde.tsx @@ -285,7 +285,7 @@ export function ListemGovde({ asChild className="h-11 w-full shrink-0 cursor-pointer bg-orange-500 text-white transition-[background-color,transform] duration-200 active:scale-[0.97] hover:bg-orange-600 sm:w-auto" > - + Analizleri aç — {paketFiyati} TL @@ -366,7 +366,7 @@ export function ListemGovde({ size="lg" className="mt-5 w-full cursor-pointer bg-orange-500 text-white transition-[background-color,transform] duration-200 active:scale-[0.97] hover:bg-orange-600" > - + Kişisel analizi aç diff --git a/src/features/sihirbaz/components/cta-sira-form.tsx b/src/features/sihirbaz/components/cta-sira-form.tsx index d35a757..6003c8f 100644 --- a/src/features/sihirbaz/components/cta-sira-form.tsx +++ b/src/features/sihirbaz/components/cta-sira-form.tsx @@ -21,6 +21,7 @@ import { DrawerTitle, } from "@/components/ui/drawer"; import { SectionEyebrow } from "@/components/pixel-decor"; +import { useGorunumOlayi } from "@/components/use-gorunum-olayi"; import { SihirbazAdimlarLazy, preloadSihirbazAdimlar, @@ -73,6 +74,10 @@ export function CtaSiraForm({ baslik }: { baslik?: string }) { const [yukleniyor, setYukleniyor] = useState(false); const [hata, setHata] = useState(null); const [girisli, setGirisli] = useState(false); + // Huni girişi ölçümü: ref yalnızca profilsiz daldaki karta bağlanır; kayıtlı + // profilli ziyaretçi formu hiç görmediğinden sayılmaz. Sayfa ayrımı için + // özellik gerekmez — Rybbit olayın pathname'ini zaten yazar. + const formRef = useGorunumOlayi("cta_form_goruntulendi"); // Yazarken binlik ayracıyla biçimle (85000 → 85.000) function siralamaDegisti(ham: string) { @@ -108,6 +113,7 @@ export function CtaSiraForm({ baslik }: { baslik?: string }) { sira_kovasi: siraKovasi(value), }); } catch { + olay("sira_form_hata", { kaynak: "cta" }); setHata("Bir şeyler ters gitti, tekrar dener misin?"); } finally { setYukleniyor(false); @@ -159,7 +165,10 @@ export function CtaSiraForm({ baslik }: { baslik?: string }) { } return ( -
+

{baslik ?? "Sıralamanla nereye yerleşirsin? Saniyede gör."}

diff --git a/src/features/sihirbaz/components/hero-form.tsx b/src/features/sihirbaz/components/hero-form.tsx index 484746b..ec4f94e 100644 --- a/src/features/sihirbaz/components/hero-form.tsx +++ b/src/features/sihirbaz/components/hero-form.tsx @@ -85,6 +85,7 @@ export function HeroForm() { sira_kovasi: siraKovasi(value), }); } catch { + olay("sira_form_hata", { kaynak: "hero" }); setHata("Bir şeyler ters gitti, tekrar dener misin?"); } finally { setYukleniyor(false); diff --git a/src/features/sihirbaz/components/sihirbaz-cagri-karti.tsx b/src/features/sihirbaz/components/sihirbaz-cagri-karti.tsx index 2568f83..d5a3f90 100644 --- a/src/features/sihirbaz/components/sihirbaz-cagri-karti.tsx +++ b/src/features/sihirbaz/components/sihirbaz-cagri-karti.tsx @@ -9,7 +9,7 @@ import { useReducedMotion } from "motion/react"; import { Button } from "@/components/ui/button"; import { PixelField } from "@/components/pixel-decor"; import { useGorunumOlayi } from "@/components/use-gorunum-olayi"; -import { olay } from "@/lib/analitik"; +import { olay, olayBirKez, siraKovasi } from "@/lib/analitik"; import type { SihirbazFacetleri } from "@/types/yokatlas"; import { SIHIRBAZ_AC_EVENT, SIHIRBAZ_STORAGE_KEY, sihirbazDogrula, type SihirbazSecimleri } from "@/features/sihirbaz/sihirbaz-sabitler"; import { tercihProfiliYaz } from "@/features/sihirbaz/sihirbaz-profil"; @@ -82,6 +82,22 @@ export function SihirbazBolumu({ // görsün diye ref'te tutulur; olay dinleyicisi bir kez bağlanır. const bekleyenRef = useRef(null); + // Huni adımı: sonuç sayfası görüntülendi. URL sıralamayı taşıdığı için + // pathname maskeli (bkz. app/layout.tsx); huni bu olayla kurulur. Oturum + // başına girişli/anonim için birer kez — girişten dönen ziyaretçi ikinci + // kez "girisli" olarak sayılır. Ham sıra gönderilmez, yalnızca kovası. + useEffect(() => { + olayBirKez( + "sonuc_goruntulendi", + { + girisli: girisliMi ? "evet" : "hayir", + sira_kovasi: siraKovasi(sira), + }, + girisliMi ? "girisli" : "anonim", + ); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + // Mount sonrası storage'dan bekleyen seçimleri oku useEffect(() => { try { diff --git a/src/lib/analitik.ts b/src/lib/analitik.ts index c60c552..b7dedb5 100644 --- a/src/lib/analitik.ts +++ b/src/lib/analitik.ts @@ -30,7 +30,13 @@ export type OlayAdi = | "kilit_goruntulendi" | "kredi_bitti" | "tadimlik_goruntulendi" - | "kaydet_banner_goruntulendi"; + | "kaydet_banner_goruntulendi" + | "cta_form_goruntulendi" + | "sira_form_hata" + | "sonuc_goruntulendi" + | "paket_goruntulendi" + | "liste_uretim_hata" + | "giris_hata"; export type Ozellikler = Record;