fix(paket): girişsiz ziyaretçide boş sayfa — kart gövdesi statik kabukta, yalnız CTA dinamik
Kök neden: React Flight ~3,2 KB'ı aşan dinamik parçayı ayrı satırlara bölüyor; PPR resume bunlara verdiği segment kimliğini kabukta geç tamamlanan sınırın (kök loading.tsx) kimliğiyle çakıştırıyor (S:3 iki kez) → HierarchyRequestError. Lokal prod build'de yeniden üretildi, düzeltmeden sonra 5/5 temiz. Girişsiz CTA metni: "ilk 3 tercihini ücretsiz gör" (sayılar sabitlerden). Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
@@ -4,10 +4,7 @@ import type { Metadata } from "next";
|
|||||||
import { SectionEyebrow } from "@/components/pixel-decor";
|
import { SectionEyebrow } from "@/components/pixel-decor";
|
||||||
import { Parallax } from "@/components/parallax";
|
import { Parallax } from "@/components/parallax";
|
||||||
import { SiteFooter } from "@/components/site-footer";
|
import { SiteFooter } from "@/components/site-footer";
|
||||||
import {
|
import { PaketSatinAl } from "@/features/odeme/components/paket-satinal";
|
||||||
PaketSatinAl,
|
|
||||||
PaketSatinAlSkeleton,
|
|
||||||
} from "@/features/odeme/components/paket-satinal";
|
|
||||||
import { sayfaMetadata } from "@/lib/seo";
|
import { sayfaMetadata } from "@/lib/seo";
|
||||||
|
|
||||||
export const metadata: Metadata = sayfaMetadata({
|
export const metadata: Metadata = sayfaMetadata({
|
||||||
@@ -57,9 +54,9 @@ export default function PaketPage({
|
|||||||
</p>
|
</p>
|
||||||
</Parallax>
|
</Parallax>
|
||||||
|
|
||||||
<Suspense fallback={<PaketSatinAlSkeleton />}>
|
{/* Kart gövdesi statik kabukta; yalnız CTA alanları içeride kendi
|
||||||
|
Suspense sınırlarıyla istek anında gelir (bkz. paket-satinal.tsx). */}
|
||||||
<PaketSatinAl />
|
<PaketSatinAl />
|
||||||
</Suspense>
|
|
||||||
|
|
||||||
<p className="mt-10 text-center text-xs leading-relaxed text-slate-500">
|
<p className="mt-10 text-center text-xs leading-relaxed text-slate-500">
|
||||||
KolayTercih bir karar destek aracıdır; öneriler resmî YÖK Atlas
|
KolayTercih bir karar destek aracıdır; öneriler resmî YÖK Atlas
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import { Suspense } from "react";
|
||||||
import Image from "next/image";
|
import Image from "next/image";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import {
|
import {
|
||||||
@@ -11,7 +12,8 @@ import {
|
|||||||
ShieldCheck,
|
ShieldCheck,
|
||||||
} from "lucide-react";
|
} from "lucide-react";
|
||||||
import { getCurrentUser } from "@/lib/session";
|
import { getCurrentUser } from "@/lib/session";
|
||||||
import { URUNLER, DENEME_KREDISI } from "@/lib/credits";
|
import { URUNLER, DENEME_KREDISI, RAPOR_KREDI } from "@/lib/credits";
|
||||||
|
import { ACIK_SATIR } from "@/lib/rapor-maske";
|
||||||
import { VERI_YIL_SAYISI } from "@/lib/veri-kapsami";
|
import { VERI_YIL_SAYISI } from "@/lib/veri-kapsami";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import { Parallax } from "@/components/parallax";
|
import { Parallax } from "@/components/parallax";
|
||||||
@@ -39,11 +41,17 @@ const OZELLIKLER = [
|
|||||||
},
|
},
|
||||||
] as const;
|
] as const;
|
||||||
|
|
||||||
// Paket + top-up kartları: kullanıcının paket/kredi durumuna göre CTA'lar
|
// Paket + top-up kartları. Kartların gövdesi (başlık, fiyat, özellikler,
|
||||||
// değişir, okuma burada yaşar.
|
// iyzico bandı) STATİK kabukta kalır; kullanıcıya bağlı olan yalnızca iki küçük
|
||||||
export async function PaketSatinAl() {
|
// CTA alanıdır (PaketCta, TopupCta) ve her biri kendi Suspense sınırındadır.
|
||||||
const u = await getCurrentUser();
|
//
|
||||||
|
// Neden bu kadar küçük parçalar: React Flight ~3,2 KB'ı aşan bir dinamik
|
||||||
|
// parçanın devamını ayrı satırlara böler; PPR resume bunlara yeni segment
|
||||||
|
// kimliği verirken kabukta geç tamamlanan sınırın (kök loading.tsx) kimliğiyle
|
||||||
|
// çakışıyor (Next 16.2 / React 19.2) ve tarayıcıda HierarchyRequestError ile
|
||||||
|
// sayfa boş kalıyordu. Dinamik parçayı bu sınırın altında tut: kartın gövdesini
|
||||||
|
// yeniden bu async bileşenlerin içine TAŞIMA.
|
||||||
|
export function PaketSatinAl() {
|
||||||
return (
|
return (
|
||||||
<Parallax
|
<Parallax
|
||||||
strength={6}
|
strength={6}
|
||||||
@@ -71,44 +79,9 @@ export async function PaketSatinAl() {
|
|||||||
))}
|
))}
|
||||||
</ul>
|
</ul>
|
||||||
<div className="mt-8">
|
<div className="mt-8">
|
||||||
{u ? (
|
<Suspense fallback={<Skeleton className="h-12 w-full rounded-full" />}>
|
||||||
u.hasPaket ? (
|
<PaketCta />
|
||||||
<div className="flex items-center gap-2 rounded-xl border border-emerald-200 bg-emerald-50 px-4 py-3 text-sm font-medium text-emerald-800">
|
</Suspense>
|
||||||
<CheckCircle2 className="size-4" aria-hidden />
|
|
||||||
Paketin aktif —{" "}
|
|
||||||
{/* ?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">
|
|
||||||
listeni oluştur
|
|
||||||
</Link>
|
|
||||||
</div>
|
|
||||||
) : (
|
|
||||||
<SatinAlForm
|
|
||||||
urun="paket"
|
|
||||||
label={`${tl(URUNLER.paket.amountKurus)} TL — Paketi al`}
|
|
||||||
vurgulu
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
) : (
|
|
||||||
<div className="space-y-2">
|
|
||||||
<Button
|
|
||||||
asChild
|
|
||||||
className="h-12 w-full cursor-pointer bg-orange-500 px-8 text-white transition-colors duration-200 hover:bg-orange-600"
|
|
||||||
>
|
|
||||||
<Link
|
|
||||||
href={`/giris?callback=${encodeURIComponent("/paket")}`}
|
|
||||||
>
|
|
||||||
Giriş yap — sonra {tl(URUNLER.paket.amountKurus)} TL'ye
|
|
||||||
al
|
|
||||||
<ArrowRight className="size-4" aria-hidden />
|
|
||||||
</Link>
|
|
||||||
</Button>
|
|
||||||
<p className="text-center text-xs text-slate-500">
|
|
||||||
Girişte {DENEME_KREDISI} deneme kredisi de tanımlanır.
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
<Image
|
<Image
|
||||||
src="/odeme/iyzico-logo-band.svg"
|
src="/odeme/iyzico-logo-band.svg"
|
||||||
@@ -139,47 +112,95 @@ export async function PaketSatinAl() {
|
|||||||
{tl(URUNLER.topup.amountKurus)} TL.
|
{tl(URUNLER.topup.amountKurus)} TL.
|
||||||
</p>
|
</p>
|
||||||
<div className="mt-4">
|
<div className="mt-4">
|
||||||
{/* Top-up hasPaket vermez: paketsiz biri alırsa parasını verip
|
<Suspense fallback={<Skeleton className="h-11 w-full rounded-full" />}>
|
||||||
yine maskeli liste görür. Satın alma yalnızca paketliye
|
<TopupCta />
|
||||||
açık; paketsize durum dürüstçe açıklanır. */}
|
</Suspense>
|
||||||
{u?.hasPaket ? (
|
|
||||||
<SatinAlForm
|
|
||||||
urun="topup"
|
|
||||||
label={`+${URUNLER.topup.credits} kredi — ${tl(URUNLER.topup.amountKurus)} TL`}
|
|
||||||
/>
|
|
||||||
) : u ? (
|
|
||||||
<p className="rounded-xl border border-slate-200 bg-slate-50 px-3 py-2.5 text-xs leading-5 text-slate-600">
|
|
||||||
Ek kredi, kilitli analizleri açmaz — o yüzden önce paket
|
|
||||||
gerekir. Paket zaten {URUNLER.paket.credits} kredi içerir;
|
|
||||||
bitince buradan yükleyebilirsin.
|
|
||||||
</p>
|
|
||||||
) : (
|
|
||||||
<Button
|
|
||||||
asChild
|
|
||||||
variant="outline"
|
|
||||||
className="h-11 w-full cursor-pointer"
|
|
||||||
>
|
|
||||||
<Link
|
|
||||||
href={`/giris?callback=${encodeURIComponent("/paket")}`}
|
|
||||||
>
|
|
||||||
Önce giriş yap
|
|
||||||
</Link>
|
|
||||||
</Button>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
</Parallax>
|
</Parallax>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function PaketSatinAlSkeleton() {
|
/** Ana paket kartının kullanıcıya bağlı CTA'sı — bilerek küçük (bkz. üstteki not). */
|
||||||
|
async function PaketCta() {
|
||||||
|
const u = await getCurrentUser();
|
||||||
|
|
||||||
|
if (!u) {
|
||||||
return (
|
return (
|
||||||
<div
|
<div className="space-y-2">
|
||||||
aria-live="polite"
|
<Button
|
||||||
className="mt-10 grid gap-6 sm:grid-cols-[1fr_auto]"
|
asChild
|
||||||
|
className="h-12 w-full cursor-pointer bg-orange-500 px-8 text-white transition-colors duration-200 hover:bg-orange-600"
|
||||||
>
|
>
|
||||||
<Skeleton className="h-80 w-full rounded-2xl" />
|
<Link href={`/giris?callback=${encodeURIComponent("/paket")}`}>
|
||||||
<Skeleton className="h-80 w-full rounded-2xl sm:w-64" />
|
Giriş yap — ilk {ACIK_SATIR} tercihini ücretsiz gör
|
||||||
|
<ArrowRight className="size-4" aria-hidden />
|
||||||
|
</Link>
|
||||||
|
</Button>
|
||||||
|
<p className="text-center text-xs text-slate-500">
|
||||||
|
Girişte {DENEME_KREDISI} deneme kredisi tanımlanır; liste{" "}
|
||||||
|
{RAPOR_KREDI} kredi, ilk {ACIK_SATIR} satır açık. Paketi sonra
|
||||||
|
alırsın.
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (u.hasPaket) {
|
||||||
|
return (
|
||||||
|
<div className="flex items-center gap-2 rounded-xl border border-emerald-200 bg-emerald-50 px-4 py-3 text-sm font-medium text-emerald-800">
|
||||||
|
<CheckCircle2 className="size-4" aria-hidden />
|
||||||
|
Paketin aktif —{" "}
|
||||||
|
{/* ?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">
|
||||||
|
listeni oluştur
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<SatinAlForm
|
||||||
|
urun="paket"
|
||||||
|
label={`${tl(URUNLER.paket.amountKurus)} TL — Paketi al`}
|
||||||
|
vurgulu
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Top-up kartının kullanıcıya bağlı CTA'sı — bilerek küçük (bkz. üstteki not). */
|
||||||
|
async function TopupCta() {
|
||||||
|
const u = await getCurrentUser();
|
||||||
|
|
||||||
|
// Top-up hasPaket vermez: paketsiz biri alırsa parasını verip yine maskeli
|
||||||
|
// liste görür. Satın alma yalnızca paketliye açık; paketsize durum dürüstçe
|
||||||
|
// açıklanır.
|
||||||
|
if (u?.hasPaket) {
|
||||||
|
return (
|
||||||
|
<SatinAlForm
|
||||||
|
urun="topup"
|
||||||
|
label={`+${URUNLER.topup.credits} kredi — ${tl(URUNLER.topup.amountKurus)} TL`}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (u) {
|
||||||
|
return (
|
||||||
|
<p className="rounded-xl border border-slate-200 bg-slate-50 px-3 py-2.5 text-xs leading-5 text-slate-600">
|
||||||
|
Ek kredi, kilitli analizleri açmaz — o yüzden önce paket gerekir.
|
||||||
|
Paket zaten {URUNLER.paket.credits} kredi içerir; bitince buradan
|
||||||
|
yükleyebilirsin.
|
||||||
|
</p>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Button asChild variant="outline" className="h-11 w-full cursor-pointer">
|
||||||
|
<Link href={`/giris?callback=${encodeURIComponent("/paket")}`}>
|
||||||
|
Önce giriş yap
|
||||||
|
</Link>
|
||||||
|
</Button>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user