merge(olcum/liste-uretim-basladi): feat(olcum): /listem?uret=1 yollarına kaynak parametresi
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -20,12 +20,18 @@ export const metadata: Metadata = {
|
||||
export default function ListemPage({
|
||||
searchParams,
|
||||
}: {
|
||||
searchParams: Promise<{ acildi?: string; uret?: string }>;
|
||||
// kaynak: üretim denemesini hangi yüzeyin başlattığı (bkz.
|
||||
// uretim-basladi-olayi.tsx). Doğrulaması okuyan tarafta, allowlist ile.
|
||||
searchParams: Promise<{ acildi?: string; uret?: string; kaynak?: string }>;
|
||||
}) {
|
||||
return (
|
||||
<Suspense fallback={<ListemIcerikSkeleton />}>
|
||||
{searchParams.then(({ acildi, uret }) => (
|
||||
<ListemIcerik uret={uret === "1"} acildi={acildi === "1"} />
|
||||
{searchParams.then(({ acildi, uret, kaynak }) => (
|
||||
<ListemIcerik
|
||||
uret={uret === "1"}
|
||||
acildi={acildi === "1"}
|
||||
kaynak={kaynak}
|
||||
/>
|
||||
))}
|
||||
</Suspense>
|
||||
);
|
||||
|
||||
@@ -154,7 +154,7 @@ async function PaketCta() {
|
||||
{/* ?uret=1: rapor varsa /listem raporu gösterir (bayrak yok
|
||||
sayılır), yoksa bekleyen seçimlerle üretim başlar; seçim
|
||||
de yoksa boş durum sihirbaza yönlendirir. */}
|
||||
<Link href="/listem?uret=1" className="underline">
|
||||
<Link href="/listem?uret=1&kaynak=odeme_sonrasi" className="underline">
|
||||
listeni oluştur
|
||||
</Link>
|
||||
</div>
|
||||
|
||||
@@ -20,6 +20,7 @@ import type { Mesaj } from "./sohbet-client";
|
||||
import { ListemGovde } from "./listem-govde";
|
||||
import { ListemBosCta } from "./listem-bos-cta";
|
||||
import { ListeUretici } from "./liste-uretici";
|
||||
import { UretimBasladiOlayi } from "./uretim-basladi-olayi";
|
||||
|
||||
const PAKET_FIYATI = (URUNLER.paket.amountKurus / 100).toLocaleString("tr-TR", {
|
||||
maximumFractionDigits: 0,
|
||||
@@ -28,9 +29,13 @@ const PAKET_FIYATI = (URUNLER.paket.amountKurus / 100).toLocaleString("tr-TR", {
|
||||
export async function ListemIcerik({
|
||||
uret,
|
||||
acildi,
|
||||
kaynak,
|
||||
}: {
|
||||
uret: boolean;
|
||||
acildi: boolean;
|
||||
/** Üretim denemesini başlatan yüzey (`?kaynak=`); doğrulaması
|
||||
* UretimBasladiOlayi'nın allowlist'inde. */
|
||||
kaynak?: string;
|
||||
}) {
|
||||
// Tercihleri girili olan (profil çerezi duran) ziyaretçi girişten dönünce
|
||||
// "Henüz listen yok" boşluğuyla karşılaşmasın: callback ?uret=1 taşır,
|
||||
@@ -39,7 +44,14 @@ export async function ListemIcerik({
|
||||
const cerezProfili = cerezdenTercihProfili(
|
||||
(await cookies()).get(PROFIL_CEREZ_ADI)?.value,
|
||||
);
|
||||
const geriYol = uret || cerezProfili ? "/listem?uret=1" : "/listem";
|
||||
// `kaynak` giriş round-trip'inde korunur; yoksa girişten dönen herkes
|
||||
// "bilinmiyor" okunurdu. `uret=1` HER ZAMAN ilk parametre kalmalı:
|
||||
// giris-karti.tsx:31 funnel metnini `startsWith("/listem?uret=1")` ile
|
||||
// seçiyor, sıra bozulursa "Seçimlerin hazır" metni kaybolur.
|
||||
const geriYol =
|
||||
uret || cerezProfili
|
||||
? `/listem?uret=1${kaynak ? `&kaynak=${encodeURIComponent(kaynak)}` : ""}`
|
||||
: "/listem";
|
||||
const session = await verifySession(geriYol);
|
||||
const [user, satir, sohbetGecmisi] = await Promise.all([
|
||||
getCurrentUser(),
|
||||
@@ -71,11 +83,20 @@ export async function ListemIcerik({
|
||||
{/* ?uret=1: sihirbazdan yönlendirildi — bekleyen seçimlerle üretim
|
||||
burada koşar, animasyon ve hata/kredi durumları burada yaşar. */}
|
||||
{uret ? (
|
||||
<ListeUretici
|
||||
hasPaket={Boolean(user.hasPaket)}
|
||||
sunucuProfil={cerezProfili}
|
||||
bosDurum={bosDurum}
|
||||
/>
|
||||
<>
|
||||
{/* Denemenin paydası: üretim gövdesiyle kardeş, aynı koşulda
|
||||
mount olur (bkz. uretim-basladi-olayi.tsx). */}
|
||||
<UretimBasladiOlayi
|
||||
kaynak={kaynak ?? "bilinmiyor"}
|
||||
paketli={Boolean(user.hasPaket)}
|
||||
sunucuProfil={cerezProfili}
|
||||
/>
|
||||
<ListeUretici
|
||||
hasPaket={Boolean(user.hasPaket)}
|
||||
sunucuProfil={cerezProfili}
|
||||
bosDurum={bosDurum}
|
||||
/>
|
||||
</>
|
||||
) : (
|
||||
bosDurum
|
||||
)}
|
||||
|
||||
70
src/features/rapor/components/uretim-basladi-olayi.tsx
Normal file
70
src/features/rapor/components/uretim-basladi-olayi.tsx
Normal file
@@ -0,0 +1,70 @@
|
||||
"use client";
|
||||
|
||||
// Üretim denemesinin paydasını ölçen görünmez bileşen: /listem?uret=1 ile
|
||||
// üretim gövdesi kurulduğunda `liste_uretim_basladi` atar. Bugüne kadar
|
||||
// yalnız başarı (`liste_olusturuldu`) sayıldığı için "kaç kişi denedi, kaçı
|
||||
// listeye ulaştı" sorusunun paydası yoktu.
|
||||
//
|
||||
// Olay bilerek üretimi başlatan `liste-uretici.tsx`'te değil, onun kardeşi
|
||||
// olan bu dosyada duruyor (o dosya bu turda başka bir işin altında). İkisi de
|
||||
// `listem-icerik.tsx`'te aynı `uret` dalında mount edildiğinden koşulları
|
||||
// birebir aynı: biri mount oluyorsa diğeri de oluyor.
|
||||
|
||||
import { useEffect, useRef } from "react";
|
||||
import { olay } from "@/lib/analitik";
|
||||
import type { TercihProfili } from "@/features/sihirbaz/sihirbaz-sabitler";
|
||||
import { tercihProfiliOku } from "@/features/sihirbaz/sihirbaz-profil";
|
||||
|
||||
/**
|
||||
* `?kaynak=` sözleşmesinin izinli değerleri. Değer URL'den, yani kullanıcının
|
||||
* denetiminden geliyor: allowlist olmadan Rybbit'e keyfi dizgi yazılabilir
|
||||
* (kardinalite patlaması, panele enjekte edilen metin). Listede olmayan her
|
||||
* şey `"bilinmiyor"`a düşer. Yeni bir üretim yüzeyi eklerken değerini önce
|
||||
* buraya ekle, sonra href'e koy.
|
||||
*/
|
||||
const IZINLI_KAYNAKLAR = new Set([
|
||||
"sihirbaz",
|
||||
"hazir_cta",
|
||||
"giris_donusu",
|
||||
"bos_durum",
|
||||
"odeme_sonrasi",
|
||||
"bilinmiyor",
|
||||
]);
|
||||
|
||||
export function UretimBasladiOlayi({
|
||||
kaynak,
|
||||
paketli,
|
||||
sunucuProfil,
|
||||
}: {
|
||||
/** `?kaynak=` ham değeri; allowlist dışındaysa "bilinmiyor" raporlanır. */
|
||||
kaynak: string;
|
||||
paketli: boolean;
|
||||
/** Profil çerezinden SSR'da çözülen yedek profil (bkz. listem-icerik). */
|
||||
sunucuProfil: TercihProfili | null;
|
||||
}) {
|
||||
// Mount başına tek atış. StrictMode geliştirmede effect'i iki kez koşuyor;
|
||||
// `liste-uretici.tsx`'teki `basladi` ref'iyle aynı desen. `olayBirKez`
|
||||
// BİLEREK kullanılmıyor: o oturum başına tekilleştirir, oysa aynı oturumda
|
||||
// ikinci deneme de paydaya girmeli.
|
||||
const atildi = useRef(false);
|
||||
|
||||
useEffect(() => {
|
||||
if (atildi.current) return;
|
||||
atildi.current = true;
|
||||
const kaynakGuvenli = IZINLI_KAYNAKLAR.has(kaynak) ? kaynak : "bilinmiyor";
|
||||
olay("liste_uretim_basladi", {
|
||||
kaynak: kaynakGuvenli,
|
||||
paketli: paketli ? "evet" : "hayir",
|
||||
// Üretimin gerçekten başlayabilmesinin koşulu: bu ifade
|
||||
// `liste-uretici.tsx`'teki `tercihProfiliOku() ?? sunucuProfil`
|
||||
// ifadesinin ikizidir — ikisi ayrı dosyada olduğu için elle eşit
|
||||
// tutulmalı. O dosya açıldığında (bkz. backlog: olayın gerçek çağrı
|
||||
// yerine taşınması) tek yere alınacak. Ham sıralama/seçim GÖNDERİLMEZ
|
||||
// (bkz. lib/analitik.ts kuralı) — yalnız profilin varlığı.
|
||||
profil: (tercihProfiliOku() ?? sunucuProfil) ? "var" : "yok",
|
||||
});
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
|
||||
return null;
|
||||
}
|
||||
@@ -17,7 +17,14 @@ import { SihirbazModal } from "./sihirbaz-modal";
|
||||
|
||||
// Üretim /listem'de koşar (bkz. listem/liste-uretici.tsx); sihirbaz
|
||||
// tamamlanınca seçimler storage'a yazılıp kullanıcı buraya yönlendirilir.
|
||||
const LISTEM_URET_HREF = "/listem?uret=1";
|
||||
// `kaynak` denemenin hangi yüzeyden başladığını ölçer (bkz.
|
||||
// rapor/components/uretim-basladi-olayi.tsx — izinli değerler orada).
|
||||
// `uret=1` HER ZAMAN ilk parametre kalmalı: giris-karti.tsx:31 funnel
|
||||
// metnini `callbackURL.startsWith("/listem?uret=1")` ile seçiyor, sıra
|
||||
// bozulursa girişteki "Seçimlerin hazır" metni kaybolur.
|
||||
function listemUretHref(kaynak: string) {
|
||||
return `/listem?uret=1&kaynak=${kaynak}`;
|
||||
}
|
||||
|
||||
// SSR'da false, client'ta true — storage'a ancak mount sonrası dokunuruz
|
||||
const bosAbone = () => () => {};
|
||||
@@ -182,10 +189,12 @@ export function SihirbazBolumu({
|
||||
await modalCikisiniOynat();
|
||||
setElleAcik(false);
|
||||
if (!girisliMi) {
|
||||
router.push(`/giris?callback=${encodeURIComponent(LISTEM_URET_HREF)}`);
|
||||
router.push(
|
||||
`/giris?callback=${encodeURIComponent(listemUretHref("sihirbaz"))}`,
|
||||
);
|
||||
return;
|
||||
}
|
||||
router.push(LISTEM_URET_HREF);
|
||||
router.push(listemUretHref("sihirbaz"));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -213,7 +222,7 @@ export function SihirbazBolumu({
|
||||
if (otomatikGonder && !otomatikBasladi.current) {
|
||||
otomatikBasladi.current = true;
|
||||
// replace: geri tuşu bu sayfaya dönünce tekrar /listem'e sekmesin
|
||||
router.replace(LISTEM_URET_HREF);
|
||||
router.replace(listemUretHref("giris_donusu"));
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [otomatikGonder]);
|
||||
@@ -242,7 +251,7 @@ export function SihirbazBolumu({
|
||||
// kaydedilmesi olarak sunulur. Girişten dönüşte üretim /listem'de başlar.
|
||||
// Sinyal URL'den (?hazir=1) gelir ki SSR'da doğru panel render edilsin.
|
||||
const girisBekliyor = Boolean(!girisliMi && secimlerHazir && !raporVar);
|
||||
const girisUrl = `/giris?callback=${encodeURIComponent(LISTEM_URET_HREF)}`;
|
||||
const girisUrl = `/giris?callback=${encodeURIComponent(listemUretHref("hazir_cta"))}`;
|
||||
// Kapı 1 ölçümü: panel yalnızca girisBekliyor dalında render edilir; ref
|
||||
// başka dalda bağlanmadığından observer da yalnızca o durumda kurulur.
|
||||
const hazirPanelRef = useGorunumOlayi<HTMLElement>("hazir_panel_goruntulendi");
|
||||
|
||||
@@ -17,6 +17,12 @@ export type OlayAdi =
|
||||
| "sihirbaz_adim"
|
||||
| "sihirbaz_tamamlandi"
|
||||
| "giris_denendi"
|
||||
// Üretim denemesinin paydası: /listem?uret=1 ile üretim gövdesi mount
|
||||
// olduğunda atılır (sihirbaz, hazır panel CTA'sı, giriş dönüşü, boş durum
|
||||
// CTA'sı ve ödeme sonrası kartı — hangisi olduğu `kaynak` özelliğinde).
|
||||
// Bilerek `olayBirKez` KULLANILMAZ: aynı oturumdaki ikinci deneme de
|
||||
// sayılmalı, yoksa payda başarıdan (liste_olusturuldu) küçük kalır.
|
||||
| "liste_uretim_basladi"
|
||||
| "liste_olusturuldu"
|
||||
| "liste_guncellendi"
|
||||
| "liste_revize"
|
||||
|
||||
Reference in New Issue
Block a user