All checks were successful
Deploy / deploy (push) Successful in 6m23s
- TemaSaglayici (next-themes) + alt bilgide TemaSecici - globals.css: .dark bloğunda palet token'ları çevrilir; bileşenlere dark: sınıfı yazılmaz - yüzeyler bg-white → bg-card, koyu haplar text-slate-50, SVG'lerde sabit renk yerine değişken - DESIGN.md §2.3 + tasarim-denetim kuralları (yuzey-beyaz, koyu-hap-beyaz-yazi, dark-sinif), AGENTS.md karanlık mod bölümü - docs: koyu tema denetimi raporu Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
162 lines
6.2 KiB
TypeScript
162 lines
6.2 KiB
TypeScript
import Link from "next/link";
|
||
import { ArrowRight, CheckCircle2, RotateCcw, XCircle } from "lucide-react";
|
||
import { verifySession } from "@/lib/session";
|
||
import { siparisGetir } from "../odeme-queries";
|
||
import { kullanicininRaporu } from "@/features/rapor/rapor-queries";
|
||
import { iadeEdilecekMi, 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";
|
||
// Oturum ve sipariş sorgusu bağımsız; sahiplik kontrolü aşağıda yapılıyor
|
||
const [session, order] = await Promise.all([
|
||
verifySession(geriDonus),
|
||
siparis ? siparisGetir(siparis) : Promise.resolve(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. "paid" siparişte
|
||
// de çağrılır — kredisi yazılmamış eski kayıtları onarır (idempotent).
|
||
const durum =
|
||
order && sahibiMi ? await odemeyiSonuclandir(order.id) : "not_found";
|
||
|
||
const basarili = durum === "paid";
|
||
|
||
// Zaten paketliyken alınan ikinci paket ödemesi: kredi yazılmadı, iade edilir
|
||
const iade =
|
||
basarili && order
|
||
? await iadeEdilecekMi({ ...order, status: "paid" })
|
||
: false;
|
||
if (iade) {
|
||
return (
|
||
<div className="rounded-2xl border border-amber-200 bg-card p-8 shadow-sm">
|
||
<RotateCcw className="mx-auto size-12 text-amber-500" aria-hidden />
|
||
<h1 className="mt-4 font-heading text-2xl font-bold">
|
||
Bu ödeme iade edilecek
|
||
</h1>
|
||
<p className="mt-2 text-sm text-slate-600">
|
||
Tercih Dönemi Paketi'n zaten aktifti; paket tek seferlik olduğu
|
||
için bu ikinci ödemeyi iade ediyoruz — senin bir şey yapmana gerek
|
||
yok. İade en geç 14 gün içinde yapılır; kartına yansıması bankana
|
||
göre birkaç gün daha sürebilir. Sorun olursa{" "}
|
||
<a
|
||
href="mailto:destek@kolaytercih.com"
|
||
className="font-medium text-primary underline-offset-2 hover:underline"
|
||
>
|
||
destek@kolaytercih.com
|
||
</a>
|
||
'a yaz.
|
||
</p>
|
||
<PagePixelDivider seed={79} className="mx-auto mt-5" />
|
||
<div className="mt-6 flex flex-col gap-2">
|
||
<Button
|
||
asChild
|
||
className="h-11 cursor-pointer bg-orange-500 text-white transition-colors duration-200 hover:bg-orange-600"
|
||
>
|
||
<Link href="/listem">
|
||
Listeme dön
|
||
<ArrowRight className="size-4" aria-hidden />
|
||
</Link>
|
||
</Button>
|
||
</div>
|
||
</div>
|
||
);
|
||
}
|
||
|
||
// 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-card 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-card 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?kaynak=odeme_hata">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-card 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>
|
||
);
|
||
}
|