Added translation to payments
All checks were successful
Build frontend / build (push) Successful in 1m43s

This commit is contained in:
Rahaf
2026-07-02 10:38:55 +03:00
parent dab5b62fb7
commit c7698741c7
3 changed files with 68 additions and 49 deletions

View File

@ -60,6 +60,9 @@ const resources = {
"termsOfUse": "Terms of Use", "termsOfUse": "Terms of Use",
"privacyPolicy": "Privacy Policy", "privacyPolicy": "Privacy Policy",
"invalidCredentials": "Invalid credentials", "invalidCredentials": "Invalid credentials",
"serverError": "Server error, please try again later",
"apiNotFound": "Service temporarily unavailable",
"tooManyRequests": "Too many requests, please wait",
"connectionError": "A connection error occurred", "connectionError": "A connection error occurred",
"phoneInvalid": "Invalid phone number", "phoneInvalid": "Invalid phone number",
"resendSuccess": "Verification code resent", "resendSuccess": "Verification code resent",
@ -1488,6 +1491,16 @@ const resources = {
"payments.cashPendingDialogDesc2": "The platform will confirm the payment after receiving the amount.", "payments.cashPendingDialogDesc2": "The platform will confirm the payment after receiving the amount.",
"payments.platformOfficeFullAddress": "Platform Office: Abu Rumaneh, Al-Malki Street, Damascus", "payments.platformOfficeFullAddress": "Platform Office: Abu Rumaneh, Al-Malki Street, Damascus",
"payments.closeButton": "Close", "payments.closeButton": "Close",
"payments.propertyLabel": "Property",
"payments.method.cash": "Cash Payment",
"payments.method.cashDesc": "Pay now with automatic booking confirmation",
"payments.method.office": "Cash",
"payments.method.officeDesc": "Pay at the platform office. Confirmed later.",
"payments.method.transfer": "Internal Transfer",
"payments.method.transferDesc": "Transfer from internal account then send reference",
"payments.method.electronic": "Electronic Payment",
"payments.method.electronicDesc": "Electronic payment will be activated later",
/* ─── Onboarding ─── */ /* ─── Onboarding ─── */
"onboarding.step1.title": "Welcome to SweetHome", "onboarding.step1.title": "Welcome to SweetHome",
@ -1655,6 +1668,9 @@ const resources = {
"termsOfUse": "شروط الاستخدام", "termsOfUse": "شروط الاستخدام",
"privacyPolicy": "سياسة الخصوصية", "privacyPolicy": "سياسة الخصوصية",
"invalidCredentials": "بيانات الدخول غير صحيحة", "invalidCredentials": "بيانات الدخول غير صحيحة",
"serverError": "خطأ في الخادم، يرجى المحاولة لاحقاً",
"apiNotFound": "الخدمة غير متاحة مؤقتاً",
"tooManyRequests": "طلبات كثيرة جداً، يرجى الانتظار",
"connectionError": "حدث خطأ في الاتصال", "connectionError": "حدث خطأ في الاتصال",
"phoneInvalid": "رقم الهاتف غير صالح", "phoneInvalid": "رقم الهاتف غير صالح",
"resendSuccess": "تم إرسال رمز التحقق مجدداً", "resendSuccess": "تم إرسال رمز التحقق مجدداً",
@ -2924,6 +2940,16 @@ const resources = {
"payments.cashPendingDialogDesc2": "ستقوم المنصة بتأكيد الدفع بعد استلام المبلع.", "payments.cashPendingDialogDesc2": "ستقوم المنصة بتأكيد الدفع بعد استلام المبلع.",
"payments.platformOfficeFullAddress": "مكتب المنصة: أبو رمانة، شارع المالكي، دمشق", "payments.platformOfficeFullAddress": "مكتب المنصة: أبو رمانة، شارع المالكي، دمشق",
"payments.closeButton": "إغلاق", "payments.closeButton": "إغلاق",
"payments.propertyLabel": "عقار",
"payments.method.cash": "الدفع النقدي",
"payments.method.cashDesc": "ادفع الآن عبر شام كاش مع تأكيد تلقائي للحجز",
"payments.method.office": "نقداً",
"payments.method.officeDesc": "ادفع في مكتب المنصة. يتم التأكيد لاحقاً",
"payments.method.transfer": "تحويل داخلي",
"payments.method.transferDesc": "حول من حساب داخلي ثم أرسل المرجع",
"payments.method.electronic": "دفع إلكتروني",
"payments.method.electronicDesc": "سيتم تفعيل الدفع الإلكتروني لاحقاً",
/* ─── Onboarding ─── */ /* ─── Onboarding ─── */
"onboarding.step1.title": "مرحباً بك في SweetHome", "onboarding.step1.title": "مرحباً بك في SweetHome",

View File

@ -153,8 +153,21 @@ export default function LoginPage() {
} }
setStep("otp"); setStep("otp");
} else if (result.status >= 500) {
console.error("[Login] Server error:", result.status, result.data);
toast.error(t("serverError"), {
style: { background: "#fee2e2", color: "#991b1b" },
});
} else if (result.status === 404) {
console.error("[Login] API endpoint not found:", result.status);
toast.error(t("apiNotFound"), {
style: { background: "#fee2e2", color: "#991b1b" },
});
} else if (result.status === 429) {
toast.error(t("tooManyRequests"), {
style: { background: "#fee2e2", color: "#991b1b" },
});
} else { } else {
// Other error
console.error("[Login] Unexpected status:", result.status, result.data); console.error("[Login] Unexpected status:", result.status, result.data);
toast.error( toast.error(
result.data?.message || result.data || t("invalidCredentials"), result.data?.message || result.data || t("invalidCredentials"),

View File

@ -30,47 +30,27 @@ import { payDeposit, getMyTransaction } from '@/app/utils/api';
const STATUS_MAP = ['pending', 'ownerConfirmed', 'depositPaid', 'depositConfirmed', 'completed', 'cancelled']; const STATUS_MAP = ['pending', 'ownerConfirmed', 'depositPaid', 'depositConfirmed', 'completed', 'cancelled'];
const STATUS_CONFIG = { function getStatusConfig(t) {
pending: { label: 'قيد الانتظار', color: 'bg-yellow-100 text-yellow-800 border-yellow-300', icon: Clock }, return {
ownerConfirmed: { label: 'مؤكد من المالك', color: 'bg-blue-100 text-blue-800 border-blue-300', icon: ShieldCheck }, pending: { label: t('bookingStatus.pending'), color: 'bg-yellow-100 text-yellow-800 border-yellow-300', icon: Clock },
depositPaid: { label: 'تم دفع السلفة', color: 'bg-orange-100 text-orange-800 border-orange-300', icon: Wallet }, ownerConfirmed: { label: t('bookingStatus.ownerConfirmed'), color: 'bg-blue-100 text-blue-800 border-blue-300', icon: ShieldCheck },
depositConfirmed: { label: 'تم تأكيد الدفع', color: 'bg-green-100 text-green-800 border-green-300', icon: Check }, depositPaid: { label: t('bookingStatus.depositPaid'), color: 'bg-orange-100 text-orange-800 border-orange-300', icon: Wallet },
completed: { label: 'منتهي', color: 'bg-teal-100 text-teal-800 border-teal-300', icon: Check }, depositConfirmed: { label: t('bookingStatus.depositConfirmed'), color: 'bg-green-100 text-green-800 border-green-300', icon: Check },
cancelled: { label: 'ملغي', color: 'bg-red-100 text-red-800 border-red-300', icon: X }, completed: { label: t('bookingStatus.completed'), color: 'bg-teal-100 text-teal-800 border-teal-300', icon: Check },
cancelled: { label: t('bookingStatus.cancelled'), color: 'bg-red-100 text-red-800 border-red-300', icon: X },
}; };
const PAYMENT_METHODS = [
{
id: 'cash',
label: 'الدفع النقدي',
desc: 'ادفع الآن عبر شام كاش مع تأكيد تلقائي للحجز',
icon: Banknote,
active: true,
},
{
id: 'office',
label: 'نقداً',
desc: 'ادفع في مكتب المنصة. يتم التأكيد لاحقاً',
icon: Building,
active: true,
},
{
id: 'transfer',
label: 'تحويل داخلي',
desc: 'حول من حساب داخلي ثم أرسل المرجع',
icon: ArrowLeftRight,
active: false,
},
{
id: 'electronic',
label: 'دفع إلكتروني',
desc: 'سيتم تفعيل الدفع الإلكتروني والتحويل لاحقاً',
icon: CreditCard,
active: false,
} }
];
function formatCurrency(v, sign = 'ل.س') { function getPaymentMethods(t) {
return [
{ id: 'cash', label: t('payments.method.cash'), desc: t('payments.method.cashDesc'), icon: Banknote, active: true },
{ id: 'office', label: t('payments.method.office'), desc: t('payments.method.officeDesc'), icon: Building, active: true },
{ id: 'transfer', label: t('payments.method.transfer'), desc: t('payments.method.transferDesc'), icon: ArrowLeftRight, active: false },
{ id: 'electronic', label: t('payments.method.electronic'), desc: t('payments.method.electronicDesc'), icon: CreditCard, active: false },
];
}
function formatCurrency(v, sign = '') {
return `${sign} ${Number(v ?? 0).toLocaleString()}`; return `${sign} ${Number(v ?? 0).toLocaleString()}`;
} }
@ -82,7 +62,7 @@ function formatDate(date) {
} }
export default function PaymentsPage() { export default function PaymentsPage() {
const { t } = useTranslation(); const { t, i18n } = useTranslation();
const [reservations, setReservations] = useState([]); const [reservations, setReservations] = useState([]);
const [loading, setLoading] = useState(true); const [loading, setLoading] = useState(true);
const [payingId, setPayingId] = useState(null); const [payingId, setPayingId] = useState(null);
@ -109,10 +89,10 @@ export default function PaymentsPage() {
endDate: reservation.endDate, endDate: reservation.endDate,
totalPrice: reservation.totalPrice ?? transaction.amount ?? 0, totalPrice: reservation.totalPrice ?? transaction.amount ?? 0,
depositAmount: transaction.amount ?? reservation.totalPrice ?? 0, depositAmount: transaction.amount ?? reservation.totalPrice ?? 0,
currencySign: currency.sign || 'ل.س', currencySign: currency.sign || t('currency.syp'),
currencyName: currency.name || '', currencyName: currency.name || '',
currencyRate: currency.rate, currencyRate: currency.rate,
propertyName: propertyInfo.name || propertyInfo.address || reservation.propertyName || `عقار #${reservation.id || ''}`, propertyName: propertyInfo.name || propertyInfo.address || reservation.propertyName || `${t('payments.propertyLabel')} #${reservation.id || ''}`,
propertyAddress: propertyInfo.address || reservation.propertyAddress || '', propertyAddress: propertyInfo.address || reservation.propertyAddress || '',
propertyCity: propertyInfo.city || reservation.city || '', propertyCity: propertyInfo.city || reservation.city || '',
_deposit: deposit, _deposit: deposit,
@ -127,7 +107,7 @@ export default function PaymentsPage() {
} finally { } finally {
setLoading(false); setLoading(false);
} }
}, []); }, [t]);
useEffect(() => { useEffect(() => {
if (AuthService.isGuest()) { if (AuthService.isGuest()) {
@ -156,7 +136,7 @@ export default function PaymentsPage() {
if (loading) { if (loading) {
return ( return (
<div className="min-h-screen bg-gray-50 flex items-center justify-center" dir="rtl"> <div className="min-h-screen bg-gray-50 flex items-center justify-center" dir={i18n.dir()}>
<Loader2 className="w-12 h-12 text-amber-500 animate-spin" /> <Loader2 className="w-12 h-12 text-amber-500 animate-spin" />
</div> </div>
); );
@ -164,7 +144,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="rtl"> <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()}>
<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 }}
@ -193,7 +173,7 @@ export default function PaymentsPage() {
const others = reservations.filter((r) => !canPay(r.status)); const others = reservations.filter((r) => !canPay(r.status));
return ( return (
<div className="min-h-screen bg-gray-50 py-8" dir="rtl"> <div className="min-h-screen bg-gray-50 py-8" dir={i18n.dir()}>
<Toaster position="top-center" reverseOrder={false} /> <Toaster position="top-center" reverseOrder={false} />
<div className="container mx-auto px-4 max-w-4xl"> <div className="container mx-auto px-4 max-w-4xl">
<motion.div <motion.div
@ -232,7 +212,7 @@ export default function PaymentsPage() {
</h2> </h2>
{others.map((r, i) => { {others.map((r, i) => {
const statusKey = STATUS_MAP[r.status] || 'pending'; const statusKey = STATUS_MAP[r.status] || 'pending';
const cfg = STATUS_CONFIG[statusKey]; const cfg = getStatusConfig(t)[statusKey];
const Icon = cfg.icon; const Icon = cfg.icon;
const amount = r.depositAmount || r.totalPrice || 0; const amount = r.depositAmount || r.totalPrice || 0;
@ -352,7 +332,7 @@ export default function PaymentsPage() {
<div className="px-6 py-5 border-b border-gray-100"> <div className="px-6 py-5 border-b border-gray-100">
<p className="text-sm font-bold text-gray-700 mb-3">{t('payments.paymentMethodLabel')}</p> <p className="text-sm font-bold text-gray-700 mb-3">{t('payments.paymentMethodLabel')}</p>
<div className="grid grid-cols-1 sm:grid-cols-2 gap-3"> <div className="grid grid-cols-1 sm:grid-cols-2 gap-3">
{PAYMENT_METHODS.map((method) => { {getPaymentMethods(t).map((method) => {
const isSelected = selectedPayment === method.id; const isSelected = selectedPayment === method.id;
const Icon = method.icon; const Icon = method.icon;
return ( return (