From 95bc87e5f71af7e4d754834e0e108a82c241996b Mon Sep 17 00:00:00 2001
From: bilalgursen
Date: Mon, 21 Sep 2026 23:34:52 +0300
Subject: [PATCH] =?UTF-8?q?fix(paket):=20giri=C5=9Fsiz=20ziyaret=C3=A7ide?=
=?UTF-8?q?=20bo=C5=9F=20sayfa=20=E2=80=94=20kart=20g=C3=B6vdesi=20statik?=
=?UTF-8?q?=20kabukta,=20yaln=C4=B1z=20CTA=20dinamik?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
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
---
src/app/paket/page.tsx | 11 +-
.../odeme/components/paket-satinal.tsx | 179 ++++++++++--------
2 files changed, 104 insertions(+), 86 deletions(-)
diff --git a/src/app/paket/page.tsx b/src/app/paket/page.tsx
index 3d8131f..a6acc93 100644
--- a/src/app/paket/page.tsx
+++ b/src/app/paket/page.tsx
@@ -4,10 +4,7 @@ import type { Metadata } from "next";
import { SectionEyebrow } from "@/components/pixel-decor";
import { Parallax } from "@/components/parallax";
import { SiteFooter } from "@/components/site-footer";
-import {
- PaketSatinAl,
- PaketSatinAlSkeleton,
-} from "@/features/odeme/components/paket-satinal";
+import { PaketSatinAl } from "@/features/odeme/components/paket-satinal";
import { sayfaMetadata } from "@/lib/seo";
export const metadata: Metadata = sayfaMetadata({
@@ -57,9 +54,9 @@ export default function PaketPage({
- }>
-
-
+ {/* 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). */}
+
KolayTercih bir karar destek aracıdır; öneriler resmî YÖK Atlas
diff --git a/src/features/odeme/components/paket-satinal.tsx b/src/features/odeme/components/paket-satinal.tsx
index 5ebf077..d5cb8d6 100644
--- a/src/features/odeme/components/paket-satinal.tsx
+++ b/src/features/odeme/components/paket-satinal.tsx
@@ -1,3 +1,4 @@
+import { Suspense } from "react";
import Image from "next/image";
import Link from "next/link";
import {
@@ -11,7 +12,8 @@ import {
ShieldCheck,
} from "lucide-react";
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 { Button } from "@/components/ui/button";
import { Parallax } from "@/components/parallax";
@@ -39,11 +41,17 @@ const OZELLIKLER = [
},
] as const;
-// Paket + top-up kartları: kullanıcının paket/kredi durumuna göre CTA'lar
-// değişir, okuma burada yaşar.
-export async function PaketSatinAl() {
- const u = await getCurrentUser();
-
+// Paket + top-up kartları. Kartların gövdesi (başlık, fiyat, özellikler,
+// iyzico bandı) STATİK kabukta kalır; kullanıcıya bağlı olan yalnızca iki küçük
+// CTA alanıdır (PaketCta, TopupCta) ve her biri kendi Suspense sınırındadır.
+//
+// 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 (
- {u ? (
- u.hasPaket ? (
-
-
- 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. */}
-
- listeni oluştur
-
-
- ) : (
-
- )
- ) : (
-
-
-
- Girişte {DENEME_KREDISI} deneme kredisi de tanımlanır.
-
-
- )}
+ }>
+
+
- {/* 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. */}
- {u?.hasPaket ? (
-
- ) : u ? (
-
- Ek kredi, kilitli analizleri açmaz — o yüzden önce paket
- gerekir. Paket zaten {URUNLER.paket.credits} kredi içerir;
- bitince buradan yükleyebilirsin.
-
- ) : (
-
- )}
+ }>
+
+
);
}
-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 (
+
+
+
+ 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.
+
+
+ );
+ }
+
+ if (u.hasPaket) {
+ return (
+
+
+ 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. */}
+
+ listeni oluştur
+
+
+ );
+ }
+
return (
-
-
-
-
+
+ );
+}
+
+/** 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 (
+
+ );
+ }
+
+ if (u) {
+ return (
+
+ Ek kredi, kilitli analizleri açmaz — o yüzden önce paket gerekir.
+ Paket zaten {URUNLER.paket.credits} kredi içerir; bitince buradan
+ yükleyebilirsin.
+