refactor: odeme feature'ı + son süpürme — geçiş tamamlandı (Faz 10)
Some checks failed
Deploy / deploy (push) Failing after 3m46s
Some checks failed
Deploy / deploy (push) Failing after 3m46s
- paket/actions.ts → odeme-actions.ts; satin-al-form + rapor-yok-cta features/odeme/components/ altına; odeme-queries.ts (siparisGetir) - Yeni async OdemeSonucKarti: verifySession + sahiplik kontrolü + self-healing odemeyiSonuclandir çağrısı akıştaki yerinde (artık Suspense altındaki feature bileşeninde, aynı render semantiği); başarı/hata kartları içeride - Yeni async PaketSatinAl: paket + top-up kartları, getCurrentUser okuması içeride; statik hero/dipnot sayfada kaldı - İki sayfa senkron searchParams.then(); paket hata uyarısı sayfada çözülmüş JSX (hata yokken hiçbir şey çizilmediği için fallback bilerek null); paket/loading.tsx + odeme/sonuc/loading.tsx silindi - Süpürme doğrulandı: app/ altında route-dışı bileşen dosyası kalmadı; eski yol grep'leri sıfır; 7 *-queries server-only, 3 *-actions 'use server'; async sayfa sıfır; /api/odeme/callback el değmedi - Davranış değişikliği yok Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
121
src/features/odeme/components/odeme-sonuc-karti.tsx
Normal file
121
src/features/odeme/components/odeme-sonuc-karti.tsx
Normal file
@@ -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 ? (
|
||||
<div className="rounded-2xl border border-emerald-200 bg-white p-8 shadow-sm">
|
||||
<CheckCircle2
|
||||
className="mx-auto size-12 text-emerald-500"
|
||||
aria-hidden
|
||||
/>
|
||||
<h1 className="mt-4 font-heading text-2xl font-bold">
|
||||
Ödeme alındı 🎉
|
||||
</h1>
|
||||
<p className="mt-2 text-sm text-slate-600">
|
||||
<span className="font-semibold">
|
||||
{order!.credits} kredi
|
||||
</span>{" "}
|
||||
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."
|
||||
: "."}
|
||||
</p>
|
||||
<PagePixelDivider seed={79} className="mx-auto mt-5" />
|
||||
<div className="mt-6 flex flex-col gap-2">
|
||||
{listeHref ? (
|
||||
<>
|
||||
<Button
|
||||
asChild
|
||||
className="h-11 cursor-pointer bg-orange-500 text-white transition-colors duration-200 hover:bg-orange-600"
|
||||
>
|
||||
<Link href={listeHref}>
|
||||
Listeni aç — kilidi kaldırdık
|
||||
<ArrowRight className="size-4" aria-hidden />
|
||||
</Link>
|
||||
</Button>
|
||||
<p className="text-center text-xs text-slate-500">
|
||||
24 tercihin tamamı, gerekçeler, riskler ve PDF artık açık.
|
||||
</p>
|
||||
</>
|
||||
) : (
|
||||
<RaporYokCta />
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<div className="rounded-2xl border border-red-200 bg-white p-8 shadow-sm">
|
||||
<XCircle className="mx-auto size-12 text-red-500" aria-hidden />
|
||||
<h1 className="mt-4 font-heading text-2xl font-bold">
|
||||
Ödeme tamamlanamadı
|
||||
</h1>
|
||||
<p className="mt-2 text-sm text-slate-600">
|
||||
{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{" "}
|
||||
<Link href="/iletisim" className="underline">
|
||||
bizimle iletişime geç
|
||||
</Link>
|
||||
.
|
||||
</>
|
||||
)}
|
||||
</p>
|
||||
<PagePixelDivider seed={83} className="mx-auto mt-5" />
|
||||
<Button asChild variant="outline" className="mt-6 h-11 cursor-pointer">
|
||||
<Link href="/paket">Tekrar dene</Link>
|
||||
</Button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// iyzico doğrulaması beklenirken ortalanmış sonuç kartı iskeleti.
|
||||
export function OdemeSonucKartiSkeleton() {
|
||||
return (
|
||||
<div
|
||||
aria-live="polite"
|
||||
className="rounded-2xl border border-slate-200 bg-white p-8 shadow-sm"
|
||||
>
|
||||
<Skeleton className="mx-auto size-12 rounded-full" />
|
||||
<Skeleton className="mx-auto mt-5 h-7 w-2/3" />
|
||||
<Skeleton className="mx-auto mt-3 h-5 w-full" />
|
||||
<Skeleton className="mx-auto mt-2 h-5 w-3/4" />
|
||||
<Skeleton className="mx-auto mt-6 h-12 w-full rounded-lg" />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user