Files
nobat724_front/app/payment/[uuid]/page.js
T
hamedandClaude Sonnet 4.6 59e0a0fe4f fix: resolve critical bugs and security issues across the project
Security:
- Disable SSL verification only in development (lib/req.js)
- Wrap all JSON.parse(cookie) calls in try-catch via safeJsonParse utility
- Sanitize dangerouslySetInnerHTML in blog/clinic with sanitizeHtml utility
- Fix open redirect in payment page — validate URL origin before redirect
- Fix cookie cleanup on 401 — use js-cookie with correct domain scope

Performance:
- Wrap ItemDoctor with React.memo to prevent unnecessary re-renders
- Replace <img> with Next.js <Image> in blog Caption component

Functionality:
- Fix memory leak in Recode.js — store intervals in refs, cleanup on unmount
- Add null guard on retryIcon.current before classList manipulation
- Fix getParsedUserInfo in helper to handle malformed cookie gracefully

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-07 09:19:20 +03:30

234 lines
9.9 KiB
JavaScript
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"use client";
import { useEffect, useState } from "react";
import { useParams, useRouter } from "next/navigation";
import { request } from "@/services/response";
import Cookies from "js-cookie";
import Layout from "@/components/layout";
export default function PaymentDetailsPage() {
const params = useParams();
const router = useRouter();
const [payment, setPayment] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
// بررسی لاگین بودن کاربر
const userInfo = Cookies.get("userInfo");
if (!userInfo) {
router.push("/login");
return;
}
const fetchPaymentDetails = async () => {
try {
setLoading(true);
const response = await request.getPayment(params.uuid);
setPayment(response);
} catch (err) {
setError("خطا در دریافت اطلاعات پرداخت");
console.error("Payment fetch error:", err);
} finally {
setLoading(false);
}
};
if (params.uuid) {
fetchPaymentDetails();
}
}, [params.uuid, router]);
const getStatusLabel = (status) => {
const statusMap = {
pending: "در انتظار پرداخت",
completed: "پرداخت موفق",
failed: "پرداخت ناموفق",
canceled: "لغو شده",
};
return statusMap[status] || status;
};
const getStatusColor = (status) => {
const colorMap = {
pending: "text-yellow-600 bg-yellow-50",
completed: "text-green-600 bg-green-50",
failed: "text-red-600 bg-red-50",
canceled: "text-gray-600 bg-gray-50",
};
return colorMap[status] || "text-gray-600 bg-gray-50";
};
const getPaymentMethodLabel = (method) => {
const methodMap = {
mellat: "بانک ملت",
saman: "سامان کیش",
};
return methodMap[method] || method;
};
const formatDate = (timestamp) => {
const date = new Date(parseInt(timestamp) * 1000);
return new Intl.DateTimeFormat("fa-IR", {
year: "numeric",
month: "long",
day: "numeric",
hour: "2-digit",
minute: "2-digit",
}).format(date);
};
const formatAmount = (amount) => {
return new Intl.NumberFormat("fa-IR").format(parseInt(amount));
};
if (loading) {
return (
<Layout>
<div className="flex items-center justify-center min-h-screen">
<div className="flex flex-col items-center gap-4">
<div className="w-12 h-12 border-4 border-[#5559CE] border-t-transparent rounded-full animate-spin"></div>
<p className="text-[#3B3B3B] text-[16px]">در حال بارگذاری...</p>
</div>
</div>
</Layout>
);
}
if (error) {
return (
<Layout>
<div className="flex items-center justify-center min-h-screen">
<div className="bg-red-50 border border-red-200 rounded-lg p-6 max-w-md">
<p className="text-red-600 text-center">{error}</p>
</div>
</div>
</Layout>
);
}
if (!payment) {
return null;
}
return (
<Layout>
<div className="container mx-auto px-4 py-8 max-w-4xl mt-20 md:mt-24">
<div className="bg-white rounded-lg shadow-md border border-gray-200 p-6 md:p-8">
<h1 className="text-2xl md:text-3xl font-bold text-[#3B3B3B] mb-6">
جزئیات پرداخت
</h1>
<div className="space-y-4">
{/* وضعیت پرداخت */}
<div className="flex justify-between items-center py-3 border-b border-gray-100">
<span className="text-[#616161] font-medium">وضعیت:</span>
<span
className={`px-4 py-2 rounded-full font-bold ${getStatusColor(
payment.status
)}`}
>
{getStatusLabel(payment.status)}
</span>
</div>
{/* مبلغ */}
<div className="flex justify-between items-center py-3 border-b border-gray-100">
<span className="text-[#616161] font-medium">مبلغ:</span>
<span className="text-[#3B3B3B] font-bold text-lg">
{formatAmount(payment.amount)} تومان
</span>
</div>
{/* روش پرداخت */}
<div className="flex justify-between items-center py-3 border-b border-gray-100">
<span className="text-[#616161] font-medium">روش پرداخت:</span>
<span className="text-[#3B3B3B] font-bold">
{getPaymentMethodLabel(payment.payment_method)}
</span>
</div>
{/* شناسه پرداخت */}
<div className="flex justify-between items-center py-3 border-b border-gray-100">
<span className="text-[#616161] font-medium">شناسه پرداخت:</span>
<span className="text-[#3B3B3B] font-mono text-sm">
{payment.uuid}
</span>
</div>
{/* شماره مرجع */}
{payment.reference_id && (
<div className="flex justify-between items-center py-3 border-b border-gray-100">
<span className="text-[#616161] font-medium">شماره مرجع:</span>
<span className="text-[#3B3B3B] font-bold">
{payment.reference_id}
</span>
</div>
)}
{/* تاریخ ایجاد */}
<div className="flex justify-between items-center py-3 border-b border-gray-100">
<span className="text-[#616161] font-medium">تاریخ ایجاد:</span>
<span className="text-[#3B3B3B]">
{formatDate(payment.created)}
</span>
</div>
{/* تاریخ آخرین تغییر */}
<div className="flex justify-between items-center py-3 border-b border-gray-100">
<span className="text-[#616161] font-medium">
آخرین بروزرسانی:
</span>
<span className="text-[#3B3B3B]">
{formatDate(payment.changed)}
</span>
</div>
{/* نوع سرویس */}
<div className="flex justify-between items-center py-3">
<span className="text-[#616161] font-medium">نوع سرویس:</span>
<span className="text-[#3B3B3B] font-bold">
{payment.bundle === "appointment" ? "رزرو نوبت" : payment.bundle}
</span>
</div>
</div>
{/* دکمه‌ها */}
<div className="mt-8 flex gap-4">
{payment.status === "pending" ? (
<>
<button
onClick={() => {
try {
const apiOrigin = new URL(process.env.NEXT_PUBLIC_API_URL).origin;
const paymentUrl = new URL(`/payment/${payment.uuid}`, apiOrigin);
if (paymentUrl.origin === apiOrigin) {
window.location.href = paymentUrl.href;
}
} catch {}
}}
className="flex-1 bg-[#5559CE] hover:bg-[#4448b3] text-white font-bold py-3 px-6 rounded-lg transition-colors"
>
پرداخت
</button>
<button
onClick={() => window.location.reload()}
className="flex-1 bg-gray-200 hover:bg-gray-300 text-[#3B3B3B] font-bold py-3 px-6 rounded-lg transition-colors"
>
بروزرسانی وضعیت
</button>
</>
) : null}
<button
onClick={() => router.push("/dashboard?sidebar=2")}
className="flex-1 bg-[#5559CE] hover:bg-[#4448b3] text-white font-bold py-3 px-6 rounded-lg transition-colors"
>
بازگشت به تراکنشها
</button>
</div>
</div>
</div>
</Layout>
);
}