Edit haram payment

This commit is contained in:
Rahaf
2026-07-15 19:25:39 +03:00
parent 7176031485
commit 8ed623bbe2
2 changed files with 160 additions and 26 deletions

View File

@ -1498,6 +1498,12 @@ const resources = {
"payments.closeButton": "Close",
"payments.propertyLabel": "Property",
"payments.uploadReceipt": "Upload Receipt Image",
"payments.receiptDialogTitle": "Upload payment receipt",
"payments.receiptDialogDescription": "Please upload a clear image of the transfer receipt before completing the payment.",
"payments.receiptUploadRequiredHint": "A receipt image is required for this payment method.",
"payments.receiptRequired": "Please upload the payment receipt image first.",
"payments.receiptSelected": "Selected file:",
"payments.continueToPay": "Continue to pay",
"payments.loadingPaymentMethods": "Loading payment methods...",
"payments.noPaymentMethodsAvailable": "No payment methods available.",
"payments.paymentMethodName": "Payment Method",
@ -2549,6 +2555,13 @@ const resources = {
"payment.transferDesc": "حول من حساب داخلي ثم أرسل المرجع",
"payment.electronic": "دفع إلكتروني",
"payment.electronicDesc": "سيتم تفعيل الدفع الإلكتروني والتحويل لاحقاً",
"payments.uploadReceipt": "رفع صورة الوصل",
"payments.receiptDialogTitle": "رفع وصل الدفع",
"payments.receiptDialogDescription": "يرجى رفع صورة واضحة لوصل التحويل قبل إكمال الدفع.",
"payments.receiptUploadRequiredHint": "مطلوب رفع صورة الوصل لهذه الطريقة.",
"payments.receiptRequired": "يرجى رفع صورة وصل الدفع أولاً.",
"payments.receiptSelected": "الملف المختار:",
"payments.continueToPay": "متابعة الدفع",
/* ─── Owner Properties Page ─── */
"deleteConfirmTitle": "تأكيد الحذف",

View File

@ -70,6 +70,32 @@ function isHaramText(value) {
return text.includes('haram') || text.includes('هرم') || text.includes('هرام');
}
function isReceiptRequiredPayment(value, method = null) {
const haystack = [
value,
method?.name,
method?.label,
method?.id,
method?.description,
]
.filter(Boolean)
.join(' ');
const text = normalizeText(haystack);
return (
text.includes('transfer') ||
text.includes('تحويل') ||
text.includes('حوالة') ||
text.includes('bank') ||
text.includes('بنك') ||
isHaramText(value) ||
isHaramText(method?.name) ||
isHaramText(method?.label) ||
isHaramText(method?.id) ||
isHaramText(method?.description)
);
}
export default function PaymentsPage() {
const { t, i18n } = useTranslation();
const [reservations, setReservations] = useState([]);
@ -165,13 +191,12 @@ export default function PaymentsPage() {
const selectedMethod = paymentMethods.find(
(m) => String(m.id) === String(paymentTypeId) || String(m.name) === String(paymentTypeId),
);
const requiresReceiptUpload = isReceiptRequiredPayment(paymentKey, selectedMethod);
const isHaram =
isHaramText(selectedMethod?.name) ||
isHaramText(selectedMethod?.label) ||
isHaramText(selectedMethod?.id) ||
isHaramText(selectedMethod?.description) ||
isHaramText(paymentKey);
if (requiresReceiptUpload && !paymentImageFile) {
toast.error(t('payments.receiptRequired'));
return;
}
setPayingId(reservation.id);
try {
@ -201,7 +226,7 @@ export default function PaymentsPage() {
if (isGuest) {
return (
<div className="min-h-screen bg-gradient-to-b from-amber-50/50 to-white flex items-center justify-center p-4" dir={i18n.dir()}>
<div className="min-h-screen bg-linear-to-b from-amber-50/50 to-white flex items-center justify-center p-4" dir={i18n.dir()}>
<motion.div
initial={{ opacity: 0, scale: 0.95 }}
animate={{ opacity: 1, scale: 1 }}
@ -326,7 +351,10 @@ function PaymentCard({ reservation, payingId, paymentMethods, loadingPaymentMeth
const r = reservation;
const amount = r.depositAmount || r.totalPrice || 0;
const [showCashDialog, setShowCashDialog] = useState(false);
const [showReceiptModal, setShowReceiptModal] = useState(false);
const [localPaymentImage, setLocalPaymentImage] = useState(null);
const [receiptPreviewUrl, setReceiptPreviewUrl] = useState('');
const [receiptFileName, setReceiptFileName] = useState('');
const methods = paymentMethods.length > 0 ? paymentMethods : getPaymentMethods(t);
@ -339,12 +367,48 @@ function PaymentCard({ reservation, payingId, paymentMethods, loadingPaymentMeth
return methodName === paymentKey || methodLabel === paymentKey || methodId === paymentKey;
});
const isHaramPayment =
isHaramText(selectedPayment) ||
isHaramText(selectedMethod?.name) ||
isHaramText(selectedMethod?.label) ||
isHaramText(selectedMethod?.id) ||
isHaramText(selectedMethod?.description);
const requiresReceiptUpload = isReceiptRequiredPayment(selectedPayment, selectedMethod);
useEffect(() => {
if (!localPaymentImage) {
setReceiptPreviewUrl('');
return undefined;
}
const previewUrl = URL.createObjectURL(localPaymentImage);
setReceiptPreviewUrl(previewUrl);
return () => URL.revokeObjectURL(previewUrl);
}, [localPaymentImage]);
const handleSelectMethod = (optionId, method) => {
onSelectPayment(optionId);
if (isReceiptRequiredPayment(optionId, method)) {
setShowReceiptModal(true);
return;
}
setShowReceiptModal(false);
setLocalPaymentImage(null);
setReceiptPreviewUrl('');
setReceiptFileName('');
};
const handleReceiptSelection = (event) => {
const file = event.target.files?.[0] || null;
setLocalPaymentImage(file);
setReceiptFileName(file?.name || '');
};
const handleReceiptConfirm = () => {
if (!localPaymentImage) {
toast.error(t('payments.receiptRequired'));
return;
}
setShowReceiptModal(false);
};
return (
<>
@ -431,7 +495,7 @@ function PaymentCard({ reservation, payingId, paymentMethods, loadingPaymentMeth
key={optionId}
type="button"
disabled={!isActive}
onClick={() => isActive && onSelectPayment(optionId)}
onClick={() => isActive && handleSelectMethod(optionId, method)}
className={`relative flex items-start gap-3 p-4 rounded-2xl border-2 text-right transition-all ${
!isActive
? 'border-gray-100 bg-gray-50 opacity-50 cursor-not-allowed'
@ -476,18 +540,12 @@ function PaymentCard({ reservation, payingId, paymentMethods, loadingPaymentMeth
</div>
</div>
{/* Upload receipt only for Haram */}
{isHaramPayment && (
<div className="px-6 py-5 border-b border-gray-100">
<label className="block text-sm font-bold text-gray-700 mb-3">
{t('payments.uploadReceipt')}
</label>
<input
type="file"
accept="image/*"
onChange={(e) => setLocalPaymentImage(e.target.files?.[0] || null)}
className="block w-full rounded-2xl border border-gray-300 bg-white px-4 py-3 text-sm text-gray-700 file:mr-4 file:rounded-xl file:border-0 file:bg-amber-500 file:px-4 file:py-2 file:text-white hover:file:bg-amber-600"
/>
{requiresReceiptUpload && (
<div className="px-6 py-4 bg-amber-50 border-b border-amber-100">
<div className="flex items-start gap-2 text-sm text-amber-800">
<Info className="w-4 h-4 shrink-0 mt-0.5" />
<p>{t('payments.receiptUploadRequiredHint')}</p>
</div>
</div>
)}
@ -515,6 +573,69 @@ function PaymentCard({ reservation, payingId, paymentMethods, loadingPaymentMeth
</motion.button>
</div>
{showReceiptModal && (
<div className="fixed inset-0 z-60 flex items-center justify-center bg-black/60 px-4 py-8">
<motion.div
initial={{ opacity: 0, scale: 0.95 }}
animate={{ opacity: 1, scale: 1 }}
className="w-full max-w-md rounded-3xl bg-white p-6 shadow-2xl border border-gray-200"
>
<div className="flex items-center justify-between mb-4">
<div>
<h3 className="text-xl font-bold text-gray-900">{t('payments.receiptDialogTitle')}</h3>
<p className="text-sm text-gray-600 mt-1">{t('payments.receiptDialogDescription')}</p>
</div>
<div className="w-12 h-12 bg-amber-100 rounded-2xl flex items-center justify-center">
<CreditCard className="w-6 h-6 text-amber-600" />
</div>
</div>
<label className="block text-sm font-bold text-gray-700 mb-2">
{t('payments.uploadReceipt')}
</label>
<input
type="file"
accept="image/*"
onChange={handleReceiptSelection}
className="block w-full rounded-2xl border border-gray-300 bg-white px-4 py-3 text-sm text-gray-700 file:mr-4 file:rounded-xl file:border-0 file:bg-amber-500 file:px-4 file:py-2 file:text-white hover:file:bg-amber-600"
/>
{receiptFileName && (
<div className="mt-3 rounded-2xl border border-amber-200 bg-amber-50 px-3 py-2 text-sm text-amber-800">
{t('payments.receiptSelected')} {receiptFileName}
</div>
)}
{localPaymentImage && receiptPreviewUrl && (
<div className="mt-4 overflow-hidden rounded-2xl border border-gray-200 bg-gray-50 p-2">
<img
src={receiptPreviewUrl}
alt={t('payments.uploadReceipt')}
className="h-48 w-full rounded-xl object-cover"
/>
</div>
)}
<div className="mt-6 flex flex-col gap-3 sm:flex-row">
<button
type="button"
onClick={() => setShowReceiptModal(false)}
className="w-full rounded-xl border border-gray-300 px-4 py-3 text-sm font-semibold text-gray-700 hover:bg-gray-100"
>
{t('payments.closeButton')}
</button>
<button
type="button"
onClick={handleReceiptConfirm}
className="w-full rounded-xl bg-amber-500 px-4 py-3 text-sm font-semibold text-white hover:bg-amber-600"
>
{t('payments.continueToPay')}
</button>
</div>
</motion.div>
</div>
)}
{/* Cash pay button */}
<div className="px-6 py-4 border-b border-gray-100">
<button