Edit haram payment
This commit is contained in:
@ -1498,6 +1498,12 @@ const resources = {
|
|||||||
"payments.closeButton": "Close",
|
"payments.closeButton": "Close",
|
||||||
"payments.propertyLabel": "Property",
|
"payments.propertyLabel": "Property",
|
||||||
"payments.uploadReceipt": "Upload Receipt Image",
|
"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.loadingPaymentMethods": "Loading payment methods...",
|
||||||
"payments.noPaymentMethodsAvailable": "No payment methods available.",
|
"payments.noPaymentMethodsAvailable": "No payment methods available.",
|
||||||
"payments.paymentMethodName": "Payment Method",
|
"payments.paymentMethodName": "Payment Method",
|
||||||
@ -2549,6 +2555,13 @@ const resources = {
|
|||||||
"payment.transferDesc": "حول من حساب داخلي ثم أرسل المرجع",
|
"payment.transferDesc": "حول من حساب داخلي ثم أرسل المرجع",
|
||||||
"payment.electronic": "دفع إلكتروني",
|
"payment.electronic": "دفع إلكتروني",
|
||||||
"payment.electronicDesc": "سيتم تفعيل الدفع الإلكتروني والتحويل لاحقاً",
|
"payment.electronicDesc": "سيتم تفعيل الدفع الإلكتروني والتحويل لاحقاً",
|
||||||
|
"payments.uploadReceipt": "رفع صورة الوصل",
|
||||||
|
"payments.receiptDialogTitle": "رفع وصل الدفع",
|
||||||
|
"payments.receiptDialogDescription": "يرجى رفع صورة واضحة لوصل التحويل قبل إكمال الدفع.",
|
||||||
|
"payments.receiptUploadRequiredHint": "مطلوب رفع صورة الوصل لهذه الطريقة.",
|
||||||
|
"payments.receiptRequired": "يرجى رفع صورة وصل الدفع أولاً.",
|
||||||
|
"payments.receiptSelected": "الملف المختار:",
|
||||||
|
"payments.continueToPay": "متابعة الدفع",
|
||||||
|
|
||||||
/* ─── Owner Properties Page ─── */
|
/* ─── Owner Properties Page ─── */
|
||||||
"deleteConfirmTitle": "تأكيد الحذف",
|
"deleteConfirmTitle": "تأكيد الحذف",
|
||||||
|
|||||||
@ -70,6 +70,32 @@ function isHaramText(value) {
|
|||||||
return text.includes('haram') || text.includes('هرم') || text.includes('هرام');
|
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() {
|
export default function PaymentsPage() {
|
||||||
const { t, i18n } = useTranslation();
|
const { t, i18n } = useTranslation();
|
||||||
const [reservations, setReservations] = useState([]);
|
const [reservations, setReservations] = useState([]);
|
||||||
@ -165,13 +191,12 @@ export default function PaymentsPage() {
|
|||||||
const selectedMethod = paymentMethods.find(
|
const selectedMethod = paymentMethods.find(
|
||||||
(m) => String(m.id) === String(paymentTypeId) || String(m.name) === String(paymentTypeId),
|
(m) => String(m.id) === String(paymentTypeId) || String(m.name) === String(paymentTypeId),
|
||||||
);
|
);
|
||||||
|
const requiresReceiptUpload = isReceiptRequiredPayment(paymentKey, selectedMethod);
|
||||||
|
|
||||||
const isHaram =
|
if (requiresReceiptUpload && !paymentImageFile) {
|
||||||
isHaramText(selectedMethod?.name) ||
|
toast.error(t('payments.receiptRequired'));
|
||||||
isHaramText(selectedMethod?.label) ||
|
return;
|
||||||
isHaramText(selectedMethod?.id) ||
|
}
|
||||||
isHaramText(selectedMethod?.description) ||
|
|
||||||
isHaramText(paymentKey);
|
|
||||||
|
|
||||||
setPayingId(reservation.id);
|
setPayingId(reservation.id);
|
||||||
try {
|
try {
|
||||||
@ -201,7 +226,7 @@ export default function PaymentsPage() {
|
|||||||
|
|
||||||
if (isGuest) {
|
if (isGuest) {
|
||||||
return (
|
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
|
<motion.div
|
||||||
initial={{ opacity: 0, scale: 0.95 }}
|
initial={{ opacity: 0, scale: 0.95 }}
|
||||||
animate={{ opacity: 1, scale: 1 }}
|
animate={{ opacity: 1, scale: 1 }}
|
||||||
@ -326,7 +351,10 @@ function PaymentCard({ reservation, payingId, paymentMethods, loadingPaymentMeth
|
|||||||
const r = reservation;
|
const r = reservation;
|
||||||
const amount = r.depositAmount || r.totalPrice || 0;
|
const amount = r.depositAmount || r.totalPrice || 0;
|
||||||
const [showCashDialog, setShowCashDialog] = useState(false);
|
const [showCashDialog, setShowCashDialog] = useState(false);
|
||||||
|
const [showReceiptModal, setShowReceiptModal] = useState(false);
|
||||||
const [localPaymentImage, setLocalPaymentImage] = useState(null);
|
const [localPaymentImage, setLocalPaymentImage] = useState(null);
|
||||||
|
const [receiptPreviewUrl, setReceiptPreviewUrl] = useState('');
|
||||||
|
const [receiptFileName, setReceiptFileName] = useState('');
|
||||||
|
|
||||||
const methods = paymentMethods.length > 0 ? paymentMethods : getPaymentMethods(t);
|
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;
|
return methodName === paymentKey || methodLabel === paymentKey || methodId === paymentKey;
|
||||||
});
|
});
|
||||||
|
|
||||||
const isHaramPayment =
|
const requiresReceiptUpload = isReceiptRequiredPayment(selectedPayment, selectedMethod);
|
||||||
isHaramText(selectedPayment) ||
|
|
||||||
isHaramText(selectedMethod?.name) ||
|
useEffect(() => {
|
||||||
isHaramText(selectedMethod?.label) ||
|
if (!localPaymentImage) {
|
||||||
isHaramText(selectedMethod?.id) ||
|
setReceiptPreviewUrl('');
|
||||||
isHaramText(selectedMethod?.description);
|
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 (
|
return (
|
||||||
<>
|
<>
|
||||||
@ -431,7 +495,7 @@ function PaymentCard({ reservation, payingId, paymentMethods, loadingPaymentMeth
|
|||||||
key={optionId}
|
key={optionId}
|
||||||
type="button"
|
type="button"
|
||||||
disabled={!isActive}
|
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 ${
|
className={`relative flex items-start gap-3 p-4 rounded-2xl border-2 text-right transition-all ${
|
||||||
!isActive
|
!isActive
|
||||||
? 'border-gray-100 bg-gray-50 opacity-50 cursor-not-allowed'
|
? 'border-gray-100 bg-gray-50 opacity-50 cursor-not-allowed'
|
||||||
@ -476,18 +540,12 @@ function PaymentCard({ reservation, payingId, paymentMethods, loadingPaymentMeth
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Upload receipt only for Haram */}
|
{requiresReceiptUpload && (
|
||||||
{isHaramPayment && (
|
<div className="px-6 py-4 bg-amber-50 border-b border-amber-100">
|
||||||
<div className="px-6 py-5 border-b border-gray-100">
|
<div className="flex items-start gap-2 text-sm text-amber-800">
|
||||||
<label className="block text-sm font-bold text-gray-700 mb-3">
|
<Info className="w-4 h-4 shrink-0 mt-0.5" />
|
||||||
{t('payments.uploadReceipt')}
|
<p>{t('payments.receiptUploadRequiredHint')}</p>
|
||||||
</label>
|
</div>
|
||||||
<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"
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
@ -515,6 +573,69 @@ function PaymentCard({ reservation, payingId, paymentMethods, loadingPaymentMeth
|
|||||||
</motion.button>
|
</motion.button>
|
||||||
</div>
|
</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 */}
|
{/* Cash pay button */}
|
||||||
<div className="px-6 py-4 border-b border-gray-100">
|
<div className="px-6 py-4 border-b border-gray-100">
|
||||||
<button
|
<button
|
||||||
|
|||||||
Reference in New Issue
Block a user