-
- );
-}
diff --git a/src/app/odeme/sonuc/page.tsx b/src/app/odeme/sonuc/page.tsx
index e4ff68f..74a9862 100644
--- a/src/app/odeme/sonuc/page.tsx
+++ b/src/app/odeme/sonuc/page.tsx
@@ -1,126 +1,29 @@
-import Link from "next/link";
+import { Suspense } from "react";
import type { Metadata } from "next";
-import { eq } from "drizzle-orm";
-import { ArrowRight, CheckCircle2, XCircle } from "lucide-react";
-import { verifySession } from "@/lib/session";
-import { appDb, schema } from "@/lib/appdb";
-import { kullanicininRaporu } from "@/features/rapor/rapor-queries";
-import { odemeyiSonuclandir } from "@/lib/odeme";
-import { Button } from "@/components/ui/button";
-import { PagePixelDivider } from "@/components/pixel-decor";
import { SiteFooter } from "@/components/site-footer";
-import { RaporYokCta } from "./rapor-yok-cta";
+import {
+ OdemeSonucKarti,
+ OdemeSonucKartiSkeleton,
+} from "@/features/odeme/components/odeme-sonuc-karti";
export const metadata: Metadata = {
title: "Ödeme Sonucu",
robots: { index: false, follow: false },
};
-export default async function OdemeSonucPage({
+export default function OdemeSonucPage({
searchParams,
}: {
searchParams: Promise<{ siparis?: string }>;
}) {
- const { siparis } = await searchParams;
- // Oturum düşmüşse geri dönüşte ?siparis= kaybolmasın (başarı/unblur deneyimi)
- const geriDonus = siparis
- ? `/odeme/sonuc?siparis=${encodeURIComponent(siparis)}`
- : "/paket";
- const session = await verifySession(geriDonus);
-
- const order = siparis
- ? await appDb.query.orders.findFirst({
- where: eq(schema.orders.id, siparis),
- })
- : null;
-
- // Yalnızca kendi siparişi görüntülenebilir
- const sahibiMi = order?.userId === session.user.id;
-
- // Self-healing: callback kaybolduysa burada tekrar doğrula
- const durum =
- order && sahibiMi
- ? order.status === "paid"
- ? "paid"
- : await odemeyiSonuclandir(order.id)
- : "not_found";
-
- const basarili = durum === "paid";
-
- // Rapor zaten üretildiyse ödül bir tık uzakta: kullanıcıyı doğrudan
- // rapor sayfasına döndür ki blur çözülme animasyonuyla tam listeyi görsün.
- const rapor = basarili ? await kullanicininRaporu(session.user.id) : null;
- const listeHref = rapor ? "/listem?acildi=1" : null;
-
return (
- {basarili ? (
-
-
-
- Ödeme alındı 🎉
-
-
-
- {order!.credits} kredi
- {" "}
- hesabına tanımlandı
- {order!.product === "paket"
- ? " ve Tercih Dönemi Paketi'n aktif — kişisel listen, risk raporun ve PDF çıktın hazır olduğunda burada."
- : "."}
-
-
-
- {listeHref ? (
- <>
-
-
- 24 tercihin tamamı, gerekçeler, riskler ve PDF artık açık.
-
- >
- ) : (
-
- )}
-
-
- ) : (
-
-
-
- Ödeme tamamlanamadı
-
-
- {durum === "pending" ? (
- "Ödemen hâlâ işleniyor. Birkaç saniye sonra bu sayfayı yenile — kartından çekim yapıldıysa krediler mutlaka tanımlanır."
- ) : (
- <>
- Çekim yapılmadı ya da işlem iptal edildi. Kartından para
- çekildiğini düşünüyorsan{" "}
-
- bizimle iletişime geç
-
- .
- >
- )}
-
diff --git a/src/app/paket/loading.tsx b/src/app/paket/loading.tsx
deleted file mode 100644
index 4c967c4..0000000
--- a/src/app/paket/loading.tsx
+++ /dev/null
@@ -1,23 +0,0 @@
-import { Skeleton } from "@/components/ui/skeleton";
-
-// /paket dinamik (kullanıcı kredisi) — statik kabuk için sayfa geometrisine
-// uygun iskelet: ortalanmış başlık bloğu + paket kartı şeridi.
-export default function Loading() {
- return (
-
-
-
-
-
-
-
-
-
-
-
-
- );
-}
diff --git a/src/app/paket/page.tsx b/src/app/paket/page.tsx
index 70d9ee6..b04cfc1 100644
--- a/src/app/paket/page.tsx
+++ b/src/app/paket/page.tsx
@@ -1,23 +1,13 @@
-import Image from "next/image";
+import { Suspense } from "react";
import Link from "next/link";
import type { Metadata } from "next";
-import {
- ArrowRight,
- CheckCircle2,
- Coins,
- FileText,
- ListChecks,
- MessageCircleQuestion,
- RefreshCcw,
- ShieldCheck,
-} from "lucide-react";
-import { getCurrentUser } from "@/lib/session";
-import { URUNLER, DENEME_KREDISI } from "@/lib/credits";
-import { Button } from "@/components/ui/button";
import { SectionEyebrow } from "@/components/pixel-decor";
import { Parallax } from "@/components/parallax";
-import { SatinAlForm } from "./satin-al-form";
import { SiteFooter } from "@/components/site-footer";
+import {
+ PaketSatinAl,
+ PaketSatinAlSkeleton,
+} from "@/features/odeme/components/paket-satinal";
export const metadata: Metadata = {
title: "Tercih Dönemi Paketi",
@@ -26,193 +16,57 @@ export const metadata: Metadata = {
alternates: { canonical: "/paket" },
};
-function tl(kurus: number) {
- return (kurus / 100).toLocaleString("tr-TR", { maximumFractionDigits: 0 });
-}
-
-const OZELLIKLER = [
- {
- icon: ListChecks,
- text: "Sıralamana özel, gerekçeli 24 tercihlik dengeli liste",
- },
- {
- icon: ShieldCheck,
- text: "Her tercih için yerleşme olasılığı ve risk analizi (son 4 yıl trend)",
- },
- { icon: FileText, text: "Veliyle paylaşılabilir PDF rapor" },
- { icon: RefreshCcw, text: "2 liste revizyon hakkı" },
- {
- icon: MessageCircleQuestion,
- text: `${URUNLER.paket.credits} Yapay Zeka danışman sorusu (her mesaj 1 kredi)`,
- },
-] as const;
-
-export default async function PaketPage({
+export default function PaketPage({
searchParams,
}: {
searchParams: Promise<{ hata?: string }>;
}) {
- const [u, { hata }] = await Promise.all([getCurrentUser(), searchParams]);
- // Ödeme callback'i doğrulama hatasında buraya ?hata=token|siparis ile döner
- const odemeHatasi = hata === "token" || hata === "siparis";
-
return (
-
- {odemeHatasi ? (
-
- Ödemen doğrulanamadı. Kartından çekim yapıldıysa kredilerin birkaç
- dakika içinde otomatik tanımlanır; tanımlanmazsa aşağıdan tekrar
- deneyebilirsin — çekim yapılmadıysa hiçbir ücret ödemedin.
-
- ) : null}
-
- Tek seferlik ödeme · Abonelik yok
-
- İnsan danışmanın işini,{" "}
- onda bir fiyatına
-
-
- Tercih danışmanları bu dönemde 2.000–10.000 TL alıyor. KolayTercih
- aynı işi gerçek YÖK Atlas verisiyle, dakikalar içinde yapar.
-
-
-
-
- {/* Ana paket */}
-
-
-
- Tercih Dönemi Paketi
-
-
- {tl(URUNLER.paket.amountKurus)} TL
-
+
+ {/* Ödeme callback'i doğrulama hatasında buraya ?hata=token|siparis ile
+ döner; hata yokken hiçbir şey çizilmez — fallback bilerek boş. */}
+
+ {searchParams.then(({ hata }) =>
+ hata === "token" || hata === "siparis" ? (
+
+ Ödemen doğrulanamadı. Kartından çekim yapıldıysa kredilerin
+ birkaç dakika içinde otomatik tanımlanır; tanımlanmazsa
+ aşağıdan tekrar deneyebilirsin — çekim yapılmadıysa hiçbir
+ ücret ödemedin.
-
- {OZELLIKLER.map((o) => (
-
-
- {o.text}
-
- ))}
-
-
- {u ? (
- u.hasPaket ? (
-
-
- Paketin aktif —{" "}
-
- listeni oluştur
-
-
- ) : (
-
- )
- ) : (
-
-
-
- Girişte {DENEME_KREDISI} deneme kredisi de tanımlanır.
-
-
- )}
-
-
-
- iyzico güvenli ödeme · Kart bilgilerin bize hiç ulaşmaz ·{" "}
-
- İade koşulları
-
-
- {/* 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.
-
- ) : (
-
- )}
-
-
-
-
-
- KolayTercih bir karar destek aracıdır; öneriler resmî YÖK Atlas
- verisine dayanır ancak yerleşme garantisi verilmez. Tercih
- listesinin nihai sorumluluğu adaya aittir. Sorun mu var?{" "}
-
- Bize yaz
-
- .
+ ) : null,
+ )}
+
+
+ Tek seferlik ödeme · Abonelik yok
+
+ İnsan danışmanın işini,{" "}
+ onda bir fiyatına
+
+
+ Tercih danışmanları bu dönemde 2.000–10.000 TL alıyor. KolayTercih
+ aynı işi gerçek YÖK Atlas verisiyle, dakikalar içinde yapar.
-
+
+
+ }>
+
+
+
+
+ KolayTercih bir karar destek aracıdır; öneriler resmî YÖK Atlas
+ verisine dayanır ancak yerleşme garantisi verilmez. Tercih
+ listesinin nihai sorumluluğu adaya aittir. Sorun mu var?{" "}
+
+ Bize yaz
+
+ .
+
+
);
diff --git a/src/features/odeme/components/odeme-sonuc-karti.tsx b/src/features/odeme/components/odeme-sonuc-karti.tsx
new file mode 100644
index 0000000..1abdd78
--- /dev/null
+++ b/src/features/odeme/components/odeme-sonuc-karti.tsx
@@ -0,0 +1,121 @@
+import Link from "next/link";
+import { ArrowRight, CheckCircle2, XCircle } from "lucide-react";
+import { verifySession } from "@/lib/session";
+import { siparisGetir } from "../odeme-queries";
+import { kullanicininRaporu } from "@/features/rapor/rapor-queries";
+import { odemeyiSonuclandir } from "@/lib/odeme";
+import { Button } from "@/components/ui/button";
+import { PagePixelDivider } from "@/components/pixel-decor";
+import { Skeleton } from "@/components/ui/skeleton";
+import { RaporYokCta } from "./rapor-yok-cta";
+
+export async function OdemeSonucKarti({ siparis }: { siparis?: string }) {
+ // Oturum düşmüşse geri dönüşte ?siparis= kaybolmasın (başarı/unblur deneyimi)
+ const geriDonus = siparis
+ ? `/odeme/sonuc?siparis=${encodeURIComponent(siparis)}`
+ : "/paket";
+ const session = await verifySession(geriDonus);
+
+ const order = siparis ? await siparisGetir(siparis) : null;
+
+ // Yalnızca kendi siparişi görüntülenebilir
+ const sahibiMi = order?.userId === session.user.id;
+
+ // Self-healing: callback kaybolduysa burada tekrar doğrula
+ const durum =
+ order && sahibiMi
+ ? order.status === "paid"
+ ? "paid"
+ : await odemeyiSonuclandir(order.id)
+ : "not_found";
+
+ const basarili = durum === "paid";
+
+ // Rapor zaten üretildiyse ödül bir tık uzakta: kullanıcıyı doğrudan
+ // rapor sayfasına döndür ki blur çözülme animasyonuyla tam listeyi görsün.
+ const rapor = basarili ? await kullanicininRaporu(session.user.id) : null;
+ const listeHref = rapor ? "/listem?acildi=1" : null;
+
+ return basarili ? (
+
+
+
+ Ödeme alındı 🎉
+
+
+
+ {order!.credits} kredi
+ {" "}
+ hesabına tanımlandı
+ {order!.product === "paket"
+ ? " ve Tercih Dönemi Paketi'n aktif — kişisel listen, risk raporun ve PDF çıktın hazır olduğunda burada."
+ : "."}
+
+
+
+ {listeHref ? (
+ <>
+
+
+ 24 tercihin tamamı, gerekçeler, riskler ve PDF artık açık.
+
+ >
+ ) : (
+
+ )}
+
+
+ ) : (
+
+
+
+ Ödeme tamamlanamadı
+
+
+ {durum === "pending" ? (
+ "Ödemen hâlâ işleniyor. Birkaç saniye sonra bu sayfayı yenile — kartından çekim yapıldıysa krediler mutlaka tanımlanır."
+ ) : (
+ <>
+ Çekim yapılmadı ya da işlem iptal edildi. Kartından para
+ çekildiğini düşünüyorsan{" "}
+
+ bizimle iletişime geç
+
+ .
+ >
+ )}
+
+
+
+
+ );
+}
+
+// iyzico doğrulaması beklenirken ortalanmış sonuç kartı iskeleti.
+export function OdemeSonucKartiSkeleton() {
+ return (
+
+
+
+
+
+
+
+ );
+}
diff --git a/src/features/odeme/components/paket-satinal.tsx b/src/features/odeme/components/paket-satinal.tsx
new file mode 100644
index 0000000..fa05aff
--- /dev/null
+++ b/src/features/odeme/components/paket-satinal.tsx
@@ -0,0 +1,181 @@
+import Image from "next/image";
+import Link from "next/link";
+import {
+ ArrowRight,
+ CheckCircle2,
+ Coins,
+ FileText,
+ ListChecks,
+ MessageCircleQuestion,
+ RefreshCcw,
+ ShieldCheck,
+} from "lucide-react";
+import { getCurrentUser } from "@/lib/session";
+import { URUNLER, DENEME_KREDISI } from "@/lib/credits";
+import { Button } from "@/components/ui/button";
+import { Parallax } from "@/components/parallax";
+import { Skeleton } from "@/components/ui/skeleton";
+import { SatinAlForm } from "./satin-al-form";
+
+function tl(kurus: number) {
+ return (kurus / 100).toLocaleString("tr-TR", { maximumFractionDigits: 0 });
+}
+
+const OZELLIKLER = [
+ {
+ icon: ListChecks,
+ text: "Sıralamana özel, gerekçeli 24 tercihlik dengeli liste",
+ },
+ {
+ icon: ShieldCheck,
+ text: "Her tercih için yerleşme olasılığı ve risk analizi (son 4 yıl trend)",
+ },
+ { icon: FileText, text: "Veliyle paylaşılabilir PDF rapor" },
+ { icon: RefreshCcw, text: "2 liste revizyon hakkı" },
+ {
+ icon: MessageCircleQuestion,
+ text: `${URUNLER.paket.credits} Yapay Zeka danışman sorusu (her mesaj 1 kredi)`,
+ },
+] 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();
+
+ return (
+
+ {/* Ana paket */}
+
+
+
+ Tercih Dönemi Paketi
+
+
+ {tl(URUNLER.paket.amountKurus)} TL
+
+
+
+ {OZELLIKLER.map((o) => (
+
+
+ {o.text}
+
+ ))}
+
+
+ {u ? (
+ u.hasPaket ? (
+
+
+ Paketin aktif —{" "}
+
+ listeni oluştur
+
+
+ ) : (
+
+ )
+ ) : (
+
+
+
+ Girişte {DENEME_KREDISI} deneme kredisi de tanımlanır.
+
+
+ )}
+
+
+
+ iyzico güvenli ödeme · Kart bilgilerin bize hiç ulaşmaz ·{" "}
+
+ İade koşulları
+
+
+ {/* 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.
+