forked from amer2004/SweetHome
Added translation to all site with edit style
This commit is contained in:
@ -157,7 +157,7 @@ export default function ClientLayout({ children }) {
|
|||||||
<div className="min-h-screen flex items-center justify-center">
|
<div className="min-h-screen flex items-center justify-center">
|
||||||
<div className="text-center">
|
<div className="text-center">
|
||||||
<div className="w-16 h-16 border-4 border-amber-500 border-t-transparent rounded-full animate-spin mx-auto mb-4"></div>
|
<div className="w-16 h-16 border-4 border-amber-500 border-t-transparent rounded-full animate-spin mx-auto mb-4"></div>
|
||||||
<p className="text-gray-600">جاري التحميل...</p>
|
<p className="text-gray-600" suppressHydrationWarning>{t("loading")}</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
@ -202,7 +202,7 @@ export default function ClientLayout({ children }) {
|
|||||||
<path d="M11 2a3 3 0 0 0-3 3v6.5a.5.5 0 0 0 1 0V5a2 2 0 1 1 4 0v6.5a.5.5 0 0 0 1 0V5a3 3 0 0 0-3-3z"/>
|
<path d="M11 2a3 3 0 0 0-3 3v6.5a.5.5 0 0 0 1 0V5a2 2 0 1 1 4 0v6.5a.5.5 0 0 0 1 0V5a3 3 0 0 0-3-3z"/>
|
||||||
<path d="M1.5 12.5A1.5 1.5 0 0 0 3 14h10a1.5 1.5 0 0 0 0-3H3a1.5 1.5 0 0 0-1.5 1.5z"/>
|
<path d="M1.5 12.5A1.5 1.5 0 0 0 3 14h10a1.5 1.5 0 0 0 0-3H3a1.5 1.5 0 0 0-1.5 1.5z"/>
|
||||||
</svg>
|
</svg>
|
||||||
<span className="text-sm font-semibold">تحميل التطبيق</span>
|
<span className="text-sm font-semibold">{t("downloadApp")}</span>
|
||||||
<svg className="w-4 h-4 transition-transform group-hover:rotate-180" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M19 9l-7 7-7-7"/></svg>
|
<svg className="w-4 h-4 transition-transform group-hover:rotate-180" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M19 9l-7 7-7-7"/></svg>
|
||||||
</button>
|
</button>
|
||||||
<div className="absolute right-0 mt-2 w-64 bg-white rounded-xl shadow-xl border border-gray-200 overflow-hidden z-50 opacity-0 invisible group-hover:opacity-100 group-hover:visible transition-all duration-200 translate-y-2 group-hover:translate-y-0">
|
<div className="absolute right-0 mt-2 w-64 bg-white rounded-xl shadow-xl border border-gray-200 overflow-hidden z-50 opacity-0 invisible group-hover:opacity-100 group-hover:visible transition-all duration-200 translate-y-2 group-hover:translate-y-0">
|
||||||
@ -215,8 +215,8 @@ export default function ClientLayout({ children }) {
|
|||||||
</svg>
|
</svg>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<p className="font-semibold text-gray-900 text-sm">Android</p>
|
<p className="font-semibold text-gray-900 text-sm">{t("android")}</p>
|
||||||
<p className="text-xs text-green-600">تحميل APK</p>
|
<p className="text-xs text-green-600">{t("downloadAPK")}</p>
|
||||||
</div>
|
</div>
|
||||||
</a>
|
</a>
|
||||||
<div className="flex items-center gap-3 px-4 py-3 rounded-lg opacity-50 cursor-not-allowed">
|
<div className="flex items-center gap-3 px-4 py-3 rounded-lg opacity-50 cursor-not-allowed">
|
||||||
@ -226,16 +226,16 @@ export default function ClientLayout({ children }) {
|
|||||||
</svg>
|
</svg>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<p className="font-semibold text-gray-400 text-sm">iOS</p>
|
<p className="font-semibold text-gray-400 text-sm">{t("ios")}</p>
|
||||||
<p className="text-xs text-gray-400">قريباً</p>
|
<p className="text-xs text-gray-400">{t("comingSoon")}</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<NavLink light={isHomePage} href="/">الرئيسية</NavLink>
|
<NavLink light={isHomePage} href="/">{t("home")}</NavLink>
|
||||||
<NavLink light={isHomePage} href="/properties">عقاراتنا</NavLink>
|
<NavLink light={isHomePage} href="/properties">{t("ourProperties")}</NavLink>
|
||||||
|
|
||||||
|
|
||||||
{isOwner && (
|
{isOwner && (
|
||||||
@ -243,25 +243,19 @@ export default function ClientLayout({ children }) {
|
|||||||
<NavLink light={isHomePage} href="/owner/properties">
|
<NavLink light={isHomePage} href="/owner/properties">
|
||||||
<span className="flex items-center gap-2">
|
<span className="flex items-center gap-2">
|
||||||
<Building className="w-4 h-4" />
|
<Building className="w-4 h-4" />
|
||||||
عقاراتي
|
{t("myProperties")}
|
||||||
</span>
|
</span>
|
||||||
</NavLink>
|
</NavLink>
|
||||||
<NavLink light={isHomePage} href="/owner/reservations">
|
<NavLink light={isHomePage} href="/owner/reservations">
|
||||||
<span className="flex items-center gap-2">
|
<span className="flex items-center gap-2">
|
||||||
<Calendar className="w-4 h-4" />
|
<Calendar className="w-4 h-4" />
|
||||||
الحجوزات
|
{t("reservations")}
|
||||||
</span>
|
</span>
|
||||||
</NavLink>
|
</NavLink>
|
||||||
{/* <NavLink light={isHomePage} href="/owner/calendar">
|
|
||||||
<span className="flex items-center gap-2">
|
|
||||||
<CalendarDays className="w-4 h-4" />
|
|
||||||
التقويم
|
|
||||||
</span>
|
|
||||||
</NavLink> */}
|
|
||||||
<NavLink light={isHomePage} href="/owner/profits">
|
<NavLink light={isHomePage} href="/owner/profits">
|
||||||
<span className="flex items-center gap-2">
|
<span className="flex items-center gap-2">
|
||||||
<TrendingUp className="w-4 h-4" />
|
<TrendingUp className="w-4 h-4" />
|
||||||
الأرباح
|
{t("profits")}
|
||||||
</span>
|
</span>
|
||||||
</NavLink>
|
</NavLink>
|
||||||
</>
|
</>
|
||||||
@ -272,13 +266,13 @@ export default function ClientLayout({ children }) {
|
|||||||
<NavLink light={isHomePage} href="/login">
|
<NavLink light={isHomePage} href="/login">
|
||||||
<span className="flex items-center gap-2">
|
<span className="flex items-center gap-2">
|
||||||
<LogIn className="w-4 h-4" />
|
<LogIn className="w-4 h-4" />
|
||||||
تسجيل الدخول
|
{t("login")}
|
||||||
</span>
|
</span>
|
||||||
</NavLink>
|
</NavLink>
|
||||||
<NavLink light={isHomePage} href="/auth/choose-role">
|
<NavLink light={isHomePage} href="/auth/choose-role">
|
||||||
<span className="flex items-center gap-2">
|
<span className="flex items-center gap-2">
|
||||||
<UserPlus className="w-4 h-4" />
|
<UserPlus className="w-4 h-4" />
|
||||||
إنشاء حساب
|
{t("createAccount")}
|
||||||
</span>
|
</span>
|
||||||
</NavLink>
|
</NavLink>
|
||||||
</>
|
</>
|
||||||
@ -322,13 +316,13 @@ export default function ClientLayout({ children }) {
|
|||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<p className="font-bold">
|
<p className="font-bold">
|
||||||
{user?.name || "مستخدم"}
|
{user?.name || t("userLabel")}
|
||||||
</p>
|
</p>
|
||||||
<p className="text-xs text-amber-100">
|
<p className="text-xs text-amber-100">
|
||||||
{user?.email || ""}
|
{user?.email || ""}
|
||||||
</p>
|
</p>
|
||||||
<p className="text-xs text-amber-100 mt-1">
|
<p className="text-xs text-amber-100 mt-1">
|
||||||
{UserRoleLabels[user?.role] || 'زائر'}
|
{t(UserRoleLabels[user?.role] ? `userRole.${user?.role.toLowerCase()}` : 'visitor')}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -342,9 +336,9 @@ export default function ClientLayout({ children }) {
|
|||||||
>
|
>
|
||||||
<UserCircle className="w-5 h-5 text-amber-500" />
|
<UserCircle className="w-5 h-5 text-amber-500" />
|
||||||
<div>
|
<div>
|
||||||
<p className="font-medium">الملف الشخصي</p>
|
<p className="font-medium">{t("profile")}</p>
|
||||||
<p className="text-xs text-gray-500">
|
<p className="text-xs text-gray-500">
|
||||||
عرض وتعديل معلوماتك
|
{t("viewAndEditInfo")}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</Link>
|
</Link>
|
||||||
@ -356,9 +350,9 @@ export default function ClientLayout({ children }) {
|
|||||||
>
|
>
|
||||||
<Heart className="w-5 h-5 text-amber-500" />
|
<Heart className="w-5 h-5 text-amber-500" />
|
||||||
<div>
|
<div>
|
||||||
<p className="font-medium">المفضلة</p>
|
<p className="font-medium">{t("favorites")}</p>
|
||||||
<p className="text-xs text-gray-500">
|
<p className="text-xs text-gray-500">
|
||||||
العقارات المحفوظة
|
{t("savedProperties")}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</Link>
|
</Link>
|
||||||
@ -370,9 +364,9 @@ export default function ClientLayout({ children }) {
|
|||||||
>
|
>
|
||||||
<CalendarDays className="w-5 h-5 text-amber-500" />
|
<CalendarDays className="w-5 h-5 text-amber-500" />
|
||||||
<div>
|
<div>
|
||||||
<p className="font-medium">عقاراتي المحجوزة</p>
|
<p className="font-medium">{t("myBookedProperties")}</p>
|
||||||
<p className="text-xs text-gray-500">
|
<p className="text-xs text-gray-500">
|
||||||
عرض وتقييم العقارات المحجوزة
|
{t("viewAndRateBooked")}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</Link>
|
</Link>
|
||||||
@ -384,9 +378,9 @@ export default function ClientLayout({ children }) {
|
|||||||
>
|
>
|
||||||
<Star className="w-5 h-5 text-amber-500" />
|
<Star className="w-5 h-5 text-amber-500" />
|
||||||
<div>
|
<div>
|
||||||
<p className="font-medium">تقييماتي</p>
|
<p className="font-medium">{t("myRatings")}</p>
|
||||||
<p className="text-xs text-gray-500">
|
<p className="text-xs text-gray-500">
|
||||||
التقييمات التي تلقيتها
|
{t("ratingsReceived")}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</Link>
|
</Link>
|
||||||
@ -398,9 +392,9 @@ export default function ClientLayout({ children }) {
|
|||||||
>
|
>
|
||||||
<Settings className="w-5 h-5 text-amber-500" />
|
<Settings className="w-5 h-5 text-amber-500" />
|
||||||
<div>
|
<div>
|
||||||
<p className="font-medium">الإعدادات</p>
|
<p className="font-medium">{t("settings")}</p>
|
||||||
<p className="text-xs text-gray-500">
|
<p className="text-xs text-gray-500">
|
||||||
إعدادات الحساب والأمان
|
{t("accountAndSecurity")}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</Link>
|
</Link>
|
||||||
@ -416,9 +410,9 @@ export default function ClientLayout({ children }) {
|
|||||||
>
|
>
|
||||||
<Building className="w-5 h-5 text-amber-500" />
|
<Building className="w-5 h-5 text-amber-500" />
|
||||||
<div>
|
<div>
|
||||||
<p className="font-medium">عقاراتي</p>
|
<p className="font-medium">{t("myProperties")}</p>
|
||||||
<p className="text-xs text-gray-500">
|
<p className="text-xs text-gray-500">
|
||||||
إدارة عقاراتك
|
{t("manageYourProperties")}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</Link>
|
</Link>
|
||||||
@ -430,9 +424,9 @@ export default function ClientLayout({ children }) {
|
|||||||
>
|
>
|
||||||
<PlusCircle className="w-5 h-5 text-amber-500" />
|
<PlusCircle className="w-5 h-5 text-amber-500" />
|
||||||
<div>
|
<div>
|
||||||
<p className="font-medium">إضافة عقار</p>
|
<p className="font-medium">{t("addProperty")}</p>
|
||||||
<p className="text-xs text-gray-500">
|
<p className="text-xs text-gray-500">
|
||||||
أضف عقاراً جديداً
|
{t("addNewProperty")}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</Link>
|
</Link>
|
||||||
@ -444,9 +438,9 @@ export default function ClientLayout({ children }) {
|
|||||||
>
|
>
|
||||||
<Calendar className="w-5 h-5 text-amber-500" />
|
<Calendar className="w-5 h-5 text-amber-500" />
|
||||||
<div>
|
<div>
|
||||||
<p className="font-medium">الحجوزات</p>
|
<p className="font-medium">{t("reservations")}</p>
|
||||||
<p className="text-xs text-gray-500">
|
<p className="text-xs text-gray-500">
|
||||||
إدارة حجوزاتك
|
{t("manageReservations")}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</Link>
|
</Link>
|
||||||
@ -458,9 +452,9 @@ export default function ClientLayout({ children }) {
|
|||||||
>
|
>
|
||||||
<CalendarDays className="w-5 h-5 text-amber-500" />
|
<CalendarDays className="w-5 h-5 text-amber-500" />
|
||||||
<div>
|
<div>
|
||||||
<p className="font-medium">التقويم</p>
|
<p className="font-medium">{t("calendar")}</p>
|
||||||
<p className="text-xs text-gray-500">
|
<p className="text-xs text-gray-500">
|
||||||
جدول توفر العقارات
|
{t("propertyAvailability")}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</Link>
|
</Link>
|
||||||
@ -472,9 +466,9 @@ export default function ClientLayout({ children }) {
|
|||||||
>
|
>
|
||||||
<TrendingUp className="w-5 h-5 text-amber-500" />
|
<TrendingUp className="w-5 h-5 text-amber-500" />
|
||||||
<div>
|
<div>
|
||||||
<p className="font-medium">الأرباح</p>
|
<p className="font-medium">{t("profits")}</p>
|
||||||
<p className="text-xs text-gray-500">
|
<p className="text-xs text-gray-500">
|
||||||
إحصائيات وأرباح
|
{t("statisticsAndProfits")}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</Link>
|
</Link>
|
||||||
@ -486,9 +480,9 @@ export default function ClientLayout({ children }) {
|
|||||||
>
|
>
|
||||||
<DollarSign className="w-5 h-5 text-amber-500" />
|
<DollarSign className="w-5 h-5 text-amber-500" />
|
||||||
<div>
|
<div>
|
||||||
<p className="font-medium">دفتر الحسابات</p>
|
<p className="font-medium">{t("accountBook")}</p>
|
||||||
<p className="text-xs text-gray-500">
|
<p className="text-xs text-gray-500">
|
||||||
سجل المعاملات المالية
|
{t("financialRecords")}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</Link>
|
</Link>
|
||||||
@ -506,9 +500,9 @@ export default function ClientLayout({ children }) {
|
|||||||
>
|
>
|
||||||
<Calendar className="w-5 h-5 text-amber-500" />
|
<Calendar className="w-5 h-5 text-amber-500" />
|
||||||
<div>
|
<div>
|
||||||
<p className="font-medium">حجوزاتي</p>
|
<p className="font-medium">{t("myBookings")}</p>
|
||||||
<p className="text-xs text-gray-500">
|
<p className="text-xs text-gray-500">
|
||||||
عرض حجوزاتك
|
{t("viewBookings")}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</Link>
|
</Link>
|
||||||
@ -520,9 +514,9 @@ export default function ClientLayout({ children }) {
|
|||||||
>
|
>
|
||||||
<CalendarDays className="w-5 h-5 text-amber-500" />
|
<CalendarDays className="w-5 h-5 text-amber-500" />
|
||||||
<div>
|
<div>
|
||||||
<p className="font-medium">عقاراتي المحجوزة</p>
|
<p className="font-medium">{t("myBookedProperties")}</p>
|
||||||
<p className="text-xs text-gray-500">
|
<p className="text-xs text-gray-500">
|
||||||
عرض وتقييم العقارات المحجوزة
|
{t("viewAndRateBooked")}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</Link>
|
</Link>
|
||||||
@ -534,9 +528,9 @@ export default function ClientLayout({ children }) {
|
|||||||
>
|
>
|
||||||
<Star className="w-5 h-5 text-amber-500" />
|
<Star className="w-5 h-5 text-amber-500" />
|
||||||
<div>
|
<div>
|
||||||
<p className="font-medium">تقييماتي</p>
|
<p className="font-medium">{t("myRatings")}</p>
|
||||||
<p className="text-xs text-gray-500">
|
<p className="text-xs text-gray-500">
|
||||||
التقييمات التي تلقيتها
|
{t("ratingsReceived")}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</Link>
|
</Link>
|
||||||
@ -548,9 +542,9 @@ export default function ClientLayout({ children }) {
|
|||||||
>
|
>
|
||||||
<FileText className="w-5 h-5 text-amber-500" />
|
<FileText className="w-5 h-5 text-amber-500" />
|
||||||
<div>
|
<div>
|
||||||
<p className="font-medium">البلاغات</p>
|
<p className="font-medium">{t("reports")}</p>
|
||||||
<p className="text-xs text-gray-500">
|
<p className="text-xs text-gray-500">
|
||||||
تقديم ومتابعة البلاغات
|
{t("submitAndTrackReports")}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</Link>
|
</Link>
|
||||||
@ -565,9 +559,9 @@ export default function ClientLayout({ children }) {
|
|||||||
>
|
>
|
||||||
<LogOut className="w-5 h-5" />
|
<LogOut className="w-5 h-5" />
|
||||||
<div>
|
<div>
|
||||||
<p className="font-medium">تسجيل الخروج</p>
|
<p className="font-medium">{t("logout")}</p>
|
||||||
<p className="text-xs text-red-400">
|
<p className="text-xs text-red-400">
|
||||||
إنهاء الجلسة الحالية
|
{t("endSession")}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</button>
|
</button>
|
||||||
@ -642,19 +636,19 @@ export default function ClientLayout({ children }) {
|
|||||||
|
|
||||||
{/* Download App - Mobile */}
|
{/* Download App - Mobile */}
|
||||||
<div className={`border-t my-2 ${isHomePage ? "border-white/10" : "border-gray-200"}`}></div>
|
<div className={`border-t my-2 ${isHomePage ? "border-white/10" : "border-gray-200"}`}></div>
|
||||||
<p className={`px-3 py-1 text-xs font-medium ${isHomePage ? "text-white/50" : "text-gray-400"}`}>تحميل التطبيق</p>
|
<p className={`px-3 py-1 text-xs font-medium ${isHomePage ? "text-white/50" : "text-gray-400"}`}>{t("downloadApp")}</p>
|
||||||
<a href="/files/SweetHome.apk" download onClick={closeMobileMenu}
|
<a href="/files/SweetHome.apk" download onClick={closeMobileMenu}
|
||||||
className={`flex items-center gap-2 px-3 py-2 rounded-md transition-colors ${isHomePage ? "text-green-400 hover:bg-white/10" : "text-green-600 hover:bg-green-50"}`}>
|
className={`flex items-center gap-2 px-3 py-2 rounded-md transition-colors ${isHomePage ? "text-green-400 hover:bg-white/10" : "text-green-600 hover:bg-green-50"}`}>
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" fill="currentColor" viewBox="0 0 16 16">
|
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" fill="currentColor" viewBox="0 0 16 16">
|
||||||
<path d="M2.76 3.061a.5.5 0 0 1 .679.2l1.283 2.352A8.9 8.9 0 0 1 8 5a8.9 8.9 0 0 1 3.278.613l1.283-2.352a.5.5 0 1 1 .878.478l-1.252 2.295C14.475 7.266 16 9.477 16 12H0c0-2.523 1.525-4.734 3.813-5.966L2.56 3.74a.5.5 0 0 1 .2-.678ZM5 10a1 1 0 1 0 0-2 1 1 0 0 0 0 2m6 0a1 1 0 1 0 0-2 1 1 0 0 0 0 2"/>
|
<path d="M2.76 3.061a.5.5 0 0 1 .679.2l1.283 2.352A8.9 8.9 0 0 1 8 5a8.9 8.9 0 0 1 3.278.613l1.283-2.352a.5.5 0 1 1 .878.478l-1.252 2.295C14.475 7.266 16 9.477 16 12H0c0-2.523 1.525-4.734 3.813-5.966L2.56 3.74a.5.5 0 0 1 .2-.678ZM5 10a1 1 0 1 0 0-2 1 1 0 0 0 0 2m6 0a1 1 0 1 0 0-2 1 1 0 0 0 0 2"/>
|
||||||
</svg>
|
</svg>
|
||||||
<span className="text-sm font-medium">Android - تحميل APK</span>
|
<span className="text-sm font-medium">{t("android")} - {t("downloadAPK")}</span>
|
||||||
</a>
|
</a>
|
||||||
<div className="flex items-center gap-2 px-3 py-2 rounded-md text-gray-400 cursor-not-allowed opacity-50">
|
<div className="flex items-center gap-2 px-3 py-2 rounded-md text-gray-400 cursor-not-allowed opacity-50">
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" viewBox="0 0 16 16">
|
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" viewBox="0 0 16 16">
|
||||||
<path d="M11.182.008C11.148-.03 9.923.023 8.857 1.18c-1.066 1.156-.902 2.482-.878 2.516.024.034 1.52.087 2.475-1.258.955-1.345.762-2.391.728-2.43Zm3.314 11.733c-.048-.096-2.325-1.234-2.113-3.422.212-2.189 1.675-2.789 1.698-2.854.023-.065-.597-.79-1.254-1.157a3.692 3.692 0 0 0-1.563-.434c-.108-.003-.483-.095-1.254.116-.508.139-1.653.589-1.968.607-.316.018-1.256-.522-2.267-.665-.647-.125-1.333.131-1.824.328-.49.196-1.422.754-2.074 2.237-.652 1.482-.311 3.83-.067 4.56.244.729.625 1.924 1.273 2.796.576.984 1.34 1.667 1.659 1.899.319.232 1.219.385 1.843.067.502-.308 1.408-.485 1.766-.472.357.013 1.061.154 1.782.539.571.197 1.111.115 1.652-.105.541-.221 1.324-1.059 2.238-2.758.347-.79.505-1.217.473-1.282Z"/>
|
<path d="M11.182.008C11.148-.03 9.923.023 8.857 1.18c-1.066 1.156-.902 2.482-.878 2.516.024.034 1.52.087 2.475-1.258.955-1.345.762-2.391.728-2.43Zm3.314 11.733c-.048-.096-2.325-1.234-2.113-3.422.212-2.189 1.675-2.789 1.698-2.854.023-.065-.597-.79-1.254-1.157a3.692 3.692 0 0 0-1.563-.434c-.108-.003-.483-.095-1.254.116-.508.139-1.653.589-1.968.607-.316.018-1.256-.522-2.267-.665-.647-.125-1.333.131-1.824.328-.49.196-1.422.754-2.074 2.237-.652 1.482-.311 3.83-.067 4.56.244.729.625 1.924 1.273 2.796.576.984 1.34 1.667 1.659 1.899.319.232 1.219.385 1.843.067.502-.308 1.408-.485 1.766-.472.357.013 1.061.154 1.782.539.571.197 1.111.115 1.652-.105.541-.221 1.324-1.059 2.238-2.758.347-.79.505-1.217.473-1.282Z"/>
|
||||||
</svg>
|
</svg>
|
||||||
<span className="text-sm">iOS - قريباً</span>
|
<span className="text-sm">{t("ios")} - {t("comingSoon")}</span>
|
||||||
</div>
|
</div>
|
||||||
<div className={`border-t my-2 ${isHomePage ? "border-white/10" : "border-gray-200"}`}></div>
|
<div className={`border-t my-2 ${isHomePage ? "border-white/10" : "border-gray-200"}`}></div>
|
||||||
|
|
||||||
@ -665,7 +659,7 @@ export default function ClientLayout({ children }) {
|
|||||||
>
|
>
|
||||||
<span className="flex items-center gap-2">
|
<span className="flex items-center gap-2">
|
||||||
<Building className="w-4 h-4" />
|
<Building className="w-4 h-4" />
|
||||||
عقاراتي
|
{t("myProperties")}
|
||||||
</span>
|
</span>
|
||||||
</MobileNavLink>
|
</MobileNavLink>
|
||||||
<MobileNavLink light={isHomePage}
|
<MobileNavLink light={isHomePage}
|
||||||
@ -674,7 +668,7 @@ export default function ClientLayout({ children }) {
|
|||||||
>
|
>
|
||||||
<span className="flex items-center gap-2">
|
<span className="flex items-center gap-2">
|
||||||
<Calendar className="w-4 h-4" />
|
<Calendar className="w-4 h-4" />
|
||||||
الحجوزات
|
{t("reservations")}
|
||||||
</span>
|
</span>
|
||||||
</MobileNavLink>
|
</MobileNavLink>
|
||||||
<MobileNavLink light={isHomePage}
|
<MobileNavLink light={isHomePage}
|
||||||
@ -683,7 +677,7 @@ export default function ClientLayout({ children }) {
|
|||||||
>
|
>
|
||||||
<span className="flex items-center gap-2">
|
<span className="flex items-center gap-2">
|
||||||
<CalendarDays className="w-4 h-4" />
|
<CalendarDays className="w-4 h-4" />
|
||||||
التقويم
|
{t("calendar")}
|
||||||
</span>
|
</span>
|
||||||
</MobileNavLink>
|
</MobileNavLink>
|
||||||
<MobileNavLink light={isHomePage}
|
<MobileNavLink light={isHomePage}
|
||||||
@ -692,7 +686,7 @@ export default function ClientLayout({ children }) {
|
|||||||
>
|
>
|
||||||
<span className="flex items-center gap-2">
|
<span className="flex items-center gap-2">
|
||||||
<TrendingUp className="w-4 h-4" />
|
<TrendingUp className="w-4 h-4" />
|
||||||
الأرباح
|
{t("profits")}
|
||||||
</span>
|
</span>
|
||||||
</MobileNavLink>
|
</MobileNavLink>
|
||||||
</>
|
</>
|
||||||
@ -704,7 +698,7 @@ export default function ClientLayout({ children }) {
|
|||||||
<MobileNavLink light={isHomePage} href="/login" onClick={closeMobileMenu}>
|
<MobileNavLink light={isHomePage} href="/login" onClick={closeMobileMenu}>
|
||||||
<span className="flex items-center gap-2">
|
<span className="flex items-center gap-2">
|
||||||
<LogIn className="w-4 h-4" />
|
<LogIn className="w-4 h-4" />
|
||||||
تسجيل الدخول
|
{t("login")}
|
||||||
</span>
|
</span>
|
||||||
</MobileNavLink>
|
</MobileNavLink>
|
||||||
<MobileNavLink light={isHomePage}
|
<MobileNavLink light={isHomePage}
|
||||||
@ -713,7 +707,7 @@ export default function ClientLayout({ children }) {
|
|||||||
>
|
>
|
||||||
<span className="flex items-center gap-2">
|
<span className="flex items-center gap-2">
|
||||||
<UserPlus className="w-4 h-4" />
|
<UserPlus className="w-4 h-4" />
|
||||||
إنشاء حساب
|
{t("createAccount")}
|
||||||
</span>
|
</span>
|
||||||
</MobileNavLink>
|
</MobileNavLink>
|
||||||
</>
|
</>
|
||||||
@ -740,18 +734,18 @@ export default function ClientLayout({ children }) {
|
|||||||
<main
|
<main
|
||||||
className={`${!isAuthPage && !isProfilePage && !isAuthenticated ? "pt-20" : ""} flex-1 flex flex-col ${isHomePage ? "bg-transparent" : "bg-gray-50"} ${currentLanguage === "ar" ? "text-right" : "text-left"} ${isHomePage ? "relative" : ""} ${isAuthenticated && !isAuthPage ? "pb-20" : ""}`}
|
className={`${!isAuthPage && !isProfilePage && !isAuthenticated ? "pt-20" : ""} flex-1 flex flex-col ${isHomePage ? "bg-transparent" : "bg-gray-50"} ${currentLanguage === "ar" ? "text-right" : "text-left"} ${isHomePage ? "relative" : ""} ${isAuthenticated && !isAuthPage ? "pb-20" : ""}`}
|
||||||
>
|
>
|
||||||
{!isAuthPage && (
|
{!isAuthPage && isAuthenticated && (
|
||||||
<div className={`flex items-center justify-between px-4 py-2 sticky top-0 z-20 bg-transparent backdrop-blur-3xl ${isHomePage ? "" : "border-b border-gray-200/20"}`}>
|
<div className={`flex items-center justify-between px-4 py-2 sticky top-0 z-20 bg-transparent backdrop-blur-3xl ${isHomePage ? "" : "border-b border-gray-200/20"}`}>
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<span className={`text-sm font-semibold px-3 py-1 rounded-lg ${!isAuthenticated ? (isHomePage ? 'bg-white/20 text-white' : 'bg-gray-200 text-gray-600') : isOwner ? (isHomePage ? 'bg-amber-500/30 text-amber-200' : 'bg-amber-100 text-amber-700') : (isHomePage ? 'bg-blue-500/30 text-blue-200' : 'bg-blue-100 text-blue-700')}`}>
|
<span className={`text-sm font-semibold px-3 py-1 rounded-lg ${!isAuthenticated ? (isHomePage ? 'bg-white/20 text-white' : 'bg-gray-200 text-gray-600') : isOwner ? (isHomePage ? 'bg-amber-500/30 text-amber-200' : 'bg-amber-100 text-amber-700') : (isHomePage ? 'bg-blue-500/30 text-blue-200' : 'bg-blue-100 text-blue-700')}`}>
|
||||||
{!isAuthenticated ? 'زائر' : isOwner ? 'مالك' : 'عميل'}
|
{!isAuthenticated ? t('visitor') : isOwner ? t('owner') : t('customer')}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center gap-3">
|
<div className="flex items-center gap-3">
|
||||||
<Link href="/favorites" className={`relative group flex items-center justify-center w-9 h-9 rounded-full transition-colors hover:bg-white/10 ${pathname === '/favorites' ? 'text-amber-400' : 'nav-icon'}`}>
|
<Link href="/favorites" className={`relative group flex items-center justify-center w-9 h-9 rounded-full transition-colors hover:bg-white/10 ${pathname === '/favorites' ? 'text-amber-400' : 'nav-icon'}`}>
|
||||||
<Heart className="w-5 h-5" />
|
<Heart className="w-5 h-5" />
|
||||||
<div className="absolute -bottom-8 left-1/2 -translate-x-1/2 bg-gray-800 text-white text-xs rounded-lg px-2 py-1 opacity-0 group-hover:opacity-100 transition-opacity whitespace-nowrap pointer-events-none shadow-lg">
|
<div className="absolute -bottom-8 left-1/2 -translate-x-1/2 bg-gray-800 text-white text-xs rounded-lg px-2 py-1 opacity-0 group-hover:opacity-100 transition-opacity whitespace-nowrap pointer-events-none shadow-lg">
|
||||||
المفضلة
|
{t("favorites")}
|
||||||
</div>
|
</div>
|
||||||
</Link>
|
</Link>
|
||||||
<Link href="/notifications" className={`relative group flex items-center justify-center w-9 h-9 rounded-full transition-colors hover:bg-white/10 ${pathname === '/notifications' ? 'text-amber-400' : 'nav-icon'}`}>
|
<Link href="/notifications" className={`relative group flex items-center justify-center w-9 h-9 rounded-full transition-colors hover:bg-white/10 ${pathname === '/notifications' ? 'text-amber-400' : 'nav-icon'}`}>
|
||||||
@ -762,19 +756,25 @@ export default function ClientLayout({ children }) {
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
<div className="absolute -bottom-8 left-1/2 -translate-x-1/2 bg-gray-800 text-white text-xs rounded-lg px-2 py-1 opacity-0 group-hover:opacity-100 transition-opacity whitespace-nowrap pointer-events-none shadow-lg">
|
<div className="absolute -bottom-8 left-1/2 -translate-x-1/2 bg-gray-800 text-white text-xs rounded-lg px-2 py-1 opacity-0 group-hover:opacity-100 transition-opacity whitespace-nowrap pointer-events-none shadow-lg">
|
||||||
الإشعارات
|
{t("notifications")}
|
||||||
</div>
|
</div>
|
||||||
</Link>
|
</Link>
|
||||||
<Link href="/settings" className={`relative group flex items-center justify-center w-9 h-9 rounded-full transition-colors hover:bg-white/10 ${pathname === '/settings' ? 'text-amber-400' : 'nav-icon'}`}>
|
<Link href="/settings" className={`relative group flex items-center justify-center w-9 h-9 rounded-full transition-colors hover:bg-white/10 ${pathname === '/settings' ? 'text-amber-400' : 'nav-icon'}`}>
|
||||||
<Settings className="w-5 h-5" />
|
<Settings className="w-5 h-5" />
|
||||||
<div className="absolute -bottom-8 left-1/2 -translate-x-1/2 bg-gray-800 text-white text-xs rounded-lg px-2 py-1 opacity-0 group-hover:opacity-100 transition-opacity whitespace-nowrap pointer-events-none shadow-lg">
|
<div className="absolute -bottom-8 left-1/2 -translate-x-1/2 bg-gray-800 text-white text-xs rounded-lg px-2 py-1 opacity-0 group-hover:opacity-100 transition-opacity whitespace-nowrap pointer-events-none shadow-lg">
|
||||||
الإعدادات
|
{t("settings")}
|
||||||
</div>
|
</div>
|
||||||
</Link>
|
</Link>
|
||||||
<button onClick={toggleTheme} className="relative group flex items-center justify-center w-9 h-9 rounded-full transition-colors hover:bg-white/10 nav-icon">
|
<button onClick={toggleTheme} className="relative group flex items-center justify-center w-9 h-9 rounded-full transition-colors hover:bg-white/10 nav-icon">
|
||||||
{theme === 'dark' ? <Sun className="w-5 h-5" /> : <Moon className="w-5 h-5" />}
|
{theme === 'dark' ? <Sun className="w-5 h-5" /> : <Moon className="w-5 h-5" />}
|
||||||
<div className="absolute -bottom-8 left-1/2 -translate-x-1/2 bg-gray-800 text-white text-xs rounded-lg px-2 py-1 opacity-0 group-hover:opacity-100 transition-opacity whitespace-nowrap pointer-events-none shadow-lg">
|
<div className="absolute -bottom-8 left-1/2 -translate-x-1/2 bg-gray-800 text-white text-xs rounded-lg px-2 py-1 opacity-0 group-hover:opacity-100 transition-opacity whitespace-nowrap pointer-events-none shadow-lg">
|
||||||
{theme === 'dark' ? 'وضع نهاري' : 'وضع ليلي'}
|
{theme === 'dark' ? t('lightMode') : t('darkMode')}
|
||||||
|
</div>
|
||||||
|
</button>
|
||||||
|
<button onClick={() => changeLanguage(currentLanguage === 'ar' ? 'en' : 'ar')} className="relative group flex items-center justify-center w-9 h-9 rounded-full transition-colors hover:bg-white/10 nav-icon">
|
||||||
|
<Globe className="w-5 h-5" />
|
||||||
|
<div className="absolute -bottom-8 left-1/2 -translate-x-1/2 bg-gray-800 text-white text-xs rounded-lg px-2 py-1 opacity-0 group-hover:opacity-100 transition-opacity whitespace-nowrap pointer-events-none shadow-lg">
|
||||||
|
{currentLanguage === 'ar' ? t('switchToEnglish') : t('switchToArabic')}
|
||||||
</div>
|
</div>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -4,6 +4,7 @@ import { useState, useEffect } from 'react';
|
|||||||
import { useRouter } from 'next/navigation';
|
import { useRouter } from 'next/navigation';
|
||||||
import Link from 'next/link';
|
import Link from 'next/link';
|
||||||
import { motion } from 'framer-motion';
|
import { motion } from 'framer-motion';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
import {
|
import {
|
||||||
Mail,
|
Mail,
|
||||||
Phone,
|
Phone,
|
||||||
@ -21,6 +22,7 @@ import { sendEmailOTP, sendPhoneOTP, verifyEmail, verifyPhone } from '../utils/a
|
|||||||
|
|
||||||
export default function AccountVerificationPage() {
|
export default function AccountVerificationPage() {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
const { t } = useTranslation();
|
||||||
const [user, setUser] = useState(null);
|
const [user, setUser] = useState(null);
|
||||||
const [isLoading, setIsLoading] = useState(true);
|
const [isLoading, setIsLoading] = useState(true);
|
||||||
|
|
||||||
@ -49,10 +51,10 @@ export default function AccountVerificationPage() {
|
|||||||
setSendingEmailOTP(true);
|
setSendingEmailOTP(true);
|
||||||
try {
|
try {
|
||||||
await sendEmailOTP();
|
await sendEmailOTP();
|
||||||
toast.success('تم إرسال رمز التحقق إلى بريدك الإلكتروني');
|
toast.success(t('accountVerification.emailOtpSent'));
|
||||||
setShowEmailInput(true);
|
setShowEmailInput(true);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
toast.error(err.message || 'فشل إرسال رمز التحقق');
|
toast.error(err.message || t('accountVerification.otpSendFailed'));
|
||||||
} finally {
|
} finally {
|
||||||
setSendingEmailOTP(false);
|
setSendingEmailOTP(false);
|
||||||
}
|
}
|
||||||
@ -60,7 +62,7 @@ export default function AccountVerificationPage() {
|
|||||||
|
|
||||||
const handleVerifyEmail = async () => {
|
const handleVerifyEmail = async () => {
|
||||||
if (!emailCode.trim()) {
|
if (!emailCode.trim()) {
|
||||||
toast.error('الرجاء إدخال رمز التحقق');
|
toast.error(t('accountVerification.otpRequired'));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
setVerifyingEmail(true);
|
setVerifyingEmail(true);
|
||||||
@ -68,9 +70,9 @@ export default function AccountVerificationPage() {
|
|||||||
await verifyEmail(emailCode.trim());
|
await verifyEmail(emailCode.trim());
|
||||||
setEmailVerified(true);
|
setEmailVerified(true);
|
||||||
setShowEmailInput(false);
|
setShowEmailInput(false);
|
||||||
toast.success('تم التحقق من البريد الإلكتروني بنجاح');
|
toast.success(t('accountVerification.emailVerifiedSuccess'));
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
toast.error(err.message || 'رمز التحقق غير صحيح');
|
toast.error(err.message || t('accountVerification.otpInvalid'));
|
||||||
} finally {
|
} finally {
|
||||||
setVerifyingEmail(false);
|
setVerifyingEmail(false);
|
||||||
}
|
}
|
||||||
@ -80,10 +82,10 @@ export default function AccountVerificationPage() {
|
|||||||
setSendingPhoneOTP(true);
|
setSendingPhoneOTP(true);
|
||||||
try {
|
try {
|
||||||
await sendPhoneOTP();
|
await sendPhoneOTP();
|
||||||
toast.success('تم إرسال رمز التحقق إلى هاتفك');
|
toast.success(t('accountVerification.phoneOtpSent'));
|
||||||
setShowPhoneInput(true);
|
setShowPhoneInput(true);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
toast.error(err.message || 'فشل إرسال رمز التحقق');
|
toast.error(err.message || t('accountVerification.otpSendFailed'));
|
||||||
} finally {
|
} finally {
|
||||||
setSendingPhoneOTP(false);
|
setSendingPhoneOTP(false);
|
||||||
}
|
}
|
||||||
@ -91,7 +93,7 @@ export default function AccountVerificationPage() {
|
|||||||
|
|
||||||
const handleVerifyPhone = async () => {
|
const handleVerifyPhone = async () => {
|
||||||
if (!phoneCode.trim()) {
|
if (!phoneCode.trim()) {
|
||||||
toast.error('الرجاء إدخال رمز التحقق');
|
toast.error(t('accountVerification.otpRequired'));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
setVerifyingPhone(true);
|
setVerifyingPhone(true);
|
||||||
@ -99,9 +101,9 @@ export default function AccountVerificationPage() {
|
|||||||
await verifyPhone(phoneCode.trim());
|
await verifyPhone(phoneCode.trim());
|
||||||
setPhoneVerified(true);
|
setPhoneVerified(true);
|
||||||
setShowPhoneInput(false);
|
setShowPhoneInput(false);
|
||||||
toast.success('تم التحقق من رقم الهاتف بنجاح');
|
toast.success(t('accountVerification.phoneVerifiedSuccess'));
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
toast.error(err.message || 'رمز التحقق غير صحيح');
|
toast.error(err.message || t('accountVerification.otpInvalid'));
|
||||||
} finally {
|
} finally {
|
||||||
setVerifyingPhone(false);
|
setVerifyingPhone(false);
|
||||||
}
|
}
|
||||||
@ -144,7 +146,7 @@ export default function AccountVerificationPage() {
|
|||||||
>
|
>
|
||||||
<ChevronLeft className="w-5 h-5" />
|
<ChevronLeft className="w-5 h-5" />
|
||||||
</Link>
|
</Link>
|
||||||
<h1 className="text-2xl font-bold text-gray-900">التحقق من الحساب</h1>
|
<h1 className="text-2xl font-bold text-gray-900">{t('accountVerification.title')}</h1>
|
||||||
</motion.div>
|
</motion.div>
|
||||||
|
|
||||||
<motion.div
|
<motion.div
|
||||||
@ -156,22 +158,22 @@ export default function AccountVerificationPage() {
|
|||||||
<motion.div variants={itemVariants} className="bg-white rounded-2xl shadow-sm overflow-hidden">
|
<motion.div variants={itemVariants} className="bg-white rounded-2xl shadow-sm overflow-hidden">
|
||||||
<div className="px-6 py-4 border-b border-gray-100 flex items-center gap-3">
|
<div className="px-6 py-4 border-b border-gray-100 flex items-center gap-3">
|
||||||
<Mail className="w-5 h-5 text-amber-600" />
|
<Mail className="w-5 h-5 text-amber-600" />
|
||||||
<h2 className="text-lg font-semibold text-gray-800">البريد الإلكتروني</h2>
|
<h2 className="text-lg font-semibold text-gray-800">{t('accountVerification.emailSection')}</h2>
|
||||||
{emailVerified && (
|
{emailVerified && (
|
||||||
<span className="flex items-center gap-1 text-xs text-green-600 bg-green-50 px-2 py-0.5 rounded-full">
|
<span className="flex items-center gap-1 text-xs text-green-600 bg-green-50 px-2 py-0.5 rounded-full">
|
||||||
<CheckCircle className="w-3 h-3" />
|
<CheckCircle className="w-3 h-3" />
|
||||||
موثق
|
{t('accountVerification.verified')}
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<div className="p-6">
|
<div className="p-6">
|
||||||
<div className="flex items-center justify-between mb-4">
|
<div className="flex items-center justify-between mb-4">
|
||||||
<div>
|
<div>
|
||||||
<p className="text-sm text-gray-600">{user?.email || 'بريد إلكتروني غير مسجل'}</p>
|
<p className="text-sm text-gray-600">{user?.email || t('accountVerification.emailNotRegistered')}</p>
|
||||||
<p className="text-xs text-gray-400 mt-1">
|
<p className="text-xs text-gray-400 mt-1">
|
||||||
{emailVerified
|
{emailVerified
|
||||||
? 'بريدك الإلكتروني موثق ويمكنك استخدامه لتسجيل الدخول'
|
? t('accountVerification.emailVerifiedDesc')
|
||||||
: 'يرجى توثيق بريدك الإلكتروني لحماية حسابك'}
|
: t('accountVerification.emailVerifyDesc')}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
{!emailVerified && !showEmailInput && (
|
{!emailVerified && !showEmailInput && (
|
||||||
@ -185,7 +187,7 @@ export default function AccountVerificationPage() {
|
|||||||
) : (
|
) : (
|
||||||
<Send className="w-4 h-4" />
|
<Send className="w-4 h-4" />
|
||||||
)}
|
)}
|
||||||
{sendingEmailOTP ? 'جاري الإرسال...' : 'إرسال رمز التحقق'}
|
{sendingEmailOTP ? t('accountVerification.sending') : t('accountVerification.sendOtp')}
|
||||||
</button>
|
</button>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
@ -196,13 +198,13 @@ export default function AccountVerificationPage() {
|
|||||||
animate={{ opacity: 1, height: 'auto' }}
|
animate={{ opacity: 1, height: 'auto' }}
|
||||||
className="space-y-3"
|
className="space-y-3"
|
||||||
>
|
>
|
||||||
<p className="text-xs text-gray-500">أدخل الرمز المكون من 6 أرقام الذي تم إرساله إلى بريدك الإلكتروني</p>
|
<p className="text-xs text-gray-500">{t('accountVerification.enter6DigitCode')}</p>
|
||||||
<div className="flex gap-2">
|
<div className="flex gap-2">
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
value={emailCode}
|
value={emailCode}
|
||||||
onChange={(e) => setEmailCode(e.target.value)}
|
onChange={(e) => setEmailCode(e.target.value)}
|
||||||
placeholder="رمز التحقق"
|
placeholder={t('accountVerification.otpPlaceholder')}
|
||||||
maxLength={6}
|
maxLength={6}
|
||||||
className="flex-1 px-4 py-3 border border-gray-300 rounded-xl focus:ring-2 focus:ring-amber-500 focus:border-transparent outline-none text-center text-lg tracking-widest"
|
className="flex-1 px-4 py-3 border border-gray-300 rounded-xl focus:ring-2 focus:ring-amber-500 focus:border-transparent outline-none text-center text-lg tracking-widest"
|
||||||
/>
|
/>
|
||||||
@ -216,7 +218,7 @@ export default function AccountVerificationPage() {
|
|||||||
) : (
|
) : (
|
||||||
<Key className="w-4 h-4" />
|
<Key className="w-4 h-4" />
|
||||||
)}
|
)}
|
||||||
تحقق
|
{t('accountVerification.verify')}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<button
|
<button
|
||||||
@ -224,7 +226,7 @@ export default function AccountVerificationPage() {
|
|||||||
disabled={sendingEmailOTP}
|
disabled={sendingEmailOTP}
|
||||||
className="text-xs text-amber-600 hover:text-amber-700"
|
className="text-xs text-amber-600 hover:text-amber-700"
|
||||||
>
|
>
|
||||||
إعادة إرسال الرمز
|
{t('accountVerification.resendCode')}
|
||||||
</button>
|
</button>
|
||||||
</motion.div>
|
</motion.div>
|
||||||
)}
|
)}
|
||||||
@ -232,7 +234,7 @@ export default function AccountVerificationPage() {
|
|||||||
{emailVerified && (
|
{emailVerified && (
|
||||||
<div className="flex items-center gap-2 text-green-600 bg-green-50 px-4 py-3 rounded-xl">
|
<div className="flex items-center gap-2 text-green-600 bg-green-50 px-4 py-3 rounded-xl">
|
||||||
<CheckCircle className="w-5 h-5" />
|
<CheckCircle className="w-5 h-5" />
|
||||||
<span className="text-sm font-medium">تم توثيق البريد الإلكتروني بنجاح</span>
|
<span className="text-sm font-medium">{t('accountVerification.emailVerifiedSuccess')}</span>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
@ -241,22 +243,22 @@ export default function AccountVerificationPage() {
|
|||||||
<motion.div variants={itemVariants} className="bg-white rounded-2xl shadow-sm overflow-hidden">
|
<motion.div variants={itemVariants} className="bg-white rounded-2xl shadow-sm overflow-hidden">
|
||||||
<div className="px-6 py-4 border-b border-gray-100 flex items-center gap-3">
|
<div className="px-6 py-4 border-b border-gray-100 flex items-center gap-3">
|
||||||
<Phone className="w-5 h-5 text-amber-600" />
|
<Phone className="w-5 h-5 text-amber-600" />
|
||||||
<h2 className="text-lg font-semibold text-gray-800">رقم الهاتف</h2>
|
<h2 className="text-lg font-semibold text-gray-800">{t('accountVerification.phoneSection')}</h2>
|
||||||
{phoneVerified && (
|
{phoneVerified && (
|
||||||
<span className="flex items-center gap-1 text-xs text-green-600 bg-green-50 px-2 py-0.5 rounded-full">
|
<span className="flex items-center gap-1 text-xs text-green-600 bg-green-50 px-2 py-0.5 rounded-full">
|
||||||
<CheckCircle className="w-3 h-3" />
|
<CheckCircle className="w-3 h-3" />
|
||||||
موثق
|
{t('accountVerification.verified')}
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<div className="p-6">
|
<div className="p-6">
|
||||||
<div className="flex items-center justify-between mb-4">
|
<div className="flex items-center justify-between mb-4">
|
||||||
<div>
|
<div>
|
||||||
<p className="text-sm text-gray-600">{user?.phone || 'رقم هاتف غير مسجل'}</p>
|
<p className="text-sm text-gray-600">{user?.phone || t('accountVerification.phoneNotRegistered')}</p>
|
||||||
<p className="text-xs text-gray-400 mt-1">
|
<p className="text-xs text-gray-400 mt-1">
|
||||||
{phoneVerified
|
{phoneVerified
|
||||||
? 'رقم هاتفك موثق ويمكنك استخدامه لتسجيل الدخول'
|
? t('accountVerification.phoneVerifiedDesc')
|
||||||
: 'يرجى توثيق رقم هاتفك لحماية حسابك'}
|
: t('accountVerification.phoneVerifyDesc')}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
{!phoneVerified && !showPhoneInput && (
|
{!phoneVerified && !showPhoneInput && (
|
||||||
@ -270,7 +272,7 @@ export default function AccountVerificationPage() {
|
|||||||
) : (
|
) : (
|
||||||
<Send className="w-4 h-4" />
|
<Send className="w-4 h-4" />
|
||||||
)}
|
)}
|
||||||
{sendingPhoneOTP ? 'جاري الإرسال...' : 'إرسال رمز التحقق'}
|
{sendingPhoneOTP ? t('accountVerification.sending') : t('accountVerification.sendOtp')}
|
||||||
</button>
|
</button>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
@ -281,13 +283,13 @@ export default function AccountVerificationPage() {
|
|||||||
animate={{ opacity: 1, height: 'auto' }}
|
animate={{ opacity: 1, height: 'auto' }}
|
||||||
className="space-y-3"
|
className="space-y-3"
|
||||||
>
|
>
|
||||||
<p className="text-xs text-gray-500">أدخل الرمز المكون من 6 أرقام الذي تم إرساله إلى هاتفك</p>
|
<p className="text-xs text-gray-500">{t('accountVerification.enter6DigitCodePhone')}</p>
|
||||||
<div className="flex gap-2">
|
<div className="flex gap-2">
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
value={phoneCode}
|
value={phoneCode}
|
||||||
onChange={(e) => setPhoneCode(e.target.value)}
|
onChange={(e) => setPhoneCode(e.target.value)}
|
||||||
placeholder="رمز التحقق"
|
placeholder={t('accountVerification.otpPlaceholder')}
|
||||||
maxLength={6}
|
maxLength={6}
|
||||||
className="flex-1 px-4 py-3 border border-gray-300 rounded-xl focus:ring-2 focus:ring-amber-500 focus:border-transparent outline-none text-center text-lg tracking-widest"
|
className="flex-1 px-4 py-3 border border-gray-300 rounded-xl focus:ring-2 focus:ring-amber-500 focus:border-transparent outline-none text-center text-lg tracking-widest"
|
||||||
/>
|
/>
|
||||||
@ -301,7 +303,7 @@ export default function AccountVerificationPage() {
|
|||||||
) : (
|
) : (
|
||||||
<Key className="w-4 h-4" />
|
<Key className="w-4 h-4" />
|
||||||
)}
|
)}
|
||||||
تحقق
|
{t('accountVerification.verify')}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<button
|
<button
|
||||||
@ -309,7 +311,7 @@ export default function AccountVerificationPage() {
|
|||||||
disabled={sendingPhoneOTP}
|
disabled={sendingPhoneOTP}
|
||||||
className="text-xs text-amber-600 hover:text-amber-700"
|
className="text-xs text-amber-600 hover:text-amber-700"
|
||||||
>
|
>
|
||||||
إعادة إرسال الرمز
|
{t('accountVerification.resendCode')}
|
||||||
</button>
|
</button>
|
||||||
</motion.div>
|
</motion.div>
|
||||||
)}
|
)}
|
||||||
@ -317,7 +319,7 @@ export default function AccountVerificationPage() {
|
|||||||
{phoneVerified && (
|
{phoneVerified && (
|
||||||
<div className="flex items-center gap-2 text-green-600 bg-green-50 px-4 py-3 rounded-xl">
|
<div className="flex items-center gap-2 text-green-600 bg-green-50 px-4 py-3 rounded-xl">
|
||||||
<CheckCircle className="w-5 h-5" />
|
<CheckCircle className="w-5 h-5" />
|
||||||
<span className="text-sm font-medium">تم توثيق رقم الهاتف بنجاح</span>
|
<span className="text-sm font-medium">{t('accountVerification.phoneVerifiedSuccess')}</span>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
@ -326,20 +328,20 @@ export default function AccountVerificationPage() {
|
|||||||
<motion.div variants={itemVariants} className="bg-gradient-to-br from-amber-500 to-amber-600 rounded-2xl p-6 text-white">
|
<motion.div variants={itemVariants} className="bg-gradient-to-br from-amber-500 to-amber-600 rounded-2xl p-6 text-white">
|
||||||
<div className="flex items-center gap-3 mb-3">
|
<div className="flex items-center gap-3 mb-3">
|
||||||
<Shield className="w-6 h-6" />
|
<Shield className="w-6 h-6" />
|
||||||
<h3 className="text-lg font-semibold">لماذا يجب توثيق حسابك؟</h3>
|
<h3 className="text-lg font-semibold">{t('accountVerification.whyVerify')}</h3>
|
||||||
</div>
|
</div>
|
||||||
<ul className="space-y-2 text-sm text-amber-50">
|
<ul className="space-y-2 text-sm text-amber-50">
|
||||||
<li className="flex items-center gap-2">
|
<li className="flex items-center gap-2">
|
||||||
<CheckCircle className="w-4 h-4 flex-shrink-0" />
|
<CheckCircle className="w-4 h-4 flex-shrink-0" />
|
||||||
حماية حسابك من الوصول غير المصرح به
|
{t('accountVerification.reason1')}
|
||||||
</li>
|
</li>
|
||||||
<li className="flex items-center gap-2">
|
<li className="flex items-center gap-2">
|
||||||
<CheckCircle className="w-4 h-4 flex-shrink-0" />
|
<CheckCircle className="w-4 h-4 flex-shrink-0" />
|
||||||
استعادة كلمة المرور بسهولة في حال فقدانها
|
{t('accountVerification.reason2')}
|
||||||
</li>
|
</li>
|
||||||
<li className="flex items-center gap-2">
|
<li className="flex items-center gap-2">
|
||||||
<CheckCircle className="w-4 h-4 flex-shrink-0" />
|
<CheckCircle className="w-4 h-4 flex-shrink-0" />
|
||||||
زيادة الثقة مع مالكي العقارات والمستأجرين
|
{t('accountVerification.reason3')}
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</motion.div>
|
</motion.div>
|
||||||
|
|||||||
@ -3,8 +3,10 @@
|
|||||||
import { motion } from 'framer-motion';
|
import { motion } from 'framer-motion';
|
||||||
import Link from 'next/link';
|
import Link from 'next/link';
|
||||||
import { Home, Building, Briefcase, ArrowLeft } from 'lucide-react';
|
import { Home, Building, Briefcase, ArrowLeft } from 'lucide-react';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
export default function ChooseRolePage() {
|
export default function ChooseRolePage() {
|
||||||
|
const { t, i18n } = useTranslation();
|
||||||
const containerVariants = {
|
const containerVariants = {
|
||||||
hidden: { opacity: 0 },
|
hidden: { opacity: 0 },
|
||||||
visible: {
|
visible: {
|
||||||
@ -35,7 +37,7 @@ export default function ChooseRolePage() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="min-h-screen bg-gradient-to-br from-gray-950 via-gray-900 to-gray-950 flex items-center justify-center p-4 relative overflow-hidden">
|
<div dir={i18n.language === 'ar' ? 'rtl' : 'ltr'} className="min-h-screen bg-gradient-to-br from-gray-950 via-gray-900 to-gray-950 flex items-center justify-center p-4 relative overflow-hidden">
|
||||||
<div className="absolute inset-0 overflow-hidden">
|
<div className="absolute inset-0 overflow-hidden">
|
||||||
{[...Array(20)].map((_, i) => (
|
{[...Array(20)].map((_, i) => (
|
||||||
<motion.div
|
<motion.div
|
||||||
@ -77,7 +79,7 @@ export default function ChooseRolePage() {
|
|||||||
<motion.div whileHover={{ x: -5 }}>
|
<motion.div whileHover={{ x: -5 }}>
|
||||||
<ArrowLeft className="w-4 h-4" />
|
<ArrowLeft className="w-4 h-4" />
|
||||||
</motion.div>
|
</motion.div>
|
||||||
<span>العودة للرئيسية</span>
|
<span>{t('backToHome')}</span>
|
||||||
</Link>
|
</Link>
|
||||||
</motion.div>
|
</motion.div>
|
||||||
|
|
||||||
@ -89,10 +91,10 @@ export default function ChooseRolePage() {
|
|||||||
className="text-4xl md:text-5xl font-bold text-white mb-4"
|
className="text-4xl md:text-5xl font-bold text-white mb-4"
|
||||||
animate={floatingAnimation}
|
animate={floatingAnimation}
|
||||||
>
|
>
|
||||||
مرحباً بك في SweetHome
|
{t('chooseRole.welcomeToSweetHome')}
|
||||||
</motion.h1>
|
</motion.h1>
|
||||||
<p className="text-xl text-gray-400">
|
<p className="text-xl text-gray-400">
|
||||||
اختر نوع الحساب المناسب لك
|
{t('chooseRole.subtitle')}
|
||||||
</p>
|
</p>
|
||||||
</motion.div>
|
</motion.div>
|
||||||
|
|
||||||
@ -123,23 +125,23 @@ export default function ChooseRolePage() {
|
|||||||
<Building className="w-10 h-10 text-white" />
|
<Building className="w-10 h-10 text-white" />
|
||||||
</motion.div>
|
</motion.div>
|
||||||
|
|
||||||
<h2 className="text-3xl font-bold mb-3">صاحب عقار</h2>
|
<h2 className="text-3xl font-bold mb-3">{t('chooseRole.owner')}</h2>
|
||||||
<p className="text-amber-100 mb-6">
|
<p className="text-amber-100 mb-6">
|
||||||
تريد عرض عقار للإيجار أو البيع؟ انضم كمالك عقار وابدأ الآن
|
{t('chooseRole.ownerDesc')}
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<ul className="space-y-2 text-sm text-amber-100">
|
<ul className="space-y-2 text-sm text-amber-100">
|
||||||
<li className="flex items-center gap-2">
|
<li className="flex items-center gap-2">
|
||||||
<span className="w-1.5 h-1.5 bg-white rounded-full" />
|
<span className="w-1.5 h-1.5 bg-white rounded-full" />
|
||||||
إضافة عقارات غير محدودة
|
{t('chooseRole.ownerFeature1')}
|
||||||
</li>
|
</li>
|
||||||
<li className="flex items-center gap-2">
|
<li className="flex items-center gap-2">
|
||||||
<span className="w-1.5 h-1.5 bg-white rounded-full" />
|
<span className="w-1.5 h-1.5 bg-white rounded-full" />
|
||||||
إدارة الحجوزات بسهولة
|
{t('chooseRole.ownerFeature2')}
|
||||||
</li>
|
</li>
|
||||||
<li className="flex items-center gap-2">
|
<li className="flex items-center gap-2">
|
||||||
<span className="w-1.5 h-1.5 bg-white rounded-full" />
|
<span className="w-1.5 h-1.5 bg-white rounded-full" />
|
||||||
تواصل مباشر مع المستأجرين
|
{t('chooseRole.ownerFeature3')}
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
@ -148,7 +150,7 @@ export default function ChooseRolePage() {
|
|||||||
whileHover={{ scale: 1.02 }}
|
whileHover={{ scale: 1.02 }}
|
||||||
whileTap={{ scale: 0.98 }}
|
whileTap={{ scale: 0.98 }}
|
||||||
>
|
>
|
||||||
إنشاء حساب مالك
|
{t('chooseRole.createOwnerAccount')}
|
||||||
</motion.button>
|
</motion.button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -181,23 +183,23 @@ export default function ChooseRolePage() {
|
|||||||
<Home className="w-10 h-10 text-white" />
|
<Home className="w-10 h-10 text-white" />
|
||||||
</motion.div>
|
</motion.div>
|
||||||
|
|
||||||
<h2 className="text-3xl font-bold mb-3">مستأجر / مشتري</h2>
|
<h2 className="text-3xl font-bold mb-3">{t('chooseRole.tenant')}</h2>
|
||||||
<p className="text-blue-100 mb-6">
|
<p className="text-blue-100 mb-6">
|
||||||
تبحث عن عقار للإيجار أو الشراء؟ انضم كعميل وابحث عن منزل أحلامك
|
{t('chooseRole.tenantDesc')}
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<ul className="space-y-2 text-sm text-blue-100">
|
<ul className="space-y-2 text-sm text-blue-100">
|
||||||
<li className="flex items-center gap-2">
|
<li className="flex items-center gap-2">
|
||||||
<span className="w-1.5 h-1.5 bg-white rounded-full" />
|
<span className="w-1.5 h-1.5 bg-white rounded-full" />
|
||||||
آلاف العقارات المتاحة
|
{t('chooseRole.tenantFeature1')}
|
||||||
</li>
|
</li>
|
||||||
<li className="flex items-center gap-2">
|
<li className="flex items-center gap-2">
|
||||||
<span className="w-1.5 h-1.5 bg-white rounded-full" />
|
<span className="w-1.5 h-1.5 bg-white rounded-full" />
|
||||||
بحث متقدم بفلاتر ذكية
|
{t('chooseRole.tenantFeature2')}
|
||||||
</li>
|
</li>
|
||||||
<li className="flex items-center gap-2">
|
<li className="flex items-center gap-2">
|
||||||
<span className="w-1.5 h-1.5 bg-white rounded-full" />
|
<span className="w-1.5 h-1.5 bg-white rounded-full" />
|
||||||
حجز وإستئجار فوري
|
{t('chooseRole.tenantFeature3')}
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
@ -206,7 +208,7 @@ export default function ChooseRolePage() {
|
|||||||
whileHover={{ scale: 1.02 }}
|
whileHover={{ scale: 1.02 }}
|
||||||
whileTap={{ scale: 0.98 }}
|
whileTap={{ scale: 0.98 }}
|
||||||
>
|
>
|
||||||
إنشاء حساب مستأجر
|
{t('chooseRole.createTenantAccount')}
|
||||||
</motion.button>
|
</motion.button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -239,23 +241,23 @@ export default function ChooseRolePage() {
|
|||||||
<Briefcase className="w-10 h-10 text-white" />
|
<Briefcase className="w-10 h-10 text-white" />
|
||||||
</motion.div>
|
</motion.div>
|
||||||
|
|
||||||
<h2 className="text-3xl font-bold mb-3">وكيل عقاري</h2>
|
<h2 className="text-3xl font-bold mb-3">{t('chooseRole.agent')}</h2>
|
||||||
<p className="text-purple-100 mb-6">
|
<p className="text-purple-100 mb-6">
|
||||||
تدير عقارات للغير؟ انضم كوسيط عقاري واحصل على فرص مميزة
|
{t('chooseRole.agentDesc')}
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<ul className="space-y-2 text-sm text-purple-100">
|
<ul className="space-y-2 text-sm text-purple-100">
|
||||||
<li className="flex items-center gap-2">
|
<li className="flex items-center gap-2">
|
||||||
<span className="w-1.5 h-1.5 bg-white rounded-full" />
|
<span className="w-1.5 h-1.5 bg-white rounded-full" />
|
||||||
إدارة عقارات العملاء
|
{t('chooseRole.agentFeature1')}
|
||||||
</li>
|
</li>
|
||||||
<li className="flex items-center gap-2">
|
<li className="flex items-center gap-2">
|
||||||
<span className="w-1.5 h-1.5 bg-white rounded-full" />
|
<span className="w-1.5 h-1.5 bg-white rounded-full" />
|
||||||
عمولات وأرباح مضمونة
|
{t('chooseRole.agentFeature2')}
|
||||||
</li>
|
</li>
|
||||||
<li className="flex items-center gap-2">
|
<li className="flex items-center gap-2">
|
||||||
<span className="w-1.5 h-1.5 bg-white rounded-full" />
|
<span className="w-1.5 h-1.5 bg-white rounded-full" />
|
||||||
لوحة تحكم متكاملة
|
{t('chooseRole.agentFeature3')}
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
@ -264,7 +266,7 @@ export default function ChooseRolePage() {
|
|||||||
whileHover={{ scale: 1.02 }}
|
whileHover={{ scale: 1.02 }}
|
||||||
whileTap={{ scale: 0.98 }}
|
whileTap={{ scale: 0.98 }}
|
||||||
>
|
>
|
||||||
إنشاء حساب وكيل
|
{t('chooseRole.createAgentAccount')}
|
||||||
</motion.button>
|
</motion.button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -276,12 +278,12 @@ export default function ChooseRolePage() {
|
|||||||
variants={itemVariants}
|
variants={itemVariants}
|
||||||
className="text-center text-gray-400 mt-8"
|
className="text-center text-gray-400 mt-8"
|
||||||
>
|
>
|
||||||
لديك حساب بالفعل؟{' '}
|
{t('chooseRole.alreadyHaveAccount')}{' '}
|
||||||
<Link
|
<Link
|
||||||
href="/login"
|
href="/login"
|
||||||
className="text-amber-400 hover:text-amber-300 font-medium transition-colors"
|
className="text-amber-400 hover:text-amber-300 font-medium transition-colors"
|
||||||
>
|
>
|
||||||
تسجيل الدخول
|
{t('login')}
|
||||||
</Link>
|
</Link>
|
||||||
</motion.p>
|
</motion.p>
|
||||||
</motion.div>
|
</motion.div>
|
||||||
|
|||||||
@ -3,12 +3,14 @@
|
|||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import { useRouter } from 'next/navigation';
|
import { useRouter } from 'next/navigation';
|
||||||
import { motion } from 'framer-motion';
|
import { motion } from 'framer-motion';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
import { ShieldAlert, LogOut, MessageSquare, Send, Loader2 } from 'lucide-react';
|
import { ShieldAlert, LogOut, MessageSquare, Send, Loader2 } from 'lucide-react';
|
||||||
import toast, { Toaster } from 'react-hot-toast';
|
import toast, { Toaster } from 'react-hot-toast';
|
||||||
import AuthService from '../services/AuthService';
|
import AuthService from '../services/AuthService';
|
||||||
import { sendGeneralReport } from '../utils/api';
|
import { sendGeneralReport } from '../utils/api';
|
||||||
|
|
||||||
export default function BlockedPage() {
|
export default function BlockedPage() {
|
||||||
|
const { t } = useTranslation();
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const [form, setForm] = useState({ subject: '', body: '' });
|
const [form, setForm] = useState({ subject: '', body: '' });
|
||||||
const [isSubmitting, setIsSubmitting] = useState(false);
|
const [isSubmitting, setIsSubmitting] = useState(false);
|
||||||
@ -28,7 +30,7 @@ export default function BlockedPage() {
|
|||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
|
|
||||||
if (!form.subject.trim() || !form.body.trim()) {
|
if (!form.subject.trim() || !form.body.trim()) {
|
||||||
toast.error('يرجى تعبئة الموضوع والرسالة');
|
toast.error(t('blocked.fillAllFields'));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -38,9 +40,9 @@ export default function BlockedPage() {
|
|||||||
await sendGeneralReport(form.subject.trim(), form.body.trim());
|
await sendGeneralReport(form.subject.trim(), form.body.trim());
|
||||||
setIsSent(true);
|
setIsSent(true);
|
||||||
setForm({ subject: '', body: '' });
|
setForm({ subject: '', body: '' });
|
||||||
toast.success('تم إرسال طلب الدعم بنجاح');
|
toast.success(t('blocked.reportSent'));
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
toast.error('حدث خطأ أثناء إرسال طلب الدعم. حاول مرة أخرى');
|
toast.error(t('blocked.sendError'));
|
||||||
} finally {
|
} finally {
|
||||||
setIsSubmitting(false);
|
setIsSubmitting(false);
|
||||||
}
|
}
|
||||||
@ -63,9 +65,9 @@ export default function BlockedPage() {
|
|||||||
>
|
>
|
||||||
<ShieldAlert className="w-12 h-12 text-red-600" />
|
<ShieldAlert className="w-12 h-12 text-red-600" />
|
||||||
</motion.div>
|
</motion.div>
|
||||||
<h1 className="text-4xl font-bold text-gray-900 mb-3">الحساب محظور</h1>
|
<h1 className="text-4xl font-bold text-gray-900 mb-3">{t('blocked.title')}</h1>
|
||||||
<p className="text-gray-600 text-lg max-w-2xl mx-auto">
|
<p className="text-gray-600 text-lg max-w-2xl mx-auto">
|
||||||
تم تقييد وصولك إلى التطبيق. يمكنك تسجيل الخروج أو مراسلة دعم العملاء للمساعدة في حل المشكلة.
|
{t('blocked.description')}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -80,9 +82,9 @@ export default function BlockedPage() {
|
|||||||
<div className="w-14 h-14 bg-red-50 rounded-2xl flex items-center justify-center mb-6">
|
<div className="w-14 h-14 bg-red-50 rounded-2xl flex items-center justify-center mb-6">
|
||||||
<LogOut className="w-7 h-7 text-red-600" />
|
<LogOut className="w-7 h-7 text-red-600" />
|
||||||
</div>
|
</div>
|
||||||
<h2 className="text-2xl font-bold text-gray-900 mb-3">تسجيل الخروج</h2>
|
<h2 className="text-2xl font-bold text-gray-900 mb-3">{t('blocked.logoutTitle')}</h2>
|
||||||
<p className="text-gray-600 leading-7">
|
<p className="text-gray-600 leading-7">
|
||||||
إنهاء الجلسة الحالية وإزالة بيانات الدخول من هذا الجهاز.
|
{t('blocked.logoutDesc')}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -92,13 +94,13 @@ export default function BlockedPage() {
|
|||||||
className="mt-8 w-full bg-red-600 hover:bg-red-700 text-white rounded-2xl py-4 font-bold transition-colors flex items-center justify-center gap-3"
|
className="mt-8 w-full bg-red-600 hover:bg-red-700 text-white rounded-2xl py-4 font-bold transition-colors flex items-center justify-center gap-3"
|
||||||
>
|
>
|
||||||
<LogOut className="w-5 h-5" />
|
<LogOut className="w-5 h-5" />
|
||||||
تسجيل الخروج
|
{t('blocked.logoutButton')}
|
||||||
</button>
|
</button>
|
||||||
</motion.div>
|
</motion.div>
|
||||||
|
|
||||||
<motion.div
|
<motion.div
|
||||||
initial={{ opacity: 0, x: 24 }}
|
initial={{ opacity: 0, x: 24 }}
|
||||||
animate={{ opacity: 1, x: 0 }}
|
animate={{ opacity: 1, y: 0 }}
|
||||||
transition={{ delay: 0.2 }}
|
transition={{ delay: 0.2 }}
|
||||||
className="bg-white rounded-3xl shadow-sm border border-gray-200 p-8"
|
className="bg-white rounded-3xl shadow-sm border border-gray-200 p-8"
|
||||||
>
|
>
|
||||||
@ -107,30 +109,30 @@ export default function BlockedPage() {
|
|||||||
<MessageSquare className="w-7 h-7 text-amber-600" />
|
<MessageSquare className="w-7 h-7 text-amber-600" />
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<h2 className="text-2xl font-bold text-gray-900">مراسلة دعم العملاء</h2>
|
<h2 className="text-2xl font-bold text-gray-900">{t('blocked.contactSupportTitle')}</h2>
|
||||||
<p className="text-gray-600 mt-1">أرسل تفاصيل المشكلة وسنقوم بمراجعتها.</p>
|
<p className="text-gray-600 mt-1">{t('blocked.contactSupportDesc')}</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<form onSubmit={handleSubmit} className="space-y-5">
|
<form onSubmit={handleSubmit} className="space-y-5">
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-sm font-bold text-gray-700 mb-2">الموضوع</label>
|
<label className="block text-sm font-bold text-gray-700 mb-2">{t('subject')}</label>
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
value={form.subject}
|
value={form.subject}
|
||||||
onChange={(event) => updateField('subject', event.target.value)}
|
onChange={(event) => updateField('subject', event.target.value)}
|
||||||
placeholder="اكتب موضوع الرسالة"
|
placeholder={t('blocked.subjectPlaceholder')}
|
||||||
className="w-full px-4 py-3 bg-white border border-gray-200 rounded-2xl focus:outline-none focus:ring-2 focus:ring-amber-500 focus:border-transparent text-gray-900 placeholder-gray-400"
|
className="w-full px-4 py-3 bg-white border border-gray-200 rounded-2xl focus:outline-none focus:ring-2 focus:ring-amber-500 focus:border-transparent text-gray-900 placeholder-gray-400"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-sm font-bold text-gray-700 mb-2">الرسالة</label>
|
<label className="block text-sm font-bold text-gray-700 mb-2">{t('blocked.messageLabel')}</label>
|
||||||
<textarea
|
<textarea
|
||||||
value={form.body}
|
value={form.body}
|
||||||
onChange={(event) => updateField('body', event.target.value)}
|
onChange={(event) => updateField('body', event.target.value)}
|
||||||
rows={6}
|
rows={6}
|
||||||
placeholder="اشرح المشكلة بالتفصيل"
|
placeholder={t('blocked.messagePlaceholder')}
|
||||||
className="w-full px-4 py-3 bg-white border border-gray-200 rounded-2xl focus:outline-none focus:ring-2 focus:ring-amber-500 focus:border-transparent text-gray-900 placeholder-gray-400 resize-none"
|
className="w-full px-4 py-3 bg-white border border-gray-200 rounded-2xl focus:outline-none focus:ring-2 focus:ring-amber-500 focus:border-transparent text-gray-900 placeholder-gray-400 resize-none"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@ -143,17 +145,17 @@ export default function BlockedPage() {
|
|||||||
{isSubmitting ? (
|
{isSubmitting ? (
|
||||||
<>
|
<>
|
||||||
<Loader2 className="w-5 h-5 animate-spin" />
|
<Loader2 className="w-5 h-5 animate-spin" />
|
||||||
جاري الإرسال...
|
{t('blocked.sending')}
|
||||||
</>
|
</>
|
||||||
) : isSent ? (
|
) : isSent ? (
|
||||||
<>
|
<>
|
||||||
<Send className="w-5 h-5" />
|
<Send className="w-5 h-5" />
|
||||||
تم الإرسال
|
{t('blocked.sentButton')}
|
||||||
</>
|
</>
|
||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
<Send className="w-5 h-5" />
|
<Send className="w-5 h-5" />
|
||||||
إرسال الرسالة
|
{t('blocked.sendButton')}
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
import { useState, useEffect, useCallback } from 'react';
|
import { useState, useEffect, useCallback } from 'react';
|
||||||
import { motion, AnimatePresence } from 'framer-motion';
|
import { motion, AnimatePresence } from 'framer-motion';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
import {
|
import {
|
||||||
Home, Star, MapPin, Calendar, Clock, Check, X, Loader2,
|
Home, Star, MapPin, Calendar, Clock, Check, X, Loader2,
|
||||||
User, MessageCircle, ChevronDown, Image as ImageIcon,
|
User, MessageCircle, ChevronDown, Image as ImageIcon,
|
||||||
@ -14,27 +15,29 @@ import PropertyRatingForm from '../components/ratings/PropertyRatingForm';
|
|||||||
const STATUS_MAP = ['pending', 'ownerConfirmed', 'depositPaid', 'depositConfirmed', 'completed', 'cancelled'];
|
const STATUS_MAP = ['pending', 'ownerConfirmed', 'depositPaid', 'depositConfirmed', 'completed', 'cancelled'];
|
||||||
|
|
||||||
const STATUS_CONFIG = {
|
const STATUS_CONFIG = {
|
||||||
pending: { label: 'قيد الانتظار', color: 'bg-yellow-100 text-yellow-800 border-yellow-300' },
|
pending: { color: 'bg-yellow-100 text-yellow-800 border-yellow-300' },
|
||||||
ownerConfirmed: { label: 'مؤكد من المالك', color: 'bg-blue-100 text-blue-800 border-blue-300' },
|
ownerConfirmed: { color: 'bg-blue-100 text-blue-800 border-blue-300' },
|
||||||
depositPaid: { label: 'تم دفع السلفة', color: 'bg-orange-100 text-orange-800 border-orange-300' },
|
depositPaid: { color: 'bg-orange-100 text-orange-800 border-orange-300' },
|
||||||
depositConfirmed: { label: 'مؤكد', color: 'bg-green-100 text-green-800 border-green-300' },
|
depositConfirmed: { color: 'bg-green-100 text-green-800 border-green-300' },
|
||||||
completed: { label: 'منتهي', color: 'bg-teal-100 text-teal-800 border-teal-300' },
|
completed: { color: 'bg-teal-100 text-teal-800 border-teal-300' },
|
||||||
cancelled: { label: 'ملغي', color: 'bg-red-100 text-red-800 border-red-300' },
|
cancelled: { color: 'bg-red-100 text-red-800 border-red-300' },
|
||||||
};
|
};
|
||||||
|
|
||||||
const API_BASE = process.env.NEXT_PUBLIC_API_URL || 'https://45.93.137.91.nip.io/api';
|
const API_BASE = process.env.NEXT_PUBLIC_API_URL || 'https://45.93.137.91.nip.io/api';
|
||||||
|
|
||||||
function StatusBadge({ code }) {
|
function StatusBadge({ code }) {
|
||||||
|
const { t } = useTranslation();
|
||||||
const key = STATUS_MAP[code] || 'pending';
|
const key = STATUS_MAP[code] || 'pending';
|
||||||
const cfg = STATUS_CONFIG[key];
|
const cfg = STATUS_CONFIG[key];
|
||||||
return (
|
return (
|
||||||
<span className={`inline-flex items-center gap-1 px-3 py-1 rounded-full text-xs font-medium border ${cfg.color}`}>
|
<span className={`inline-flex items-center gap-1 px-3 py-1 rounded-full text-xs font-medium border ${cfg.color}`}>
|
||||||
<Clock className="w-3 h-3" /> {cfg.label}
|
<Clock className="w-3 h-3" /> {t(`bookingStatus.${key}`)}
|
||||||
</span>
|
</span>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function ReservationCard({ reservation: r, onRate }) {
|
function ReservationCard({ reservation: r, onRate }) {
|
||||||
|
const { t } = useTranslation();
|
||||||
const isCompleted = STATUS_MAP[r.status] === 'completed';
|
const isCompleted = STATUS_MAP[r.status] === 'completed';
|
||||||
const imgSrc = r.propertyImage || r._prop?.images?.[0] || (r.propertyInfo?.images?.[0]);
|
const imgSrc = r.propertyImage || r._prop?.images?.[0] || (r.propertyInfo?.images?.[0]);
|
||||||
const imageUrl = imgSrc ? `${API_BASE}${imgSrc}` : null;
|
const imageUrl = imgSrc ? `${API_BASE}${imgSrc}` : null;
|
||||||
@ -49,7 +52,7 @@ function ReservationCard({ reservation: r, onRate }) {
|
|||||||
<div className="flex justify-between items-start">
|
<div className="flex justify-between items-start">
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<Home className="w-5 h-5 text-amber-500" />
|
<Home className="w-5 h-5 text-amber-500" />
|
||||||
<span className="font-semibold text-gray-900">عقار #{r.propertyId || r.id}</span>
|
<span className="font-semibold text-gray-900">{t('propertyId', { id: r.propertyId || r.id })}</span>
|
||||||
</div>
|
</div>
|
||||||
<StatusBadge code={r.status} />
|
<StatusBadge code={r.status} />
|
||||||
</div>
|
</div>
|
||||||
@ -73,38 +76,38 @@ function ReservationCard({ reservation: r, onRate }) {
|
|||||||
|
|
||||||
{(beds > 0 || baths > 0) && (
|
{(beds > 0 || baths > 0) && (
|
||||||
<div className="flex gap-3 text-sm text-gray-600">
|
<div className="flex gap-3 text-sm text-gray-600">
|
||||||
{beds > 0 && <span>{beds} غرف</span>}
|
{beds > 0 && <span>{beds} {t('rooms')}</span>}
|
||||||
{baths > 0 && <span>{baths} حمامات</span>}
|
{baths > 0 && <span>{baths} {t('bathrooms')}</span>}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<div className="grid grid-cols-2 gap-3">
|
<div className="grid grid-cols-2 gap-3">
|
||||||
<div className="bg-gray-50 p-3 rounded-xl text-center">
|
<div className="bg-gray-50 p-3 rounded-xl text-center">
|
||||||
<Calendar className="w-4 h-4 text-amber-500 mx-auto mb-1" />
|
<Calendar className="w-4 h-4 text-amber-500 mx-auto mb-1" />
|
||||||
<div className="text-xs text-gray-500">من</div>
|
<div className="text-xs text-gray-500">{t('from')}</div>
|
||||||
<div className="text-sm font-medium">{new Date(r.startDate).toLocaleDateString('ar')}</div>
|
<div className="text-sm font-medium">{new Date(r.startDate).toLocaleDateString('ar')}</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="bg-gray-50 p-3 rounded-xl text-center">
|
<div className="bg-gray-50 p-3 rounded-xl text-center">
|
||||||
<Calendar className="w-4 h-4 text-amber-500 mx-auto mb-1" />
|
<Calendar className="w-4 h-4 text-amber-500 mx-auto mb-1" />
|
||||||
<div className="text-xs text-gray-500">إلى</div>
|
<div className="text-xs text-gray-500">{t('to')}</div>
|
||||||
<div className="text-sm font-medium">{new Date(r.endDate).toLocaleDateString('ar')}</div>
|
<div className="text-sm font-medium">{new Date(r.endDate).toLocaleDateString('ar')}</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="bg-amber-50 p-3 rounded-xl text-center">
|
<div className="bg-amber-50 p-3 rounded-xl text-center">
|
||||||
<div className="text-lg font-bold text-amber-600">{r.totalPrice?.toLocaleString() ?? '—'}</div>
|
<div className="text-lg font-bold text-amber-600">{r.totalPrice?.toLocaleString() ?? '—'}</div>
|
||||||
<div className="text-xs text-gray-500">السعر الإجمالي</div>
|
<div className="text-xs text-gray-500">{t('totalPrice')}</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex items-center gap-2 text-xs text-gray-400">
|
<div className="flex items-center gap-2 text-xs text-gray-400">
|
||||||
<User className="w-3 h-3" />
|
<User className="w-3 h-3" />
|
||||||
<span>رقم الحجز: #{r.id}</span>
|
<span>{t('reservationNumber', { id: r.id })}</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{isCompleted && (
|
{isCompleted && (
|
||||||
<button onClick={onRate}
|
<button onClick={onRate}
|
||||||
className="w-full bg-amber-500 hover:bg-amber-600 text-white py-2.5 rounded-xl text-sm font-medium transition flex items-center justify-center gap-2">
|
className="w-full bg-amber-500 hover:bg-amber-600 text-white py-2.5 rounded-xl text-sm font-medium transition flex items-center justify-center gap-2">
|
||||||
<Star className="w-4 h-4" /> تقييم العقار
|
<Star className="w-4 h-4" /> {t('rateProperty')}
|
||||||
</button>
|
</button>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
@ -113,6 +116,7 @@ function ReservationCard({ reservation: r, onRate }) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export default function BookedPropertiesPage() {
|
export default function BookedPropertiesPage() {
|
||||||
|
const { t } = useTranslation();
|
||||||
const [reservations, setReservations] = useState([]);
|
const [reservations, setReservations] = useState([]);
|
||||||
const [loading, setLoading] = useState(true);
|
const [loading, setLoading] = useState(true);
|
||||||
const [ratingReservation, setRatingReservation] = useState(null);
|
const [ratingReservation, setRatingReservation] = useState(null);
|
||||||
@ -120,7 +124,7 @@ export default function BookedPropertiesPage() {
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!AuthService.getToken()) {
|
if (!AuthService.getToken()) {
|
||||||
toast.error('يرجى تسجيل الدخول أولاً');
|
toast.error(t('loginRequired'));
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -133,7 +137,7 @@ export default function BookedPropertiesPage() {
|
|||||||
setReservations(Array.isArray(data) ? data : []);
|
setReservations(Array.isArray(data) ? data : []);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error(err);
|
console.error(err);
|
||||||
toast.error('فشل تحميل الحجوزات');
|
toast.error(t('failedToLoadBookings'));
|
||||||
setReservations([]);
|
setReservations([]);
|
||||||
} finally {
|
} finally {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
@ -155,14 +159,14 @@ export default function BookedPropertiesPage() {
|
|||||||
<motion.div initial={{ opacity: 0, y: -20 }} animate={{ opacity: 1, y: 0 }} className="mb-8">
|
<motion.div initial={{ opacity: 0, y: -20 }} animate={{ opacity: 1, y: 0 }} className="mb-8">
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<div>
|
<div>
|
||||||
<h1 className="text-3xl font-bold text-gray-900 mb-2">العقارات المحجوزة</h1>
|
<h1 className="text-3xl font-bold text-gray-900 mb-2">{t('bookedProperties')}</h1>
|
||||||
<p className="text-gray-600">لديك {reservations.length} حجز</p>
|
<p className="text-gray-600">{t('youHaveBookings', { count: reservations.length })}</p>
|
||||||
</div>
|
</div>
|
||||||
{reservations.length > 0 && (
|
{reservations.length > 0 && (
|
||||||
<button onClick={() => setExpandedId(expandedId ? null : 'all')}
|
<button onClick={() => setExpandedId(expandedId ? null : 'all')}
|
||||||
className="flex items-center gap-1 text-sm text-amber-600 hover:text-amber-700 transition">
|
className="flex items-center gap-1 text-sm text-amber-600 hover:text-amber-700 transition">
|
||||||
<ChevronDown className={`w-4 h-4 transition-transform ${expandedId ? 'rotate-180' : ''}`} />
|
<ChevronDown className={`w-4 h-4 transition-transform ${expandedId ? 'rotate-180' : ''}`} />
|
||||||
{expandedId ? 'طي الكل' : 'عرض الكل'}
|
{expandedId ? t('collapseAll') : t('showAll')}
|
||||||
</button>
|
</button>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
@ -172,8 +176,8 @@ export default function BookedPropertiesPage() {
|
|||||||
<motion.div initial={{ opacity: 0, y: 20 }} animate={{ opacity: 1, y: 0 }}
|
<motion.div initial={{ opacity: 0, y: 20 }} animate={{ opacity: 1, y: 0 }}
|
||||||
className="bg-white rounded-2xl p-12 text-center border-2 border-dashed border-gray-300">
|
className="bg-white rounded-2xl p-12 text-center border-2 border-dashed border-gray-300">
|
||||||
<Home className="w-16 h-16 text-amber-500 mx-auto mb-4" />
|
<Home className="w-16 h-16 text-amber-500 mx-auto mb-4" />
|
||||||
<h3 className="text-xl font-bold text-gray-900 mb-2">لا توجد حجوزات</h3>
|
<h3 className="text-xl font-bold text-gray-900 mb-2">{t('noBookings')}</h3>
|
||||||
<p className="text-gray-500">لم تقم بحجز أي عقار حتى الآن</p>
|
<p className="text-gray-500">{t('noBookingsDesc')}</p>
|
||||||
</motion.div>
|
</motion.div>
|
||||||
) : (
|
) : (
|
||||||
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
|
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
|
||||||
@ -188,8 +192,8 @@ export default function BookedPropertiesPage() {
|
|||||||
className="mt-8 bg-amber-50 border border-amber-200 rounded-xl p-4 flex items-start gap-3">
|
className="mt-8 bg-amber-50 border border-amber-200 rounded-xl p-4 flex items-start gap-3">
|
||||||
<MessageCircle className="w-5 h-5 text-amber-600 flex-shrink-0 mt-0.5" />
|
<MessageCircle className="w-5 h-5 text-amber-600 flex-shrink-0 mt-0.5" />
|
||||||
<div>
|
<div>
|
||||||
<p className="text-amber-800 font-medium">تقييم العقارات</p>
|
<p className="text-amber-800 font-medium">{t('rateProperties')}</p>
|
||||||
<p className="text-amber-600 text-sm">يمكنك تقييم العقارات المنتهية حجزها لمساعدة المستأجرين الآخرين</p>
|
<p className="text-amber-600 text-sm">{t('ratePropertiesDesc')}</p>
|
||||||
</div>
|
</div>
|
||||||
</motion.div>
|
</motion.div>
|
||||||
)}
|
)}
|
||||||
@ -209,7 +213,7 @@ export default function BookedPropertiesPage() {
|
|||||||
</button>
|
</button>
|
||||||
<PropertyRatingForm
|
<PropertyRatingForm
|
||||||
reservationId={ratingReservation.id}
|
reservationId={ratingReservation.id}
|
||||||
onSuccess={() => { setRatingReservation(null); toast.success('تم إرسال التقييم بنجاح!'); }}
|
onSuccess={() => { setRatingReservation(null); toast.success(t('ratingSubmitted')); }}
|
||||||
onCancel={() => setRatingReservation(null)}
|
onCancel={() => setRatingReservation(null)}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -5,11 +5,13 @@ import { motion } from 'framer-motion';
|
|||||||
import { useRouter } from 'next/navigation';
|
import { useRouter } from 'next/navigation';
|
||||||
import toast, { Toaster } from 'react-hot-toast';
|
import toast, { Toaster } from 'react-hot-toast';
|
||||||
import { Lock, Eye, EyeOff, ArrowLeft, Shield } from 'lucide-react';
|
import { Lock, Eye, EyeOff, ArrowLeft, Shield } from 'lucide-react';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
import { changePassword } from '../utils/api';
|
import { changePassword } from '../utils/api';
|
||||||
import AuthService from '../services/AuthService';
|
import AuthService from '../services/AuthService';
|
||||||
|
|
||||||
export default function ChangePasswordPage() {
|
export default function ChangePasswordPage() {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
const { t } = useTranslation();
|
||||||
const [form, setForm] = useState({ oldPassword: '', newPassword: '', confirmPassword: '' });
|
const [form, setForm] = useState({ oldPassword: '', newPassword: '', confirmPassword: '' });
|
||||||
const [show, setShow] = useState({ old: false, new: false, confirm: false });
|
const [show, setShow] = useState({ old: false, new: false, confirm: false });
|
||||||
const [isLoading, setIsLoading] = useState(false);
|
const [isLoading, setIsLoading] = useState(false);
|
||||||
@ -22,28 +24,28 @@ export default function ChangePasswordPage() {
|
|||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
|
||||||
if (!AuthService.isAuthenticated()) {
|
if (!AuthService.isAuthenticated()) {
|
||||||
toast.error('يرجى تسجيل الدخول أولاً');
|
toast.error(t('changePassword.loginRequired'));
|
||||||
router.push('/login');
|
router.push('/login');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (form.newPassword !== form.confirmPassword) {
|
if (form.newPassword !== form.confirmPassword) {
|
||||||
toast.error('كلمة المرور الجديدة وتأكيدها غير متطابقتين');
|
toast.error(t('changePassword.passwordsDoNotMatch'));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (form.newPassword.length < 6) {
|
if (form.newPassword.length < 6) {
|
||||||
toast.error('كلمة المرور الجديدة يجب أن تكون 6 أحرف على الأقل');
|
toast.error(t('validation.passwordMinLength'));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
setIsLoading(true);
|
setIsLoading(true);
|
||||||
try {
|
try {
|
||||||
await changePassword(form.oldPassword, form.newPassword);
|
await changePassword(form.oldPassword, form.newPassword);
|
||||||
toast.success('تم تغيير كلمة المرور بنجاح');
|
toast.success(t('changePassword.changeSuccess'));
|
||||||
setTimeout(() => router.push('/profile'), 1200);
|
setTimeout(() => router.push('/profile'), 1200);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
toast.error(err?.message || 'فشل تغيير كلمة المرور');
|
toast.error(err?.message || t('changePassword.changeFailed'));
|
||||||
} finally {
|
} finally {
|
||||||
setIsLoading(false);
|
setIsLoading(false);
|
||||||
}
|
}
|
||||||
@ -71,7 +73,7 @@ export default function ChangePasswordPage() {
|
|||||||
className="flex items-center gap-2 text-gray-600 hover:text-amber-600 transition-colors"
|
className="flex items-center gap-2 text-gray-600 hover:text-amber-600 transition-colors"
|
||||||
>
|
>
|
||||||
<ArrowLeft className="w-5 h-5" />
|
<ArrowLeft className="w-5 h-5" />
|
||||||
<span>العودة للملف الشخصي</span>
|
<span>{t('changePassword.backToProfile')}</span>
|
||||||
</button>
|
</button>
|
||||||
</motion.div>
|
</motion.div>
|
||||||
|
|
||||||
@ -90,7 +92,7 @@ export default function ChangePasswordPage() {
|
|||||||
animate={{ y: 0, opacity: 1 }}
|
animate={{ y: 0, opacity: 1 }}
|
||||||
className="text-3xl font-bold text-white mb-2"
|
className="text-3xl font-bold text-white mb-2"
|
||||||
>
|
>
|
||||||
تغيير كلمة المرور
|
{t('changePassword.title')}
|
||||||
</motion.h1>
|
</motion.h1>
|
||||||
<motion.p
|
<motion.p
|
||||||
initial={{ y: 20, opacity: 0 }}
|
initial={{ y: 20, opacity: 0 }}
|
||||||
@ -98,14 +100,14 @@ export default function ChangePasswordPage() {
|
|||||||
transition={{ delay: 0.1 }}
|
transition={{ delay: 0.1 }}
|
||||||
className="text-amber-100"
|
className="text-amber-100"
|
||||||
>
|
>
|
||||||
أدخل كلمة المرور الحالية والجديدة
|
{t('changePassword.subtitle')}
|
||||||
</motion.p>
|
</motion.p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<form onSubmit={handleSubmit} className="p-8 space-y-6">
|
<form onSubmit={handleSubmit} className="p-8 space-y-6">
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-sm font-medium text-gray-700 mb-2">
|
<label className="block text-sm font-medium text-gray-700 mb-2">
|
||||||
كلمة المرور الحالية
|
{t('changePassword.currentPasswordLabel')}
|
||||||
</label>
|
</label>
|
||||||
<div className="relative">
|
<div className="relative">
|
||||||
<div className="absolute inset-y-0 right-0 pr-3 flex items-center pointer-events-none">
|
<div className="absolute inset-y-0 right-0 pr-3 flex items-center pointer-events-none">
|
||||||
@ -116,7 +118,7 @@ export default function ChangePasswordPage() {
|
|||||||
value={form.oldPassword}
|
value={form.oldPassword}
|
||||||
onChange={handleChange('oldPassword')}
|
onChange={handleChange('oldPassword')}
|
||||||
className={inputClass}
|
className={inputClass}
|
||||||
placeholder="أدخل كلمة المرور الحالية"
|
placeholder={t('changePassword.currentPasswordPlaceholder')}
|
||||||
required
|
required
|
||||||
/>
|
/>
|
||||||
<button
|
<button
|
||||||
@ -131,7 +133,7 @@ export default function ChangePasswordPage() {
|
|||||||
|
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-sm font-medium text-gray-700 mb-2">
|
<label className="block text-sm font-medium text-gray-700 mb-2">
|
||||||
كلمة المرور الجديدة
|
{t('changePassword.newPasswordLabel')}
|
||||||
</label>
|
</label>
|
||||||
<div className="relative">
|
<div className="relative">
|
||||||
<div className="absolute inset-y-0 right-0 pr-3 flex items-center pointer-events-none">
|
<div className="absolute inset-y-0 right-0 pr-3 flex items-center pointer-events-none">
|
||||||
@ -142,7 +144,7 @@ export default function ChangePasswordPage() {
|
|||||||
value={form.newPassword}
|
value={form.newPassword}
|
||||||
onChange={handleChange('newPassword')}
|
onChange={handleChange('newPassword')}
|
||||||
className={inputClass}
|
className={inputClass}
|
||||||
placeholder="أدخل كلمة المرور الجديدة"
|
placeholder={t('changePassword.newPasswordPlaceholder')}
|
||||||
required
|
required
|
||||||
minLength={6}
|
minLength={6}
|
||||||
/>
|
/>
|
||||||
@ -158,7 +160,7 @@ export default function ChangePasswordPage() {
|
|||||||
|
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-sm font-medium text-gray-700 mb-2">
|
<label className="block text-sm font-medium text-gray-700 mb-2">
|
||||||
تأكيد كلمة المرور الجديدة
|
{t('changePassword.confirmNewPasswordLabel')}
|
||||||
</label>
|
</label>
|
||||||
<div className="relative">
|
<div className="relative">
|
||||||
<div className="absolute inset-y-0 right-0 pr-3 flex items-center pointer-events-none">
|
<div className="absolute inset-y-0 right-0 pr-3 flex items-center pointer-events-none">
|
||||||
@ -169,7 +171,7 @@ export default function ChangePasswordPage() {
|
|||||||
value={form.confirmPassword}
|
value={form.confirmPassword}
|
||||||
onChange={handleChange('confirmPassword')}
|
onChange={handleChange('confirmPassword')}
|
||||||
className={inputClass}
|
className={inputClass}
|
||||||
placeholder="أعد إدخال كلمة المرور الجديدة"
|
placeholder={t('changePassword.confirmPasswordPlaceholder')}
|
||||||
required
|
required
|
||||||
minLength={6}
|
minLength={6}
|
||||||
/>
|
/>
|
||||||
@ -193,10 +195,10 @@ export default function ChangePasswordPage() {
|
|||||||
{isLoading ? (
|
{isLoading ? (
|
||||||
<div className="flex items-center justify-center gap-2">
|
<div className="flex items-center justify-center gap-2">
|
||||||
<div className="w-5 h-5 border-2 border-white border-t-transparent rounded-full animate-spin" />
|
<div className="w-5 h-5 border-2 border-white border-t-transparent rounded-full animate-spin" />
|
||||||
<span>جاري الحفظ...</span>
|
<span>{t('changePassword.saving')}</span>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
'تغيير كلمة المرور'
|
t('changePassword.changePassword')
|
||||||
)}
|
)}
|
||||||
</motion.button>
|
</motion.button>
|
||||||
</form>
|
</form>
|
||||||
|
|||||||
@ -4,8 +4,10 @@ import Link from "next/link";
|
|||||||
import { usePathname } from "next/navigation";
|
import { usePathname } from "next/navigation";
|
||||||
import { Home, Building, Calendar, CreditCard, Briefcase, BookOpen } from "lucide-react";
|
import { Home, Building, Calendar, CreditCard, Briefcase, BookOpen } from "lucide-react";
|
||||||
import React, { useEffect, useState } from "react";
|
import React, { useEffect, useState } from "react";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
|
||||||
export default function BottomNav({ isOwner, isOwnerOrAgent }) {
|
export default function BottomNav({ isOwner, isOwnerOrAgent }) {
|
||||||
|
const { t } = useTranslation();
|
||||||
const pathname = usePathname();
|
const pathname = usePathname();
|
||||||
const [isMounted, setIsMounted] = useState(false);
|
const [isMounted, setIsMounted] = useState(false);
|
||||||
const bookingsHref = isOwner ? "/owner/reservations" : "/reservations";
|
const bookingsHref = isOwner ? "/owner/reservations" : "/reservations";
|
||||||
@ -15,12 +17,12 @@ export default function BottomNav({ isOwner, isOwnerOrAgent }) {
|
|||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const items = [
|
const items = [
|
||||||
{ href: "/", label: "الرئيسية", icon: Home },
|
{ href: "/", label: t("home"), icon: Home },
|
||||||
{ href: "/properties", label: "عقاراتنا", icon: Building },
|
{ href: "/properties", label: t("ourProperties"), icon: Building },
|
||||||
...(isOwnerOrAgent ? [{ href: "/owner/properties", label: "عقاراتي", icon: Briefcase }] : []),
|
...(isOwnerOrAgent ? [{ href: "/owner/properties", label: t("myProperties"), icon: Briefcase }] : []),
|
||||||
{ href: bookingsHref, label: "الحجوزات", icon: Calendar },
|
{ href: bookingsHref, label: t("reservations"), icon: Calendar },
|
||||||
{ href: "/payments", label: "المدفوعات", icon: CreditCard },
|
{ href: "/payments", label: t("payments"), icon: CreditCard },
|
||||||
...(isOwnerOrAgent ? [{ href: "/owner/account-book", label: "دفتر الحسابات", icon: BookOpen }] : []),
|
...(isOwnerOrAgent ? [{ href: "/owner/account-book", label: t("accountBook"), icon: BookOpen }] : []),
|
||||||
];
|
];
|
||||||
|
|
||||||
const isActive = (href) => {
|
const isActive = (href) => {
|
||||||
|
|||||||
@ -4,11 +4,13 @@ import { useState } from 'react';
|
|||||||
import { motion } from 'framer-motion';
|
import { motion } from 'framer-motion';
|
||||||
import Link from 'next/link';
|
import Link from 'next/link';
|
||||||
import { Heart, Bell, CreditCard, Settings } from 'lucide-react';
|
import { Heart, Bell, CreditCard, Settings } from 'lucide-react';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
import { useFavorites } from '@/app/contexts/FavoritesContext';
|
import { useFavorites } from '@/app/contexts/FavoritesContext';
|
||||||
import { useNotifications } from '@/app/contexts/NotificationsContext';
|
import { useNotifications } from '@/app/contexts/NotificationsContext';
|
||||||
import AuthService from '@/app/services/AuthService';
|
import AuthService from '@/app/services/AuthService';
|
||||||
|
|
||||||
export default function FloatingSidebar({ isRTL }) {
|
export default function FloatingSidebar({ isRTL }) {
|
||||||
|
const { t } = useTranslation();
|
||||||
const { favorites } = useFavorites();
|
const { favorites } = useFavorites();
|
||||||
const { unreadCount } = useNotifications();
|
const { unreadCount } = useNotifications();
|
||||||
const [tooltip, setTooltip] = useState(null);
|
const [tooltip, setTooltip] = useState(null);
|
||||||
@ -88,7 +90,7 @@ export default function FloatingSidebar({ isRTL }) {
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</Link>
|
</Link>
|
||||||
{renderTooltip('favorites', 'المفضلة')}
|
{renderTooltip('favorites', t('favorites'))}
|
||||||
</motion.div>
|
</motion.div>
|
||||||
<motion.div
|
<motion.div
|
||||||
className="relative group"
|
className="relative group"
|
||||||
@ -116,7 +118,7 @@ export default function FloatingSidebar({ isRTL }) {
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</Link>
|
</Link>
|
||||||
{renderTooltip('notifications', 'الإشعارات')}
|
{renderTooltip('notifications', t('notifications'))}
|
||||||
</motion.div>
|
</motion.div>
|
||||||
<motion.div
|
<motion.div
|
||||||
className="relative group"
|
className="relative group"
|
||||||
@ -133,7 +135,7 @@ export default function FloatingSidebar({ isRTL }) {
|
|||||||
>
|
>
|
||||||
<CreditCard className="w-7 h-7 text-gray-600 transition-colors group-hover:text-amber-600" />
|
<CreditCard className="w-7 h-7 text-gray-600 transition-colors group-hover:text-amber-600" />
|
||||||
</Link>
|
</Link>
|
||||||
{renderTooltip('payments', 'المدفوعات')}
|
{renderTooltip('payments', t('payments'))}
|
||||||
</motion.div>
|
</motion.div>
|
||||||
<motion.div
|
<motion.div
|
||||||
className="relative group"
|
className="relative group"
|
||||||
@ -150,7 +152,7 @@ export default function FloatingSidebar({ isRTL }) {
|
|||||||
>
|
>
|
||||||
<Settings className="w-7 h-7 text-gray-600 transition-colors group-hover:text-amber-600" />
|
<Settings className="w-7 h-7 text-gray-600 transition-colors group-hover:text-amber-600" />
|
||||||
</Link>
|
</Link>
|
||||||
{renderTooltip('settings', 'الإعدادات')}
|
{renderTooltip('settings', t('settings'))}
|
||||||
</motion.div>
|
</motion.div>
|
||||||
</div>
|
</div>
|
||||||
</motion.div>
|
</motion.div>
|
||||||
|
|||||||
@ -21,46 +21,46 @@ export default function HeroSearch({ onSearch, isAuthenticated }) {
|
|||||||
const [showLoginDialog, setShowLoginDialog] = useState(false);
|
const [showLoginDialog, setShowLoginDialog] = useState(false);
|
||||||
|
|
||||||
const cities = [
|
const cities = [
|
||||||
{ id: 'all', label: 'جميع المدن' },
|
{ id: 'all', label: t('allCities') },
|
||||||
{ id: 'دمشق', label: 'دمشق' },
|
{ id: 'دمشق', label: t('damascus') },
|
||||||
{ id: 'حلب', label: 'حلب' },
|
{ id: 'حلب', label: t('aleppo') },
|
||||||
{ id: 'حمص', label: 'حمص' },
|
{ id: 'حمص', label: t('homs') },
|
||||||
{ id: 'اللاذقية', label: 'اللاذقية' },
|
{ id: 'اللاذقية', label: t('latakia') },
|
||||||
{ id: 'درعا', label: 'درعا' }
|
{ id: 'درعا', label: t('daraa') }
|
||||||
];
|
];
|
||||||
|
|
||||||
const propertyTypes = [
|
const propertyTypes = [
|
||||||
{ id: 'all', label: 'الكل' },
|
{ id: 'all', label: t('all') },
|
||||||
{ id: 'apartment', label: 'شقق سكنية' },
|
{ id: 'apartment', label: t('residentialApartments') },
|
||||||
{ id: 'studio', label: 'استوديو' },
|
{ id: 'studio', label: t('studio') },
|
||||||
{ id: 'commercial', label: 'عقار تجاري' },
|
{ id: 'commercial', label: t('commercialProperty') },
|
||||||
{ id: 'villa', label: 'فيلا / مزرعة' }
|
{ id: 'villa', label: t('villaFarm') }
|
||||||
];
|
];
|
||||||
|
|
||||||
const priceRanges = [
|
const priceRanges = [
|
||||||
{ id: 'all', label: 'جميع الأسعار' },
|
{ id: 'all', label: t('allPrices') },
|
||||||
{ id: '0-500', label: 'أقل من 50$' },
|
{ id: '0-500', label: t('priceRange1') },
|
||||||
{ id: '500-1000', label: '50$ - 100$' },
|
{ id: '500-1000', label: t('priceRange2') },
|
||||||
{ id: '1000-2000', label: '100$ - 200$' },
|
{ id: '1000-2000', label: t('priceRange3') },
|
||||||
{ id: '2000-3000', label: '200$ - 300$' },
|
{ id: '2000-3000', label: t('priceRange4') },
|
||||||
{ id: '3000+', label: 'أكثر من 300$' }
|
{ id: '3000+', label: t('priceRange5') }
|
||||||
];
|
];
|
||||||
|
|
||||||
const identityTypes = [
|
const identityTypes = [
|
||||||
{ id: 'syrian', label: 'هوية سورية' },
|
{ id: 'syrian', label: t('syrianId') },
|
||||||
{ id: 'passport', label: 'جواز سفر' }
|
{ id: 'passport', label: t('passport') }
|
||||||
];
|
];
|
||||||
|
|
||||||
const ownerSources = [
|
const ownerSources = [
|
||||||
{ id: 'all', label: 'الكل' },
|
{ id: 'all', label: t('all') },
|
||||||
{ id: 'owner', label: 'من المالك' },
|
{ id: 'owner', label: t('fromOwner') },
|
||||||
{ id: 'agency', label: 'من مكتب عقاري' }
|
{ id: 'agency', label: t('fromAgency') }
|
||||||
];
|
];
|
||||||
|
|
||||||
const rentPeriods = [
|
const rentPeriods = [
|
||||||
{ id: 'all', label: 'الكل' },
|
{ id: 'all', label: t('all') },
|
||||||
{ id: 'daily', label: 'إيجار يومي' },
|
{ id: 'daily', label: t('dailyRent') },
|
||||||
{ id: 'monthly', label: 'إيجار شهري' }
|
{ id: 'monthly', label: t('monthlyRent') }
|
||||||
];
|
];
|
||||||
|
|
||||||
const handleTabClick = (tab) => {
|
const handleTabClick = (tab) => {
|
||||||
@ -190,7 +190,7 @@ export default function HeroSearch({ onSearch, isAuthenticated }) {
|
|||||||
<div>
|
<div>
|
||||||
<label className="block text-sm font-medium text-white mb-2">
|
<label className="block text-sm font-medium text-white mb-2">
|
||||||
<div className="flex items-center gap-1">
|
<div className="flex items-center gap-1">
|
||||||
نوع الوثيقة
|
{t("identityTypeLabel")}
|
||||||
</div>
|
</div>
|
||||||
</label>
|
</label>
|
||||||
<select
|
<select
|
||||||
@ -213,7 +213,7 @@ export default function HeroSearch({ onSearch, isAuthenticated }) {
|
|||||||
</div>
|
</div>
|
||||||
<div className="grid grid-cols-1 md:grid-cols-4 gap-4 mt-4">
|
<div className="grid grid-cols-1 md:grid-cols-4 gap-4 mt-4">
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-sm font-medium text-white mb-2">مصدر العرض</label>
|
<label className="block text-sm font-medium text-white mb-2">{t("ownerSourceLabel")}</label>
|
||||||
<select
|
<select
|
||||||
value={filters.ownerSource}
|
value={filters.ownerSource}
|
||||||
onChange={(e) => setFilters({ ...filters, ownerSource: e.target.value })}
|
onChange={(e) => setFilters({ ...filters, ownerSource: e.target.value })}
|
||||||
@ -236,7 +236,7 @@ export default function HeroSearch({ onSearch, isAuthenticated }) {
|
|||||||
|
|
||||||
{activeTab === 'rent' && (
|
{activeTab === 'rent' && (
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-sm font-medium text-white mb-2">نوع الإيجار</label>
|
<label className="block text-sm font-medium text-white mb-2">{t("rentTypeLabel")}</label>
|
||||||
<select
|
<select
|
||||||
value={filters.rentPeriod}
|
value={filters.rentPeriod}
|
||||||
onChange={(e) => setFilters({ ...filters, rentPeriod: e.target.value })}
|
onChange={(e) => setFilters({ ...filters, rentPeriod: e.target.value })}
|
||||||
@ -266,7 +266,7 @@ export default function HeroSearch({ onSearch, isAuthenticated }) {
|
|||||||
onChange={(e) => setFilters({ ...filters, availableToday: e.target.checked })}
|
onChange={(e) => setFilters({ ...filters, availableToday: e.target.checked })}
|
||||||
className="w-5 h-5 text-amber-500 rounded border-gray-300 bg-white"
|
className="w-5 h-5 text-amber-500 rounded border-gray-300 bg-white"
|
||||||
/>
|
/>
|
||||||
<span className="font-medium">عرض فقط العقارات المتاحة من اليوم</span>
|
<span className="font-medium">{t("availableToday")}</span>
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -289,13 +289,13 @@ export default function HeroSearch({ onSearch, isAuthenticated }) {
|
|||||||
<div className="flex items-center gap-3 mb-5">
|
<div className="flex items-center gap-3 mb-5">
|
||||||
<ShieldCheck className="w-7 h-7 text-amber-500" />
|
<ShieldCheck className="w-7 h-7 text-amber-500" />
|
||||||
<div>
|
<div>
|
||||||
<h3 className="text-lg font-semibold text-gray-900">يرجى تسجيل الدخول</h3>
|
<h3 className="text-lg font-semibold text-gray-900">{t("loginRequired")}</h3>
|
||||||
<p className="text-sm text-gray-600">للوصول إلى خيارات التأجير والبيع، يجب أن تكون مسجلاً.</p>
|
<p className="text-sm text-gray-600">{t("loginRequiredDesc")}</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
<div className="rounded-2xl bg-gray-50 p-4">
|
<div className="rounded-2xl bg-gray-50 p-4">
|
||||||
<p className="text-sm text-gray-700">اضغط على تسجيل الدخول لاستكمال البحث أو إدارة عقاراتك.</p>
|
<p className="text-sm text-gray-700">{t("loginPrompt")}</p>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex flex-col gap-3 sm:flex-row sm:justify-end">
|
<div className="flex flex-col gap-3 sm:flex-row sm:justify-end">
|
||||||
<button
|
<button
|
||||||
@ -303,13 +303,13 @@ export default function HeroSearch({ onSearch, isAuthenticated }) {
|
|||||||
onClick={() => setShowLoginDialog(false)}
|
onClick={() => setShowLoginDialog(false)}
|
||||||
className="w-full sm:w-auto px-5 py-3 rounded-xl border border-gray-300 text-gray-700 hover:bg-gray-100 transition-colors"
|
className="w-full sm:w-auto px-5 py-3 rounded-xl border border-gray-300 text-gray-700 hover:bg-gray-100 transition-colors"
|
||||||
>
|
>
|
||||||
إغلاق
|
{t("close")}
|
||||||
</button>
|
</button>
|
||||||
<Link
|
<Link
|
||||||
href="/login"
|
href="/login"
|
||||||
className="w-full sm:w-auto px-5 py-3 rounded-xl bg-amber-500 text-white font-semibold text-center hover:bg-amber-600 transition-colors"
|
className="w-full sm:w-auto px-5 py-3 rounded-xl bg-amber-500 text-white font-semibold text-center hover:bg-amber-600 transition-colors"
|
||||||
>
|
>
|
||||||
تسجيل الدخول
|
{t("login")}
|
||||||
</Link>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import { X, Loader2 } from 'lucide-react';
|
import { X, Loader2 } from 'lucide-react';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
import toast from 'react-hot-toast';
|
import toast from 'react-hot-toast';
|
||||||
import StarRating from './StarRating';
|
import StarRating from './StarRating';
|
||||||
import { addCustomerRating } from '../../utils/ratings';
|
import { addCustomerRating } from '../../utils/ratings';
|
||||||
@ -10,11 +11,12 @@ const RatingField = ({ label, value, onChange }) => (
|
|||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<label className="block text-sm font-medium text-gray-700">{label} <span className="text-red-500">*</span></label>
|
<label className="block text-sm font-medium text-gray-700">{label} <span className="text-red-500">*</span></label>
|
||||||
<StarRating rating={value} onRatingChange={onChange} size={28} />
|
<StarRating rating={value} onRatingChange={onChange} size={28} />
|
||||||
{value === 0 && <p className="text-xs text-red-500">مطلوب</p>}
|
{value === 0 && <p className="text-xs text-red-500">{/* required text handled in parent */}</p>}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
||||||
export default function CustomerRatingForm({ reservationId, onSuccess, onCancel }) {
|
export default function CustomerRatingForm({ reservationId, onSuccess, onCancel }) {
|
||||||
|
const { t } = useTranslation();
|
||||||
const [furnitureIntegrityRating, setFurnitureIntegrityRating] = useState(0);
|
const [furnitureIntegrityRating, setFurnitureIntegrityRating] = useState(0);
|
||||||
const [termsComplianceRating, setTermsComplianceRating] = useState(0);
|
const [termsComplianceRating, setTermsComplianceRating] = useState(0);
|
||||||
const [renterBehaviorRating, setRenterBehaviorRating] = useState(0);
|
const [renterBehaviorRating, setRenterBehaviorRating] = useState(0);
|
||||||
@ -22,9 +24,9 @@ export default function CustomerRatingForm({ reservationId, onSuccess, onCancel
|
|||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
|
|
||||||
const validate = () => {
|
const validate = () => {
|
||||||
if (furnitureIntegrityRating === 0) return 'الحفاظ على الأثاث';
|
if (furnitureIntegrityRating === 0) return t('ratings.furnitureIntegrity');
|
||||||
if (termsComplianceRating === 0) return 'الالتزام بالشروط';
|
if (termsComplianceRating === 0) return t('ratings.termsCompliance');
|
||||||
if (renterBehaviorRating === 0) return 'سلوك المستأجر';
|
if (renterBehaviorRating === 0) return t('ratings.renterBehavior');
|
||||||
return null;
|
return null;
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -32,7 +34,7 @@ export default function CustomerRatingForm({ reservationId, onSuccess, onCancel
|
|||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
const missing = validate();
|
const missing = validate();
|
||||||
if (missing) {
|
if (missing) {
|
||||||
toast.error(`يرجى تقييم: ${missing}`);
|
toast.error(t('ratings.pleaseRate') + ': ' + missing);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -45,10 +47,10 @@ export default function CustomerRatingForm({ reservationId, onSuccess, onCancel
|
|||||||
renterBehaviorRating,
|
renterBehaviorRating,
|
||||||
comment: comment.trim() || null,
|
comment: comment.trim() || null,
|
||||||
});
|
});
|
||||||
toast.success('تم إرسال تقييم المستأجر بنجاح!');
|
toast.success(t('ratings.customerRatingSubmitted'));
|
||||||
onSuccess?.();
|
onSuccess?.();
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
toast.error('حدث خطأ، حاول مرة أخرى');
|
toast.error(t('ratings.submitError'));
|
||||||
} finally {
|
} finally {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
}
|
}
|
||||||
@ -57,7 +59,7 @@ export default function CustomerRatingForm({ reservationId, onSuccess, onCancel
|
|||||||
return (
|
return (
|
||||||
<div className="bg-white rounded-2xl shadow-lg border border-gray-200 p-6 max-w-lg mx-auto">
|
<div className="bg-white rounded-2xl shadow-lg border border-gray-200 p-6 max-w-lg mx-auto">
|
||||||
<div className="flex justify-between items-center mb-4">
|
<div className="flex justify-between items-center mb-4">
|
||||||
<h3 className="text-xl font-bold text-gray-900">تقييم المستأجر</h3>
|
<h3 className="text-xl font-bold text-gray-900">{t('ratings.customerRating')}</h3>
|
||||||
{onCancel && (
|
{onCancel && (
|
||||||
<button onClick={onCancel} className="text-gray-400 hover:text-gray-600">
|
<button onClick={onCancel} className="text-gray-400 hover:text-gray-600">
|
||||||
<X size={20} />
|
<X size={20} />
|
||||||
@ -65,17 +67,17 @@ export default function CustomerRatingForm({ reservationId, onSuccess, onCancel
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<form onSubmit={handleSubmit} className="space-y-5">
|
<form onSubmit={handleSubmit} className="space-y-5">
|
||||||
<RatingField label="الحفاظ على الأثاث" value={furnitureIntegrityRating} onChange={setFurnitureIntegrityRating} />
|
<RatingField label={t('ratings.furnitureIntegrity')} value={furnitureIntegrityRating} onChange={setFurnitureIntegrityRating} />
|
||||||
<RatingField label="الالتزام بالشروط" value={termsComplianceRating} onChange={setTermsComplianceRating} />
|
<RatingField label={t('ratings.termsCompliance')} value={termsComplianceRating} onChange={setTermsComplianceRating} />
|
||||||
<RatingField label="سلوك المستأجر" value={renterBehaviorRating} onChange={setRenterBehaviorRating} />
|
<RatingField label={t('ratings.renterBehavior')} value={renterBehaviorRating} onChange={setRenterBehaviorRating} />
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-sm font-medium text-gray-700">تعليق (اختياري)</label>
|
<label className="block text-sm font-medium text-gray-700">{t('ratings.commentLabel')}</label>
|
||||||
<textarea
|
<textarea
|
||||||
rows={3}
|
rows={3}
|
||||||
value={comment}
|
value={comment}
|
||||||
onChange={(e) => setComment(e.target.value)}
|
onChange={(e) => setComment(e.target.value)}
|
||||||
className="w-full mt-1 px-4 py-2 border border-gray-300 rounded-xl focus:ring-2 focus:ring-amber-500"
|
className="w-full mt-1 px-4 py-2 border border-gray-300 rounded-xl focus:ring-2 focus:ring-amber-500"
|
||||||
placeholder="شارك تجربتك مع المستأجر..."
|
placeholder={t('ratings.commentPlaceholder')}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<button
|
<button
|
||||||
@ -84,7 +86,7 @@ export default function CustomerRatingForm({ reservationId, onSuccess, onCancel
|
|||||||
className="w-full bg-amber-500 hover:bg-amber-600 text-white font-bold py-3 rounded-xl transition flex items-center justify-center gap-2 disabled:opacity-50"
|
className="w-full bg-amber-500 hover:bg-amber-600 text-white font-bold py-3 rounded-xl transition flex items-center justify-center gap-2 disabled:opacity-50"
|
||||||
>
|
>
|
||||||
{loading && <Loader2 className="w-5 h-5 animate-spin" />}
|
{loading && <Loader2 className="w-5 h-5 animate-spin" />}
|
||||||
{loading ? 'جاري الإرسال...' : 'إرسال التقييم'}
|
{loading ? t('ratings.sending') : t('ratings.submitRating')}
|
||||||
</button>
|
</button>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -26,4 +26,13 @@ const BookingStatusColors = Object.freeze({
|
|||||||
[BookingStatus.cancelled]: 'red',
|
[BookingStatus.cancelled]: 'red',
|
||||||
});
|
});
|
||||||
|
|
||||||
export { BookingStatus, BookingStatusLabels, BookingStatusColors };
|
const BookingStatusTranslationKeys = Object.freeze({
|
||||||
|
[BookingStatus.PENDING]: 'bookingStatus.pending',
|
||||||
|
[BookingStatus.ownerConfirmed]: 'bookingStatus.ownerConfirmed',
|
||||||
|
[BookingStatus.depositPaid]: 'bookingStatus.depositPaid',
|
||||||
|
[BookingStatus.depositConfirmed]: 'bookingStatus.depositConfirmed',
|
||||||
|
[BookingStatus.completed]: 'bookingStatus.completed',
|
||||||
|
[BookingStatus.cancelled]: 'bookingStatus.cancelled',
|
||||||
|
});
|
||||||
|
|
||||||
|
export { BookingStatus, BookingStatusLabels, BookingStatusColors, BookingStatusTranslationKeys };
|
||||||
|
|||||||
@ -56,4 +56,17 @@ const BuildingTypeByKey = Object.freeze({
|
|||||||
warehouse: BuildingType.WAREHOUSE,
|
warehouse: BuildingType.WAREHOUSE,
|
||||||
});
|
});
|
||||||
|
|
||||||
export { BuildingType, BuildingTypeLabels, BuildingTypeKeys, BuildingTypeByKey };
|
const BuildingTypeTranslationKeys = Object.freeze({
|
||||||
|
[BuildingType.APARTMENT]: 'buildingType.apartment',
|
||||||
|
[BuildingType.VILLA]: 'buildingType.villa',
|
||||||
|
[BuildingType.HOUSE]: 'buildingType.house',
|
||||||
|
[BuildingType.SWEET]: 'buildingType.sweet',
|
||||||
|
[BuildingType.ROOM]: 'buildingType.room',
|
||||||
|
[BuildingType.STUDIO]: 'buildingType.studio',
|
||||||
|
[BuildingType.OFFICE]: 'buildingType.office',
|
||||||
|
[BuildingType.FARMS]: 'buildingType.farms',
|
||||||
|
[BuildingType.SHOP]: 'buildingType.shop',
|
||||||
|
[BuildingType.WAREHOUSE]: 'buildingType.warehouse',
|
||||||
|
});
|
||||||
|
|
||||||
|
export { BuildingType, BuildingTypeLabels, BuildingTypeKeys, BuildingTypeByKey, BuildingTypeTranslationKeys };
|
||||||
|
|||||||
@ -10,4 +10,10 @@ const CancellationReasonLabels = Object.freeze({
|
|||||||
[CancellationReason.OWNER_CANCELED]: 'إلغاء من المالك',
|
[CancellationReason.OWNER_CANCELED]: 'إلغاء من المالك',
|
||||||
});
|
});
|
||||||
|
|
||||||
export { CancellationReason, CancellationReasonLabels };
|
const CancellationReasonTranslationKeys = Object.freeze({
|
||||||
|
[CancellationReason.NONE]: 'cancellationReason.none',
|
||||||
|
[CancellationReason.DEPOSIT_NOT_PAID]: 'cancellationReason.noDeposit',
|
||||||
|
[CancellationReason.OWNER_CANCELED]: 'cancellationReason.ownerCancellation',
|
||||||
|
});
|
||||||
|
|
||||||
|
export { CancellationReason, CancellationReasonLabels, CancellationReasonTranslationKeys };
|
||||||
|
|||||||
@ -35,4 +35,20 @@ function extractCity(address) {
|
|||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
|
|
||||||
export { City, CitiesList, extractCity };
|
const CityTranslationKeys = Object.freeze({
|
||||||
|
[City.DAMASCUS]: 'city.damascus',
|
||||||
|
[City.ALEPPO]: 'city.aleppo',
|
||||||
|
[City.HOMS]: 'city.homs',
|
||||||
|
[City.LATAKIA]: 'city.latakia',
|
||||||
|
[City.DARAA]: 'city.daraa',
|
||||||
|
[City.TARTOUS]: 'city.tartous',
|
||||||
|
[City.SUWEIDA]: 'city.suweida',
|
||||||
|
[City.DEIR_EZZOR]: 'city.deirEzzor',
|
||||||
|
[City.RAQQA]: 'city.raqqa',
|
||||||
|
[City.IDLIB]: 'city.idlib',
|
||||||
|
[City.HASAKAH]: 'city.hasakah',
|
||||||
|
[City.QAMISHLI]: 'city.qamishli',
|
||||||
|
[City.RURAL_DAMASCUS]: 'city.ruralDamascus',
|
||||||
|
});
|
||||||
|
|
||||||
|
export { City, CitiesList, extractCity, CityTranslationKeys };
|
||||||
|
|||||||
@ -16,4 +16,10 @@ const CommissionTypeLabels = Object.freeze({
|
|||||||
[CommissionType.FROM_BOTH]: 'من الاثنين',
|
[CommissionType.FROM_BOTH]: 'من الاثنين',
|
||||||
});
|
});
|
||||||
|
|
||||||
export { CommissionType, CommissionTypeLabels };
|
const CommissionTypeTranslationKeys = Object.freeze({
|
||||||
|
[CommissionType.FROM_OWNER]: 'commissionType.fromOwner',
|
||||||
|
[CommissionType.FROM_TENANT]: 'commissionType.fromTenant',
|
||||||
|
[CommissionType.FROM_BOTH]: 'commissionType.fromBoth',
|
||||||
|
});
|
||||||
|
|
||||||
|
export { CommissionType, CommissionTypeLabels, CommissionTypeTranslationKeys };
|
||||||
|
|||||||
@ -23,4 +23,11 @@ const CurrencySymbols = Object.freeze({
|
|||||||
[Currency.TRY]: 'TRY',
|
[Currency.TRY]: 'TRY',
|
||||||
});
|
});
|
||||||
|
|
||||||
export { Currency, CurrencyLabels, CurrencySymbols };
|
const CurrencyTranslationKeys = Object.freeze({
|
||||||
|
[Currency.SYP]: 'currency.syp',
|
||||||
|
[Currency.USD]: 'currency.usd',
|
||||||
|
[Currency.EUR]: 'currency.eur',
|
||||||
|
[Currency.TRY]: 'currency.try',
|
||||||
|
});
|
||||||
|
|
||||||
|
export { Currency, CurrencyLabels, CurrencySymbols, CurrencyTranslationKeys };
|
||||||
|
|||||||
@ -14,4 +14,9 @@ const CustomerTypeLabels = Object.freeze({
|
|||||||
[CustomerType.FAMILY]: 'عائلي',
|
[CustomerType.FAMILY]: 'عائلي',
|
||||||
});
|
});
|
||||||
|
|
||||||
export { CustomerType, CustomerTypeLabels };
|
const CustomerTypeTranslationKeys = Object.freeze({
|
||||||
|
[CustomerType.PERSONAL]: 'customerType.personal',
|
||||||
|
[CustomerType.FAMILY]: 'customerType.family',
|
||||||
|
});
|
||||||
|
|
||||||
|
export { CustomerType, CustomerTypeLabels, CustomerTypeTranslationKeys };
|
||||||
|
|||||||
@ -20,4 +20,9 @@ const IdentityTypeFlags = Object.freeze({
|
|||||||
[IdentityType.PASSPORT]: '🛂',
|
[IdentityType.PASSPORT]: '🛂',
|
||||||
});
|
});
|
||||||
|
|
||||||
export { IdentityType, IdentityTypeLabels, IdentityTypeFlags };
|
const IdentityTypeTranslationKeys = Object.freeze({
|
||||||
|
[IdentityType.SYRIAN]: 'identityType.syrian',
|
||||||
|
[IdentityType.PASSPORT]: 'identityType.passport',
|
||||||
|
});
|
||||||
|
|
||||||
|
export { IdentityType, IdentityTypeLabels, IdentityTypeFlags, IdentityTypeTranslationKeys };
|
||||||
|
|||||||
@ -14,4 +14,9 @@ const OwnerTypeLabels = Object.freeze({
|
|||||||
[OwnerType.REAL_ESTATE_AGENCY]: 'وكالة عقارية',
|
[OwnerType.REAL_ESTATE_AGENCY]: 'وكالة عقارية',
|
||||||
});
|
});
|
||||||
|
|
||||||
export { OwnerType, OwnerTypeLabels };
|
const OwnerTypeTranslationKeys = Object.freeze({
|
||||||
|
[OwnerType.PERSON]: 'ownerType.person',
|
||||||
|
[OwnerType.REAL_ESTATE_AGENCY]: 'ownerType.agency',
|
||||||
|
});
|
||||||
|
|
||||||
|
export { OwnerType, OwnerTypeLabels, OwnerTypeTranslationKeys };
|
||||||
|
|||||||
@ -38,4 +38,20 @@ const PropertyServiceLabels = Object.freeze({
|
|||||||
// All services as array
|
// All services as array
|
||||||
const PropertyServicesList = Object.freeze(Object.values(PropertyService));
|
const PropertyServicesList = Object.freeze(Object.values(PropertyService));
|
||||||
|
|
||||||
export { PropertyService, PropertyServiceLabels, PropertyServicesList };
|
const PropertyServiceTranslationKeys = Object.freeze({
|
||||||
|
[PropertyService.ELECTRICITY]: 'propertyService.electricity',
|
||||||
|
[PropertyService.INTERNET]: 'propertyService.internet',
|
||||||
|
[PropertyService.HEATING]: 'propertyService.heating',
|
||||||
|
[PropertyService.WATER]: 'propertyService.water',
|
||||||
|
[PropertyService.POOL]: 'propertyService.pool',
|
||||||
|
[PropertyService.PRIVATE_GARDEN]: 'propertyService.privateGarden',
|
||||||
|
[PropertyService.PARKING]: 'propertyService.parking',
|
||||||
|
[PropertyService.SECURITY_247]: 'propertyService.security247',
|
||||||
|
[PropertyService.CENTRAL_HEATING]: 'propertyService.centralHeating',
|
||||||
|
[PropertyService.CENTRAL_AIR_CONDITIONING]: 'propertyService.centralAirConditioning',
|
||||||
|
[PropertyService.EQUIPPED_KITCHEN]: 'propertyService.equippedKitchen',
|
||||||
|
[PropertyService.MAIDS_ROOM]: 'propertyService.maidsRoom',
|
||||||
|
[PropertyService.ELEVATOR]: 'propertyService.elevator',
|
||||||
|
});
|
||||||
|
|
||||||
|
export { PropertyService, PropertyServiceLabels, PropertyServicesList, PropertyServiceTranslationKeys };
|
||||||
|
|||||||
@ -27,4 +27,10 @@ const PropertyStatusByKey = Object.freeze({
|
|||||||
booked: PropertyStatus.BOOKED,
|
booked: PropertyStatus.BOOKED,
|
||||||
});
|
});
|
||||||
|
|
||||||
export { PropertyStatus, PropertyStatusLabels, PropertyStatusKeys, PropertyStatusByKey };
|
const PropertyStatusTranslationKeys = Object.freeze({
|
||||||
|
[PropertyStatus.AVAILABLE]: 'propertyStatus.available',
|
||||||
|
[PropertyStatus.NOT_AVAILABLE]: 'propertyStatus.notAvailable',
|
||||||
|
[PropertyStatus.BOOKED]: 'propertyStatus.booked',
|
||||||
|
});
|
||||||
|
|
||||||
|
export { PropertyStatus, PropertyStatusLabels, PropertyStatusKeys, PropertyStatusByKey, PropertyStatusTranslationKeys };
|
||||||
|
|||||||
@ -18,4 +18,10 @@ const PropertyTermLabels = Object.freeze({
|
|||||||
// All terms as array
|
// All terms as array
|
||||||
const PropertyTermsList = Object.freeze(Object.values(PropertyTerm));
|
const PropertyTermsList = Object.freeze(Object.values(PropertyTerm));
|
||||||
|
|
||||||
export { PropertyTerm, PropertyTermLabels, PropertyTermsList };
|
const PropertyTermTranslationKeys = Object.freeze({
|
||||||
|
[PropertyTerm.NO_SMOKING]: 'propertyTerm.noSmoking',
|
||||||
|
[PropertyTerm.NO_ANIMALS]: 'propertyTerm.noAnimals',
|
||||||
|
[PropertyTerm.NO_PARTIES]: 'propertyTerm.noParties',
|
||||||
|
});
|
||||||
|
|
||||||
|
export { PropertyTerm, PropertyTermLabels, PropertyTermsList, PropertyTermTranslationKeys };
|
||||||
|
|||||||
@ -13,4 +13,9 @@ const RentPropertyConditionLabels = Object.freeze({
|
|||||||
[RentPropertyCondition.WITHOUT_FURNITURE]: 'غير مفروش',
|
[RentPropertyCondition.WITHOUT_FURNITURE]: 'غير مفروش',
|
||||||
});
|
});
|
||||||
|
|
||||||
export { RentPropertyCondition, RentPropertyConditionLabels };
|
const RentPropertyConditionTranslationKeys = Object.freeze({
|
||||||
|
[RentPropertyCondition.WITH_FURNITURE]: 'rentCondition.furnished',
|
||||||
|
[RentPropertyCondition.WITHOUT_FURNITURE]: 'rentCondition.unfurnished',
|
||||||
|
});
|
||||||
|
|
||||||
|
export { RentPropertyCondition, RentPropertyConditionLabels, RentPropertyConditionTranslationKeys };
|
||||||
|
|||||||
@ -14,4 +14,10 @@ const RentPropertyTypeLabels = Object.freeze({
|
|||||||
[RentPropertyType.SEMI_FURNISHED]: 'مفروش جزئياً',
|
[RentPropertyType.SEMI_FURNISHED]: 'مفروش جزئياً',
|
||||||
});
|
});
|
||||||
|
|
||||||
export { RentPropertyType, RentPropertyTypeLabels };
|
const RentPropertyTypeTranslationKeys = Object.freeze({
|
||||||
|
[RentPropertyType.FURNISHED]: 'rentPropertyType.fullyFurnished',
|
||||||
|
[RentPropertyType.UNFURNISHED]: 'rentPropertyType.unfurnished',
|
||||||
|
[RentPropertyType.SEMI_FURNISHED]: 'rentPropertyType.partiallyFurnished',
|
||||||
|
});
|
||||||
|
|
||||||
|
export { RentPropertyType, RentPropertyTypeLabels, RentPropertyTypeTranslationKeys };
|
||||||
|
|||||||
@ -13,4 +13,9 @@ const RentTypeLabels = Object.freeze({
|
|||||||
[RentType.DAILY]: 'يومي',
|
[RentType.DAILY]: 'يومي',
|
||||||
});
|
});
|
||||||
|
|
||||||
export { RentType, RentTypeLabels };
|
const RentTypeTranslationKeys = Object.freeze({
|
||||||
|
[RentType.MONTHLY]: 'rentType.monthly',
|
||||||
|
[RentType.DAILY]: 'rentType.daily',
|
||||||
|
});
|
||||||
|
|
||||||
|
export { RentType, RentTypeLabels, RentTypeTranslationKeys };
|
||||||
|
|||||||
@ -8,4 +8,9 @@ const SalePropertiesStatusLabels = Object.freeze({
|
|||||||
[SalePropertiesStatus.CONFIRMED]: 'مؤكد',
|
[SalePropertiesStatus.CONFIRMED]: 'مؤكد',
|
||||||
});
|
});
|
||||||
|
|
||||||
export { SalePropertiesStatus, SalePropertiesStatusLabels };
|
const SalePropertiesStatusTranslationKeys = Object.freeze({
|
||||||
|
[SalePropertiesStatus.PENDING]: 'saleStatus.pending',
|
||||||
|
[SalePropertiesStatus.CONFIRMED]: 'saleStatus.confirmed',
|
||||||
|
});
|
||||||
|
|
||||||
|
export { SalePropertiesStatus, SalePropertiesStatusLabels, SalePropertiesStatusTranslationKeys };
|
||||||
|
|||||||
@ -8,4 +8,9 @@ const TransactionTypeLabels = Object.freeze({
|
|||||||
[TransactionType.MANUAL]: 'يدوي',
|
[TransactionType.MANUAL]: 'يدوي',
|
||||||
});
|
});
|
||||||
|
|
||||||
export { TransactionType, TransactionTypeLabels };
|
const TransactionTypeTranslationKeys = Object.freeze({
|
||||||
|
[TransactionType.ONLINE]: 'transactionType.online',
|
||||||
|
[TransactionType.MANUAL]: 'transactionType.manual',
|
||||||
|
});
|
||||||
|
|
||||||
|
export { TransactionType, TransactionTypeLabels, TransactionTypeTranslationKeys };
|
||||||
|
|||||||
@ -24,4 +24,11 @@ const UserRoleColors = Object.freeze({
|
|||||||
[UserRole.AGENT]: 'purple',
|
[UserRole.AGENT]: 'purple',
|
||||||
});
|
});
|
||||||
|
|
||||||
export { UserRole, UserRoleLabels, UserRoleColors };
|
const UserRoleTranslationKeys = Object.freeze({
|
||||||
|
[UserRole.GUEST]: 'userRole.guest',
|
||||||
|
[UserRole.CUSTOMER]: 'userRole.customer',
|
||||||
|
[UserRole.OWNER]: 'userRole.owner',
|
||||||
|
[UserRole.AGENT]: 'userRole.agent',
|
||||||
|
});
|
||||||
|
|
||||||
|
export { UserRole, UserRoleLabels, UserRoleColors, UserRoleTranslationKeys };
|
||||||
|
|||||||
@ -2,47 +2,25 @@
|
|||||||
|
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import { motion, AnimatePresence } from 'framer-motion';
|
import { motion, AnimatePresence } from 'framer-motion';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
import { HelpCircle, ChevronDown, Search } from 'lucide-react';
|
import { HelpCircle, ChevronDown, Search } from 'lucide-react';
|
||||||
|
|
||||||
const faqs = [
|
|
||||||
{
|
|
||||||
question: 'كيفية البحث عن عقار؟',
|
|
||||||
answer: 'يمكنك البحث عن عقار من خلال استخدام شريط البحث في الصفحة الرئيسية، حيث يمكنك تحديد المدينة ونوع العقار والسعر. كما يمكنك استخدام الفلاتر المتقدمة لتحديد عدد الغرف والحمامات والمساحة وطرق الدفع. يمكنك أيضاً تصفح العقارات المميزة والتوصيات المقدمة خصيصاً لك.',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
question: 'كيف أحجز عقاراً؟',
|
|
||||||
answer: 'بعد العثور على العقار المناسب، يمكنك النقر على "حجز" واختيار تواريخ الإقامة. سيطلب منك تسجيل الدخول أو إنشاء حساب إذا لم يكن لديك حساب. بعد ذلك، قم بتأكيد الحجز وسيتم إخطار المالك بطلبك. يمكنك متابعة حالة حجزك من صفحة الحجوزات.',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
question: 'كيف أضيف عقاراً؟',
|
|
||||||
answer: 'لتسجيل عقارك على المنصة، يجب عليك أولاً إنشاء حساب مالك. بعد تسجيل الدخول، انتقل إلى لوحة التحكم واختر "إضافة عقار". قم بتعبئة جميع المعلومات المطلوبة مثل الموقع، المساحة، عدد الغرف، الصور، السعر، وغيرها من التفاصيل. بعد المراجعة، سيتم نشر عقارك على المنصة.',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
question: 'كيف أتواصل مع المالك؟',
|
|
||||||
answer: 'بعد تسجيل الدخول، يمكنك التواصل مع المالك من خلال صفحة العقار بالضغط على زر "تواصل مع المالك". ستظهر لك معلومات الاتصال بالمالك بما في ذلك رقم الهاتف والبريد الإلكتروني. يمكنك أيضاً مراسلة المالك مباشرة من خلال نظام المراسلة الداخلي في المنصة.',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
question: 'ما هي سياسة الإلغاء؟',
|
|
||||||
answer: 'سياسة الإلغاء تختلف حسب كل عقار. بشكل عام، يمكن إلغاء الحجز قبل 48 ساعة من موعد الوصول للحصول على استرداد كامل. للإلغاء خلال أقل من 48 ساعة، قد يتم تطبيق رسوم إلغاء. ننصحك بمراجعة سياسة الإلغاء المدونة في صفحة العقار قبل تأكيد الحجز.',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
question: 'كيف أقيم العقار؟',
|
|
||||||
answer: 'بعد انتهاء إقامتك، يمكنك تقييم العقار من خلال صفحة الحجوزات. اختر الحجز المناسب ثم اضغط على "تقييم". يمكنك إعطاء تقييم بالنجوم وكتابة تعليق يصف تجربتك. تقييمات المستخدمين تساعد الآخرين في اتخاذ القرار المناسب.',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
question: 'هل يمكنني تغيير كلمة المرور؟',
|
|
||||||
answer: 'نعم، يمكنك تغيير كلمة المرور من خلال صفحة الملف الشخصي. اختر "تغيير كلمة المرور" وأدخل كلمة المرور الحالية ثم كلمة المرور الجديدة. يجب أن تتكون كلمة المرور الجديدة من 8 أحرف على الأقل وتحتوي على حرف كبير وحرف صغير ورقم.',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
question: 'كيف أحذف حسابي؟',
|
|
||||||
answer: 'لحذف حسابك نهائياً، يرجى التواصل مع فريق الدعم الفني من خلال صفحة الدعم. سنقوم بمعالجة طلبك خلال 24 ساعة. يرجى العلم أنه لا يمكن استعادة الحساب بعد حذفه، وسيتم فقدان جميع البيانات والحجوزات المرتبطة به.',
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
export default function FAQPage() {
|
export default function FAQPage() {
|
||||||
|
const { t } = useTranslation();
|
||||||
const [openIndex, setOpenIndex] = useState(null);
|
const [openIndex, setOpenIndex] = useState(null);
|
||||||
const [searchTerm, setSearchTerm] = useState('');
|
const [searchTerm, setSearchTerm] = useState('');
|
||||||
|
|
||||||
|
const faqs = [
|
||||||
|
{ question: t('faq.q1'), answer: t('faq.a1') },
|
||||||
|
{ question: t('faq.q2'), answer: t('faq.a2') },
|
||||||
|
{ question: t('faq.q3'), answer: t('faq.a3') },
|
||||||
|
{ question: t('faq.q4'), answer: t('faq.a4') },
|
||||||
|
{ question: t('faq.q5'), answer: t('faq.a5') },
|
||||||
|
{ question: t('faq.q6'), answer: t('faq.a6') },
|
||||||
|
{ question: t('faq.q7'), answer: t('faq.a7') },
|
||||||
|
{ question: t('faq.q8'), answer: t('faq.a8') },
|
||||||
|
];
|
||||||
|
|
||||||
const filteredFaqs = faqs.filter(
|
const filteredFaqs = faqs.filter(
|
||||||
(faq) =>
|
(faq) =>
|
||||||
faq.question.includes(searchTerm) ||
|
faq.question.includes(searchTerm) ||
|
||||||
@ -64,9 +42,9 @@ export default function FAQPage() {
|
|||||||
<div className="w-20 h-20 bg-amber-100 rounded-2xl flex items-center justify-center mx-auto mb-6 shadow-lg shadow-amber-100">
|
<div className="w-20 h-20 bg-amber-100 rounded-2xl flex items-center justify-center mx-auto mb-6 shadow-lg shadow-amber-100">
|
||||||
<HelpCircle className="w-10 h-10 text-amber-600" />
|
<HelpCircle className="w-10 h-10 text-amber-600" />
|
||||||
</div>
|
</div>
|
||||||
<h1 className="text-4xl font-bold text-gray-900 mb-4">الأسئلة الشائعة</h1>
|
<h1 className="text-4xl font-bold text-gray-900 mb-4">{t('faq.pageTitle')}</h1>
|
||||||
<p className="text-lg text-gray-600 max-w-2xl mx-auto">
|
<p className="text-lg text-gray-600 max-w-2xl mx-auto">
|
||||||
إجابات على أكثر الأسئلة شيوعاً حول استخدام المنصة
|
{t('faq.pageDescription')}
|
||||||
</p>
|
</p>
|
||||||
</motion.div>
|
</motion.div>
|
||||||
|
|
||||||
@ -79,7 +57,7 @@ export default function FAQPage() {
|
|||||||
<Search className="absolute right-4 top-1/2 -translate-y-1/2 w-5 h-5 text-gray-400" />
|
<Search className="absolute right-4 top-1/2 -translate-y-1/2 w-5 h-5 text-gray-400" />
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
placeholder="ابحث في الأسئلة الشائعة..."
|
placeholder={t('faq.searchPlaceholder')}
|
||||||
value={searchTerm}
|
value={searchTerm}
|
||||||
onChange={(e) => setSearchTerm(e.target.value)}
|
onChange={(e) => setSearchTerm(e.target.value)}
|
||||||
className="w-full pr-12 pl-4 py-4 bg-white border border-gray-200 rounded-2xl shadow-sm focus:outline-none focus:ring-2 focus:ring-amber-500 focus:border-transparent text-gray-900 placeholder-gray-400"
|
className="w-full pr-12 pl-4 py-4 bg-white border border-gray-200 rounded-2xl shadow-sm focus:outline-none focus:ring-2 focus:ring-amber-500 focus:border-transparent text-gray-900 placeholder-gray-400"
|
||||||
@ -134,7 +112,7 @@ export default function FAQPage() {
|
|||||||
animate={{ opacity: 1 }}
|
animate={{ opacity: 1 }}
|
||||||
className="text-center py-12"
|
className="text-center py-12"
|
||||||
>
|
>
|
||||||
<p className="text-gray-500 text-lg">لا توجد نتائج للبحث</p>
|
<p className="text-gray-500 text-lg">{t('faq.noResults')}</p>
|
||||||
</motion.div>
|
</motion.div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
import { useRouter } from 'next/navigation';
|
import { useRouter } from 'next/navigation';
|
||||||
import { motion } from 'framer-motion';
|
import { motion } from 'framer-motion';
|
||||||
import Link from 'next/link';
|
import Link from 'next/link';
|
||||||
@ -10,6 +11,7 @@ import { useFavorites } from '@/app/contexts/FavoritesContext';
|
|||||||
import AuthService from '@/app/services/AuthService';
|
import AuthService from '@/app/services/AuthService';
|
||||||
|
|
||||||
export default function FavoritesPage() {
|
export default function FavoritesPage() {
|
||||||
|
const { t } = useTranslation();
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const { favorites, isLoading: favoritesLoading, removeFavorite } = useFavorites();
|
const { favorites, isLoading: favoritesLoading, removeFavorite } = useFavorites();
|
||||||
|
|
||||||
@ -23,7 +25,7 @@ export default function FavoritesPage() {
|
|||||||
}, [router]);
|
}, [router]);
|
||||||
|
|
||||||
const formatCurrency = (amount) => {
|
const formatCurrency = (amount) => {
|
||||||
return amount?.toLocaleString() + ' ل.س';
|
return amount?.toLocaleString() + ' ' + t('currency-syp-suffix');
|
||||||
};
|
};
|
||||||
|
|
||||||
if (favoritesLoading && favorites.length === 0) {
|
if (favoritesLoading && favorites.length === 0) {
|
||||||
@ -31,7 +33,7 @@ export default function FavoritesPage() {
|
|||||||
<div className="min-h-screen flex items-center justify-center">
|
<div className="min-h-screen flex items-center justify-center">
|
||||||
<div className="text-center">
|
<div className="text-center">
|
||||||
<div className="w-16 h-16 border-4 border-amber-500 border-t-transparent rounded-full animate-spin mx-auto mb-4" />
|
<div className="w-16 h-16 border-4 border-amber-500 border-t-transparent rounded-full animate-spin mx-auto mb-4" />
|
||||||
<p className="text-gray-600">جاري التحميل...</p>
|
<p className="text-gray-600">{t('loading')}</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
@ -41,20 +43,20 @@ export default function FavoritesPage() {
|
|||||||
<div className="min-h-screen bg-gray-50 py-8">
|
<div className="min-h-screen bg-gray-50 py-8">
|
||||||
<div className="container mx-auto px-4 max-w-6xl">
|
<div className="container mx-auto px-4 max-w-6xl">
|
||||||
<div className="mb-8">
|
<div className="mb-8">
|
||||||
<h1 className="text-3xl font-bold text-gray-900 mb-2">المفضلة</h1>
|
<h1 className="text-3xl font-bold text-gray-900 mb-2">{t('favorites')}</h1>
|
||||||
<p className="text-gray-600">العقارات التي قمت بحفظها</p>
|
<p className="text-gray-600">{t('saved-properties')}</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{favorites.length === 0 ? (
|
{favorites.length === 0 ? (
|
||||||
<div className="bg-white rounded-2xl p-12 text-center border-2 border-dashed border-gray-300">
|
<div className="bg-white rounded-2xl p-12 text-center border-2 border-dashed border-gray-300">
|
||||||
<Heart className="w-16 h-16 text-gray-300 mx-auto mb-4" />
|
<Heart className="w-16 h-16 text-gray-300 mx-auto mb-4" />
|
||||||
<h3 className="text-xl font-bold text-gray-700 mb-2">لا توجد عقارات في المفضلة</h3>
|
<h3 className="text-xl font-bold text-gray-700 mb-2">{t('no-favorites')}</h3>
|
||||||
<p className="text-gray-500 mb-6">يمكنك إضافة العقارات التي تعجبك بالنقر على أيقونة القلب</p>
|
<p className="text-gray-500 mb-6">{t('add-favorites-hint')}</p>
|
||||||
<Link
|
<Link
|
||||||
href="/properties"
|
href="/properties"
|
||||||
className="inline-flex items-center gap-2 bg-amber-500 text-white px-6 py-3 rounded-xl font-medium hover:bg-amber-600 transition-colors"
|
className="inline-flex items-center gap-2 bg-amber-500 text-white px-6 py-3 rounded-xl font-medium hover:bg-amber-600 transition-colors"
|
||||||
>
|
>
|
||||||
استعرض العقارات
|
{t('browse-properties')}
|
||||||
</Link>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
@ -93,7 +95,7 @@ export default function FavoritesPage() {
|
|||||||
<div>
|
<div>
|
||||||
<div className="flex items-center gap-2 mb-2">
|
<div className="flex items-center gap-2 mb-2">
|
||||||
<span className="px-2 py-1 bg-gray-100 text-gray-700 rounded-lg text-xs font-medium">
|
<span className="px-2 py-1 bg-gray-100 text-gray-700 rounded-lg text-xs font-medium">
|
||||||
{property.type === 'apartment' ? 'شقة' : property.type === 'villa' ? 'فيلا' : 'بيت'}
|
{property.type === 'apartment' ? t('buildingType.apartment') : property.type === 'villa' ? t('buildingType.villa') : t('buildingType.house')}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<h3 className="font-bold text-gray-900 mb-1 line-clamp-1">{property.title}</h3>
|
<h3 className="font-bold text-gray-900 mb-1 line-clamp-1">{property.title}</h3>
|
||||||
@ -106,7 +108,7 @@ export default function FavoritesPage() {
|
|||||||
</div>
|
</div>
|
||||||
<div className="text-left">
|
<div className="text-left">
|
||||||
<div className="text-xl font-bold text-gray-900">{formatCurrency(property.price)}</div>
|
<div className="text-xl font-bold text-gray-900">{formatCurrency(property.price)}</div>
|
||||||
<div className="text-xs text-gray-500">/{property.priceUnit === 'daily' ? 'يوم' : 'شهر'}</div>
|
<div className="text-xs text-gray-500">/{property.priceUnit === 'daily' ? t('day') : t('month')}</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -131,7 +133,7 @@ export default function FavoritesPage() {
|
|||||||
href={`/property/${property.id}`}
|
href={`/property/${property.id}`}
|
||||||
className="block w-full bg-amber-500 text-white py-3 rounded-xl font-medium hover:bg-amber-600 transition-colors text-center"
|
className="block w-full bg-amber-500 text-white py-3 rounded-xl font-medium hover:bg-amber-600 transition-colors text-center"
|
||||||
>
|
>
|
||||||
عرض التفاصيل
|
{t('viewDetails')}
|
||||||
</Link>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
</motion.div>
|
</motion.div>
|
||||||
|
|||||||
@ -6,10 +6,12 @@ import { useRouter } from 'next/navigation';
|
|||||||
import Link from 'next/link';
|
import Link from 'next/link';
|
||||||
import toast, { Toaster } from 'react-hot-toast';
|
import toast, { Toaster } from 'react-hot-toast';
|
||||||
import { Mail, Key, Lock, CheckCircle, ArrowLeft, RefreshCw } from 'lucide-react';
|
import { Mail, Key, Lock, CheckCircle, ArrowLeft, RefreshCw } from 'lucide-react';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
import { requestForgetPasswordOtp, verifyForgetPasswordOtp } from '../utils/api';
|
import { requestForgetPasswordOtp, verifyForgetPasswordOtp } from '../utils/api';
|
||||||
|
|
||||||
export default function ForgotPasswordPage() {
|
export default function ForgotPasswordPage() {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
const { t } = useTranslation();
|
||||||
const [step, setStep] = useState(1);
|
const [step, setStep] = useState(1);
|
||||||
const [email, setEmail] = useState('');
|
const [email, setEmail] = useState('');
|
||||||
const [code, setCode] = useState('');
|
const [code, setCode] = useState('');
|
||||||
@ -20,17 +22,17 @@ export default function ForgotPasswordPage() {
|
|||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
|
||||||
if (!email.trim()) {
|
if (!email.trim()) {
|
||||||
toast.error('يرجى إدخال البريد الإلكتروني');
|
toast.error(t('forgotPassword.emailRequired'));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
setIsLoading(true);
|
setIsLoading(true);
|
||||||
try {
|
try {
|
||||||
await requestForgetPasswordOtp(email);
|
await requestForgetPasswordOtp(email);
|
||||||
toast.success('تم إرسال رمز التحقق إلى بريدك الإلكتروني');
|
toast.success(t('forgotPassword.otpSent'));
|
||||||
setStep(2);
|
setStep(2);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
toast.error(err?.message || 'فشل إرسال رمز التحقق');
|
toast.error(err?.message || t('forgotPassword.otpSendFailed'));
|
||||||
} finally {
|
} finally {
|
||||||
setIsLoading(false);
|
setIsLoading(false);
|
||||||
}
|
}
|
||||||
@ -40,22 +42,22 @@ export default function ForgotPasswordPage() {
|
|||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
|
||||||
if (!code.trim()) {
|
if (!code.trim()) {
|
||||||
toast.error('يرجى إدخال رمز التحقق');
|
toast.error(t('forgotPassword.otpRequired'));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (newPassword.length < 6) {
|
if (newPassword.length < 6) {
|
||||||
toast.error('كلمة المرور الجديدة يجب أن تكون 6 أحرف على الأقل');
|
toast.error(t('validation.passwordMinLength'));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
setIsLoading(true);
|
setIsLoading(true);
|
||||||
try {
|
try {
|
||||||
await verifyForgetPasswordOtp(email, code, newPassword);
|
await verifyForgetPasswordOtp(email, code, newPassword);
|
||||||
toast.success('تم إعادة تعيين كلمة المرور بنجاح');
|
toast.success(t('forgotPassword.resetSuccess'));
|
||||||
setTimeout(() => router.push('/login'), 1200);
|
setTimeout(() => router.push('/login'), 1200);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
toast.error(err?.message || 'فشل التحقق من الرمز');
|
toast.error(err?.message || t('forgotPassword.otpVerifyFailed'));
|
||||||
} finally {
|
} finally {
|
||||||
setIsLoading(false);
|
setIsLoading(false);
|
||||||
}
|
}
|
||||||
@ -84,7 +86,7 @@ export default function ForgotPasswordPage() {
|
|||||||
>
|
>
|
||||||
<Link href="/login" className="flex items-center gap-2 text-gray-600 hover:text-amber-600 transition-colors group">
|
<Link href="/login" className="flex items-center gap-2 text-gray-600 hover:text-amber-600 transition-colors group">
|
||||||
<ArrowLeft className="w-5 h-5 group-hover:-translate-x-1 transition-transform" />
|
<ArrowLeft className="w-5 h-5 group-hover:-translate-x-1 transition-transform" />
|
||||||
<span>العودة لتسجيل الدخول</span>
|
<span>{t('forgotPassword.backToLogin')}</span>
|
||||||
</Link>
|
</Link>
|
||||||
</motion.div>
|
</motion.div>
|
||||||
|
|
||||||
@ -95,7 +97,7 @@ export default function ForgotPasswordPage() {
|
|||||||
animate={{ y: 0, opacity: 1 }}
|
animate={{ y: 0, opacity: 1 }}
|
||||||
className="text-3xl font-bold text-white mb-2"
|
className="text-3xl font-bold text-white mb-2"
|
||||||
>
|
>
|
||||||
استعادة كلمة المرور
|
{t('forgotPassword.title')}
|
||||||
</motion.h1>
|
</motion.h1>
|
||||||
<motion.p
|
<motion.p
|
||||||
initial={{ y: 20, opacity: 0 }}
|
initial={{ y: 20, opacity: 0 }}
|
||||||
@ -103,7 +105,7 @@ export default function ForgotPasswordPage() {
|
|||||||
transition={{ delay: 0.1 }}
|
transition={{ delay: 0.1 }}
|
||||||
className="text-amber-100"
|
className="text-amber-100"
|
||||||
>
|
>
|
||||||
{step === 1 ? 'أدخل بريدك الإلكتروني لاستلام رمز التحقق' : 'أدخل رمز التحقق وكلمة المرور الجديدة'}
|
{step === 1 ? t('forgotPassword.step1Instruction') : t('forgotPassword.step2Instruction')}
|
||||||
</motion.p>
|
</motion.p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -120,7 +122,7 @@ export default function ForgotPasswordPage() {
|
|||||||
>
|
>
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-sm font-medium text-gray-700 mb-2">
|
<label className="block text-sm font-medium text-gray-700 mb-2">
|
||||||
البريد الإلكتروني
|
{t('forgotPassword.emailLabel')}
|
||||||
</label>
|
</label>
|
||||||
<div className="relative group">
|
<div className="relative group">
|
||||||
<div className="absolute inset-y-0 right-0 pr-3 flex items-center pointer-events-none">
|
<div className="absolute inset-y-0 right-0 pr-3 flex items-center pointer-events-none">
|
||||||
@ -131,7 +133,7 @@ export default function ForgotPasswordPage() {
|
|||||||
value={email}
|
value={email}
|
||||||
onChange={(e) => setEmail(e.target.value)}
|
onChange={(e) => setEmail(e.target.value)}
|
||||||
className={inputClass}
|
className={inputClass}
|
||||||
placeholder="أدخل بريدك الإلكتروني"
|
placeholder={t('forgotPassword.emailPlaceholder')}
|
||||||
dir="ltr"
|
dir="ltr"
|
||||||
required
|
required
|
||||||
/>
|
/>
|
||||||
@ -148,10 +150,10 @@ export default function ForgotPasswordPage() {
|
|||||||
{isLoading ? (
|
{isLoading ? (
|
||||||
<div className="flex items-center justify-center gap-2">
|
<div className="flex items-center justify-center gap-2">
|
||||||
<div className="w-5 h-5 border-2 border-white border-t-transparent rounded-full animate-spin" />
|
<div className="w-5 h-5 border-2 border-white border-t-transparent rounded-full animate-spin" />
|
||||||
<span>جاري الإرسال...</span>
|
<span>{t('forgotPassword.sending')}</span>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
'إرسال رمز التحقق'
|
t('forgotPassword.sendOtp')
|
||||||
)}
|
)}
|
||||||
</motion.button>
|
</motion.button>
|
||||||
</motion.form>
|
</motion.form>
|
||||||
@ -168,7 +170,7 @@ export default function ForgotPasswordPage() {
|
|||||||
>
|
>
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-sm font-medium text-gray-700 mb-2">
|
<label className="block text-sm font-medium text-gray-700 mb-2">
|
||||||
رمز التحقق
|
{t('forgotPassword.otpLabel')}
|
||||||
</label>
|
</label>
|
||||||
<div className="relative group">
|
<div className="relative group">
|
||||||
<div className="absolute inset-y-0 right-0 pr-3 flex items-center pointer-events-none">
|
<div className="absolute inset-y-0 right-0 pr-3 flex items-center pointer-events-none">
|
||||||
@ -179,7 +181,7 @@ export default function ForgotPasswordPage() {
|
|||||||
value={code}
|
value={code}
|
||||||
onChange={(e) => setCode(e.target.value)}
|
onChange={(e) => setCode(e.target.value)}
|
||||||
className={inputClass}
|
className={inputClass}
|
||||||
placeholder="أدخل رمز التحقق"
|
placeholder={t('forgotPassword.otpPlaceholder')}
|
||||||
dir="ltr"
|
dir="ltr"
|
||||||
required
|
required
|
||||||
/>
|
/>
|
||||||
@ -188,7 +190,7 @@ export default function ForgotPasswordPage() {
|
|||||||
|
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-sm font-medium text-gray-700 mb-2">
|
<label className="block text-sm font-medium text-gray-700 mb-2">
|
||||||
كلمة المرور الجديدة
|
{t('forgotPassword.newPasswordLabel')}
|
||||||
</label>
|
</label>
|
||||||
<div className="relative group">
|
<div className="relative group">
|
||||||
<div className="absolute inset-y-0 right-0 pr-3 flex items-center pointer-events-none">
|
<div className="absolute inset-y-0 right-0 pr-3 flex items-center pointer-events-none">
|
||||||
@ -199,7 +201,7 @@ export default function ForgotPasswordPage() {
|
|||||||
value={newPassword}
|
value={newPassword}
|
||||||
onChange={(e) => setNewPassword(e.target.value)}
|
onChange={(e) => setNewPassword(e.target.value)}
|
||||||
className={inputClass}
|
className={inputClass}
|
||||||
placeholder="أدخل كلمة المرور الجديدة"
|
placeholder={t('forgotPassword.newPasswordPlaceholder')}
|
||||||
required
|
required
|
||||||
minLength={6}
|
minLength={6}
|
||||||
/>
|
/>
|
||||||
@ -216,10 +218,10 @@ export default function ForgotPasswordPage() {
|
|||||||
{isLoading ? (
|
{isLoading ? (
|
||||||
<div className="flex items-center justify-center gap-2">
|
<div className="flex items-center justify-center gap-2">
|
||||||
<div className="w-5 h-5 border-2 border-white border-t-transparent rounded-full animate-spin" />
|
<div className="w-5 h-5 border-2 border-white border-t-transparent rounded-full animate-spin" />
|
||||||
<span>جاري التحقق...</span>
|
<span>{t('forgotPassword.verifying')}</span>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
'إعادة تعيين كلمة المرور'
|
t('forgotPassword.resetPassword')
|
||||||
)}
|
)}
|
||||||
</motion.button>
|
</motion.button>
|
||||||
|
|
||||||
@ -230,7 +232,7 @@ export default function ForgotPasswordPage() {
|
|||||||
className="text-sm text-amber-600 hover:text-amber-700 transition-colors flex items-center justify-center gap-1 mx-auto"
|
className="text-sm text-amber-600 hover:text-amber-700 transition-colors flex items-center justify-center gap-1 mx-auto"
|
||||||
>
|
>
|
||||||
<RefreshCw className="w-4 h-4" />
|
<RefreshCw className="w-4 h-4" />
|
||||||
<span>تغيير البريد الإلكتروني</span>
|
<span>{t('forgotPassword.changeEmail')}</span>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</motion.form>
|
</motion.form>
|
||||||
|
|||||||
2828
app/i18n/config.js
2828
app/i18n/config.js
File diff suppressed because it is too large
Load Diff
@ -1,6 +1,7 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
import { motion, AnimatePresence } from "framer-motion";
|
import { motion, AnimatePresence } from "framer-motion";
|
||||||
import toast, { Toaster } from "react-hot-toast";
|
import toast, { Toaster } from "react-hot-toast";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
@ -35,6 +36,7 @@ import AuthService from "../services/AuthService";
|
|||||||
|
|
||||||
export default function LoginPage() {
|
export default function LoginPage() {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
// Step: 'login' | 'otp'
|
// Step: 'login' | 'otp'
|
||||||
const [step, setStep] = useState("login");
|
const [step, setStep] = useState("login");
|
||||||
@ -59,16 +61,16 @@ export default function LoginPage() {
|
|||||||
if (!formData.credential) {
|
if (!formData.credential) {
|
||||||
newErrors.credential =
|
newErrors.credential =
|
||||||
loginMethod === "email"
|
loginMethod === "email"
|
||||||
? "البريد الإلكتروني مطلوب"
|
? t("emailRequired")
|
||||||
: "رقم الهاتف مطلوب";
|
: t("phoneRequired");
|
||||||
// } else if (loginMethod === 'email' && !isEmail(formData.credential)) {
|
// } else if (loginMethod === 'email' && !isEmail(formData.credential)) {
|
||||||
// newErrors.credential = 'البريد الإلكتروني غير صالح';
|
// newErrors.credential = 'البريد الإلكتروني غير صالح';
|
||||||
// } else if (loginMethod === 'phone' && !isPhoneNumber(formData.credential)) {
|
// } else if (loginMethod === 'phone' && !isPhoneNumber(formData.credential)) {
|
||||||
newErrors.credential = "رقم الهاتف غير صالح";
|
newErrors.credential = t("phoneInvalid");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!formData.password) {
|
if (!formData.password) {
|
||||||
newErrors.password = "كلمة المرور مطلوبة";
|
newErrors.password = t("passwordRequired");
|
||||||
}
|
}
|
||||||
|
|
||||||
setErrors(newErrors);
|
setErrors(newErrors);
|
||||||
@ -119,7 +121,7 @@ export default function LoginPage() {
|
|||||||
const userRole = AuthService.isOwner() ? "owner" : "customer";
|
const userRole = AuthService.isOwner() ? "owner" : "customer";
|
||||||
|
|
||||||
setIsSuccess(true);
|
setIsSuccess(true);
|
||||||
toast.success("تم تسجيل الدخول بنجاح!", {
|
toast.success(t("loginSuccess"), {
|
||||||
style: { background: "#dcfce7", color: "#166534" },
|
style: { background: "#dcfce7", color: "#166534" },
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -134,7 +136,7 @@ export default function LoginPage() {
|
|||||||
if (tempToken) {
|
if (tempToken) {
|
||||||
AuthService.addToken(tempToken);
|
AuthService.addToken(tempToken);
|
||||||
}
|
}
|
||||||
toast("يرجى إدخال رمز التحقق", {
|
toast(t("otpRequired"), {
|
||||||
icon: "🔐",
|
icon: "🔐",
|
||||||
style: { background: "#fef3c7", color: "#92400e" },
|
style: { background: "#fef3c7", color: "#92400e" },
|
||||||
});
|
});
|
||||||
@ -155,7 +157,7 @@ export default function LoginPage() {
|
|||||||
// Other error
|
// 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 || "بيانات الدخول غير صحيحة",
|
result.data?.message || result.data || t("invalidCredentials"),
|
||||||
{
|
{
|
||||||
style: { background: "#fee2e2", color: "#991b1b" },
|
style: { background: "#fee2e2", color: "#991b1b" },
|
||||||
},
|
},
|
||||||
@ -163,7 +165,7 @@ export default function LoginPage() {
|
|||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error("[Login] Error:", err);
|
console.error("[Login] Error:", err);
|
||||||
toast.error(err.message || "حدث خطأ في الاتصال", {
|
toast.error(err.message || t("connectionError"), {
|
||||||
style: { background: "#fee2e2", color: "#991b1b" },
|
style: { background: "#fee2e2", color: "#991b1b" },
|
||||||
});
|
});
|
||||||
} finally {
|
} finally {
|
||||||
@ -174,7 +176,7 @@ export default function LoginPage() {
|
|||||||
const handleVerifyOTP = async (e) => {
|
const handleVerifyOTP = async (e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
if (!otpCode || otpCode.length < 4) {
|
if (!otpCode || otpCode.length < 4) {
|
||||||
setOtpError("يرجى إدخال رمز التحقق");
|
setOtpError(t("otpRequired"));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -196,7 +198,7 @@ export default function LoginPage() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
setIsSuccess(true);
|
setIsSuccess(true);
|
||||||
toast.success("تم التحقق بنجاح!", {
|
toast.success(t("verifySuccess"), {
|
||||||
style: { background: "#dcfce7", color: "#166534" },
|
style: { background: "#dcfce7", color: "#166534" },
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -205,11 +207,11 @@ export default function LoginPage() {
|
|||||||
}, 1500);
|
}, 1500);
|
||||||
} else {
|
} else {
|
||||||
console.error("[OTP] Verification failed:", result.data);
|
console.error("[OTP] Verification failed:", result.data);
|
||||||
setOtpError(result.data?.message || "رمز التحقق غير صحيح");
|
setOtpError(result.data?.message || t("wrongOTP"));
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error("[OTP] Error:", err);
|
console.error("[OTP] Error:", err);
|
||||||
setOtpError(err.message || "حدث خطأ في التحقق");
|
setOtpError(err.message || t("verifyError"));
|
||||||
} finally {
|
} finally {
|
||||||
setIsLoading(false);
|
setIsLoading(false);
|
||||||
}
|
}
|
||||||
@ -222,12 +224,12 @@ export default function LoginPage() {
|
|||||||
} else {
|
} else {
|
||||||
await sendPhoneOTP();
|
await sendPhoneOTP();
|
||||||
}
|
}
|
||||||
toast.success("تم إرسال رمز التحقق مجدداً", {
|
toast.success(t("resendSuccess"), {
|
||||||
style: { background: "#dcfce7", color: "#166534" },
|
style: { background: "#dcfce7", color: "#166534" },
|
||||||
});
|
});
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error("[OTP] Resend failed:", err);
|
console.error("[OTP] Resend failed:", err);
|
||||||
toast.error("فشل إرسال الرمز");
|
toast.error(t("resendFailed"));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -331,7 +333,7 @@ export default function LoginPage() {
|
|||||||
>
|
>
|
||||||
<ArrowLeft className="w-4 h-4" />
|
<ArrowLeft className="w-4 h-4" />
|
||||||
</motion.div>
|
</motion.div>
|
||||||
<span>العودة للرئيسية</span>
|
<span>{t("backToHome")}</span>
|
||||||
</Link>
|
</Link>
|
||||||
</motion.div>
|
</motion.div>
|
||||||
|
|
||||||
@ -373,7 +375,7 @@ export default function LoginPage() {
|
|||||||
</motion.div>
|
</motion.div>
|
||||||
<h1 className="text-3xl font-bold text-white mb-2">SweetHome</h1>
|
<h1 className="text-3xl font-bold text-white mb-2">SweetHome</h1>
|
||||||
<p className="text-amber-100">
|
<p className="text-amber-100">
|
||||||
{step === "otp" ? "أدخل رمز التحقق" : "مرحباً بعودتك!"}
|
{step === "otp" ? t("enterOTP") : t("welcomeBack")}
|
||||||
</p>
|
</p>
|
||||||
</motion.div>
|
</motion.div>
|
||||||
</div>
|
</div>
|
||||||
@ -404,7 +406,7 @@ export default function LoginPage() {
|
|||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
<Mail className="w-4 h-4" />
|
<Mail className="w-4 h-4" />
|
||||||
بريد إلكتروني
|
{t("emailMethod")}
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
@ -419,7 +421,7 @@ export default function LoginPage() {
|
|||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
<Phone className="w-4 h-4" />
|
<Phone className="w-4 h-4" />
|
||||||
رقم الهاتف
|
{t("phoneMethod")}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -427,8 +429,8 @@ export default function LoginPage() {
|
|||||||
<div>
|
<div>
|
||||||
<label className="block text-sm font-medium text-gray-300 mb-2">
|
<label className="block text-sm font-medium text-gray-300 mb-2">
|
||||||
{loginMethod === "email"
|
{loginMethod === "email"
|
||||||
? "البريد الإلكتروني"
|
? t("emailLabel")
|
||||||
: "رقم الهاتف"}
|
: t("phoneLabel")}
|
||||||
</label>
|
</label>
|
||||||
<div className="relative group">
|
<div className="relative group">
|
||||||
<div className="absolute inset-y-0 right-0 pr-3 flex items-center pointer-events-none">
|
<div className="absolute inset-y-0 right-0 pr-3 flex items-center pointer-events-none">
|
||||||
@ -454,8 +456,8 @@ export default function LoginPage() {
|
|||||||
}`}
|
}`}
|
||||||
placeholder={
|
placeholder={
|
||||||
loginMethod === "email"
|
loginMethod === "email"
|
||||||
? "example@email.com"
|
? t("emailPlaceholder")
|
||||||
: "+963XXXXXXXXX"
|
: t("phonePlaceholder")
|
||||||
}
|
}
|
||||||
dir="ltr"
|
dir="ltr"
|
||||||
/>
|
/>
|
||||||
@ -474,7 +476,7 @@ export default function LoginPage() {
|
|||||||
{/* Password */}
|
{/* Password */}
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-sm font-medium text-gray-300 mb-2">
|
<label className="block text-sm font-medium text-gray-300 mb-2">
|
||||||
كلمة المرور
|
{t("password")}
|
||||||
</label>
|
</label>
|
||||||
<div className="relative group">
|
<div className="relative group">
|
||||||
<div className="absolute inset-y-0 right-0 pr-3 flex items-center pointer-events-none">
|
<div className="absolute inset-y-0 right-0 pr-3 flex items-center pointer-events-none">
|
||||||
@ -496,7 +498,7 @@ export default function LoginPage() {
|
|||||||
className={`w-full pr-12 pl-12 py-4 bg-white/5 border rounded-xl focus:outline-none focus:ring-2 focus:ring-amber-500 focus:border-transparent text-white placeholder-gray-500 transition-all ${
|
className={`w-full pr-12 pl-12 py-4 bg-white/5 border rounded-xl focus:outline-none focus:ring-2 focus:ring-amber-500 focus:border-transparent text-white placeholder-gray-500 transition-all ${
|
||||||
errors.password ? "border-red-500" : "border-gray-700"
|
errors.password ? "border-red-500" : "border-gray-700"
|
||||||
}`}
|
}`}
|
||||||
placeholder="أدخل كلمة المرور"
|
placeholder={t("passwordPlaceholder")}
|
||||||
/>
|
/>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
@ -536,14 +538,14 @@ export default function LoginPage() {
|
|||||||
className="w-4 h-4 rounded border-gray-600 bg-white/5 text-amber-500 focus:ring-amber-500 focus:ring-offset-0"
|
className="w-4 h-4 rounded border-gray-600 bg-white/5 text-amber-500 focus:ring-amber-500 focus:ring-offset-0"
|
||||||
/>
|
/>
|
||||||
<span className="text-sm text-gray-400 group-hover:text-white transition-colors">
|
<span className="text-sm text-gray-400 group-hover:text-white transition-colors">
|
||||||
تذكرني
|
{t("rememberMe")}
|
||||||
</span>
|
</span>
|
||||||
</label>
|
</label>
|
||||||
<Link
|
<Link
|
||||||
href="/forgot-password"
|
href="/forgot-password"
|
||||||
className="text-sm text-amber-400 hover:text-amber-300 transition-colors"
|
className="text-sm text-amber-400 hover:text-amber-300 transition-colors"
|
||||||
>
|
>
|
||||||
نسيت كلمة المرور؟
|
{t("forgotPassword")}
|
||||||
</Link>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -559,17 +561,17 @@ export default function LoginPage() {
|
|||||||
{isLoading ? (
|
{isLoading ? (
|
||||||
<>
|
<>
|
||||||
<Loader2 className="w-5 h-5 animate-spin" />
|
<Loader2 className="w-5 h-5 animate-spin" />
|
||||||
جاري تسجيل الدخول...
|
{t("loggingIn")}
|
||||||
</>
|
</>
|
||||||
) : isSuccess ? (
|
) : isSuccess ? (
|
||||||
<>
|
<>
|
||||||
<CheckCircle className="w-5 h-5" />
|
<CheckCircle className="w-5 h-5" />
|
||||||
تم بنجاح!
|
{t("success")}
|
||||||
</>
|
</>
|
||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
<LogIn className="w-5 h-5" />
|
<LogIn className="w-5 h-5" />
|
||||||
تسجيل الدخول
|
{t("login")}
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</span>
|
</span>
|
||||||
@ -588,7 +590,7 @@ export default function LoginPage() {
|
|||||||
<div className="text-center mb-4">
|
<div className="text-center mb-4">
|
||||||
<Shield className="w-12 h-12 text-amber-500 mx-auto mb-3" />
|
<Shield className="w-12 h-12 text-amber-500 mx-auto mb-3" />
|
||||||
<p className="text-gray-300 text-sm">
|
<p className="text-gray-300 text-sm">
|
||||||
تم إرسال رمز التحقق إلى{" "}
|
{t("sendOTPTo")}{" "}
|
||||||
<span className="text-white font-medium" dir="ltr">
|
<span className="text-white font-medium" dir="ltr">
|
||||||
{formData.credential}
|
{formData.credential}
|
||||||
</span>
|
</span>
|
||||||
@ -597,7 +599,7 @@ export default function LoginPage() {
|
|||||||
|
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-sm font-medium text-gray-300 mb-2">
|
<label className="block text-sm font-medium text-gray-300 mb-2">
|
||||||
رمز التحقق
|
{t("otpLabel")}
|
||||||
</label>
|
</label>
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
@ -635,17 +637,17 @@ export default function LoginPage() {
|
|||||||
{isLoading ? (
|
{isLoading ? (
|
||||||
<>
|
<>
|
||||||
<Loader2 className="w-5 h-5 animate-spin" />
|
<Loader2 className="w-5 h-5 animate-spin" />
|
||||||
جاري التحقق...
|
{t("verifying")}
|
||||||
</>
|
</>
|
||||||
) : isSuccess ? (
|
) : isSuccess ? (
|
||||||
<>
|
<>
|
||||||
<CheckCircle className="w-5 h-5" />
|
<CheckCircle className="w-5 h-5" />
|
||||||
تم بنجاح!
|
{t("success")}
|
||||||
</>
|
</>
|
||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
<KeyRound className="w-5 h-5" />
|
<KeyRound className="w-5 h-5" />
|
||||||
تحقق
|
{t("verify")}
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</span>
|
</span>
|
||||||
@ -661,14 +663,14 @@ export default function LoginPage() {
|
|||||||
}}
|
}}
|
||||||
className="text-gray-400 hover:text-white transition-colors"
|
className="text-gray-400 hover:text-white transition-colors"
|
||||||
>
|
>
|
||||||
← العودة
|
← {t("back")}
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={resendOTP}
|
onClick={resendOTP}
|
||||||
className="text-amber-400 hover:text-amber-300 transition-colors"
|
className="text-amber-400 hover:text-amber-300 transition-colors"
|
||||||
>
|
>
|
||||||
إعادة إرسال الرمز
|
{t("resendCode")}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</motion.form>
|
</motion.form>
|
||||||
@ -679,12 +681,12 @@ export default function LoginPage() {
|
|||||||
variants={itemVariants}
|
variants={itemVariants}
|
||||||
className="text-center text-gray-400 mt-6"
|
className="text-center text-gray-400 mt-6"
|
||||||
>
|
>
|
||||||
ليس لديك حساب؟{" "}
|
{t("noAccount")}{" "}
|
||||||
<Link
|
<Link
|
||||||
href="/auth/choose-role"
|
href="/auth/choose-role"
|
||||||
className="text-amber-400 hover:text-amber-300 font-medium transition-colors"
|
className="text-amber-400 hover:text-amber-300 font-medium transition-colors"
|
||||||
>
|
>
|
||||||
إنشاء حساب جديد
|
{t("createNewAccount")}
|
||||||
</Link>
|
</Link>
|
||||||
</motion.p>
|
</motion.p>
|
||||||
</div>
|
</div>
|
||||||
@ -694,19 +696,19 @@ export default function LoginPage() {
|
|||||||
variants={itemVariants}
|
variants={itemVariants}
|
||||||
className="text-center text-gray-500 text-xs mt-4"
|
className="text-center text-gray-500 text-xs mt-4"
|
||||||
>
|
>
|
||||||
بتسجيل الدخول، أنت توافق على{" "}
|
{t("agreeTerms")}{" "}
|
||||||
<Link
|
<Link
|
||||||
href="/terms"
|
href="/terms"
|
||||||
className="text-amber-400 hover:text-amber-300 transition-colors"
|
className="text-amber-400 hover:text-amber-300 transition-colors"
|
||||||
>
|
>
|
||||||
شروط الاستخدام
|
{t("termsOfUse")}
|
||||||
</Link>{" "}
|
</Link>{" "}
|
||||||
و{" "}
|
{t("and")}{" "}
|
||||||
<Link
|
<Link
|
||||||
href="/privacy"
|
href="/privacy"
|
||||||
className="text-amber-400 hover:text-amber-300 transition-colors"
|
className="text-amber-400 hover:text-amber-300 transition-colors"
|
||||||
>
|
>
|
||||||
سياسة الخصوصية
|
{t("privacyPolicy")}
|
||||||
</Link>
|
</Link>
|
||||||
</motion.p>
|
</motion.p>
|
||||||
</motion.div>
|
</motion.div>
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
import { useState, useEffect } from 'react';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { motion } from 'framer-motion';
|
import { motion } from 'framer-motion';
|
||||||
import { Star, User, Calendar, MessageSquare, ThumbsUp, Loader2 } from 'lucide-react';
|
import { Star, User, Calendar, MessageSquare, ThumbsUp, Loader2 } from 'lucide-react';
|
||||||
import toast, { Toaster } from 'react-hot-toast';
|
import toast, { Toaster } from 'react-hot-toast';
|
||||||
@ -9,10 +9,10 @@ import AuthService from '../services/AuthService';
|
|||||||
import StarRating from '../components/ratings/StarRating';
|
import StarRating from '../components/ratings/StarRating';
|
||||||
|
|
||||||
const RATING_FIELDS = [
|
const RATING_FIELDS = [
|
||||||
{ key: 'cleanRating', label: 'النظافة' },
|
{ key: 'cleanRating', label: 'ratings.cleanliness' },
|
||||||
{ key: 'servicesRating', label: 'الخدمات' },
|
{ key: 'servicesRating', label: 'ratings.services' },
|
||||||
{ key: 'ownerBehaviorRating', label: 'سلوك المالك' },
|
{ key: 'ownerBehaviorRating', label: 'ratings.ownerBehavior' },
|
||||||
{ key: 'experienceRating', label: 'التجربة العامة' },
|
{ key: 'experienceRating', label: 'ratings.experience' },
|
||||||
];
|
];
|
||||||
|
|
||||||
function calcOverall(ratings) {
|
function calcOverall(ratings) {
|
||||||
@ -24,7 +24,7 @@ function calcOverall(ratings) {
|
|||||||
return total / ratings.length;
|
return total / ratings.length;
|
||||||
}
|
}
|
||||||
|
|
||||||
function RatingCard({ rating }) {
|
function RatingCard({ rating, t }) {
|
||||||
const dateStr = rating.createdAt
|
const dateStr = rating.createdAt
|
||||||
? new Date(rating.createdAt).toLocaleDateString('ar')
|
? new Date(rating.createdAt).toLocaleDateString('ar')
|
||||||
: null;
|
: null;
|
||||||
@ -49,7 +49,7 @@ function RatingCard({ rating }) {
|
|||||||
<div className="space-y-3 mb-4">
|
<div className="space-y-3 mb-4">
|
||||||
{RATING_FIELDS.map(({ key, label }) => (
|
{RATING_FIELDS.map(({ key, label }) => (
|
||||||
<div key={key} className="flex items-center justify-between">
|
<div key={key} className="flex items-center justify-between">
|
||||||
<span className="text-sm text-gray-600">{label}</span>
|
<span className="text-sm text-gray-600">{t(label)}</span>
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<StarRating rating={Number(rating[key]) || 0} size={18} readOnly />
|
<StarRating rating={Number(rating[key]) || 0} size={18} readOnly />
|
||||||
<span className="text-sm font-medium text-gray-700 w-6 text-left">
|
<span className="text-sm font-medium text-gray-700 w-6 text-left">
|
||||||
@ -71,18 +71,19 @@ function RatingCard({ rating }) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export default function MyRatesPage() {
|
export default function MyRatesPage() {
|
||||||
|
const { t } = useTranslation();
|
||||||
const [ratings, setRatings] = useState([]);
|
const [ratings, setRatings] = useState([]);
|
||||||
const [loading, setLoading] = useState(true);
|
const [loading, setLoading] = useState(true);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const user = AuthService.getUser();
|
const user = AuthService.getUser();
|
||||||
if (!user || !user.id) {
|
if (!user || !user.id) {
|
||||||
toast.error('يرجى تسجيل الدخول أولاً');
|
toast.error(t('auth.loginRequired'));
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
loadRatings(user.id);
|
loadRatings(user.id);
|
||||||
}, []);
|
}, [t]);
|
||||||
|
|
||||||
async function loadRatings(userId) {
|
async function loadRatings(userId) {
|
||||||
try {
|
try {
|
||||||
@ -91,7 +92,7 @@ export default function MyRatesPage() {
|
|||||||
setRatings(list);
|
setRatings(list);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error(err);
|
console.error(err);
|
||||||
toast.error('فشل تحميل التقييمات');
|
toast.error(t('ratings.loadError'));
|
||||||
setRatings([]);
|
setRatings([]);
|
||||||
} finally {
|
} finally {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
@ -113,8 +114,8 @@ export default function MyRatesPage() {
|
|||||||
<Toaster position="top-center" reverseOrder={false} />
|
<Toaster position="top-center" reverseOrder={false} />
|
||||||
<div className="container mx-auto px-4">
|
<div className="container mx-auto px-4">
|
||||||
<motion.div initial={{ opacity: 0, y: -20 }} animate={{ opacity: 1, y: 0 }} className="mb-8">
|
<motion.div initial={{ opacity: 0, y: -20 }} animate={{ opacity: 1, y: 0 }} className="mb-8">
|
||||||
<h1 className="text-3xl font-bold text-gray-900 mb-2">تقييماتي</h1>
|
<h1 className="text-3xl font-bold text-gray-900 mb-2">{t('ratings.myRatings')}</h1>
|
||||||
<p className="text-gray-600">التقييمات التي تلقيتها من مالكي العقارات</p>
|
<p className="text-gray-600">{t('ratings.receivedFromOwners')}</p>
|
||||||
</motion.div>
|
</motion.div>
|
||||||
|
|
||||||
{ratings.length > 0 && (
|
{ratings.length > 0 && (
|
||||||
@ -125,9 +126,9 @@ export default function MyRatesPage() {
|
|||||||
<StarRating rating={Math.round(overall)} size={24} readOnly />
|
<StarRating rating={Math.round(overall)} size={24} readOnly />
|
||||||
</div>
|
</div>
|
||||||
<div className="text-4xl font-bold">{overall.toFixed(1)}</div>
|
<div className="text-4xl font-bold">{overall.toFixed(1)}</div>
|
||||||
<div className="text-amber-100 text-sm mt-1">المعدل العام</div>
|
<div className="text-amber-100 text-sm mt-1">{t('ratings.overallAverage')}</div>
|
||||||
<div className="text-amber-100 text-xs mt-2">
|
<div className="text-amber-100 text-xs mt-2">
|
||||||
بناءً على {ratings.length} {ratings.length === 1 ? 'تقييم' : 'تقييمات'}
|
{t('ratings.basedOn')} {ratings.length} {ratings.length === 1 ? t('ratings.review') : t('ratings.reviews')}
|
||||||
</div>
|
</div>
|
||||||
</motion.div>
|
</motion.div>
|
||||||
)}
|
)}
|
||||||
@ -136,13 +137,13 @@ export default function MyRatesPage() {
|
|||||||
<motion.div initial={{ opacity: 0, y: 20 }} animate={{ opacity: 1, y: 0 }}
|
<motion.div initial={{ opacity: 0, y: 20 }} animate={{ opacity: 1, y: 0 }}
|
||||||
className="bg-white rounded-2xl p-12 text-center border-2 border-dashed border-gray-300">
|
className="bg-white rounded-2xl p-12 text-center border-2 border-dashed border-gray-300">
|
||||||
<Star className="w-16 h-16 text-gray-300 mx-auto mb-4" />
|
<Star className="w-16 h-16 text-gray-300 mx-auto mb-4" />
|
||||||
<h3 className="text-xl font-bold text-gray-900 mb-2">لا توجد تقييمات</h3>
|
<h3 className="text-xl font-bold text-gray-900 mb-2">{t('ratings.noRatings')}</h3>
|
||||||
<p className="text-gray-500">لم تقم باستئجار أي عقار بعد لتتلقى تقييمات</p>
|
<p className="text-gray-500">{t('ratings.noRatingsDescription')}</p>
|
||||||
</motion.div>
|
</motion.div>
|
||||||
) : (
|
) : (
|
||||||
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
|
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
|
||||||
{ratings.map((r, i) => (
|
{ratings.map((r, i) => (
|
||||||
<RatingCard key={r.id || i} rating={r} />
|
<RatingCard key={r.id || i} rating={r} t={t} />
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
import { useRouter } from 'next/navigation';
|
import { useRouter } from 'next/navigation';
|
||||||
import { motion, AnimatePresence } from 'framer-motion';
|
import { motion, AnimatePresence } from 'framer-motion';
|
||||||
import { Bell, CheckCircle, XCircle, Calendar, MessageCircle, CheckCheck, Loader2 } from 'lucide-react';
|
import { Bell, CheckCircle, XCircle, Calendar, MessageCircle, CheckCheck, Loader2 } from 'lucide-react';
|
||||||
@ -8,6 +9,7 @@ import AuthService from '@/app/services/AuthService';
|
|||||||
import { getUserNotifications } from '@/app/utils/api';
|
import { getUserNotifications } from '@/app/utils/api';
|
||||||
|
|
||||||
export default function NotificationsPage() {
|
export default function NotificationsPage() {
|
||||||
|
const { t } = useTranslation();
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const [notifications, setNotifications] = useState([]);
|
const [notifications, setNotifications] = useState([]);
|
||||||
const [unreadCount, setUnreadCount] = useState(0);
|
const [unreadCount, setUnreadCount] = useState(0);
|
||||||
@ -32,7 +34,7 @@ export default function NotificationsPage() {
|
|||||||
setUnreadCount(items.length);
|
setUnreadCount(items.length);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('Error fetching notifications:', err);
|
console.error('Error fetching notifications:', err);
|
||||||
setError(err.message || 'فشل تحميل الإشعارات');
|
setError(err.message || t('fetch-notifications-failed'));
|
||||||
} finally {
|
} finally {
|
||||||
setIsLoading(false);
|
setIsLoading(false);
|
||||||
}
|
}
|
||||||
@ -59,7 +61,7 @@ export default function NotificationsPage() {
|
|||||||
className="text-center"
|
className="text-center"
|
||||||
>
|
>
|
||||||
<Loader2 className="w-12 h-12 text-amber-500 mx-auto mb-4 animate-spin" />
|
<Loader2 className="w-12 h-12 text-amber-500 mx-auto mb-4 animate-spin" />
|
||||||
<p className="text-gray-600">جاري التحميل...</p>
|
<p className="text-gray-600">{t('loading')}</p>
|
||||||
</motion.div>
|
</motion.div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
@ -74,7 +76,7 @@ export default function NotificationsPage() {
|
|||||||
className="text-center"
|
className="text-center"
|
||||||
>
|
>
|
||||||
<XCircle className="w-16 h-16 text-red-500 mx-auto mb-4" />
|
<XCircle className="w-16 h-16 text-red-500 mx-auto mb-4" />
|
||||||
<h3 className="text-xl font-bold text-gray-700 mb-2">خطأ في التحميل</h3>
|
<h3 className="text-xl font-bold text-gray-700 mb-2">{t('loading-error')}</h3>
|
||||||
<p className="text-gray-500 mb-4">{error}</p>
|
<p className="text-gray-500 mb-4">{error}</p>
|
||||||
<motion.button
|
<motion.button
|
||||||
whileHover={{ scale: 1.05 }}
|
whileHover={{ scale: 1.05 }}
|
||||||
@ -82,7 +84,7 @@ export default function NotificationsPage() {
|
|||||||
onClick={fetchNotifications}
|
onClick={fetchNotifications}
|
||||||
className="px-6 py-2 bg-amber-500 text-white rounded-xl font-medium hover:bg-amber-600 transition-colors"
|
className="px-6 py-2 bg-amber-500 text-white rounded-xl font-medium hover:bg-amber-600 transition-colors"
|
||||||
>
|
>
|
||||||
إعادة المحاولة
|
{t('retry')}
|
||||||
</motion.button>
|
</motion.button>
|
||||||
</motion.div>
|
</motion.div>
|
||||||
</div>
|
</div>
|
||||||
@ -98,9 +100,9 @@ export default function NotificationsPage() {
|
|||||||
className="flex justify-between items-center mb-8"
|
className="flex justify-between items-center mb-8"
|
||||||
>
|
>
|
||||||
<div>
|
<div>
|
||||||
<h1 className="text-3xl font-bold text-gray-900 mb-2">الإشعارات</h1>
|
<h1 className="text-3xl font-bold text-gray-900 mb-2">{t('notifications')}</h1>
|
||||||
<p className="text-gray-600">
|
<p className="text-gray-600">
|
||||||
{unreadCount > 0 ? `لديك ${unreadCount} إشعار غير مقروء` : 'جميع الإشعارات مقروءة'}
|
{unreadCount > 0 ? t('unread-notifications', { count: unreadCount }) : t('all-notifications-read')}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
{unreadCount > 0 && (
|
{unreadCount > 0 && (
|
||||||
@ -111,7 +113,7 @@ export default function NotificationsPage() {
|
|||||||
className="flex items-center gap-2 px-4 py-2 bg-white border border-gray-200 rounded-xl text-sm font-medium text-gray-700 hover:bg-gray-50 transition-all shadow-sm"
|
className="flex items-center gap-2 px-4 py-2 bg-white border border-gray-200 rounded-xl text-sm font-medium text-gray-700 hover:bg-gray-50 transition-all shadow-sm"
|
||||||
>
|
>
|
||||||
<CheckCheck className="w-4 h-4 text-amber-500" />
|
<CheckCheck className="w-4 h-4 text-amber-500" />
|
||||||
تحديد الكل كمقروء
|
{t('mark-all-read')}
|
||||||
</motion.button>
|
</motion.button>
|
||||||
)}
|
)}
|
||||||
</motion.div>
|
</motion.div>
|
||||||
@ -123,8 +125,8 @@ export default function NotificationsPage() {
|
|||||||
className="bg-white rounded-2xl p-12 text-center border-2 border-dashed border-gray-300"
|
className="bg-white rounded-2xl p-12 text-center border-2 border-dashed border-gray-300"
|
||||||
>
|
>
|
||||||
<Bell className="w-16 h-16 text-gray-300 mx-auto mb-4" />
|
<Bell className="w-16 h-16 text-gray-300 mx-auto mb-4" />
|
||||||
<h3 className="text-xl font-bold text-gray-700 mb-2">لا توجد إشعارات</h3>
|
<h3 className="text-xl font-bold text-gray-700 mb-2">{t('no-notifications')}</h3>
|
||||||
<p className="text-gray-500">ستظهر هنا الإشعارات المتعلقة بحجوزاتك ومدفوعاتك</p>
|
<p className="text-gray-500">{t('notifications-empty-hint')}</p>
|
||||||
</motion.div>
|
</motion.div>
|
||||||
) : (
|
) : (
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
|
|||||||
@ -5,33 +5,35 @@ import { motion, AnimatePresence } from 'framer-motion';
|
|||||||
import { useRouter } from 'next/navigation';
|
import { useRouter } from 'next/navigation';
|
||||||
import Link from 'next/link';
|
import Link from 'next/link';
|
||||||
import { Home, Search, Rocket, ArrowLeft, ArrowRight } from 'lucide-react';
|
import { Home, Search, Rocket, ArrowLeft, ArrowRight } from 'lucide-react';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
const STORAGE_KEY = 'sweethome_onboarding_completed';
|
const STORAGE_KEY = 'sweethome_onboarding_completed';
|
||||||
|
|
||||||
const steps = [
|
export default function OnboardingPage() {
|
||||||
|
const { t, i18n } = useTranslation();
|
||||||
|
const router = useRouter();
|
||||||
|
const [currentStep, setCurrentStep] = useState(0);
|
||||||
|
|
||||||
|
const steps = [
|
||||||
{
|
{
|
||||||
icon: Home,
|
icon: Home,
|
||||||
title: 'مرحباً بك في SweetHome',
|
title: t('onboarding.step1.title'),
|
||||||
description: 'نحن هنا لمساعدتك في إيجاد العقار المثالي الذي تحلم به. اكتشف آلاف العقارات المتاحة للإيجار والبيع في جميع أنحاء سوريا.',
|
description: t('onboarding.step1.description'),
|
||||||
gradient: 'from-amber-500 to-orange-600',
|
gradient: 'from-amber-500 to-orange-600',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
icon: Search,
|
icon: Search,
|
||||||
title: 'ابحث عن منزل أحلامك',
|
title: t('onboarding.step2.title'),
|
||||||
description: 'استخدم محرك البحث الذكي لدينا للعثور على العقار المناسب. تصفح حسب الموقع، السعر، النوع، والمزيد من الفلاتر المتقدمة.',
|
description: t('onboarding.step2.description'),
|
||||||
gradient: 'from-blue-500 to-indigo-600',
|
gradient: 'from-blue-500 to-indigo-600',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
icon: Rocket,
|
icon: Rocket,
|
||||||
title: 'انطلق الآن',
|
title: t('onboarding.step3.title'),
|
||||||
description: 'انشئ حسابك مجاناً وابدأ رحلة البحث عن منزلك الجديد. يمكنك حفظ العقارات المفضلة والتواصل مع المالكين مباشرة.',
|
description: t('onboarding.step3.description'),
|
||||||
gradient: 'from-emerald-500 to-teal-600',
|
gradient: 'from-emerald-500 to-teal-600',
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
export default function OnboardingPage() {
|
|
||||||
const router = useRouter();
|
|
||||||
const [currentStep, setCurrentStep] = useState(0);
|
|
||||||
const [direction, setDirection] = useState(0);
|
const [direction, setDirection] = useState(0);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@ -74,7 +76,7 @@ export default function OnboardingPage() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="min-h-screen bg-gradient-to-br from-gray-950 via-gray-900 to-gray-950 flex items-center justify-center p-4 relative overflow-hidden">
|
<div dir={i18n.language === 'ar' ? 'rtl' : 'ltr'} className="min-h-screen bg-gradient-to-br from-gray-950 via-gray-900 to-gray-950 flex items-center justify-center p-4 relative overflow-hidden">
|
||||||
<div className="absolute inset-0 overflow-hidden">
|
<div className="absolute inset-0 overflow-hidden">
|
||||||
{[...Array(30)].map((_, i) => (
|
{[...Array(30)].map((_, i) => (
|
||||||
<motion.div
|
<motion.div
|
||||||
@ -199,7 +201,7 @@ export default function OnboardingPage() {
|
|||||||
className="flex items-center gap-2 px-5 py-3 rounded-xl text-gray-300 hover:text-white hover:bg-white/10 transition-all border border-white/10"
|
className="flex items-center gap-2 px-5 py-3 rounded-xl text-gray-300 hover:text-white hover:bg-white/10 transition-all border border-white/10"
|
||||||
>
|
>
|
||||||
<ArrowRight className="w-5 h-5" />
|
<ArrowRight className="w-5 h-5" />
|
||||||
<span>السابق</span>
|
<span>{t('onboarding.prev')}</span>
|
||||||
</motion.button>
|
</motion.button>
|
||||||
) : (
|
) : (
|
||||||
<div />
|
<div />
|
||||||
@ -212,7 +214,7 @@ export default function OnboardingPage() {
|
|||||||
onClick={nextStep}
|
onClick={nextStep}
|
||||||
className={`flex items-center gap-2 px-6 py-3 rounded-xl text-white font-medium bg-gradient-to-r ${step.gradient} hover:shadow-lg transition-all`}
|
className={`flex items-center gap-2 px-6 py-3 rounded-xl text-white font-medium bg-gradient-to-r ${step.gradient} hover:shadow-lg transition-all`}
|
||||||
>
|
>
|
||||||
<span>التالي</span>
|
<span>{t('onboarding.next')}</span>
|
||||||
<ArrowLeft className="w-5 h-5" />
|
<ArrowLeft className="w-5 h-5" />
|
||||||
</motion.button>
|
</motion.button>
|
||||||
) : (
|
) : (
|
||||||
@ -223,7 +225,7 @@ export default function OnboardingPage() {
|
|||||||
whileTap={{ scale: 0.95 }}
|
whileTap={{ scale: 0.95 }}
|
||||||
className="px-6 py-3 rounded-xl bg-white text-gray-900 font-bold hover:bg-gray-100 transition-all"
|
className="px-6 py-3 rounded-xl bg-white text-gray-900 font-bold hover:bg-gray-100 transition-all"
|
||||||
>
|
>
|
||||||
تسجيل جديد
|
{t('onboarding.register')}
|
||||||
</motion.button>
|
</motion.button>
|
||||||
</Link>
|
</Link>
|
||||||
<Link href="/login" onClick={handleFinish}>
|
<Link href="/login" onClick={handleFinish}>
|
||||||
@ -232,7 +234,7 @@ export default function OnboardingPage() {
|
|||||||
whileTap={{ scale: 0.95 }}
|
whileTap={{ scale: 0.95 }}
|
||||||
className="px-6 py-3 rounded-xl bg-gradient-to-r from-emerald-500 to-teal-600 text-white font-bold hover:shadow-lg transition-all"
|
className="px-6 py-3 rounded-xl bg-gradient-to-r from-emerald-500 to-teal-600 text-white font-bold hover:shadow-lg transition-all"
|
||||||
>
|
>
|
||||||
تسجيل دخول
|
{t('onboarding.login')}
|
||||||
</motion.button>
|
</motion.button>
|
||||||
</Link>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -2,14 +2,12 @@
|
|||||||
|
|
||||||
import { useState, useEffect } from 'react';
|
import { useState, useEffect } from 'react';
|
||||||
import { motion } from 'framer-motion';
|
import { motion } from 'framer-motion';
|
||||||
import { useRouter } from 'next/navigation';
|
import { useTranslation } from 'react-i18next';
|
||||||
import {
|
import {
|
||||||
DollarSign,
|
DollarSign,
|
||||||
TrendingUp,
|
TrendingUp,
|
||||||
Calendar,
|
Calendar,
|
||||||
Building,
|
|
||||||
Loader2,
|
Loader2,
|
||||||
ArrowLeft,
|
|
||||||
Star,
|
Star,
|
||||||
} from 'lucide-react';
|
} from 'lucide-react';
|
||||||
import toast, { Toaster } from 'react-hot-toast';
|
import toast, { Toaster } from 'react-hot-toast';
|
||||||
@ -34,7 +32,7 @@ const StatCard = ({ title, value, icon: Icon, color, subtitle, isNA }) => (
|
|||||||
);
|
);
|
||||||
|
|
||||||
export default function OwnerAccountBookPage() {
|
export default function OwnerAccountBookPage() {
|
||||||
const router = useRouter();
|
const { t, i18n } = useTranslation();
|
||||||
const [user, setUser] = useState(null);
|
const [user, setUser] = useState(null);
|
||||||
const [isLoading, setIsLoading] = useState(true);
|
const [isLoading, setIsLoading] = useState(true);
|
||||||
const [stats, setStats] = useState({
|
const [stats, setStats] = useState({
|
||||||
@ -53,11 +51,9 @@ export default function OwnerAccountBookPage() {
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (AuthService.isGuest()) {
|
if (AuthService.isGuest()) {
|
||||||
router.push('/auth/choose-role');
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (!AuthService.isOwner()) {
|
if (!AuthService.isOwner()) {
|
||||||
router.push('/');
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -93,18 +89,18 @@ export default function OwnerAccountBookPage() {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
} catch {
|
} catch {
|
||||||
toast.error('تعذر تحميل إحصائيات الحساب');
|
toast.error(t('accountBook.loadError'));
|
||||||
} finally {
|
} finally {
|
||||||
setIsLoading(false);
|
setIsLoading(false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
fetchData();
|
fetchData();
|
||||||
}, [router]);
|
}, []);
|
||||||
|
|
||||||
const formatCurrency = (amount) => {
|
const formatCurrency = (amount) => {
|
||||||
const num = Number(amount);
|
const num = Number(amount);
|
||||||
if (isNaN(num)) return 'N/A';
|
if (isNaN(num)) return 'N/A';
|
||||||
return num.toLocaleString() + ' ل.س';
|
return num.toLocaleString() + ' ' + t('currency-syp-suffix');
|
||||||
};
|
};
|
||||||
|
|
||||||
const formatNumber = (val) => {
|
const formatNumber = (val) => {
|
||||||
@ -126,32 +122,23 @@ export default function OwnerAccountBookPage() {
|
|||||||
<div className="min-h-screen bg-gray-50 flex items-center justify-center">
|
<div className="min-h-screen bg-gray-50 flex items-center justify-center">
|
||||||
<div className="text-center">
|
<div className="text-center">
|
||||||
<Loader2 className="w-12 h-12 text-amber-500 animate-spin mx-auto mb-4" />
|
<Loader2 className="w-12 h-12 text-amber-500 animate-spin mx-auto mb-4" />
|
||||||
<p className="text-gray-600">جاري تحميل بيانات الحساب...</p>
|
<p className="text-gray-600">{t('accountBook.loading')}</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
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.language === 'ar' ? 'rtl' : 'ltr'}>
|
||||||
<Toaster position="top-center" reverseOrder={false} />
|
<Toaster position="top-center" reverseOrder={false} />
|
||||||
<div className="container mx-auto px-4 max-w-7xl">
|
<div className="container mx-auto px-4 max-w-7xl">
|
||||||
<motion.div
|
<motion.div
|
||||||
initial={{ opacity: 0, y: -20 }}
|
initial={{ opacity: 0, y: -20 }}
|
||||||
animate={{ opacity: 1, y: 0 }}
|
animate={{ opacity: 1, y: 0 }}
|
||||||
className="flex flex-col md:flex-row justify-between items-start md:items-center mb-8 gap-4"
|
className="mb-8"
|
||||||
>
|
>
|
||||||
<div className="flex items-center gap-4">
|
<h1 className="text-3xl font-bold text-gray-900">{t('accountBook')}</h1>
|
||||||
<button
|
<p className="text-gray-600">{t('financialRecords')}</p>
|
||||||
onClick={() => router.back()}
|
|
||||||
className="p-2 hover:bg-gray-200 rounded-xl transition-colors"
|
|
||||||
>
|
|
||||||
<ArrowLeft className="w-5 h-5 text-gray-600" />
|
|
||||||
</button>
|
|
||||||
<div>
|
|
||||||
<h1 className="text-3xl font-bold text-gray-900">دفتر الحسابات</h1>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</motion.div>
|
</motion.div>
|
||||||
|
|
||||||
<motion.div
|
<motion.div
|
||||||
@ -159,24 +146,24 @@ export default function OwnerAccountBookPage() {
|
|||||||
animate={{ opacity: 1, y: 0 }}
|
animate={{ opacity: 1, y: 0 }}
|
||||||
className="mb-8"
|
className="mb-8"
|
||||||
>
|
>
|
||||||
<h2 className="text-xl font-bold text-gray-900 mb-4">نظرة مالية للمالك</h2>
|
<h2 className="text-xl font-bold text-gray-900 mb-4">{t('accountBook.ownerFinancialOverview')}</h2>
|
||||||
<div className="grid grid-cols-1 sm:grid-cols-3 gap-6">
|
<div className="grid grid-cols-1 sm:grid-cols-3 gap-6">
|
||||||
<StatCard
|
<StatCard
|
||||||
title="الإيرادات"
|
title={t('accountBook.revenue')}
|
||||||
value={formatCurrency(stats.financialRevenue)}
|
value={formatCurrency(stats.financialRevenue)}
|
||||||
icon={TrendingUp}
|
icon={TrendingUp}
|
||||||
color="bg-emerald-500"
|
color="bg-emerald-500"
|
||||||
isNA={isNA(stats.financialRevenue)}
|
isNA={isNA(stats.financialRevenue)}
|
||||||
/>
|
/>
|
||||||
<StatCard
|
<StatCard
|
||||||
title="العمولة"
|
title={t('accountBook.commission')}
|
||||||
value={formatCurrency(stats.financialCommission)}
|
value={formatCurrency(stats.financialCommission)}
|
||||||
icon={DollarSign}
|
icon={DollarSign}
|
||||||
color="bg-orange-500"
|
color="bg-orange-500"
|
||||||
isNA={isNA(stats.financialCommission)}
|
isNA={isNA(stats.financialCommission)}
|
||||||
/>
|
/>
|
||||||
<StatCard
|
<StatCard
|
||||||
title="الرصيد المتبقي"
|
title={t('accountBook.remainingBalance')}
|
||||||
value={formatCurrency(stats.financialBalance)}
|
value={formatCurrency(stats.financialBalance)}
|
||||||
icon={DollarSign}
|
icon={DollarSign}
|
||||||
color="bg-blue-500"
|
color="bg-blue-500"
|
||||||
@ -191,38 +178,38 @@ export default function OwnerAccountBookPage() {
|
|||||||
transition={{ delay: 0.1 }}
|
transition={{ delay: 0.1 }}
|
||||||
className="mb-8"
|
className="mb-8"
|
||||||
>
|
>
|
||||||
<h2 className="text-xl font-bold text-gray-900 mb-4">إحصائيات العقارات المباشرة</h2>
|
<h2 className="text-xl font-bold text-gray-900 mb-4">{t('accountBook.directPropertyStats')}</h2>
|
||||||
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-5 gap-6">
|
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-5 gap-6">
|
||||||
<StatCard
|
<StatCard
|
||||||
title="الإيرادات"
|
title={t('accountBook.revenue')}
|
||||||
value={formatCurrency(stats.directRevenue)}
|
value={formatCurrency(stats.directRevenue)}
|
||||||
icon={TrendingUp}
|
icon={TrendingUp}
|
||||||
color="bg-emerald-500"
|
color="bg-emerald-500"
|
||||||
isNA={isNA(stats.directRevenue)}
|
isNA={isNA(stats.directRevenue)}
|
||||||
/>
|
/>
|
||||||
<StatCard
|
<StatCard
|
||||||
title="العمولة"
|
title={t('accountBook.commission')}
|
||||||
value={formatCurrency(stats.directCommission)}
|
value={formatCurrency(stats.directCommission)}
|
||||||
icon={DollarSign}
|
icon={DollarSign}
|
||||||
color="bg-orange-500"
|
color="bg-orange-500"
|
||||||
isNA={isNA(stats.directCommission)}
|
isNA={isNA(stats.directCommission)}
|
||||||
/>
|
/>
|
||||||
<StatCard
|
<StatCard
|
||||||
title="الرصيد المتبقي"
|
title={t('accountBook.remainingBalance')}
|
||||||
value={formatCurrency(stats.directBalance)}
|
value={formatCurrency(stats.directBalance)}
|
||||||
icon={DollarSign}
|
icon={DollarSign}
|
||||||
color="bg-blue-500"
|
color="bg-blue-500"
|
||||||
isNA={isNA(stats.directBalance)}
|
isNA={isNA(stats.directBalance)}
|
||||||
/>
|
/>
|
||||||
<StatCard
|
<StatCard
|
||||||
title="التقييم العام"
|
title={t('accountBook.overallRating')}
|
||||||
value={formatRating(stats.directRating)}
|
value={formatRating(stats.directRating)}
|
||||||
icon={Star}
|
icon={Star}
|
||||||
color="bg-purple-500"
|
color="bg-purple-500"
|
||||||
isNA={isNA(stats.directRating)}
|
isNA={isNA(stats.directRating)}
|
||||||
/>
|
/>
|
||||||
<StatCard
|
<StatCard
|
||||||
title="مرات التأجيل"
|
title={t('accountBook.postponements')}
|
||||||
value={formatNumber(stats.directPostponements)}
|
value={formatNumber(stats.directPostponements)}
|
||||||
icon={Calendar}
|
icon={Calendar}
|
||||||
color="bg-red-500"
|
color="bg-red-500"
|
||||||
|
|||||||
@ -4,6 +4,7 @@ import { useState, useEffect } from 'react';
|
|||||||
import { motion, AnimatePresence } from 'framer-motion';
|
import { motion, AnimatePresence } from 'framer-motion';
|
||||||
import { useRouter } from 'next/navigation';
|
import { useRouter } from 'next/navigation';
|
||||||
import Link from 'next/link';
|
import Link from 'next/link';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
import {
|
import {
|
||||||
Calendar,
|
Calendar,
|
||||||
Home,
|
Home,
|
||||||
@ -37,6 +38,7 @@ import AuthService from '../../services/AuthService';
|
|||||||
import Image from 'next/image';
|
import Image from 'next/image';
|
||||||
|
|
||||||
const OwnerBookingCalendar = ({ property, onDateSelect, selectedDates }) => {
|
const OwnerBookingCalendar = ({ property, onDateSelect, selectedDates }) => {
|
||||||
|
const { t } = useTranslation();
|
||||||
const [currentMonth, setCurrentMonth] = useState(new Date());
|
const [currentMonth, setCurrentMonth] = useState(new Date());
|
||||||
const [hoverDate, setHoverDate] = useState(null);
|
const [hoverDate, setHoverDate] = useState(null);
|
||||||
|
|
||||||
@ -53,8 +55,13 @@ const OwnerBookingCalendar = ({ property, onDateSelect, selectedDates }) => {
|
|||||||
).getDay();
|
).getDay();
|
||||||
|
|
||||||
const monthNames = [
|
const monthNames = [
|
||||||
'يناير', 'فبراير', 'مارس', 'إبريل', 'مايو', 'يونيو',
|
t('january'), t('february'), t('march'), t('april'), t('may'), t('june'),
|
||||||
'يوليو', 'أغسطس', 'سبتمبر', 'أكتوبر', 'نوفمبر', 'ديسمبر'
|
t('july'), t('august'), t('september'), t('october'), t('november'), t('december')
|
||||||
|
];
|
||||||
|
|
||||||
|
const dayNames = [
|
||||||
|
t('dayFull.sunday'), t('dayFull.monday'), t('dayFull.tuesday'), t('dayFull.wednesday'),
|
||||||
|
t('dayFull.thursday'), t('dayFull.friday'), t('dayFull.saturday')
|
||||||
];
|
];
|
||||||
|
|
||||||
const isDateBooked = (date) => {
|
const isDateBooked = (date) => {
|
||||||
@ -134,7 +141,6 @@ const OwnerBookingCalendar = ({ property, onDateSelect, selectedDates }) => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="bg-white rounded-2xl shadow-sm border border-gray-200 p-6">
|
<div className="bg-white rounded-2xl shadow-sm border border-gray-200 p-6">
|
||||||
{/* رأس التقويم */}
|
|
||||||
<div className="flex items-center justify-between mb-6">
|
<div className="flex items-center justify-between mb-6">
|
||||||
<button
|
<button
|
||||||
onClick={() => setCurrentMonth(new Date(currentMonth.getFullYear(), currentMonth.getMonth() - 1, 1))}
|
onClick={() => setCurrentMonth(new Date(currentMonth.getFullYear(), currentMonth.getMonth() - 1, 1))}
|
||||||
@ -156,15 +162,10 @@ const OwnerBookingCalendar = ({ property, onDateSelect, selectedDates }) => {
|
|||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* أيام الأسبوع */}
|
|
||||||
<div className="grid grid-cols-7 gap-1 mb-3 text-center text-sm font-medium text-gray-500">
|
<div className="grid grid-cols-7 gap-1 mb-3 text-center text-sm font-medium text-gray-500">
|
||||||
<div>أحد</div>
|
{dayNames.map((name, i) => (
|
||||||
<div>إثنين</div>
|
<div key={i}>{name}</div>
|
||||||
<div>ثلاثاء</div>
|
))}
|
||||||
<div>أربعاء</div>
|
|
||||||
<div>خميس</div>
|
|
||||||
<div>جمعة</div>
|
|
||||||
<div>سبت</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="grid grid-cols-7 gap-1">
|
<div className="grid grid-cols-7 gap-1">
|
||||||
@ -174,19 +175,19 @@ const OwnerBookingCalendar = ({ property, onDateSelect, selectedDates }) => {
|
|||||||
<div className="flex flex-wrap gap-4 mt-6 pt-4 border-t border-gray-200 text-xs">
|
<div className="flex flex-wrap gap-4 mt-6 pt-4 border-t border-gray-200 text-xs">
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<div className="w-3 h-3 bg-red-100 rounded" />
|
<div className="w-3 h-3 bg-red-100 rounded" />
|
||||||
<span className="text-gray-600">محجوز</span>
|
<span className="text-gray-600">{t('booked')}</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<div className="w-3 h-3 bg-amber-500 rounded" />
|
<div className="w-3 h-3 bg-amber-500 rounded" />
|
||||||
<span className="text-gray-600">محدد</span>
|
<span className="text-gray-600">{t('selected')}</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<div className="w-3 h-3 bg-amber-100 rounded" />
|
<div className="w-3 h-3 bg-amber-100 rounded" />
|
||||||
<span className="text-gray-600">ضمن الفترة</span>
|
<span className="text-gray-600">{t('inRange')}</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<div className="w-3 h-3 border-2 border-amber-500 rounded" />
|
<div className="w-3 h-3 border-2 border-amber-500 rounded" />
|
||||||
<span className="text-gray-600">اليوم</span>
|
<span className="text-gray-600">{t('today')}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -194,16 +195,18 @@ const OwnerBookingCalendar = ({ property, onDateSelect, selectedDates }) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const BookingCard = ({ booking, onViewDetails, onContact }) => {
|
const BookingCard = ({ booking, onViewDetails, onContact }) => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
const formatCurrency = (amount) => {
|
const formatCurrency = (amount) => {
|
||||||
return amount?.toLocaleString() + ' ل.س';
|
return amount?.toLocaleString() + ' ' + t('syp');
|
||||||
};
|
};
|
||||||
|
|
||||||
const getStatusBadge = (status) => {
|
const getStatusBadge = (status) => {
|
||||||
const statusConfig = {
|
const statusConfig = {
|
||||||
pending: { label: 'قيد الانتظار', color: 'bg-yellow-100 text-yellow-800', icon: Clock },
|
pending: { label: t('pending'), color: 'bg-yellow-100 text-yellow-800', icon: Clock },
|
||||||
confirmed: { label: 'مؤكد', color: 'bg-green-100 text-green-800', icon: CheckCircle },
|
confirmed: { label: t('confirmed'), color: 'bg-green-100 text-green-800', icon: CheckCircle },
|
||||||
cancelled: { label: 'ملغي', color: 'bg-red-100 text-red-800', icon: XCircle },
|
cancelled: { label: t('cancelled'), color: 'bg-red-100 text-red-800', icon: XCircle },
|
||||||
completed: { label: 'منتهي', color: 'bg-gray-100 text-gray-800', icon: CheckCircle }
|
completed: { label: t('completed'), color: 'bg-gray-100 text-gray-800', icon: CheckCircle }
|
||||||
};
|
};
|
||||||
|
|
||||||
const config = statusConfig[status] || statusConfig.pending;
|
const config = statusConfig[status] || statusConfig.pending;
|
||||||
@ -237,7 +240,7 @@ const BookingCard = ({ booking, onViewDetails, onContact }) => {
|
|||||||
</div>
|
</div>
|
||||||
<div className="text-left">
|
<div className="text-left">
|
||||||
<div className="text-lg font-bold text-amber-600">{formatCurrency(booking.totalAmount)}</div>
|
<div className="text-lg font-bold text-amber-600">{formatCurrency(booking.totalAmount)}</div>
|
||||||
<div className="text-xs text-gray-500">إجمالي المبلغ</div>
|
<div className="text-xs text-gray-500">{t('totalAmount')}</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -261,18 +264,18 @@ const BookingCard = ({ booking, onViewDetails, onContact }) => {
|
|||||||
<div className="grid grid-cols-3 gap-3 mb-4 text-center">
|
<div className="grid grid-cols-3 gap-3 mb-4 text-center">
|
||||||
<div className="bg-gray-50 p-2 rounded-lg">
|
<div className="bg-gray-50 p-2 rounded-lg">
|
||||||
<Calendar className="w-4 h-4 text-amber-500 mx-auto mb-1" />
|
<Calendar className="w-4 h-4 text-amber-500 mx-auto mb-1" />
|
||||||
<div className="text-xs text-gray-500">من</div>
|
<div className="text-xs text-gray-500">{t('from')}</div>
|
||||||
<div className="text-sm font-medium">{booking.startDate}</div>
|
<div className="text-sm font-medium">{booking.startDate}</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="bg-gray-50 p-2 rounded-lg">
|
<div className="bg-gray-50 p-2 rounded-lg">
|
||||||
<Calendar className="w-4 h-4 text-amber-500 mx-auto mb-1" />
|
<Calendar className="w-4 h-4 text-amber-500 mx-auto mb-1" />
|
||||||
<div className="text-xs text-gray-500">إلى</div>
|
<div className="text-xs text-gray-500">{t('to')}</div>
|
||||||
<div className="text-sm font-medium">{booking.endDate}</div>
|
<div className="text-sm font-medium">{booking.endDate}</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="bg-gray-50 p-2 rounded-lg">
|
<div className="bg-gray-50 p-2 rounded-lg">
|
||||||
<Clock className="w-4 h-4 text-amber-500 mx-auto mb-1" />
|
<Clock className="w-4 h-4 text-amber-500 mx-auto mb-1" />
|
||||||
<div className="text-xs text-gray-500">المدة</div>
|
<div className="text-xs text-gray-500">{t('duration')}</div>
|
||||||
<div className="text-sm font-medium">{booking.days} يوم</div>
|
<div className="text-sm font-medium">{booking.days} {t('days')}</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -282,15 +285,8 @@ const BookingCard = ({ booking, onViewDetails, onContact }) => {
|
|||||||
className="flex-1 bg-gray-100 text-gray-700 py-2 rounded-xl text-sm font-medium hover:bg-gray-200 transition-colors flex items-center justify-center gap-2"
|
className="flex-1 bg-gray-100 text-gray-700 py-2 rounded-xl text-sm font-medium hover:bg-gray-200 transition-colors flex items-center justify-center gap-2"
|
||||||
>
|
>
|
||||||
<Eye className="w-4 h-4" />
|
<Eye className="w-4 h-4" />
|
||||||
التفاصيل
|
{t('details')}
|
||||||
</button>
|
</button>
|
||||||
{/* <button
|
|
||||||
onClick={() => onContact(booking)}
|
|
||||||
className="flex-1 bg-amber-500 text-white py-2 rounded-xl text-sm font-medium hover:bg-amber-600 transition-colors flex items-center justify-center gap-2"
|
|
||||||
>
|
|
||||||
<MessageCircle className="w-4 h-4" />
|
|
||||||
تواصل
|
|
||||||
</button> */}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</motion.div>
|
</motion.div>
|
||||||
@ -298,10 +294,11 @@ const BookingCard = ({ booking, onViewDetails, onContact }) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const BookingDetailsModal = ({ booking, isOpen, onClose }) => {
|
const BookingDetailsModal = ({ booking, isOpen, onClose }) => {
|
||||||
|
const { t } = useTranslation();
|
||||||
if (!isOpen || !booking) return null;
|
if (!isOpen || !booking) return null;
|
||||||
|
|
||||||
const formatCurrency = (amount) => {
|
const formatCurrency = (amount) => {
|
||||||
return amount?.toLocaleString() + ' ل.س';
|
return amount?.toLocaleString() + ' ' + t('syp');
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -321,80 +318,80 @@ const BookingDetailsModal = ({ booking, isOpen, onClose }) => {
|
|||||||
>
|
>
|
||||||
<div className="sticky top-0 bg-gradient-to-r from-amber-500 to-amber-600 p-6 text-white">
|
<div className="sticky top-0 bg-gradient-to-r from-amber-500 to-amber-600 p-6 text-white">
|
||||||
<div className="flex justify-between items-center">
|
<div className="flex justify-between items-center">
|
||||||
<h2 className="text-xl font-bold">تفاصيل الحجز</h2>
|
<h2 className="text-xl font-bold">{t('bookingDetails')}</h2>
|
||||||
<button onClick={onClose} className="p-1 hover:bg-white/20 rounded-full">
|
<button onClick={onClose} className="p-1 hover:bg-white/20 rounded-full">
|
||||||
<XCircle className="w-6 h-6" />
|
<XCircle className="w-6 h-6" />
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<p className="text-amber-100 text-sm mt-1">رقم الحجز: #{booking.id}</p>
|
<p className="text-amber-100 text-sm mt-1">{t('bookingId', { id: booking.id })}</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="p-6 space-y-6">
|
<div className="p-6 space-y-6">
|
||||||
<div className="bg-gray-50 p-4 rounded-xl">
|
<div className="bg-gray-50 p-4 rounded-xl">
|
||||||
<h3 className="font-bold text-gray-900 mb-3">معلومات العقار</h3>
|
<h3 className="font-bold text-gray-900 mb-3">{t('propertyInfo')}</h3>
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<p><span className="text-gray-500">العقار:</span> {booking.propertyTitle}</p>
|
<p><span className="text-gray-500">{t('property')}:</span> {booking.propertyTitle}</p>
|
||||||
<p><span className="text-gray-500">الموقع:</span> {booking.location}</p>
|
<p><span className="text-gray-500">{t('location')}:</span> {booking.location}</p>
|
||||||
{booking.propertyDetails && (
|
{booking.propertyDetails && (
|
||||||
<div className="flex gap-3 mt-2">
|
<div className="flex gap-3 mt-2">
|
||||||
<span className="text-sm bg-white px-2 py-1 rounded-lg">{booking.propertyDetails.bedrooms} غرف</span>
|
<span className="text-sm bg-white px-2 py-1 rounded-lg">{booking.propertyDetails.bedrooms} {t('rooms')}</span>
|
||||||
<span className="text-sm bg-white px-2 py-1 rounded-lg">{booking.propertyDetails.bathrooms} حمامات</span>
|
<span className="text-sm bg-white px-2 py-1 rounded-lg">{booking.propertyDetails.bathrooms} {t('bathrooms')}</span>
|
||||||
<span className="text-sm bg-white px-2 py-1 rounded-lg">{booking.propertyDetails.area} م²</span>
|
<span className="text-sm bg-white px-2 py-1 rounded-lg">{booking.propertyDetails.area} {t('sqm')}</span>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="bg-gray-50 p-4 rounded-xl">
|
<div className="bg-gray-50 p-4 rounded-xl">
|
||||||
<h3 className="font-bold text-gray-900 mb-3">معلومات المستأجر</h3>
|
<h3 className="font-bold text-gray-900 mb-3">{t('tenantInfo')}</h3>
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<p><span className="text-gray-500">الاسم:</span> {booking.tenantName}</p>
|
<p><span className="text-gray-500">{t('name')}</span> {booking.tenantName}</p>
|
||||||
<p><span className="text-gray-500">البريد الإلكتروني:</span> {booking.tenantEmail}</p>
|
<p><span className="text-gray-500">{t('email')}</span> {booking.tenantEmail}</p>
|
||||||
<p><span className="text-gray-500">رقم الهاتف:</span> {booking.tenantPhone}</p>
|
<p><span className="text-gray-500">{t('phone')}</span> {booking.tenantPhone}</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="bg-gray-50 p-4 rounded-xl">
|
<div className="bg-gray-50 p-4 rounded-xl">
|
||||||
<h3 className="font-bold text-gray-900 mb-3">تفاصيل الحجز</h3>
|
<h3 className="font-bold text-gray-900 mb-3">{t('bookingDetails')}</h3>
|
||||||
<div className="grid grid-cols-2 gap-4">
|
<div className="grid grid-cols-2 gap-4">
|
||||||
<div>
|
<div>
|
||||||
<p className="text-gray-500">تاريخ البداية</p>
|
<p className="text-gray-500">{t('startDate')}</p>
|
||||||
<p className="font-medium">{booking.startDate}</p>
|
<p className="font-medium">{booking.startDate}</p>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<p className="text-gray-500">تاريخ النهاية</p>
|
<p className="text-gray-500">{t('endDate')}</p>
|
||||||
<p className="font-medium">{booking.endDate}</p>
|
<p className="font-medium">{booking.endDate}</p>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<p className="text-gray-500">عدد الأيام</p>
|
<p className="text-gray-500">{t('durationDays', { days: booking.days })}</p>
|
||||||
<p className="font-medium">{booking.days} يوم</p>
|
<p className="font-medium">{booking.days} {t('days')}</p>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<p className="text-gray-500">حالة الحجز</p>
|
<p className="text-gray-500">{t('bookingStatus')}</p>
|
||||||
<p className="font-medium">{booking.status === 'pending' ? 'قيد الانتظار' :
|
<p className="font-medium">{booking.status === 'pending' ? t('pending') :
|
||||||
booking.status === 'confirmed' ? 'مؤكد' :
|
booking.status === 'confirmed' ? t('confirmed') :
|
||||||
booking.status === 'cancelled' ? 'ملغي' : 'منتهي'}</p>
|
booking.status === 'cancelled' ? t('cancelled') : t('completed')}</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="bg-amber-50 p-4 rounded-xl">
|
<div className="bg-amber-50 p-4 rounded-xl">
|
||||||
<h3 className="font-bold text-amber-700 mb-3">المعلومات المالية</h3>
|
<h3 className="font-bold text-amber-700 mb-3">{t('financialInfo')}</h3>
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<div className="flex justify-between">
|
<div className="flex justify-between">
|
||||||
<span className="text-gray-600">السعر اليومي</span>
|
<span className="text-gray-600">{t('dailyPrice')}</span>
|
||||||
<span className="font-medium">{formatCurrency(booking.dailyPrice)}</span>
|
<span className="font-medium">{formatCurrency(booking.dailyPrice)}</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex justify-between">
|
<div className="flex justify-between">
|
||||||
<span className="text-gray-600">المدة ({booking.days} أيام)</span>
|
<span className="text-gray-600">{t('durationDays', { days: booking.days })}</span>
|
||||||
<span className="font-medium">{formatCurrency(booking.dailyPrice * booking.days)}</span>
|
<span className="font-medium">{formatCurrency(booking.dailyPrice * booking.days)}</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex justify-between">
|
<div className="flex justify-between">
|
||||||
<span className="text-gray-600">سلفة الضمان</span>
|
<span className="text-gray-600">{t('securityDeposit')}</span>
|
||||||
<span className="font-medium">{formatCurrency(booking.securityDeposit || 0)}</span>
|
<span className="font-medium">{formatCurrency(booking.securityDeposit || 0)}</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex justify-between pt-2 border-t border-amber-200 font-bold">
|
<div className="flex justify-between pt-2 border-t border-amber-200 font-bold">
|
||||||
<span className="text-gray-900">الإجمالي</span>
|
<span className="text-gray-900">{t('total')}</span>
|
||||||
<span className="text-amber-600 text-lg">{formatCurrency(booking.totalAmount)}</span>
|
<span className="text-amber-600 text-lg">{formatCurrency(booking.totalAmount)}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -402,7 +399,7 @@ const BookingDetailsModal = ({ booking, isOpen, onClose }) => {
|
|||||||
|
|
||||||
{booking.notes && (
|
{booking.notes && (
|
||||||
<div className="bg-gray-50 p-4 rounded-xl">
|
<div className="bg-gray-50 p-4 rounded-xl">
|
||||||
<h3 className="font-bold text-gray-900 mb-2">ملاحظات</h3>
|
<h3 className="font-bold text-gray-900 mb-2">{t('notes')}</h3>
|
||||||
<p className="text-gray-600">{booking.notes}</p>
|
<p className="text-gray-600">{booking.notes}</p>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
@ -413,6 +410,7 @@ const BookingDetailsModal = ({ booking, isOpen, onClose }) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export default function OwnerBookingsPage() {
|
export default function OwnerBookingsPage() {
|
||||||
|
const { t, i18n } = useTranslation();
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const [user, setUser] = useState(null);
|
const [user, setUser] = useState(null);
|
||||||
const [bookings, setBookings] = useState([]);
|
const [bookings, setBookings] = useState([]);
|
||||||
@ -449,10 +447,10 @@ export default function OwnerBookingsPage() {
|
|||||||
{
|
{
|
||||||
id: 'BK001',
|
id: 'BK001',
|
||||||
propertyId: 1,
|
propertyId: 1,
|
||||||
propertyTitle: 'فيلا فاخرة في المزة',
|
propertyTitle: t('ownerBookings.mockB1Title'),
|
||||||
location: 'دمشق، المزة',
|
location: t('ownerBookings.mockB1Location'),
|
||||||
propertyDetails: { bedrooms: 5, bathrooms: 4, area: 450 },
|
propertyDetails: { bedrooms: 5, bathrooms: 4, area: 450 },
|
||||||
tenantName: 'أحمد محمد',
|
tenantName: t('ownerBookings.mockB1Tenant'),
|
||||||
tenantEmail: 'ahmed@example.com',
|
tenantEmail: 'ahmed@example.com',
|
||||||
tenantPhone: '0933111222',
|
tenantPhone: '0933111222',
|
||||||
startDate: '2024-03-10',
|
startDate: '2024-03-10',
|
||||||
@ -463,15 +461,15 @@ export default function OwnerBookingsPage() {
|
|||||||
securityDeposit: 500000,
|
securityDeposit: 500000,
|
||||||
status: 'confirmed',
|
status: 'confirmed',
|
||||||
createdAt: '2024-02-25',
|
createdAt: '2024-02-25',
|
||||||
notes: 'طلب الحجز من خلال الموقع'
|
notes: t('ownerBookings.mockB1Notes')
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'BK002',
|
id: 'BK002',
|
||||||
propertyId: 2,
|
propertyId: 2,
|
||||||
propertyTitle: 'شقة حديثة في الشهباء',
|
propertyTitle: t('ownerBookings.mockB2Title'),
|
||||||
location: 'حلب، الشهباء',
|
location: t('ownerBookings.mockB2Location'),
|
||||||
propertyDetails: { bedrooms: 3, bathrooms: 2, area: 180 },
|
propertyDetails: { bedrooms: 3, bathrooms: 2, area: 180 },
|
||||||
tenantName: 'سارة أحمد',
|
tenantName: t('ownerBookings.mockB2Tenant'),
|
||||||
tenantEmail: 'sara@example.com',
|
tenantEmail: 'sara@example.com',
|
||||||
tenantPhone: '0945123789',
|
tenantPhone: '0945123789',
|
||||||
startDate: '2024-03-05',
|
startDate: '2024-03-05',
|
||||||
@ -482,15 +480,15 @@ export default function OwnerBookingsPage() {
|
|||||||
securityDeposit: 250000,
|
securityDeposit: 250000,
|
||||||
status: 'pending',
|
status: 'pending',
|
||||||
createdAt: '2024-02-24',
|
createdAt: '2024-02-24',
|
||||||
notes: 'تحتاج إلى تأكيد'
|
notes: t('ownerBookings.mockB2Notes')
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'BK003',
|
id: 'BK003',
|
||||||
propertyId: 3,
|
propertyId: 3,
|
||||||
propertyTitle: 'بيت عائلي في بابا عمرو',
|
propertyTitle: t('ownerBookings.mockB3Title'),
|
||||||
location: 'حمص، بابا عمرو',
|
location: t('ownerBookings.mockB3Location'),
|
||||||
propertyDetails: { bedrooms: 4, bathrooms: 3, area: 300 },
|
propertyDetails: { bedrooms: 4, bathrooms: 3, area: 300 },
|
||||||
tenantName: 'محمد الحلبي',
|
tenantName: t('ownerBookings.mockB3Tenant'),
|
||||||
tenantEmail: 'mohammed@example.com',
|
tenantEmail: 'mohammed@example.com',
|
||||||
tenantPhone: '0956123456',
|
tenantPhone: '0956123456',
|
||||||
startDate: '2024-02-20',
|
startDate: '2024-02-20',
|
||||||
@ -501,7 +499,7 @@ export default function OwnerBookingsPage() {
|
|||||||
securityDeposit: 500000,
|
securityDeposit: 500000,
|
||||||
status: 'completed',
|
status: 'completed',
|
||||||
createdAt: '2024-02-15',
|
createdAt: '2024-02-15',
|
||||||
notes: 'تم إنهاء الإيجار بنجاح'
|
notes: t('ownerBookings.mockB3Notes')
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
setBookings(mockBookings);
|
setBookings(mockBookings);
|
||||||
@ -518,7 +516,7 @@ export default function OwnerBookingsPage() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const handleContact = (booking) => {
|
const handleContact = (booking) => {
|
||||||
toast.success(`جاري فتح محادثة مع ${booking.tenantName}`, {
|
toast.success(t('openingChat', { name: booking.tenantName }), {
|
||||||
icon: '💬',
|
icon: '💬',
|
||||||
style: { background: '#dcfce7', color: '#166534' }
|
style: { background: '#dcfce7', color: '#166534' }
|
||||||
});
|
});
|
||||||
@ -531,7 +529,7 @@ export default function OwnerBookingsPage() {
|
|||||||
setBookings(updatedBookings);
|
setBookings(updatedBookings);
|
||||||
setFilteredBookings(updatedBookings);
|
setFilteredBookings(updatedBookings);
|
||||||
localStorage.setItem('ownerBookings', JSON.stringify(updatedBookings));
|
localStorage.setItem('ownerBookings', JSON.stringify(updatedBookings));
|
||||||
toast.success(`تم تحديث حالة الحجز بنجاح`);
|
toast.success(t('statusUpdated'));
|
||||||
};
|
};
|
||||||
|
|
||||||
const statusCounts = {
|
const statusCounts = {
|
||||||
@ -547,14 +545,14 @@ export default function OwnerBookingsPage() {
|
|||||||
<div className="min-h-screen bg-gray-50 flex items-center justify-center">
|
<div className="min-h-screen bg-gray-50 flex items-center justify-center">
|
||||||
<div className="text-center">
|
<div className="text-center">
|
||||||
<Loader2 className="w-12 h-12 text-amber-500 animate-spin mx-auto mb-4" />
|
<Loader2 className="w-12 h-12 text-amber-500 animate-spin mx-auto mb-4" />
|
||||||
<p className="text-gray-600">جاري تحميل الحجوزات...</p>
|
<p className="text-gray-600">{t('loading')}</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="min-h-screen bg-gray-50 py-8">
|
<div className="min-h-screen bg-gray-50 py-8" dir={i18n.language === 'ar' ? 'rtl' : 'ltr'}>
|
||||||
<Toaster position="top-center" reverseOrder={false} />
|
<Toaster position="top-center" reverseOrder={false} />
|
||||||
|
|
||||||
<BookingDetailsModal
|
<BookingDetailsModal
|
||||||
@ -570,8 +568,8 @@ export default function OwnerBookingsPage() {
|
|||||||
className="flex flex-col md:flex-row justify-between items-start md:items-center mb-8 gap-4"
|
className="flex flex-col md:flex-row justify-between items-start md:items-center mb-8 gap-4"
|
||||||
>
|
>
|
||||||
<div>
|
<div>
|
||||||
<h1 className="text-3xl font-bold text-gray-900 mb-2">حجوزاتي</h1>
|
<h1 className="text-3xl font-bold text-gray-900 mb-2">{t('myBookings')}</h1>
|
||||||
<p className="text-gray-600">مرحباً {user?.name}، لديك {bookings.length} حجز</p>
|
<p className="text-gray-600">{t('welcomeBookings', { name: user?.name, count: bookings.length })}</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex gap-3">
|
<div className="flex gap-3">
|
||||||
@ -580,12 +578,8 @@ export default function OwnerBookingsPage() {
|
|||||||
className="px-4 py-2 bg-white border border-gray-300 rounded-xl text-gray-700 hover:bg-gray-50 transition-colors flex items-center gap-2"
|
className="px-4 py-2 bg-white border border-gray-300 rounded-xl text-gray-700 hover:bg-gray-50 transition-colors flex items-center gap-2"
|
||||||
>
|
>
|
||||||
<Calendar className="w-5 h-5" />
|
<Calendar className="w-5 h-5" />
|
||||||
{showCalendar ? 'إخفاء التقويم' : 'عرض التقويم'}
|
{showCalendar ? t('hideCalendar') : t('showCalendar')}
|
||||||
</button>
|
</button>
|
||||||
{/* <button className="px-4 py-2 bg-green-600 text-white rounded-xl hover:bg-green-700 transition-colors flex items-center gap-2">
|
|
||||||
<Download className="w-5 h-5" />
|
|
||||||
تصدير التقرير
|
|
||||||
</button> */}
|
|
||||||
</div>
|
</div>
|
||||||
</motion.div>
|
</motion.div>
|
||||||
|
|
||||||
@ -598,7 +592,7 @@ export default function OwnerBookingsPage() {
|
|||||||
onClick={() => setFilterStatus('all')}
|
onClick={() => setFilterStatus('all')}
|
||||||
>
|
>
|
||||||
<div className="text-2xl font-bold text-gray-900">{statusCounts.all}</div>
|
<div className="text-2xl font-bold text-gray-900">{statusCounts.all}</div>
|
||||||
<div className="text-sm text-gray-600">جميع الحجوزات</div>
|
<div className="text-sm text-gray-600">{t('allBookings')}</div>
|
||||||
</motion.div>
|
</motion.div>
|
||||||
<motion.div
|
<motion.div
|
||||||
initial={{ opacity: 0, y: 20 }}
|
initial={{ opacity: 0, y: 20 }}
|
||||||
@ -610,7 +604,7 @@ export default function OwnerBookingsPage() {
|
|||||||
onClick={() => setFilterStatus('pending')}
|
onClick={() => setFilterStatus('pending')}
|
||||||
>
|
>
|
||||||
<div className="text-2xl font-bold text-yellow-600">{statusCounts.pending}</div>
|
<div className="text-2xl font-bold text-yellow-600">{statusCounts.pending}</div>
|
||||||
<div className="text-sm text-gray-600">قيد الانتظار</div>
|
<div className="text-sm text-gray-600">{t('pending')}</div>
|
||||||
</motion.div>
|
</motion.div>
|
||||||
<motion.div
|
<motion.div
|
||||||
initial={{ opacity: 0, y: 20 }}
|
initial={{ opacity: 0, y: 20 }}
|
||||||
@ -622,7 +616,7 @@ export default function OwnerBookingsPage() {
|
|||||||
onClick={() => setFilterStatus('confirmed')}
|
onClick={() => setFilterStatus('confirmed')}
|
||||||
>
|
>
|
||||||
<div className="text-2xl font-bold text-green-600">{statusCounts.confirmed}</div>
|
<div className="text-2xl font-bold text-green-600">{statusCounts.confirmed}</div>
|
||||||
<div className="text-sm text-gray-600">مؤكدة</div>
|
<div className="text-sm text-gray-600">{t('confirmed')}</div>
|
||||||
</motion.div>
|
</motion.div>
|
||||||
<motion.div
|
<motion.div
|
||||||
initial={{ opacity: 0, y: 20 }}
|
initial={{ opacity: 0, y: 20 }}
|
||||||
@ -634,7 +628,7 @@ export default function OwnerBookingsPage() {
|
|||||||
onClick={() => setFilterStatus('completed')}
|
onClick={() => setFilterStatus('completed')}
|
||||||
>
|
>
|
||||||
<div className="text-2xl font-bold text-gray-600">{statusCounts.completed}</div>
|
<div className="text-2xl font-bold text-gray-600">{statusCounts.completed}</div>
|
||||||
<div className="text-sm text-gray-600">منتهية</div>
|
<div className="text-sm text-gray-600">{t('completed')}</div>
|
||||||
</motion.div>
|
</motion.div>
|
||||||
<motion.div
|
<motion.div
|
||||||
initial={{ opacity: 0, y: 20 }}
|
initial={{ opacity: 0, y: 20 }}
|
||||||
@ -646,7 +640,7 @@ export default function OwnerBookingsPage() {
|
|||||||
onClick={() => setFilterStatus('cancelled')}
|
onClick={() => setFilterStatus('cancelled')}
|
||||||
>
|
>
|
||||||
<div className="text-2xl font-bold text-red-600">{statusCounts.cancelled}</div>
|
<div className="text-2xl font-bold text-red-600">{statusCounts.cancelled}</div>
|
||||||
<div className="text-sm text-gray-600">ملغية</div>
|
<div className="text-sm text-gray-600">{t('cancelled')}</div>
|
||||||
</motion.div>
|
</motion.div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -655,7 +649,7 @@ export default function OwnerBookingsPage() {
|
|||||||
<Search className="absolute left-3 top-1/2 transform -translate-y-1/2 w-5 h-5 text-gray-400" />
|
<Search className="absolute left-3 top-1/2 transform -translate-y-1/2 w-5 h-5 text-gray-400" />
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
placeholder="ابحث باسم العقار أو المستأجر.."
|
placeholder={t('searchBookings')}
|
||||||
value={searchTerm}
|
value={searchTerm}
|
||||||
onChange={(e) => setSearchTerm(e.target.value)}
|
onChange={(e) => setSearchTerm(e.target.value)}
|
||||||
className="w-full pl-10 pr-4 py-3 border border-gray-300 rounded-xl focus:outline-none focus:ring-2 focus:ring-amber-500"
|
className="w-full pl-10 pr-4 py-3 border border-gray-300 rounded-xl focus:outline-none focus:ring-2 focus:ring-amber-500"
|
||||||
@ -667,21 +661,21 @@ export default function OwnerBookingsPage() {
|
|||||||
value={dateRange.start}
|
value={dateRange.start}
|
||||||
onChange={(e) => setDateRange({...dateRange, start: e.target.value})}
|
onChange={(e) => setDateRange({...dateRange, start: e.target.value})}
|
||||||
className="px-4 py-3 border border-gray-300 rounded-xl focus:outline-none focus:ring-2 focus:ring-amber-500"
|
className="px-4 py-3 border border-gray-300 rounded-xl focus:outline-none focus:ring-2 focus:ring-amber-500"
|
||||||
placeholder="من تاريخ"
|
placeholder={t('filterDateFrom')}
|
||||||
/>
|
/>
|
||||||
<input
|
<input
|
||||||
type="date"
|
type="date"
|
||||||
value={dateRange.end}
|
value={dateRange.end}
|
||||||
onChange={(e) => setDateRange({...dateRange, end: e.target.value})}
|
onChange={(e) => setDateRange({...dateRange, end: e.target.value})}
|
||||||
className="px-4 py-3 border border-gray-300 rounded-xl focus:outline-none focus:ring-2 focus:ring-amber-500"
|
className="px-4 py-3 border border-gray-300 rounded-xl focus:outline-none focus:ring-2 focus:ring-amber-500"
|
||||||
placeholder="إلى تاريخ"
|
placeholder={t('filterDateTo')}
|
||||||
/>
|
/>
|
||||||
{(dateRange.start || dateRange.end) && (
|
{(dateRange.start || dateRange.end) && (
|
||||||
<button
|
<button
|
||||||
onClick={() => setDateRange({ start: '', end: '' })}
|
onClick={() => setDateRange({ start: '', end: '' })}
|
||||||
className="px-4 py-3 bg-gray-100 text-gray-700 rounded-xl hover:bg-gray-200 transition-colors"
|
className="px-4 py-3 bg-gray-100 text-gray-700 rounded-xl hover:bg-gray-200 transition-colors"
|
||||||
>
|
>
|
||||||
مسح
|
{t('clear')}
|
||||||
</button>
|
</button>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
@ -708,16 +702,16 @@ export default function OwnerBookingsPage() {
|
|||||||
<div className="w-24 h-24 bg-amber-100 rounded-full flex items-center justify-center mx-auto mb-4">
|
<div className="w-24 h-24 bg-amber-100 rounded-full flex items-center justify-center mx-auto mb-4">
|
||||||
<Calendar className="w-12 h-12 text-amber-600" />
|
<Calendar className="w-12 h-12 text-amber-600" />
|
||||||
</div>
|
</div>
|
||||||
<h3 className="text-xl font-bold text-gray-900 mb-2">لا توجد حجوزات</h3>
|
<h3 className="text-xl font-bold text-gray-900 mb-2">{t('noBookings')}</h3>
|
||||||
<p className="text-gray-600 mb-4">
|
<p className="text-gray-600 mb-4">
|
||||||
{filterStatus !== 'all' ? 'لا توجد حجوزات في هذه الفئة' : 'لم يتم استلام أي حجوزات بعد'}
|
{filterStatus !== 'all' ? t('noBookingsInCategory') : t('noBookingsReceived')}
|
||||||
</p>
|
</p>
|
||||||
{filterStatus !== 'all' && (
|
{filterStatus !== 'all' && (
|
||||||
<button
|
<button
|
||||||
onClick={() => setFilterStatus('all')}
|
onClick={() => setFilterStatus('all')}
|
||||||
className="inline-flex items-center gap-2 bg-amber-500 text-white px-6 py-3 rounded-xl font-medium hover:bg-amber-600"
|
className="inline-flex items-center gap-2 bg-amber-500 text-white px-6 py-3 rounded-xl font-medium hover:bg-amber-600"
|
||||||
>
|
>
|
||||||
عرض جميع الحجوزات
|
{t('viewAllBookings')}
|
||||||
</button>
|
</button>
|
||||||
)}
|
)}
|
||||||
</motion.div>
|
</motion.div>
|
||||||
|
|||||||
@ -4,6 +4,7 @@ import { useState, useEffect } from 'react';
|
|||||||
import { motion, AnimatePresence } from 'framer-motion';
|
import { motion, AnimatePresence } from 'framer-motion';
|
||||||
import { useRouter } from 'next/navigation';
|
import { useRouter } from 'next/navigation';
|
||||||
import Link from 'next/link';
|
import Link from 'next/link';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
import {
|
import {
|
||||||
Calendar,
|
Calendar,
|
||||||
ChevronLeft,
|
ChevronLeft,
|
||||||
@ -39,13 +40,14 @@ import toast, { Toaster } from 'react-hot-toast';
|
|||||||
import AuthService from '../../services/AuthService';
|
import AuthService from '../../services/AuthService';
|
||||||
|
|
||||||
const MonthlyCalendar = ({ properties, selectedPropertyId, onDateClick, onPropertySelect }) => {
|
const MonthlyCalendar = ({ properties, selectedPropertyId, onDateClick, onPropertySelect }) => {
|
||||||
|
const { t } = useTranslation();
|
||||||
const [currentMonth, setCurrentMonth] = useState(new Date());
|
const [currentMonth, setCurrentMonth] = useState(new Date());
|
||||||
const [selectedDate, setSelectedDate] = useState(null);
|
const [selectedDate, setSelectedDate] = useState(null);
|
||||||
const [viewType, setViewType] = useState('grid');
|
const [viewType, setViewType] = useState('grid');
|
||||||
|
|
||||||
const monthNames = [
|
const monthNames = [
|
||||||
'يناير', 'فبراير', 'مارس', 'إبريل', 'مايو', 'يونيو',
|
t('calendar.month1'), t('calendar.month2'), t('calendar.month3'), t('calendar.month4'), t('calendar.month5'), t('calendar.month6'),
|
||||||
'يوليو', 'أغسطس', 'سبتمبر', 'أكتوبر', 'نوفمبر', 'ديسمبر'
|
t('calendar.month7'), t('calendar.month8'), t('calendar.month9'), t('calendar.month10'), t('calendar.month11'), t('calendar.month12')
|
||||||
];
|
];
|
||||||
|
|
||||||
const daysInMonth = new Date(
|
const daysInMonth = new Date(
|
||||||
@ -76,17 +78,17 @@ const MonthlyCalendar = ({ properties, selectedPropertyId, onDateClick, onProper
|
|||||||
const totalProperties = properties.length;
|
const totalProperties = properties.length;
|
||||||
const bookedCount = properties.filter(p => isDateBookedForProperty(date, p)).length;
|
const bookedCount = properties.filter(p => isDateBookedForProperty(date, p)).length;
|
||||||
|
|
||||||
if (bookedCount === 0) return { status: 'all_available', label: 'جميع العقارات متاحة', color: 'bg-green-100 text-green-800' };
|
if (bookedCount === 0) return { status: 'all_available', label: t('calendar.allAvailable'), color: 'bg-green-100 text-green-800' };
|
||||||
if (bookedCount === totalProperties) return { status: 'all_booked', label: 'جميع العقارات محجوزة', color: 'bg-red-100 text-red-800' };
|
if (bookedCount === totalProperties) return { status: 'all_booked', label: t('calendar.allBooked'), color: 'bg-red-100 text-red-800' };
|
||||||
return { status: 'partial', label: `${bookedCount}/${totalProperties} محجوز`, color: 'bg-yellow-100 text-yellow-800' };
|
return { status: 'partial', label: t('calendar.bookedCount', { count: bookedCount, total: totalProperties }), color: 'bg-yellow-100 text-yellow-800' };
|
||||||
} else {
|
} else {
|
||||||
const property = properties.find(p => p.id === selectedPropertyId);
|
const property = properties.find(p => p.id === selectedPropertyId);
|
||||||
if (!property) return { status: 'no_property', label: 'غير متاح', color: 'bg-gray-100 text-gray-500' };
|
if (!property) return { status: 'no_property', label: t('calendar.notAvailable'), color: 'bg-gray-100 text-gray-500' };
|
||||||
|
|
||||||
const isBooked = isDateBookedForProperty(date, property);
|
const isBooked = isDateBookedForProperty(date, property);
|
||||||
return {
|
return {
|
||||||
status: isBooked ? 'booked' : 'available',
|
status: isBooked ? 'booked' : 'available',
|
||||||
label: isBooked ? 'محجوز' : 'متاح',
|
label: isBooked ? t('calendar.booked') : t('calendar.available'),
|
||||||
color: isBooked ? 'bg-red-100 text-red-800' : 'bg-green-100 text-green-800'
|
color: isBooked ? 'bg-red-100 text-red-800' : 'bg-green-100 text-green-800'
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@ -171,7 +173,7 @@ const MonthlyCalendar = ({ properties, selectedPropertyId, onDateClick, onProper
|
|||||||
onClick={() => setCurrentMonth(new Date())}
|
onClick={() => setCurrentMonth(new Date())}
|
||||||
className="px-4 py-2 bg-amber-500 text-white rounded-xl text-sm hover:bg-amber-600 transition-colors"
|
className="px-4 py-2 bg-amber-500 text-white rounded-xl text-sm hover:bg-amber-600 transition-colors"
|
||||||
>
|
>
|
||||||
اليوم
|
{t('calendar.today')}
|
||||||
</button>
|
</button>
|
||||||
<div className="flex border border-gray-200 rounded-xl overflow-hidden">
|
<div className="flex border border-gray-200 rounded-xl overflow-hidden">
|
||||||
<button
|
<button
|
||||||
@ -192,7 +194,7 @@ const MonthlyCalendar = ({ properties, selectedPropertyId, onDateClick, onProper
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="grid grid-cols-7 gap-1 p-4 bg-gray-50 border-b border-gray-200">
|
<div className="grid grid-cols-7 gap-1 p-4 bg-gray-50 border-b border-gray-200">
|
||||||
{['أحد', 'إثنين', 'ثلاثاء', 'أربعاء', 'خميس', 'جمعة', 'سبت'].map((day, index) => (
|
{[t('calendar.day1'), t('calendar.day2'), t('calendar.day3'), t('calendar.day4'), t('calendar.day5'), t('calendar.day6'), t('calendar.day7')].map((day, index) => (
|
||||||
<div key={index} className="text-center text-sm font-medium text-gray-600 py-2">
|
<div key={index} className="text-center text-sm font-medium text-gray-600 py-2">
|
||||||
{day}
|
{day}
|
||||||
</div>
|
</div>
|
||||||
@ -209,23 +211,23 @@ const MonthlyCalendar = ({ properties, selectedPropertyId, onDateClick, onProper
|
|||||||
<div className="flex flex-wrap gap-4 justify-center text-xs">
|
<div className="flex flex-wrap gap-4 justify-center text-xs">
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<div className="w-3 h-3 bg-green-100 rounded" />
|
<div className="w-3 h-3 bg-green-100 rounded" />
|
||||||
<span className="text-gray-600">متاح</span>
|
<span className="text-gray-600">{t('calendar.available')}</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<div className="w-3 h-3 bg-red-100 rounded" />
|
<div className="w-3 h-3 bg-red-100 rounded" />
|
||||||
<span className="text-gray-600">محجوز</span>
|
<span className="text-gray-600">{t('calendar.booked')}</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<div className="w-3 h-3 bg-yellow-100 rounded" />
|
<div className="w-3 h-3 bg-yellow-100 rounded" />
|
||||||
<span className="text-gray-600">محجوز جزئياً</span>
|
<span className="text-gray-600">{t('calendar.partiallyBooked')}</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<div className="w-3 h-3 ring-2 ring-amber-500 rounded" />
|
<div className="w-3 h-3 ring-2 ring-amber-500 rounded" />
|
||||||
<span className="text-gray-600">اليوم</span>
|
<span className="text-gray-600">{t('calendar.today')}</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<div className="w-3 h-3 ring-2 ring-blue-500 rounded" />
|
<div className="w-3 h-3 ring-2 ring-blue-500 rounded" />
|
||||||
<span className="text-gray-600">محدد</span>
|
<span className="text-gray-600">{t('calendar.selected')}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -234,8 +236,9 @@ const MonthlyCalendar = ({ properties, selectedPropertyId, onDateClick, onProper
|
|||||||
};
|
};
|
||||||
|
|
||||||
const PropertyCalendarList = ({ properties, selectedDate, onPropertyClick }) => {
|
const PropertyCalendarList = ({ properties, selectedDate, onPropertyClick }) => {
|
||||||
|
const { t } = useTranslation();
|
||||||
const formatCurrency = (amount) => {
|
const formatCurrency = (amount) => {
|
||||||
return amount?.toLocaleString() + ' ل.س';
|
return amount?.toLocaleString() + ' ' + t('calendar.syp');
|
||||||
};
|
};
|
||||||
|
|
||||||
const isDateBooked = (property, date) => {
|
const isDateBooked = (property, date) => {
|
||||||
@ -264,8 +267,8 @@ const PropertyCalendarList = ({ properties, selectedDate, onPropertyClick }) =>
|
|||||||
return (
|
return (
|
||||||
<div className="bg-white rounded-2xl shadow-sm border border-gray-200 p-12 text-center">
|
<div className="bg-white rounded-2xl shadow-sm border border-gray-200 p-12 text-center">
|
||||||
<CalendarDays className="w-16 h-16 text-gray-300 mx-auto mb-4" />
|
<CalendarDays className="w-16 h-16 text-gray-300 mx-auto mb-4" />
|
||||||
<h3 className="text-lg font-bold text-gray-700 mb-2">اختر تاريخاً</h3>
|
<h3 className="text-lg font-bold text-gray-700 mb-2">{t('calendar.selectDate')}</h3>
|
||||||
<p className="text-gray-500">اضغط على أي يوم في التقويم لعرض حالة العقارات في ذلك التاريخ</p>
|
<p className="text-gray-500">{t('calendar.clickDayHint')}</p>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -282,7 +285,7 @@ const PropertyCalendarList = ({ properties, selectedDate, onPropertyClick }) =>
|
|||||||
<div className="p-4 md:p-6 border-b border-gray-200 bg-gradient-to-r from-amber-50 to-amber-100">
|
<div className="p-4 md:p-6 border-b border-gray-200 bg-gradient-to-r from-amber-50 to-amber-100">
|
||||||
<h3 className="text-lg font-bold text-gray-900 flex items-center gap-2">
|
<h3 className="text-lg font-bold text-gray-900 flex items-center gap-2">
|
||||||
<CalendarDays className="w-5 h-5 text-amber-500" />
|
<CalendarDays className="w-5 h-5 text-amber-500" />
|
||||||
حالة العقارات في تاريخ: {formattedDate}
|
{t('calendar.statusForDate')} {formattedDate}
|
||||||
</h3>
|
</h3>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -306,7 +309,7 @@ const PropertyCalendarList = ({ properties, selectedDate, onPropertyClick }) =>
|
|||||||
<span className={`px-2 py-1 rounded-lg text-xs font-medium ${
|
<span className={`px-2 py-1 rounded-lg text-xs font-medium ${
|
||||||
isBooked ? 'bg-red-100 text-red-800' : 'bg-green-100 text-green-800'
|
isBooked ? 'bg-red-100 text-red-800' : 'bg-green-100 text-green-800'
|
||||||
}`}>
|
}`}>
|
||||||
{isBooked ? 'محجوز' : 'متاح'}
|
{isBooked ? t('calendar.booked') : t('calendar.available')}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center gap-1 text-gray-500 text-sm mb-2">
|
<div className="flex items-center gap-1 text-gray-500 text-sm mb-2">
|
||||||
@ -316,26 +319,26 @@ const PropertyCalendarList = ({ properties, selectedDate, onPropertyClick }) =>
|
|||||||
<div className="flex flex-wrap gap-3 text-sm text-gray-600">
|
<div className="flex flex-wrap gap-3 text-sm text-gray-600">
|
||||||
<div className="flex items-center gap-1">
|
<div className="flex items-center gap-1">
|
||||||
<Bed className="w-4 h-4" />
|
<Bed className="w-4 h-4" />
|
||||||
<span>{property.bedrooms} غرف</span>
|
<span>{property.bedrooms} {t('calendar.rooms')}</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center gap-1">
|
<div className="flex items-center gap-1">
|
||||||
<Bath className="w-4 h-4" />
|
<Bath className="w-4 h-4" />
|
||||||
<span>{property.bathrooms} حمامات</span>
|
<span>{property.bathrooms} {t('calendar.bathrooms')}</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center gap-1">
|
<div className="flex items-center gap-1">
|
||||||
<Square className="w-4 h-4" />
|
<Square className="w-4 h-4" />
|
||||||
<span>{property.area} م²</span>
|
<span>{property.area} {t('calendar.sqm')}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="text-right">
|
<div className="text-right">
|
||||||
<div className="text-lg font-bold text-amber-600">{formatCurrency(property.price)}</div>
|
<div className="text-lg font-bold text-amber-600">{formatCurrency(property.price)}</div>
|
||||||
<div className="text-xs text-gray-500">/يوم</div>
|
<div className="text-xs text-gray-500">{t('calendar.perDay')}</div>
|
||||||
{isBooked && booking && (
|
{isBooked && booking && (
|
||||||
<div className="mt-2 text-xs text-gray-500">
|
<div className="mt-2 text-xs text-gray-500">
|
||||||
<div>مستأجر: {booking.tenantName || 'غير معروف'}</div>
|
<div>{t('calendar.tenant')}: {booking.tenantName || t('calendar.unknown')}</div>
|
||||||
<div>من: {booking.startDate} إلى {booking.endDate}</div>
|
<div>{t('calendar.from')} {booking.startDate} {t('calendar.to')} {booking.endDate}</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
@ -349,10 +352,11 @@ const PropertyCalendarList = ({ properties, selectedDate, onPropertyClick }) =>
|
|||||||
};
|
};
|
||||||
|
|
||||||
const PropertyDetailsModal = ({ property, isOpen, onClose }) => {
|
const PropertyDetailsModal = ({ property, isOpen, onClose }) => {
|
||||||
|
const { t } = useTranslation();
|
||||||
if (!isOpen || !property) return null;
|
if (!isOpen || !property) return null;
|
||||||
|
|
||||||
const formatCurrency = (amount) => {
|
const formatCurrency = (amount) => {
|
||||||
return amount?.toLocaleString() + ' ل.س';
|
return amount?.toLocaleString() + ' ' + t('calendar.syp');
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -385,7 +389,7 @@ const PropertyDetailsModal = ({ property, isOpen, onClose }) => {
|
|||||||
<div className="p-6 space-y-6">
|
<div className="p-6 space-y-6">
|
||||||
{property.images && property.images.length > 0 && (
|
{property.images && property.images.length > 0 && (
|
||||||
<div>
|
<div>
|
||||||
<h3 className="font-bold text-gray-900 mb-3">صور العقار</h3>
|
<h3 className="font-bold text-gray-900 mb-3">{t('calendar.propertyImages')}</h3>
|
||||||
<div className="grid grid-cols-2 gap-3">
|
<div className="grid grid-cols-2 gap-3">
|
||||||
{property.images.slice(0, 4).map((image, index) => (
|
{property.images.slice(0, 4).map((image, index) => (
|
||||||
<div key={index} className="relative h-32 rounded-lg overflow-hidden bg-gray-100">
|
<div key={index} className="relative h-32 rounded-lg overflow-hidden bg-gray-100">
|
||||||
@ -400,28 +404,28 @@ const PropertyDetailsModal = ({ property, isOpen, onClose }) => {
|
|||||||
<div className="bg-gray-50 p-3 rounded-xl text-center">
|
<div className="bg-gray-50 p-3 rounded-xl text-center">
|
||||||
<Bed className="w-5 h-5 text-amber-500 mx-auto mb-1" />
|
<Bed className="w-5 h-5 text-amber-500 mx-auto mb-1" />
|
||||||
<div className="text-sm font-bold">{property.bedrooms}</div>
|
<div className="text-sm font-bold">{property.bedrooms}</div>
|
||||||
<div className="text-xs text-gray-500">غرف نوم</div>
|
<div className="text-xs text-gray-500">{t('calendar.bedrooms')}</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="bg-gray-50 p-3 rounded-xl text-center">
|
<div className="bg-gray-50 p-3 rounded-xl text-center">
|
||||||
<Bath className="w-5 h-5 text-amber-500 mx-auto mb-1" />
|
<Bath className="w-5 h-5 text-amber-500 mx-auto mb-1" />
|
||||||
<div className="text-sm font-bold">{property.bathrooms}</div>
|
<div className="text-sm font-bold">{property.bathrooms}</div>
|
||||||
<div className="text-xs text-gray-500">حمامات</div>
|
<div className="text-xs text-gray-500">{t('calendar.bathrooms')}</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="bg-gray-50 p-3 rounded-xl text-center">
|
<div className="bg-gray-50 p-3 rounded-xl text-center">
|
||||||
<Square className="w-5 h-5 text-amber-500 mx-auto mb-1" />
|
<Square className="w-5 h-5 text-amber-500 mx-auto mb-1" />
|
||||||
<div className="text-sm font-bold">{property.area}</div>
|
<div className="text-sm font-bold">{property.area}</div>
|
||||||
<div className="text-xs text-gray-500">م²</div>
|
<div className="text-xs text-gray-500">{t('calendar.sqm')}</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="bg-gray-50 p-3 rounded-xl text-center">
|
<div className="bg-gray-50 p-3 rounded-xl text-center">
|
||||||
<DollarSign className="w-5 h-5 text-amber-500 mx-auto mb-1" />
|
<DollarSign className="w-5 h-5 text-amber-500 mx-auto mb-1" />
|
||||||
<div className="text-sm font-bold">{formatCurrency(property.price)}</div>
|
<div className="text-sm font-bold">{formatCurrency(property.price)}</div>
|
||||||
<div className="text-xs text-gray-500">/يوم</div>
|
<div className="text-xs text-gray-500">{t('calendar.perDay')}</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{property.features && property.features.length > 0 && (
|
{property.features && property.features.length > 0 && (
|
||||||
<div>
|
<div>
|
||||||
<h3 className="font-bold text-gray-900 mb-3">المميزات</h3>
|
<h3 className="font-bold text-gray-900 mb-3">{t('calendar.features')}</h3>
|
||||||
<div className="flex flex-wrap gap-2">
|
<div className="flex flex-wrap gap-2">
|
||||||
{property.features.map((feature, index) => (
|
{property.features.map((feature, index) => (
|
||||||
<span key={index} className="px-2 py-1 bg-gray-100 text-gray-700 rounded-lg text-xs">
|
<span key={index} className="px-2 py-1 bg-gray-100 text-gray-700 rounded-lg text-xs">
|
||||||
@ -436,14 +440,14 @@ const PropertyDetailsModal = ({ property, isOpen, onClose }) => {
|
|||||||
<div>
|
<div>
|
||||||
<h3 className="font-bold text-gray-900 mb-3 flex items-center gap-2">
|
<h3 className="font-bold text-gray-900 mb-3 flex items-center gap-2">
|
||||||
<Clock className="w-4 h-4 text-amber-500" />
|
<Clock className="w-4 h-4 text-amber-500" />
|
||||||
الحجوزات القادمة
|
{t('calendar.upcomingBookings')}
|
||||||
</h3>
|
</h3>
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
{property.bookings.slice(0, 3).map((booking, index) => (
|
{property.bookings.slice(0, 3).map((booking, index) => (
|
||||||
<div key={index} className="bg-gray-50 p-3 rounded-lg flex justify-between items-center">
|
<div key={index} className="bg-gray-50 p-3 rounded-lg flex justify-between items-center">
|
||||||
<div>
|
<div>
|
||||||
<p className="font-medium text-gray-900">{booking.startDate} - {booking.endDate}</p>
|
<p className="font-medium text-gray-900">{booking.startDate} - {booking.endDate}</p>
|
||||||
<p className="text-xs text-gray-500">مستأجر: {booking.tenantName || 'غير معروف'}</p>
|
<p className="text-xs text-gray-500">{t('calendar.tenant')}: {booking.tenantName || t('calendar.unknown')}</p>
|
||||||
</div>
|
</div>
|
||||||
<span className="text-sm font-bold text-amber-600">{formatCurrency(booking.totalAmount)}</span>
|
<span className="text-sm font-bold text-amber-600">{formatCurrency(booking.totalAmount)}</span>
|
||||||
</div>
|
</div>
|
||||||
@ -458,13 +462,13 @@ const PropertyDetailsModal = ({ property, isOpen, onClose }) => {
|
|||||||
href={`/owner/properties/edit?id=${property.id}`}
|
href={`/owner/properties/edit?id=${property.id}`}
|
||||||
className="flex-1 bg-amber-500 text-white py-3 rounded-xl text-center font-medium hover:bg-amber-600 transition-colors"
|
className="flex-1 bg-amber-500 text-white py-3 rounded-xl text-center font-medium hover:bg-amber-600 transition-colors"
|
||||||
>
|
>
|
||||||
تعديل العقار
|
{t('calendar.editProperty')}
|
||||||
</Link>
|
</Link>
|
||||||
<button
|
<button
|
||||||
onClick={() => window.location.href = `/owner/bookings?property=${property.id}`}
|
onClick={() => window.location.href = `/owner/bookings?property=${property.id}`}
|
||||||
className="flex-1 bg-gray-100 text-gray-700 py-3 rounded-xl text-center font-medium hover:bg-gray-200 transition-colors"
|
className="flex-1 bg-gray-100 text-gray-700 py-3 rounded-xl text-center font-medium hover:bg-gray-200 transition-colors"
|
||||||
>
|
>
|
||||||
عرض الحجوزات
|
{t('calendar.viewBookings')}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</motion.div>
|
</motion.div>
|
||||||
@ -473,6 +477,7 @@ const PropertyDetailsModal = ({ property, isOpen, onClose }) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export default function OwnerCalendarPage() {
|
export default function OwnerCalendarPage() {
|
||||||
|
const { t, i18n } = useTranslation();
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const [user, setUser] = useState(null);
|
const [user, setUser] = useState(null);
|
||||||
const [properties, setProperties] = useState([]);
|
const [properties, setProperties] = useState([]);
|
||||||
@ -509,44 +514,44 @@ export default function OwnerCalendarPage() {
|
|||||||
const mockProperties = [
|
const mockProperties = [
|
||||||
{
|
{
|
||||||
id: 1,
|
id: 1,
|
||||||
title: 'فيلا فاخرة في المزة',
|
title: t('calendar.mockVillaTitle'),
|
||||||
location: 'دمشق، المزة',
|
location: t('calendar.mockVillaLocation'),
|
||||||
bedrooms: 5,
|
bedrooms: 5,
|
||||||
bathrooms: 4,
|
bathrooms: 4,
|
||||||
area: 450,
|
area: 450,
|
||||||
price: 500000,
|
price: 500000,
|
||||||
features: ['مسبح', 'حديقة خاصة', 'موقف سيارات', 'أمن 24/7'],
|
features: [t('swimmingPool'), t('privateGarden'), t('parking'), t('propertyService.security247')],
|
||||||
images: ['/villa1.jpg'],
|
images: ['/villa1.jpg'],
|
||||||
status: 'available',
|
status: 'available',
|
||||||
bookings: [
|
bookings: [
|
||||||
{ startDate: '2024-03-10', endDate: '2024-03-15', totalAmount: 2500000, tenantName: 'أحمد محمد' },
|
{ startDate: '2024-03-10', endDate: '2024-03-15', totalAmount: 2500000, tenantName: t('calendar.mockTenant1') },
|
||||||
{ startDate: '2024-03-20', endDate: '2024-03-25', totalAmount: 2500000, tenantName: 'سارة أحمد' }
|
{ startDate: '2024-03-20', endDate: '2024-03-25', totalAmount: 2500000, tenantName: t('calendar.mockTenant2') }
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 2,
|
id: 2,
|
||||||
title: 'شقة حديثة في الشهباء',
|
title: t('calendar.mockApartmentTitle'),
|
||||||
location: 'حلب، الشهباء',
|
location: t('calendar.mockApartmentLocation'),
|
||||||
bedrooms: 3,
|
bedrooms: 3,
|
||||||
bathrooms: 2,
|
bathrooms: 2,
|
||||||
area: 180,
|
area: 180,
|
||||||
price: 250000,
|
price: 250000,
|
||||||
features: ['مطبخ مجهز', 'بلكونة', 'موقف سيارات', 'مصعد'],
|
features: [t('equippedKitchen'), t('balcony'), t('parking'), t('propertyService.elevator')],
|
||||||
images: ['/apartment1.jpg'],
|
images: ['/apartment1.jpg'],
|
||||||
status: 'available',
|
status: 'available',
|
||||||
bookings: [
|
bookings: [
|
||||||
{ startDate: '2024-03-05', endDate: '2024-03-08', totalAmount: 750000, tenantName: 'محمد علي' }
|
{ startDate: '2024-03-05', endDate: '2024-03-08', totalAmount: 750000, tenantName: t('calendar.mockTenant3') }
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 3,
|
id: 3,
|
||||||
title: 'بيت عائلي في بابا عمرو',
|
title: t('calendar.mockHouseTitle'),
|
||||||
location: 'حمص، بابا عمرو',
|
location: t('calendar.mockHouseLocation'),
|
||||||
bedrooms: 4,
|
bedrooms: 4,
|
||||||
bathrooms: 3,
|
bathrooms: 3,
|
||||||
area: 300,
|
area: 300,
|
||||||
price: 350000,
|
price: 350000,
|
||||||
features: ['حديقة كبيرة', 'موقف سيارات', 'مدفأة', 'كراج'],
|
features: [t('largeGarden'), t('parking'), t('fireplace'), t('garage')],
|
||||||
images: ['/house1.jpg'],
|
images: ['/house1.jpg'],
|
||||||
status: 'booked',
|
status: 'booked',
|
||||||
bookings: []
|
bookings: []
|
||||||
@ -586,14 +591,14 @@ export default function OwnerCalendarPage() {
|
|||||||
<div className="min-h-screen bg-gray-50 flex items-center justify-center">
|
<div className="min-h-screen bg-gray-50 flex items-center justify-center">
|
||||||
<div className="text-center">
|
<div className="text-center">
|
||||||
<Loader2 className="w-12 h-12 text-amber-500 animate-spin mx-auto mb-4" />
|
<Loader2 className="w-12 h-12 text-amber-500 animate-spin mx-auto mb-4" />
|
||||||
<p className="text-gray-600">جاري تحميل التقويم...</p>
|
<p className="text-gray-600">{t('calendar.loading')}</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="min-h-screen bg-gray-50 py-8">
|
<div className="min-h-screen bg-gray-50 py-8" dir={i18n.language === 'ar' ? 'rtl' : 'ltr'}>
|
||||||
<Toaster position="top-center" reverseOrder={false} />
|
<Toaster position="top-center" reverseOrder={false} />
|
||||||
|
|
||||||
<PropertyDetailsModal
|
<PropertyDetailsModal
|
||||||
@ -609,8 +614,8 @@ export default function OwnerCalendarPage() {
|
|||||||
className="flex flex-col md:flex-row justify-between items-start md:items-center mb-8 gap-4"
|
className="flex flex-col md:flex-row justify-between items-start md:items-center mb-8 gap-4"
|
||||||
>
|
>
|
||||||
<div>
|
<div>
|
||||||
<h1 className="text-3xl font-bold text-gray-900 mb-2">تقويم العقارات</h1>
|
<h1 className="text-3xl font-bold text-gray-900 mb-2">{t('calendar.pageTitle')}</h1>
|
||||||
<p className="text-gray-600">مرحباً {user?.name}، تتبع حالة عقاراتك عبر التقويم</p>
|
<p className="text-gray-600">{t('calendar.welcomeMessage', { name: user?.name })}</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex gap-3">
|
<div className="flex gap-3">
|
||||||
@ -619,7 +624,7 @@ export default function OwnerCalendarPage() {
|
|||||||
className="px-4 py-2 bg-white border border-gray-300 rounded-xl text-gray-700 hover:bg-gray-50 transition-colors flex items-center gap-2"
|
className="px-4 py-2 bg-white border border-gray-300 rounded-xl text-gray-700 hover:bg-gray-50 transition-colors flex items-center gap-2"
|
||||||
>
|
>
|
||||||
<Filter className="w-5 h-5" />
|
<Filter className="w-5 h-5" />
|
||||||
فلترة العقارات
|
{t('calendar.filterProperties')}
|
||||||
<ChevronDown className={`w-4 h-4 transition-transform ${showFilters ? 'rotate-180' : ''}`} />
|
<ChevronDown className={`w-4 h-4 transition-transform ${showFilters ? 'rotate-180' : ''}`} />
|
||||||
</button>
|
</button>
|
||||||
{/* <button className="px-4 py-2 bg-green-600 text-white rounded-xl hover:bg-green-700 transition-colors flex items-center gap-2">
|
{/* <button className="px-4 py-2 bg-green-600 text-white rounded-xl hover:bg-green-700 transition-colors flex items-center gap-2">
|
||||||
@ -638,7 +643,7 @@ export default function OwnerCalendarPage() {
|
|||||||
>
|
>
|
||||||
<Building className="w-6 h-6 text-amber-500 mx-auto mb-2" />
|
<Building className="w-6 h-6 text-amber-500 mx-auto mb-2" />
|
||||||
<div className="text-2xl font-bold text-gray-900">{calendarStats.totalProperties}</div>
|
<div className="text-2xl font-bold text-gray-900">{calendarStats.totalProperties}</div>
|
||||||
<div className="text-sm text-gray-600">إجمالي العقارات</div>
|
<div className="text-sm text-gray-600">{t('calendar.totalProperties')}</div>
|
||||||
</motion.div>
|
</motion.div>
|
||||||
<motion.div
|
<motion.div
|
||||||
initial={{ opacity: 0, y: 20 }}
|
initial={{ opacity: 0, y: 20 }}
|
||||||
@ -648,7 +653,7 @@ export default function OwnerCalendarPage() {
|
|||||||
>
|
>
|
||||||
<CheckCircle className="w-6 h-6 text-green-500 mx-auto mb-2" />
|
<CheckCircle className="w-6 h-6 text-green-500 mx-auto mb-2" />
|
||||||
<div className="text-2xl font-bold text-green-600">{calendarStats.availableToday}</div>
|
<div className="text-2xl font-bold text-green-600">{calendarStats.availableToday}</div>
|
||||||
<div className="text-sm text-gray-600">متاح اليوم</div>
|
<div className="text-sm text-gray-600">{t('calendar.availableToday')}</div>
|
||||||
</motion.div>
|
</motion.div>
|
||||||
<motion.div
|
<motion.div
|
||||||
initial={{ opacity: 0, y: 20 }}
|
initial={{ opacity: 0, y: 20 }}
|
||||||
@ -658,7 +663,7 @@ export default function OwnerCalendarPage() {
|
|||||||
>
|
>
|
||||||
<XCircle className="w-6 h-6 text-red-500 mx-auto mb-2" />
|
<XCircle className="w-6 h-6 text-red-500 mx-auto mb-2" />
|
||||||
<div className="text-2xl font-bold text-red-600">{calendarStats.bookedToday}</div>
|
<div className="text-2xl font-bold text-red-600">{calendarStats.bookedToday}</div>
|
||||||
<div className="text-sm text-gray-600">محجوز اليوم</div>
|
<div className="text-sm text-gray-600">{t('calendar.bookedToday')}</div>
|
||||||
</motion.div>
|
</motion.div>
|
||||||
<motion.div
|
<motion.div
|
||||||
initial={{ opacity: 0, y: 20 }}
|
initial={{ opacity: 0, y: 20 }}
|
||||||
@ -668,7 +673,7 @@ export default function OwnerCalendarPage() {
|
|||||||
>
|
>
|
||||||
<CalendarDays className="w-6 h-6 text-blue-500 mx-auto mb-2" />
|
<CalendarDays className="w-6 h-6 text-blue-500 mx-auto mb-2" />
|
||||||
<div className="text-2xl font-bold text-blue-600">{calendarStats.upcomingBookings}</div>
|
<div className="text-2xl font-bold text-blue-600">{calendarStats.upcomingBookings}</div>
|
||||||
<div className="text-sm text-gray-600">حجوزات قادمة</div>
|
<div className="text-sm text-gray-600">{t('calendar.upcomingBookings')}</div>
|
||||||
</motion.div>
|
</motion.div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -682,13 +687,13 @@ export default function OwnerCalendarPage() {
|
|||||||
>
|
>
|
||||||
<div className="bg-white rounded-2xl shadow-sm border border-gray-200 p-4">
|
<div className="bg-white rounded-2xl shadow-sm border border-gray-200 p-4">
|
||||||
<div className="flex flex-wrap gap-3 items-center">
|
<div className="flex flex-wrap gap-3 items-center">
|
||||||
<label className="text-sm font-medium text-gray-700">اختر عقاراً:</label>
|
<label className="text-sm font-medium text-gray-700">{t('calendar.selectProperty')}</label>
|
||||||
<select
|
<select
|
||||||
value={selectedPropertyId}
|
value={selectedPropertyId}
|
||||||
onChange={(e) => setSelectedPropertyId(e.target.value)}
|
onChange={(e) => setSelectedPropertyId(e.target.value)}
|
||||||
className="px-4 py-2 border border-gray-300 rounded-xl focus:outline-none focus:ring-2 focus:ring-amber-500"
|
className="px-4 py-2 border border-gray-300 rounded-xl focus:outline-none focus:ring-2 focus:ring-amber-500"
|
||||||
>
|
>
|
||||||
<option value="all">جميع العقارات</option>
|
<option value="all">{t('calendar.allProperties')}</option>
|
||||||
{properties.map((property) => (
|
{properties.map((property) => (
|
||||||
<option key={property.id} value={property.id}>{property.title}</option>
|
<option key={property.id} value={property.id}>{property.title}</option>
|
||||||
))}
|
))}
|
||||||
@ -697,7 +702,7 @@ export default function OwnerCalendarPage() {
|
|||||||
onClick={() => setSelectedPropertyId('all')}
|
onClick={() => setSelectedPropertyId('all')}
|
||||||
className="px-3 py-2 bg-gray-100 text-gray-700 rounded-lg hover:bg-gray-200 transition-colors"
|
className="px-3 py-2 bg-gray-100 text-gray-700 rounded-lg hover:bg-gray-200 transition-colors"
|
||||||
>
|
>
|
||||||
إعادة تعيين
|
{t('calendar.reset')}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -729,7 +734,7 @@ export default function OwnerCalendarPage() {
|
|||||||
className="mt-6 text-center text-sm text-gray-500"
|
className="mt-6 text-center text-sm text-gray-500"
|
||||||
>
|
>
|
||||||
<AlertCircle className="w-4 h-4 inline ml-1" />
|
<AlertCircle className="w-4 h-4 inline ml-1" />
|
||||||
اضغط على أي عقار لعرض التفاصيل الكاملة
|
{t('calendar.clickPropertyHint')}
|
||||||
</motion.div>
|
</motion.div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -298,9 +298,11 @@ import { useRouter } from 'next/navigation';
|
|||||||
import { Download, Loader2 } from 'lucide-react';
|
import { Download, Loader2 } from 'lucide-react';
|
||||||
import toast, { Toaster } from 'react-hot-toast';
|
import toast, { Toaster } from 'react-hot-toast';
|
||||||
import * as XLSX from 'xlsx';
|
import * as XLSX from 'xlsx';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
import AuthService from '@/app/services/AuthService';
|
import AuthService from '@/app/services/AuthService';
|
||||||
|
|
||||||
export default function OwnerProfitsPage() {
|
export default function OwnerProfitsPage() {
|
||||||
|
const { t, i18n } = useTranslation();
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const [user, setUser] = useState(null);
|
const [user, setUser] = useState(null);
|
||||||
const [isLoading, setIsLoading] = useState(true);
|
const [isLoading, setIsLoading] = useState(true);
|
||||||
@ -404,29 +406,29 @@ export default function OwnerProfitsPage() {
|
|||||||
const handleExportReport = () => {
|
const handleExportReport = () => {
|
||||||
try {
|
try {
|
||||||
const exportData = tableData.map((row) => ({
|
const exportData = tableData.map((row) => ({
|
||||||
'العقار': row.property,
|
[t('profits.colProperty')]: row.property,
|
||||||
'رقم الحجز': row.bookingNumber,
|
[t('profits.colBookingNumber')]: row.bookingNumber,
|
||||||
'من تاريخ': row.fromDate,
|
[t('profits.colFromDate')]: row.fromDate,
|
||||||
'حتى تاريخ': row.toDate,
|
[t('profits.colToDate')]: row.toDate,
|
||||||
'العروض المستلم': row.amountReceived,
|
[t('profits.colAmountReceived')]: row.amountReceived,
|
||||||
'عمولة المنصة': row.platformCommission,
|
[t('profits.colPlatformCommission')]: row.platformCommission,
|
||||||
'ربح المنصة (5%)': row.platformProfit,
|
[t('profits.colPlatformProfit')]: row.platformProfit,
|
||||||
'المستحق للمالك': row.ownerDue,
|
[t('profits.colOwnerDue')]: row.ownerDue,
|
||||||
'تم التحويل للمالك': row.transferredToOwner,
|
[t('profits.colTransferred')]: row.transferredToOwner,
|
||||||
'رقم وصل التحويل': row.transferReceipt,
|
[t('profits.colTransferReceipt')]: row.transferReceipt,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
exportData.push({
|
exportData.push({
|
||||||
'العقار': 'الإجمالي العام',
|
[t('profits.colProperty')]: t('profits.totalGeneral'),
|
||||||
'رقم الحجز': '',
|
[t('profits.colBookingNumber')]: '',
|
||||||
'من تاريخ': '',
|
[t('profits.colFromDate')]: '',
|
||||||
'حتى تاريخ': '',
|
[t('profits.colToDate')]: '',
|
||||||
'العروض المستلم': totals.totalAmountReceived,
|
[t('profits.colAmountReceived')]: totals.totalAmountReceived,
|
||||||
'عمولة المنصة': totals.totalCommission,
|
[t('profits.colPlatformCommission')]: totals.totalCommission,
|
||||||
'ربح المنصة (5%)': totals.totalPlatformProfit,
|
[t('profits.colPlatformProfit')]: totals.totalPlatformProfit,
|
||||||
'المستحق للمالك': totals.totalOwnerDue,
|
[t('profits.colOwnerDue')]: totals.totalOwnerDue,
|
||||||
'تم التحويل للمالك': totals.totalTransferred,
|
[t('profits.colTransferred')]: totals.totalTransferred,
|
||||||
'رقم وصل التحويل': '—',
|
[t('profits.colTransferReceipt')]: '—',
|
||||||
});
|
});
|
||||||
|
|
||||||
const worksheet = XLSX.utils.json_to_sheet(exportData);
|
const worksheet = XLSX.utils.json_to_sheet(exportData);
|
||||||
@ -445,14 +447,14 @@ export default function OwnerProfitsPage() {
|
|||||||
worksheet['!cols'] = colWidths;
|
worksheet['!cols'] = colWidths;
|
||||||
|
|
||||||
const workbook = XLSX.utils.book_new();
|
const workbook = XLSX.utils.book_new();
|
||||||
XLSX.utils.book_append_sheet(workbook, worksheet, 'أرباح المالك');
|
XLSX.utils.book_append_sheet(workbook, worksheet, t('profits.sheetName'));
|
||||||
|
|
||||||
XLSX.writeFile(workbook, `تقرير_الأرباح_${new Date().toISOString().slice(0,19).replace(/:/g, '-')}.xlsx`);
|
XLSX.writeFile(workbook, `${t('profits.fileName')}${new Date().toISOString().slice(0,19).replace(/:/g, '-')}.xlsx`);
|
||||||
|
|
||||||
toast.success('تم تصدير التقرير بنجاح!');
|
toast.success(t('profits.exportSuccess'));
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('خطأ في التصدير:', error);
|
console.error(t('profits.exportErrorLog'), error);
|
||||||
toast.error('حدث خطأ أثناء تصدير التقرير');
|
toast.error(t('profits.exportError'));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -461,14 +463,14 @@ export default function OwnerProfitsPage() {
|
|||||||
<div className="min-h-screen bg-gray-50 flex items-center justify-center">
|
<div className="min-h-screen bg-gray-50 flex items-center justify-center">
|
||||||
<div className="text-center">
|
<div className="text-center">
|
||||||
<Loader2 className="w-12 h-12 text-amber-500 animate-spin mx-auto mb-4" />
|
<Loader2 className="w-12 h-12 text-amber-500 animate-spin mx-auto mb-4" />
|
||||||
<p className="text-gray-600">جاري تحميل بيانات الأرباح...</p>
|
<p className="text-gray-600">{t('profits.loading')}</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
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.language === 'ar' ? 'rtl' : 'ltr'}>
|
||||||
<Toaster position="top-center" reverseOrder={false} />
|
<Toaster position="top-center" reverseOrder={false} />
|
||||||
<div className="container mx-auto px-4 max-w-7xl">
|
<div className="container mx-auto px-4 max-w-7xl">
|
||||||
<motion.div
|
<motion.div
|
||||||
@ -477,9 +479,9 @@ export default function OwnerProfitsPage() {
|
|||||||
className="flex flex-col md:flex-row justify-between items-start md:items-center mb-8 gap-4"
|
className="flex flex-col md:flex-row justify-between items-start md:items-center mb-8 gap-4"
|
||||||
>
|
>
|
||||||
<div>
|
<div>
|
||||||
<h1 className="text-3xl font-bold text-gray-900 mb-2">أرباح المالك</h1>
|
<h1 className="text-3xl font-bold text-gray-900 mb-2">{t('profits.pageTitle')}</h1>
|
||||||
<p className="text-gray-600">
|
<p className="text-gray-600">
|
||||||
مرحباً {user?.name}
|
{t('profits.greeting')} {user?.name}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<button
|
<button
|
||||||
@ -487,7 +489,7 @@ export default function OwnerProfitsPage() {
|
|||||||
className="px-5 py-2.5 bg-amber-500 text-white rounded-xl font-medium hover:bg-amber-600 transition-colors flex items-center gap-2 shadow-sm"
|
className="px-5 py-2.5 bg-amber-500 text-white rounded-xl font-medium hover:bg-amber-600 transition-colors flex items-center gap-2 shadow-sm"
|
||||||
>
|
>
|
||||||
<Download className="w-5 h-5" />
|
<Download className="w-5 h-5" />
|
||||||
تصدير التقرير
|
{t('profits.exportReport')}
|
||||||
</button>
|
</button>
|
||||||
</motion.div>
|
</motion.div>
|
||||||
|
|
||||||
@ -500,18 +502,18 @@ export default function OwnerProfitsPage() {
|
|||||||
<table className="min-w-full divide-y divide-gray-200 text-sm">
|
<table className="min-w-full divide-y divide-gray-200 text-sm">
|
||||||
<thead className="bg-gray-800 text-gray-100">
|
<thead className="bg-gray-800 text-gray-100">
|
||||||
<tr>
|
<tr>
|
||||||
<th className="px-4 py-4 text-center text-xs font-semibold uppercase tracking-wider">العقار</th>
|
<th className="px-4 py-4 text-center text-xs font-semibold uppercase tracking-wider">{t('profits.colProperty')}</th>
|
||||||
<th className="px-4 py-4 text-center text-xs font-semibold uppercase tracking-wider">رقم الحجز</th>
|
<th className="px-4 py-4 text-center text-xs font-semibold uppercase tracking-wider">{t('profits.colBookingNumber')}</th>
|
||||||
<th className="px-4 py-4 text-center text-xs font-semibold uppercase tracking-wider">من تاريخ</th>
|
<th className="px-4 py-4 text-center text-xs font-semibold uppercase tracking-wider">{t('profits.colFromDate')}</th>
|
||||||
<th className="px-4 py-4 text-center text-xs font-semibold uppercase tracking-wider">حتى تاريخ</th>
|
<th className="px-4 py-4 text-center text-xs font-semibold uppercase tracking-wider">{t('profits.colToDate')}</th>
|
||||||
<th className="px-4 py-4 text-center text-xs font-semibold uppercase tracking-wider">العروض المستلم</th>
|
<th className="px-4 py-4 text-center text-xs font-semibold uppercase tracking-wider">{t('profits.colAmountReceived')}</th>
|
||||||
<th className="px-4 py-4 text-center text-xs font-semibold uppercase tracking-wider">عمولة المنصة</th>
|
<th className="px-4 py-4 text-center text-xs font-semibold uppercase tracking-wider">{t('profits.colPlatformCommission')}</th>
|
||||||
<th className="px-4 py-4 text-center text-xs font-semibold uppercase tracking-wider bg-amber-50 text-amber-800">
|
<th className="px-4 py-4 text-center text-xs font-semibold uppercase tracking-wider bg-amber-50 text-amber-800">
|
||||||
ربح المنصة <span className="font-normal text-[11px] block">(5% من العربون)</span>
|
{t('profits.colPlatformProfit')} <span className="font-normal text-[11px] block">{t('profits.colPlatformProfitSub')}</span>
|
||||||
</th>
|
</th>
|
||||||
<th className="px-4 py-4 text-center text-xs font-semibold uppercase tracking-wider">المستحق للمالك</th>
|
<th className="px-4 py-4 text-center text-xs font-semibold uppercase tracking-wider">{t('profits.colOwnerDue')}</th>
|
||||||
<th className="px-4 py-4 text-center text-xs font-semibold uppercase tracking-wider">تم التحويل للمالك</th>
|
<th className="px-4 py-4 text-center text-xs font-semibold uppercase tracking-wider">{t('profits.colTransferred')}</th>
|
||||||
<th className="px-4 py-4 text-center text-xs font-semibold uppercase tracking-wider">رقم وصل التحويل</th>
|
<th className="px-4 py-4 text-center text-xs font-semibold uppercase tracking-wider">{t('profits.colTransferReceipt')}</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody className="bg-white divide-y divide-gray-100">
|
<tbody className="bg-white divide-y divide-gray-100">
|
||||||
@ -558,7 +560,7 @@ export default function OwnerProfitsPage() {
|
|||||||
<tfoot className="bg-gray-100 border-t-2 border-gray-300">
|
<tfoot className="bg-gray-100 border-t-2 border-gray-300">
|
||||||
<tr>
|
<tr>
|
||||||
<td colSpan="4" className="px-4 py-4 text-right font-bold text-gray-800">
|
<td colSpan="4" className="px-4 py-4 text-right font-bold text-gray-800">
|
||||||
الإجمالي العام
|
{t('profits.totalGeneral')}
|
||||||
</td>
|
</td>
|
||||||
<td className="px-4 py-4 text-center font-bold font-mono text-gray-800">
|
<td className="px-4 py-4 text-center font-bold font-mono text-gray-800">
|
||||||
{totals.totalAmountReceived}
|
{totals.totalAmountReceived}
|
||||||
@ -582,8 +584,8 @@ export default function OwnerProfitsPage() {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="bg-gray-50 px-6 py-3 text-xs text-gray-500 border-t border-gray-200">
|
<div className="bg-gray-50 px-6 py-3 text-xs text-gray-500 border-t border-gray-200">
|
||||||
<span className="inline-flex items-center gap-1"></span> ملاحظة:
|
<span className="inline-flex items-center gap-1"></span> {t('profits.note')}
|
||||||
<strong> ربح المنصة </strong> يُحتسب تلقائياً بنسبة <strong className="text-amber-600">5%</strong> من قيمة «العروض المستلم».
|
<strong> {t('profits.colPlatformProfit')} </strong>{t('profits.noteMiddle')}<strong className="text-amber-600">5%</strong>{t('profits.noteEnd', { colName: t('profits.colAmountReceived') })}
|
||||||
</div>
|
</div>
|
||||||
</motion.div>
|
</motion.div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -6,6 +6,7 @@ import { useRouter, useSearchParams } from 'next/navigation';
|
|||||||
import Link from 'next/link';
|
import Link from 'next/link';
|
||||||
import Image from 'next/image';
|
import Image from 'next/image';
|
||||||
import 'leaflet/dist/leaflet.css';
|
import 'leaflet/dist/leaflet.css';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
import {
|
import {
|
||||||
ArrowLeft,
|
ArrowLeft,
|
||||||
MapPin,
|
MapPin,
|
||||||
@ -74,6 +75,7 @@ export default function AddPropertyPage() {
|
|||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const searchParams = useSearchParams();
|
const searchParams = useSearchParams();
|
||||||
const purpose = searchParams.get('purpose') || 'rent';
|
const purpose = searchParams.get('purpose') || 'rent';
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
const [step, setStep] = useState(1);
|
const [step, setStep] = useState(1);
|
||||||
const totalSteps = purpose === 'sale' ? 4 : 4;
|
const totalSteps = purpose === 'sale' ? 4 : 4;
|
||||||
@ -171,15 +173,15 @@ export default function AddPropertyPage() {
|
|||||||
const fileInputRef = useRef(null);
|
const fileInputRef = useRef(null);
|
||||||
|
|
||||||
const propertyTypes = [
|
const propertyTypes = [
|
||||||
{ id: 'apartment', label: 'شقة', icon: Building },
|
{ id: 'apartment', label: t('buildingType.apartment'), icon: Building },
|
||||||
{ id: 'villa', label: 'فيلا', icon: Home },
|
{ id: 'villa', label: t('buildingType.villa'), icon: Home },
|
||||||
{ id: 'sweet', label: 'سويت', icon: Sofa },
|
{ id: 'sweet', label: t('buildingType.sweet'), icon: Sofa },
|
||||||
{ id: 'room', label: 'غرفة ضمن شقة (سكن مشترك)', icon: DoorOpen },
|
{ id: 'room', label: t('buildingType.room'), icon: DoorOpen },
|
||||||
{ id: 'studio', label: 'استوديو', icon: Sofa },
|
{ id: 'studio', label: t('buildingType.studio'), icon: Sofa },
|
||||||
{ id: 'office', label: 'مكتب', icon: Building },
|
{ id: 'office', label: t('buildingType.office'), icon: Building },
|
||||||
{ id: 'farms', label: 'مزرعة', icon: Trees },
|
{ id: 'farms', label: t('buildingType.farms'), icon: Trees },
|
||||||
{ id: 'shop', label: 'متجر', icon: Warehouse },
|
{ id: 'shop', label: t('buildingType.shop'), icon: Warehouse },
|
||||||
{ id: 'warehouse', label: 'مستودع', icon: Warehouse },
|
{ id: 'warehouse', label: t('buildingType.warehouse'), icon: Warehouse },
|
||||||
];
|
];
|
||||||
|
|
||||||
const serviceList = [
|
const serviceList = [
|
||||||
@ -199,9 +201,9 @@ export default function AddPropertyPage() {
|
|||||||
];
|
];
|
||||||
|
|
||||||
const offerTypes = [
|
const offerTypes = [
|
||||||
{ id: 'daily', label: 'إيجار يومي', icon: Clock },
|
{ id: 'daily', label: t('dailyRent'), icon: Clock },
|
||||||
{ id: 'monthly', label: 'إيجار شهري', icon: Calendar },
|
{ id: 'monthly', label: t('monthlyRent'), icon: Calendar },
|
||||||
{ id: 'both', label: 'إيجار يومي وشهري', icon: Calendar },
|
{ id: 'both', label: t('addProperty.dailyAndMonthly'), icon: Calendar },
|
||||||
].filter(Boolean);
|
].filter(Boolean);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@ -218,7 +220,7 @@ export default function AddPropertyPage() {
|
|||||||
const handleSearch = async () => {
|
const handleSearch = async () => {
|
||||||
if (!searchQuery) return;
|
if (!searchQuery) return;
|
||||||
|
|
||||||
toast.loading('جاري البحث...', { id: 'search' });
|
toast.loading(t('toast.searching'), { id: 'search' });
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await fetch(
|
const response = await fetch(
|
||||||
@ -245,23 +247,23 @@ export default function AddPropertyPage() {
|
|||||||
address: addressData.display_name || result.display_name
|
address: addressData.display_name || result.display_name
|
||||||
});
|
});
|
||||||
|
|
||||||
toast.success('تم العثور على الموقع', { id: 'search' });
|
toast.success(t('toast.locationFound'), { id: 'search' });
|
||||||
} else {
|
} else {
|
||||||
toast.error('لم يتم العثور على العنوان', { id: 'search' });
|
toast.error(t('toast.locationNotFound'), { id: 'search' });
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('خطأ في البحث:', error);
|
console.error(t('search.error'), error);
|
||||||
toast.error('حدث خطأ في البحث', { id: 'search' });
|
toast.error(t('search.error'), { id: 'search' });
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleGeolocation = () => {
|
const handleGeolocation = () => {
|
||||||
if (!navigator.geolocation) {
|
if (!navigator.geolocation) {
|
||||||
toast.error('المتصفح لا يدعم تحديد الموقع');
|
toast.error(t('toast.geolocationNotSupported'));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
toast.loading('جاري تحديد موقعك...', { id: 'geolocation' });
|
toast.loading(t('toast.geolocationLoading'), { id: 'geolocation' });
|
||||||
|
|
||||||
navigator.geolocation.getCurrentPosition(
|
navigator.geolocation.getCurrentPosition(
|
||||||
async (position) => {
|
async (position) => {
|
||||||
@ -269,7 +271,7 @@ export default function AddPropertyPage() {
|
|||||||
setMapCenter([latitude, longitude]);
|
setMapCenter([latitude, longitude]);
|
||||||
setMapZoom(18);
|
setMapZoom(18);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await fetch(
|
const response = await fetch(
|
||||||
`https://nominatim.openstreetmap.org/reverse?format=json&lat=${latitude}&lon=${longitude}&accept-language=ar`
|
`https://nominatim.openstreetmap.org/reverse?format=json&lat=${latitude}&lon=${longitude}&accept-language=ar`
|
||||||
);
|
);
|
||||||
@ -278,21 +280,21 @@ export default function AddPropertyPage() {
|
|||||||
setSelectedLocation({
|
setSelectedLocation({
|
||||||
lat: latitude,
|
lat: latitude,
|
||||||
lng: longitude,
|
lng: longitude,
|
||||||
address: data.display_name || 'موقعك الحالي'
|
address: data.display_name || t('addProperty.currentLocation')
|
||||||
});
|
});
|
||||||
|
|
||||||
toast.success('تم تحديد موقعك', { id: 'geolocation' });
|
toast.success(t('toast.geolocationSuccess'), { id: 'geolocation' });
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
setSelectedLocation({
|
setSelectedLocation({
|
||||||
lat: latitude,
|
lat: latitude,
|
||||||
lng: longitude,
|
lng: longitude,
|
||||||
address: 'موقعك الحالي'
|
address: t('addProperty.currentLocation')
|
||||||
});
|
});
|
||||||
toast.success('تم تحديد موقعك', { id: 'geolocation' });
|
toast.success(t('toast.geolocationSuccess'), { id: 'geolocation' });
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
(error) => {
|
(error) => {
|
||||||
toast.error('فشل في تحديد الموقع', { id: 'geolocation' });
|
toast.error(t('toast.geolocationFailed'), { id: 'geolocation' });
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
@ -301,9 +303,9 @@ const handleMapClick = async (coords) => {
|
|||||||
try {
|
try {
|
||||||
const [lat, lng] = coords;
|
const [lat, lng] = coords;
|
||||||
|
|
||||||
toast.loading('جاري تحديد الموقع...', { id: 'location' });
|
toast.loading(t('addProperty.locationSelecting'), { id: 'location' });
|
||||||
|
|
||||||
const response = await fetch(
|
const response = await fetch(
|
||||||
`https://nominatim.openstreetmap.org/reverse?format=json&lat=${lat}&lon=${lng}&accept-language=ar`
|
`https://nominatim.openstreetmap.org/reverse?format=json&lat=${lat}&lon=${lng}&accept-language=ar`
|
||||||
);
|
);
|
||||||
const data = await response.json();
|
const data = await response.json();
|
||||||
@ -311,22 +313,22 @@ const handleMapClick = async (coords) => {
|
|||||||
setSelectedLocation({
|
setSelectedLocation({
|
||||||
lat: lat,
|
lat: lat,
|
||||||
lng: lng,
|
lng: lng,
|
||||||
address: data.display_name || 'موقع محدد'
|
address: data.display_name || t('addProperty.defaultAddress')
|
||||||
});
|
});
|
||||||
|
|
||||||
setMapZoom(18);
|
setMapZoom(18);
|
||||||
toast.success('تم تحديد الموقع بنجاح!', { id: 'location' });
|
toast.success(t('toast.locationSelectedSuccess'), { id: 'location' });
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('خطأ في تحديد الموقع:', error);
|
console.error(t('addProperty.locationError'), error);
|
||||||
const [lat, lng] = coords;
|
const [lat, lng] = coords;
|
||||||
setSelectedLocation({
|
setSelectedLocation({
|
||||||
lat: lat,
|
lat: lat,
|
||||||
lng: lng,
|
lng: lng,
|
||||||
address: 'موقع محدد'
|
address: t('addProperty.defaultAddress')
|
||||||
});
|
});
|
||||||
setMapZoom(18);
|
setMapZoom(18);
|
||||||
toast.success('تم تحديد الموقع', { id: 'location' });
|
toast.success(t('toast.locationConfirmed'), { id: 'location' });
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
const handleMarkerDragEnd = async (lat, lng) => {
|
const handleMarkerDragEnd = async (lat, lng) => {
|
||||||
@ -338,13 +340,13 @@ const handleMarkerDragEnd = async (lat, lng) => {
|
|||||||
setSelectedLocation({
|
setSelectedLocation({
|
||||||
lat,
|
lat,
|
||||||
lng,
|
lng,
|
||||||
address: data.display_name || 'موقع محدد'
|
address: data.display_name || t('addProperty.defaultAddress')
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
setSelectedLocation({
|
setSelectedLocation({
|
||||||
lat,
|
lat,
|
||||||
lng,
|
lng,
|
||||||
address: 'موقع محدد'
|
address: t('addProperty.defaultAddress')
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -357,7 +359,7 @@ const handleMarkerDragEnd = async (lat, lng) => {
|
|||||||
address: selectedLocation.address
|
address: selectedLocation.address
|
||||||
});
|
});
|
||||||
|
|
||||||
toast.success('تم تأكيد الموقع بنجاح');
|
toast.success(t('toast.locationConfirmed'));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -370,25 +372,25 @@ const handleMarkerDragEnd = async (lat, lng) => {
|
|||||||
address: ''
|
address: ''
|
||||||
});
|
});
|
||||||
setMapZoom(15);
|
setMapZoom(15);
|
||||||
toast.info('تم إلغاء تحديد الموقع');
|
toast.info(t('toast.clearLocation'));
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleImageUpload = async (files) => {
|
const handleImageUpload = async (files) => {
|
||||||
const newImages = Array.from(files);
|
const newImages = Array.from(files);
|
||||||
|
|
||||||
if (formData.images.length + newImages.length > 5) {
|
if (formData.images.length + newImages.length > 5) {
|
||||||
toast.error('يمكنك رفع 5 صور كحد أقصى');
|
toast.error(t('toast.maxImages'));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const file of newImages) {
|
for (const file of newImages) {
|
||||||
if (!file.type.startsWith('image/')) {
|
if (!file.type.startsWith('image/')) {
|
||||||
toast.error('الرجاء اختيار صور صالحة فقط');
|
toast.error(t('toast.imageRequired'));
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (file.size > 5 * 1024 * 1024) {
|
if (file.size > 5 * 1024 * 1024) {
|
||||||
toast.error('حجم الصورة يجب أن يكون أقل من 5 ميجابايت');
|
toast.error(t('toast.imageSize'));
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -410,7 +412,7 @@ const handleMarkerDragEnd = async (lat, lng) => {
|
|||||||
setUploadedImagePaths(prev => [...prev, path]);
|
setUploadedImagePaths(prev => [...prev, path]);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('[AddProperty] Image upload failed:', err);
|
console.error('[AddProperty] Image upload failed:', err);
|
||||||
toast.error('فشل رفع الصورة: ' + file.name);
|
toast.error(`${t('toast.imageUploadFailed')}: ${file.name}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -519,48 +521,48 @@ const handleMarkerDragEnd = async (lat, lng) => {
|
|||||||
switch(step) {
|
switch(step) {
|
||||||
case 1:
|
case 1:
|
||||||
if (!formData.propertyType) {
|
if (!formData.propertyType) {
|
||||||
newErrors.propertyType = 'نوع العقار مطلوب';
|
newErrors.propertyType = t('addProperty.propertyTypeRequired');
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 2:
|
case 2:
|
||||||
if (!formData.bedrooms) {
|
if (!formData.bedrooms) {
|
||||||
newErrors.bedrooms = 'عدد الغرف مطلوب';
|
newErrors.bedrooms = t('addProperty.bedroomsRequired');
|
||||||
}
|
}
|
||||||
if (!formData.bathrooms) {
|
if (!formData.bathrooms) {
|
||||||
newErrors.bathrooms = 'عدد الحمامات مطلوب';
|
newErrors.bathrooms = t('addProperty.bathroomsRequired');
|
||||||
}
|
}
|
||||||
if (!formData.livingRooms) {
|
if (!formData.livingRooms) {
|
||||||
newErrors.livingRooms = 'عدد الصالونات مطلوب';
|
newErrors.livingRooms = t('addProperty.livingRoomsRequired');
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 3:
|
case 3:
|
||||||
if (purpose === 'sale') {
|
if (purpose === 'sale') {
|
||||||
if (!formData.salePrice) newErrors.salePrice = 'سعر البيع مطلوب';
|
if (!formData.salePrice) newErrors.salePrice = t('addProperty.salePriceRequired');
|
||||||
} else {
|
} else {
|
||||||
if (!formData.offerType) {
|
if (!formData.offerType) {
|
||||||
newErrors.offerType = 'الرجاء اختيار نوع العرض';
|
newErrors.offerType = t('addProperty.offerTypeRequired');
|
||||||
} else if (formData.offerType === 'daily' && !formData.dailyPrice) {
|
} else if (formData.offerType === 'daily' && !formData.dailyPrice) {
|
||||||
newErrors.dailyPrice = 'السعر اليومي مطلوب';
|
newErrors.dailyPrice = t('addProperty.dailyPriceRequired');
|
||||||
} else if (formData.offerType === 'monthly' && !formData.monthlyPrice) {
|
} else if (formData.offerType === 'monthly' && !formData.monthlyPrice) {
|
||||||
newErrors.monthlyPrice = 'السعر الشهري مطلوب';
|
newErrors.monthlyPrice = t('addProperty.monthlyPriceRequired');
|
||||||
} else if (formData.offerType === 'both') {
|
} else if (formData.offerType === 'both') {
|
||||||
if (!formData.dailyPrice) newErrors.dailyPrice = 'السعر اليومي مطلوب';
|
if (!formData.dailyPrice) newErrors.dailyPrice = t('addProperty.dailyPriceRequired');
|
||||||
if (!formData.monthlyPrice) newErrors.monthlyPrice = 'السعر الشهري مطلوب';
|
if (!formData.monthlyPrice) newErrors.monthlyPrice = t('addProperty.monthlyPriceRequired');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 4:
|
case 4:
|
||||||
if (!formData.lat || !formData.lng) {
|
if (!formData.lat || !formData.lng) {
|
||||||
newErrors.location = 'الرجاء تحديد موقع العقار على الخريطة';
|
newErrors.location = t('addProperty.locationRequired');
|
||||||
}
|
}
|
||||||
if (formData.images.length < 2) {
|
if (formData.images.length < 2) {
|
||||||
newErrors.images = 'يجب رفع صورتين على الأقل';
|
newErrors.images = t('addProperty.minImagesRequired');
|
||||||
}
|
}
|
||||||
if (formData.images.length > 5) {
|
if (formData.images.length > 5) {
|
||||||
newErrors.images = 'يمكن رفع 5 صور كحد أقصى';
|
newErrors.images = t('addProperty.maxImagesError');
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@ -670,7 +672,7 @@ const handleMarkerDragEnd = async (lat, lng) => {
|
|||||||
currencyId: selectedCurrencyId,
|
currencyId: selectedCurrencyId,
|
||||||
};
|
};
|
||||||
const res = await addSaleProperty(payload);
|
const res = await addSaleProperty(payload);
|
||||||
toast.success('تم إضافة عقار للبيع بنجاح!');
|
toast.success(t('toast.salePropertySuccess'));
|
||||||
} else {
|
} else {
|
||||||
const rentTypeMap = { daily: RentType.DAILY, monthly: RentType.MONTHLY, both: RentType.MONTHLY };
|
const rentTypeMap = { daily: RentType.DAILY, monthly: RentType.MONTHLY, both: RentType.MONTHLY };
|
||||||
const payload = {
|
const payload = {
|
||||||
@ -685,14 +687,14 @@ const handleMarkerDragEnd = async (lat, lng) => {
|
|||||||
allowedPaymentPeriod: formData.allowedPaymentPeriod || '',
|
allowedPaymentPeriod: formData.allowedPaymentPeriod || '',
|
||||||
};
|
};
|
||||||
const res = await addRentProperty(payload);
|
const res = await addRentProperty(payload);
|
||||||
toast.success('تم إضافة عقار للإيجار بنجاح!');
|
toast.success(t('toast.rentPropertySuccess'));
|
||||||
}
|
}
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
router.push('/owner/properties');
|
router.push('/owner/properties');
|
||||||
}, 1500);
|
}, 1500);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('[AddProperty] API error:', err);
|
console.error('[AddProperty] API error:', err);
|
||||||
toast.error(err.message || 'فشل في إضافة العقار');
|
toast.error(err.message || t('toast.propertyAddFailed'));
|
||||||
} finally {
|
} finally {
|
||||||
setIsLoading(false);
|
setIsLoading(false);
|
||||||
}
|
}
|
||||||
@ -713,15 +715,15 @@ const handleMarkerDragEnd = async (lat, lng) => {
|
|||||||
<div className="container mx-auto px-4 max-w-4xl">
|
<div className="container mx-auto px-4 max-w-4xl">
|
||||||
<div className="mb-8">
|
<div className="mb-8">
|
||||||
<div className="flex items-center justify-between mb-4">
|
<div className="flex items-center justify-between mb-4">
|
||||||
<Link
|
<Link
|
||||||
href="/owner/properties"
|
href="/owner/properties"
|
||||||
className="flex items-center gap-2 text-gray-600 hover:text-amber-600 transition-colors group"
|
className="flex items-center gap-2 text-gray-600 hover:text-amber-600 transition-colors group"
|
||||||
>
|
>
|
||||||
<ArrowLeft className="w-4 h-4 group-hover:-translate-x-1 transition-transform" />
|
<ArrowLeft className="w-4 h-4 group-hover:-translate-x-1 transition-transform" />
|
||||||
<span>العودة للعقارات</span>
|
<span>{t('addProperty.backToProperties')}</span>
|
||||||
</Link>
|
</Link>
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<span className="text-sm font-medium text-amber-600">خطوة {step} من {totalSteps}</span>
|
<span className="text-sm font-medium text-amber-600">{t('addProperty.stepCounter', { step, total: totalSteps })}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -738,10 +740,10 @@ const handleMarkerDragEnd = async (lat, lng) => {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex justify-between mt-2 text-xs text-gray-500">
|
<div className="flex justify-between mt-2 text-xs text-gray-500">
|
||||||
<span>معلومات العقار</span>
|
<span>{t('addProperty.stepInfo')}</span>
|
||||||
<span>التفاصيل والخدمات</span>
|
<span>{t('addProperty.stepDetails')}</span>
|
||||||
<span>{purpose === 'sale' ? 'سعر البيع' : 'السعر'}</span>
|
<span>{purpose === 'sale' ? t('addProperty.stepSalePrice') : t('addProperty.stepRentPrice')}</span>
|
||||||
<span>الموقع والصور</span>
|
<span>{t('addProperty.stepLocation')}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -759,13 +761,13 @@ const handleMarkerDragEnd = async (lat, lng) => {
|
|||||||
<div className="w-20 h-20 bg-amber-100 rounded-2xl flex items-center justify-center mx-auto mb-4">
|
<div className="w-20 h-20 bg-amber-100 rounded-2xl flex items-center justify-center mx-auto mb-4">
|
||||||
<Home className="w-10 h-10 text-amber-600" />
|
<Home className="w-10 h-10 text-amber-600" />
|
||||||
</div>
|
</div>
|
||||||
<h2 className="text-2xl font-bold text-gray-900 mb-2">معلومات العقار</h2>
|
<h2 className="text-2xl font-bold text-gray-900 mb-2">{t('addProperty.stepInfo')}</h2>
|
||||||
<p className="text-gray-600">اختر نوع العقار والحالة</p>
|
<p className="text-gray-600">{t('addProperty.propertyInfoSubtitle')}</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-sm font-medium text-gray-700 mb-3">
|
<label className="block text-sm font-medium text-gray-700 mb-3">
|
||||||
نوع العقار <span className="text-red-500">*</span>
|
{t('addProperty.propertyTypeLabel')} <span className="text-red-500">*</span>
|
||||||
</label>
|
</label>
|
||||||
<div className="grid grid-cols-2 md:grid-cols-4 gap-3">
|
<div className="grid grid-cols-2 md:grid-cols-4 gap-3">
|
||||||
{propertyTypes.map((type) => {
|
{propertyTypes.map((type) => {
|
||||||
@ -794,7 +796,7 @@ const handleMarkerDragEnd = async (lat, lng) => {
|
|||||||
|
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-sm font-medium text-gray-700 mb-3">
|
<label className="block text-sm font-medium text-gray-700 mb-3">
|
||||||
حالة العقار
|
{t('addProperty.furnishedStatus')}
|
||||||
</label>
|
</label>
|
||||||
<div className="flex gap-4">
|
<div className="flex gap-4">
|
||||||
<label className="flex items-center gap-2 p-3 border rounded-xl cursor-pointer hover:bg-gray-50 flex-1">
|
<label className="flex items-center gap-2 p-3 border rounded-xl cursor-pointer hover:bg-gray-50 flex-1">
|
||||||
@ -805,7 +807,7 @@ const handleMarkerDragEnd = async (lat, lng) => {
|
|||||||
onChange={() => setFormData({...formData, furnished: true})}
|
onChange={() => setFormData({...formData, furnished: true})}
|
||||||
className="w-4 h-4 text-amber-500"
|
className="w-4 h-4 text-amber-500"
|
||||||
/>
|
/>
|
||||||
<span className="text-gray-700">مفروش</span>
|
<span className="text-gray-700">{t('addProperty.furnished')}</span>
|
||||||
</label>
|
</label>
|
||||||
<label className="flex items-center gap-2 p-3 border rounded-xl cursor-pointer hover:bg-gray-50 flex-1">
|
<label className="flex items-center gap-2 p-3 border rounded-xl cursor-pointer hover:bg-gray-50 flex-1">
|
||||||
<input
|
<input
|
||||||
@ -815,21 +817,21 @@ const handleMarkerDragEnd = async (lat, lng) => {
|
|||||||
onChange={() => setFormData({...formData, furnished: false})}
|
onChange={() => setFormData({...formData, furnished: false})}
|
||||||
className="w-4 h-4 text-amber-500"
|
className="w-4 h-4 text-amber-500"
|
||||||
/>
|
/>
|
||||||
<span className="text-gray-700">غير مفروش</span>
|
<span className="text-gray-700">{t('addProperty.unfurnished')}</span>
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-sm font-medium text-gray-700 mb-2">
|
<label className="block text-sm font-medium text-gray-700 mb-2">
|
||||||
وصف إضافي (اختياري)
|
{t('addProperty.additionalDescription')}
|
||||||
</label>
|
</label>
|
||||||
<textarea
|
<textarea
|
||||||
value={formData.description}
|
value={formData.description}
|
||||||
onChange={(e) => setFormData({...formData, description: e.target.value})}
|
onChange={(e) => setFormData({...formData, description: e.target.value})}
|
||||||
rows="4"
|
rows="4"
|
||||||
className="w-full px-4 py-3 border border-gray-300 rounded-xl focus:outline-none focus:ring-2 focus:ring-amber-500"
|
className="w-full px-4 py-3 border border-gray-300 rounded-xl focus:outline-none focus:ring-2 focus:ring-amber-500"
|
||||||
placeholder="أضف وصفاً إضافياً للعقار..."
|
placeholder={t('addProperty.descriptionPlaceholder')}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</motion.div>
|
</motion.div>
|
||||||
@ -841,14 +843,14 @@ const handleMarkerDragEnd = async (lat, lng) => {
|
|||||||
<div className="w-20 h-20 bg-amber-100 rounded-2xl flex items-center justify-center mx-auto mb-4">
|
<div className="w-20 h-20 bg-amber-100 rounded-2xl flex items-center justify-center mx-auto mb-4">
|
||||||
<Layers className="w-10 h-10 text-amber-600" />
|
<Layers className="w-10 h-10 text-amber-600" />
|
||||||
</div>
|
</div>
|
||||||
<h2 className="text-2xl font-bold text-gray-900 mb-2">تفاصيل العقار</h2>
|
<h2 className="text-2xl font-bold text-gray-900 mb-2">{t('addProperty.propertyDetailsTitle')}</h2>
|
||||||
<p className="text-gray-600">أدخل التفاصيل والخدمات المتاحة</p>
|
<p className="text-gray-600">{t('addProperty.propertyDetailsSubtitle')}</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="grid grid-cols-1 md:grid-cols-4 gap-6">
|
<div className="grid grid-cols-1 md:grid-cols-4 gap-6">
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-sm font-medium text-gray-700 mb-2">
|
<label className="block text-sm font-medium text-gray-700 mb-2">
|
||||||
المساحة (م²)
|
{t('addProperty.space')}
|
||||||
</label>
|
</label>
|
||||||
<div className="relative">
|
<div className="relative">
|
||||||
<Square className="absolute right-3 top-1/2 transform -translate-y-1/2 w-4 h-4 text-gray-400" />
|
<Square className="absolute right-3 top-1/2 transform -translate-y-1/2 w-4 h-4 text-gray-400" />
|
||||||
@ -858,14 +860,14 @@ const handleMarkerDragEnd = async (lat, lng) => {
|
|||||||
value={formData.space}
|
value={formData.space}
|
||||||
onChange={(e) => setFormData({...formData, space: e.target.value})}
|
onChange={(e) => setFormData({...formData, space: e.target.value})}
|
||||||
className="w-full pr-10 pl-3 py-3 border border-gray-300 rounded-xl focus:outline-none focus:ring-2 focus:ring-amber-500"
|
className="w-full pr-10 pl-3 py-3 border border-gray-300 rounded-xl focus:outline-none focus:ring-2 focus:ring-amber-500"
|
||||||
placeholder="مثال: 120"
|
placeholder={t('addProperty.spacePlaceholder')}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-sm font-medium text-gray-700 mb-2">
|
<label className="block text-sm font-medium text-gray-700 mb-2">
|
||||||
عدد الغرف <span className="text-red-500">*</span>
|
{t('addProperty.bedrooms')} <span className="text-red-500">*</span>
|
||||||
</label>
|
</label>
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<button
|
<button
|
||||||
@ -893,7 +895,7 @@ const handleMarkerDragEnd = async (lat, lng) => {
|
|||||||
|
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-sm font-medium text-gray-700 mb-2">
|
<label className="block text-sm font-medium text-gray-700 mb-2">
|
||||||
عدد الحمامات <span className="text-red-500">*</span>
|
{t('addProperty.bathrooms')} <span className="text-red-500">*</span>
|
||||||
</label>
|
</label>
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<button
|
<button
|
||||||
@ -921,7 +923,7 @@ const handleMarkerDragEnd = async (lat, lng) => {
|
|||||||
|
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-sm font-medium text-gray-700 mb-2">
|
<label className="block text-sm font-medium text-gray-700 mb-2">
|
||||||
عدد الصالونات <span className="text-red-500">*</span>
|
{t('addProperty.livingRooms')} <span className="text-red-500">*</span>
|
||||||
</label>
|
</label>
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<button
|
<button
|
||||||
@ -950,41 +952,41 @@ const handleMarkerDragEnd = async (lat, lng) => {
|
|||||||
|
|
||||||
<div className="grid grid-cols-1 md:grid-cols-3 gap-4">
|
<div className="grid grid-cols-1 md:grid-cols-3 gap-4">
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-sm font-medium text-gray-700 mb-2">رقم الطابق</label>
|
<label className="block text-sm font-medium text-gray-700 mb-2">{t('addProperty.floorNumber')}</label>
|
||||||
<input
|
<input
|
||||||
type="number"
|
type="number"
|
||||||
value={formData.floorNumber}
|
value={formData.floorNumber}
|
||||||
onChange={(e) => setFormData({...formData, floorNumber: e.target.value})}
|
onChange={(e) => setFormData({...formData, floorNumber: e.target.value})}
|
||||||
className="w-full px-4 py-3 border border-gray-300 rounded-xl focus:outline-none focus:ring-2 focus:ring-amber-500"
|
className="w-full px-4 py-3 border border-gray-300 rounded-xl focus:outline-none focus:ring-2 focus:ring-amber-500"
|
||||||
placeholder="مثال: 3"
|
placeholder={t('addProperty.floorPlaceholder')}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-sm font-medium text-gray-700 mb-2">عدد الصالونات</label>
|
<label className="block text-sm font-medium text-gray-700 mb-2">{t('addProperty.salons')}</label>
|
||||||
<input
|
<input
|
||||||
type="number"
|
type="number"
|
||||||
min="0"
|
min="0"
|
||||||
value={formData.salons}
|
value={formData.salons}
|
||||||
onChange={(e) => setFormData({...formData, salons: e.target.value})}
|
onChange={(e) => setFormData({...formData, salons: e.target.value})}
|
||||||
className="w-full px-4 py-3 border border-gray-300 rounded-xl focus:outline-none focus:ring-2 focus:ring-amber-500"
|
className="w-full px-4 py-3 border border-gray-300 rounded-xl focus:outline-none focus:ring-2 focus:ring-amber-500"
|
||||||
placeholder="مثال: 1"
|
placeholder={t('addProperty.salonsPlaceholder')}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-sm font-medium text-gray-700 mb-2">عدد الشرفات</label>
|
<label className="block text-sm font-medium text-gray-700 mb-2">{t('addProperty.balconies')}</label>
|
||||||
<input
|
<input
|
||||||
type="number"
|
type="number"
|
||||||
min="0"
|
min="0"
|
||||||
value={formData.balconies}
|
value={formData.balconies}
|
||||||
onChange={(e) => setFormData({...formData, balconies: e.target.value})}
|
onChange={(e) => setFormData({...formData, balconies: e.target.value})}
|
||||||
className="w-full px-4 py-3 border border-gray-300 rounded-xl focus:outline-none focus:ring-2 focus:ring-amber-500"
|
className="w-full px-4 py-3 border border-gray-300 rounded-xl focus:outline-none focus:ring-2 focus:ring-amber-500"
|
||||||
placeholder="مثال: 1"
|
placeholder={t('addProperty.balconiesPlaceholder')}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<h3 className="text-lg font-bold text-gray-900 mb-4">الخدمات المتوفرة <span className="text-red-500">*</span></h3>
|
<h3 className="text-lg font-bold text-gray-900 mb-4">{t('addProperty.servicesLabel')} <span className="text-red-500">*</span></h3>
|
||||||
<div className="space-y-3">
|
<div className="space-y-3">
|
||||||
{serviceList.map((service) => {
|
{serviceList.map((service) => {
|
||||||
const Icon = service.icon;
|
const Icon = service.icon;
|
||||||
@ -1010,7 +1012,7 @@ const handleMarkerDragEnd = async (lat, lng) => {
|
|||||||
value={formData.serviceDetails[service.id] || ''}
|
value={formData.serviceDetails[service.id] || ''}
|
||||||
onChange={(e) => updateServiceDetail(service.id, e.target.value)}
|
onChange={(e) => updateServiceDetail(service.id, e.target.value)}
|
||||||
className="w-full px-3 py-2 border border-gray-200 rounded-lg text-sm focus:outline-none focus:ring-2 focus:ring-amber-500"
|
className="w-full px-3 py-2 border border-gray-200 rounded-lg text-sm focus:outline-none focus:ring-2 focus:ring-amber-500"
|
||||||
placeholder="تفاصيل الخدمة (مثال: في جميع الغرف)"
|
placeholder={t('addProperty.serviceDetailPlaceholder')}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
@ -1023,11 +1025,11 @@ const handleMarkerDragEnd = async (lat, lng) => {
|
|||||||
<div className="bg-white rounded-xl p-6 border border-gray-200 mt-6">
|
<div className="bg-white rounded-xl p-6 border border-gray-200 mt-6">
|
||||||
<h3 className="text-lg font-bold text-gray-800 mb-4 flex items-center gap-2">
|
<h3 className="text-lg font-bold text-gray-800 mb-4 flex items-center gap-2">
|
||||||
<MapPin className="w-5 h-5 text-amber-600" />
|
<MapPin className="w-5 h-5 text-amber-600" />
|
||||||
القرب من الخدمات
|
{t('addProperty.nearbyServicesTitle')}
|
||||||
</h3>
|
</h3>
|
||||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-sm font-medium text-gray-700 mb-2">المسافة إلى أقرب مدرسة (كم)</label>
|
<label className="block text-sm font-medium text-gray-700 mb-2">{t('addProperty.nearbySchoolLabel')}</label>
|
||||||
<input
|
<input
|
||||||
type="number"
|
type="number"
|
||||||
min="0"
|
min="0"
|
||||||
@ -1035,11 +1037,11 @@ const handleMarkerDragEnd = async (lat, lng) => {
|
|||||||
value={formData.nearbySchool}
|
value={formData.nearbySchool}
|
||||||
onChange={(e) => setFormData({...formData, nearbySchool: e.target.value})}
|
onChange={(e) => setFormData({...formData, nearbySchool: e.target.value})}
|
||||||
className="w-full px-4 py-3 border border-gray-300 rounded-xl focus:outline-none focus:ring-2 focus:ring-amber-500"
|
className="w-full px-4 py-3 border border-gray-300 rounded-xl focus:outline-none focus:ring-2 focus:ring-amber-500"
|
||||||
placeholder="مثال: 0.5"
|
placeholder={t('addProperty.nearbyPlaceholder')}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-sm font-medium text-gray-700 mb-2">المسافة إلى أقرب مستشفى (كم)</label>
|
<label className="block text-sm font-medium text-gray-700 mb-2">{t('addProperty.nearbyHospitalLabel')}</label>
|
||||||
<input
|
<input
|
||||||
type="number"
|
type="number"
|
||||||
min="0"
|
min="0"
|
||||||
@ -1047,11 +1049,11 @@ const handleMarkerDragEnd = async (lat, lng) => {
|
|||||||
value={formData.nearbyHospital}
|
value={formData.nearbyHospital}
|
||||||
onChange={(e) => setFormData({...formData, nearbyHospital: e.target.value})}
|
onChange={(e) => setFormData({...formData, nearbyHospital: e.target.value})}
|
||||||
className="w-full px-4 py-3 border border-gray-300 rounded-xl focus:outline-none focus:ring-2 focus:ring-amber-500"
|
className="w-full px-4 py-3 border border-gray-300 rounded-xl focus:outline-none focus:ring-2 focus:ring-amber-500"
|
||||||
placeholder="مثال: 1.0"
|
placeholder={t('addProperty.nearbyPlaceholder')}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-sm font-medium text-gray-700 mb-2">المسافة إلى أقرب مطعم (كم)</label>
|
<label className="block text-sm font-medium text-gray-700 mb-2">{t('addProperty.nearbyRestaurantLabel')}</label>
|
||||||
<input
|
<input
|
||||||
type="number"
|
type="number"
|
||||||
min="0"
|
min="0"
|
||||||
@ -1059,11 +1061,11 @@ const handleMarkerDragEnd = async (lat, lng) => {
|
|||||||
value={formData.nearbyRestaurant}
|
value={formData.nearbyRestaurant}
|
||||||
onChange={(e) => setFormData({...formData, nearbyRestaurant: e.target.value})}
|
onChange={(e) => setFormData({...formData, nearbyRestaurant: e.target.value})}
|
||||||
className="w-full px-4 py-3 border border-gray-300 rounded-xl focus:outline-none focus:ring-2 focus:ring-amber-500"
|
className="w-full px-4 py-3 border border-gray-300 rounded-xl focus:outline-none focus:ring-2 focus:ring-amber-500"
|
||||||
placeholder="مثال: 0.3"
|
placeholder={t('addProperty.nearbyPlaceholder')}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-sm font-medium text-gray-700 mb-2">المسافة إلى أقرب جامعة (كم)</label>
|
<label className="block text-sm font-medium text-gray-700 mb-2">{t('addProperty.nearbyUniversityLabel')}</label>
|
||||||
<input
|
<input
|
||||||
type="number"
|
type="number"
|
||||||
min="0"
|
min="0"
|
||||||
@ -1071,11 +1073,11 @@ const handleMarkerDragEnd = async (lat, lng) => {
|
|||||||
value={formData.nearbyUniversity}
|
value={formData.nearbyUniversity}
|
||||||
onChange={(e) => setFormData({...formData, nearbyUniversity: e.target.value})}
|
onChange={(e) => setFormData({...formData, nearbyUniversity: e.target.value})}
|
||||||
className="w-full px-4 py-3 border border-gray-300 rounded-xl focus:outline-none focus:ring-2 focus:ring-amber-500"
|
className="w-full px-4 py-3 border border-gray-300 rounded-xl focus:outline-none focus:ring-2 focus:ring-amber-500"
|
||||||
placeholder="مثال: 2.0"
|
placeholder={t('addProperty.nearbyPlaceholder')}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-sm font-medium text-gray-700 mb-2">المسافة إلى أقرب حديقة (كم)</label>
|
<label className="block text-sm font-medium text-gray-700 mb-2">{t('addProperty.nearbyParkLabel')}</label>
|
||||||
<input
|
<input
|
||||||
type="number"
|
type="number"
|
||||||
min="0"
|
min="0"
|
||||||
@ -1083,11 +1085,11 @@ const handleMarkerDragEnd = async (lat, lng) => {
|
|||||||
value={formData.nearbyPark}
|
value={formData.nearbyPark}
|
||||||
onChange={(e) => setFormData({...formData, nearbyPark: e.target.value})}
|
onChange={(e) => setFormData({...formData, nearbyPark: e.target.value})}
|
||||||
className="w-full px-4 py-3 border border-gray-300 rounded-xl focus:outline-none focus:ring-2 focus:ring-amber-500"
|
className="w-full px-4 py-3 border border-gray-300 rounded-xl focus:outline-none focus:ring-2 focus:ring-amber-500"
|
||||||
placeholder="مثال: 0.5"
|
placeholder={t('addProperty.nearbyPlaceholder')}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-sm font-medium text-gray-700 mb-2">المسافة إلى أقرب مول (كم)</label>
|
<label className="block text-sm font-medium text-gray-700 mb-2">{t('addProperty.nearbyMallLabel')}</label>
|
||||||
<input
|
<input
|
||||||
type="number"
|
type="number"
|
||||||
min="0"
|
min="0"
|
||||||
@ -1095,7 +1097,7 @@ const handleMarkerDragEnd = async (lat, lng) => {
|
|||||||
value={formData.nearbyMall}
|
value={formData.nearbyMall}
|
||||||
onChange={(e) => setFormData({...formData, nearbyMall: e.target.value})}
|
onChange={(e) => setFormData({...formData, nearbyMall: e.target.value})}
|
||||||
className="w-full px-4 py-3 border border-gray-300 rounded-xl focus:outline-none focus:ring-2 focus:ring-amber-500"
|
className="w-full px-4 py-3 border border-gray-300 rounded-xl focus:outline-none focus:ring-2 focus:ring-amber-500"
|
||||||
placeholder="مثال: 1.5"
|
placeholder={t('addProperty.nearbyPlaceholder')}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -1103,7 +1105,7 @@ const handleMarkerDragEnd = async (lat, lng) => {
|
|||||||
|
|
||||||
{purpose === 'rent' && (
|
{purpose === 'rent' && (
|
||||||
<div>
|
<div>
|
||||||
<h3 className="text-lg font-bold text-gray-900 mb-4">شروط استخدام العقار</h3>
|
<h3 className="text-lg font-bold text-gray-900 mb-4">{t('addProperty.termsTitle')}</h3>
|
||||||
<div className="grid grid-cols-2 md:grid-cols-3 gap-3">
|
<div className="grid grid-cols-2 md:grid-cols-3 gap-3">
|
||||||
{termsList.map((term) => {
|
{termsList.map((term) => {
|
||||||
const Icon = term.icon;
|
const Icon = term.icon;
|
||||||
@ -1137,14 +1139,14 @@ const handleMarkerDragEnd = async (lat, lng) => {
|
|||||||
|
|
||||||
{/* Custom Terms */}
|
{/* Custom Terms */}
|
||||||
<div className="mt-4 p-4 border border-dashed border-gray-300 rounded-xl">
|
<div className="mt-4 p-4 border border-dashed border-gray-300 rounded-xl">
|
||||||
<p className="text-sm font-medium text-gray-700 mb-2">إضافة شرط مخصص</p>
|
<p className="text-sm font-medium text-gray-700 mb-2">{t('addProperty.customTermsTitle')}</p>
|
||||||
<div className="flex gap-2">
|
<div className="flex gap-2">
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
value={customTermInput}
|
value={customTermInput}
|
||||||
onChange={(e) => setCustomTermInput(e.target.value)}
|
onChange={(e) => setCustomTermInput(e.target.value)}
|
||||||
onKeyDown={(e) => { if (e.key === 'Enter') { e.preventDefault(); addCustomTerm(); } }}
|
onKeyDown={(e) => { if (e.key === 'Enter') { e.preventDefault(); addCustomTerm(); } }}
|
||||||
placeholder="اكتب شرطاً مخصصاً..."
|
placeholder={t('addProperty.customTermPlaceholder')}
|
||||||
className="flex-1 px-3 py-2 border border-gray-300 rounded-lg text-sm focus:ring-2 focus:ring-amber-500 focus:border-transparent outline-none"
|
className="flex-1 px-3 py-2 border border-gray-300 rounded-lg text-sm focus:ring-2 focus:ring-amber-500 focus:border-transparent outline-none"
|
||||||
/>
|
/>
|
||||||
<button
|
<button
|
||||||
@ -1153,7 +1155,7 @@ const handleMarkerDragEnd = async (lat, lng) => {
|
|||||||
disabled={!customTermInput.trim()}
|
disabled={!customTermInput.trim()}
|
||||||
className="px-4 py-2 bg-amber-500 text-white rounded-lg text-sm font-medium hover:bg-amber-600 disabled:bg-gray-300 disabled:cursor-not-allowed transition-colors"
|
className="px-4 py-2 bg-amber-500 text-white rounded-lg text-sm font-medium hover:bg-amber-600 disabled:bg-gray-300 disabled:cursor-not-allowed transition-colors"
|
||||||
>
|
>
|
||||||
إضافة
|
{t('addProperty.add')}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
{customTerms.length > 0 && (
|
{customTerms.length > 0 && (
|
||||||
@ -1187,13 +1189,13 @@ const handleMarkerDragEnd = async (lat, lng) => {
|
|||||||
<div className="w-20 h-20 bg-amber-100 rounded-2xl flex items-center justify-center mx-auto mb-4">
|
<div className="w-20 h-20 bg-amber-100 rounded-2xl flex items-center justify-center mx-auto mb-4">
|
||||||
<DollarSign className="w-10 h-10 text-amber-600" />
|
<DollarSign className="w-10 h-10 text-amber-600" />
|
||||||
</div>
|
</div>
|
||||||
<h2 className="text-2xl font-bold text-gray-900 mb-2">سعر البيع</h2>
|
<h2 className="text-2xl font-bold text-gray-900 mb-2">{t('addProperty.salePriceTitle')}</h2>
|
||||||
<p className="text-gray-600">حدد سعر البيع والعملة</p>
|
<p className="text-gray-600">{t('addProperty.salePriceSubtitle')}</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-sm font-medium text-gray-700 mb-2">
|
<label className="block text-sm font-medium text-gray-700 mb-2">
|
||||||
سعر البيع (ل.س) <span className="text-red-500">*</span>
|
{t('addProperty.salePriceLabel')} <span className="text-red-500">*</span>
|
||||||
</label>
|
</label>
|
||||||
<div className="relative">
|
<div className="relative">
|
||||||
<span className="absolute right-3 top-1/2 transform -translate-y-1/2 w-5 h-5 text-gray-400 font-bold text-sm flex items-center justify-center">{currencySymbol}</span>
|
<span className="absolute right-3 top-1/2 transform -translate-y-1/2 w-5 h-5 text-gray-400 font-bold text-sm flex items-center justify-center">{currencySymbol}</span>
|
||||||
@ -1211,7 +1213,7 @@ const handleMarkerDragEnd = async (lat, lng) => {
|
|||||||
className={`w-full pr-12 pl-4 py-3 border rounded-xl focus:outline-none focus:ring-2 focus:ring-amber-500 ${
|
className={`w-full pr-12 pl-4 py-3 border rounded-xl focus:outline-none focus:ring-2 focus:ring-amber-500 ${
|
||||||
errors.salePrice ? 'border-red-500' : 'border-gray-300'
|
errors.salePrice ? 'border-red-500' : 'border-gray-300'
|
||||||
}`}
|
}`}
|
||||||
placeholder="مثال: 50000000"
|
placeholder={t('addProperty.salePricePlaceholder')}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
{errors.salePrice && <p className="text-red-500 text-sm mt-1">{errors.salePrice}</p>}
|
{errors.salePrice && <p className="text-red-500 text-sm mt-1">{errors.salePrice}</p>}
|
||||||
@ -1219,15 +1221,15 @@ const handleMarkerDragEnd = async (lat, lng) => {
|
|||||||
|
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-sm font-medium text-gray-700 mb-2">
|
<label className="block text-sm font-medium text-gray-700 mb-2">
|
||||||
العملة <span className="text-red-500">*</span>
|
{t('addProperty.currencyLabel')} <span className="text-red-500">*</span>
|
||||||
</label>
|
</label>
|
||||||
<select
|
<select
|
||||||
value={selectedCurrencyId}
|
value={selectedCurrencyId}
|
||||||
onChange={(e) => setSelectedCurrencyId(parseInt(e.target.value))}
|
onChange={(e) => setSelectedCurrencyId(parseInt(e.target.value))}
|
||||||
className="w-full px-4 py-3 border border-gray-300 rounded-xl focus:outline-none focus:ring-2 focus:ring-amber-500"
|
className="w-full px-4 py-3 border border-gray-300 rounded-xl focus:outline-none focus:ring-2 focus:ring-amber-500"
|
||||||
>
|
>
|
||||||
<option value={Currency.USD}>دولار امريكي $</option>
|
<option value={Currency.USD}>{t('addProperty.usdOption')}</option>
|
||||||
<option value={Currency.SYP}>عملة سورية SP</option>
|
<option value={Currency.SYP}>{t('addProperty.sypOption')}</option>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
</motion.div>
|
</motion.div>
|
||||||
@ -1239,13 +1241,13 @@ const handleMarkerDragEnd = async (lat, lng) => {
|
|||||||
<div className="w-20 h-20 bg-amber-100 rounded-2xl flex items-center justify-center mx-auto mb-4">
|
<div className="w-20 h-20 bg-amber-100 rounded-2xl flex items-center justify-center mx-auto mb-4">
|
||||||
<DollarSign className="w-10 h-10 text-amber-600" />
|
<DollarSign className="w-10 h-10 text-amber-600" />
|
||||||
</div>
|
</div>
|
||||||
<h2 className="text-2xl font-bold text-gray-900 mb-2">نوع العرض والسعر</h2>
|
<h2 className="text-2xl font-bold text-gray-900 mb-2">{t('addProperty.offerAndPriceTitle')}</h2>
|
||||||
<p className="text-gray-600">اختر نوع العرض وحدد السعر المناسب</p>
|
<p className="text-gray-600">{t('addProperty.offerAndPriceSubtitle')}</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-sm font-medium text-gray-700 mb-3">
|
<label className="block text-sm font-medium text-gray-700 mb-3">
|
||||||
نوع العرض <span className="text-red-500">*</span>
|
{t('addProperty.offerTypeLabel')} <span className="text-red-500">*</span>
|
||||||
</label>
|
</label>
|
||||||
<div className="grid grid-cols-2 md:grid-cols-4 gap-3">
|
<div className="grid grid-cols-2 md:grid-cols-4 gap-3">
|
||||||
{offerTypes.map((type) => {
|
{offerTypes.map((type) => {
|
||||||
@ -1272,15 +1274,15 @@ const handleMarkerDragEnd = async (lat, lng) => {
|
|||||||
{/* Currency dropdown */}
|
{/* Currency dropdown */}
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-sm font-medium text-gray-700 mb-2">
|
<label className="block text-sm font-medium text-gray-700 mb-2">
|
||||||
العملة <span className="text-red-500">*</span>
|
{t('addProperty.currencyLabel')} <span className="text-red-500">*</span>
|
||||||
</label>
|
</label>
|
||||||
<select
|
<select
|
||||||
value={selectedCurrencyId}
|
value={selectedCurrencyId}
|
||||||
onChange={(e) => setSelectedCurrencyId(parseInt(e.target.value))}
|
onChange={(e) => setSelectedCurrencyId(parseInt(e.target.value))}
|
||||||
className="w-full px-4 py-3 border border-gray-300 rounded-xl focus:outline-none focus:ring-2 focus:ring-amber-500"
|
className="w-full px-4 py-3 border border-gray-300 rounded-xl focus:outline-none focus:ring-2 focus:ring-amber-500"
|
||||||
>
|
>
|
||||||
<option value={Currency.USD}>دولار امريكي $</option>
|
<option value={Currency.USD}>{t('addProperty.usdOption')}</option>
|
||||||
<option value={Currency.SYP}>عملة سورية SP</option>
|
<option value={Currency.SYP}>{t('addProperty.sypOption')}</option>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -1292,7 +1294,7 @@ const handleMarkerDragEnd = async (lat, lng) => {
|
|||||||
{!formData.offerType && (
|
{!formData.offerType && (
|
||||||
<div className="border-2 border-dashed border-gray-200 rounded-2xl p-8 text-center">
|
<div className="border-2 border-dashed border-gray-200 rounded-2xl p-8 text-center">
|
||||||
<DollarSign className="w-8 h-8 text-gray-300 mx-auto mb-3" />
|
<DollarSign className="w-8 h-8 text-gray-300 mx-auto mb-3" />
|
||||||
<p className="text-gray-400 font-medium">اختر نوع العرض اولا</p>
|
<p className="text-gray-400 font-medium">{t('addProperty.selectOfferTypeFirst')}</p>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
@ -1307,7 +1309,7 @@ const handleMarkerDragEnd = async (lat, lng) => {
|
|||||||
>
|
>
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-sm font-medium text-gray-700 mb-2">
|
<label className="block text-sm font-medium text-gray-700 mb-2">
|
||||||
سعر الايجار اليومي ({currencySymbol}) <span className="text-red-500">*</span>
|
{t('addProperty.dailyPriceLabel')} ({currencySymbol}) <span className="text-red-500">*</span>
|
||||||
</label>
|
</label>
|
||||||
<div className="relative">
|
<div className="relative">
|
||||||
<span className="absolute right-3 top-1/2 transform -translate-y-1/2 w-5 h-5 text-gray-400 font-bold text-sm flex items-center justify-center">{currencySymbol}</span>
|
<span className="absolute right-3 top-1/2 transform -translate-y-1/2 w-5 h-5 text-gray-400 font-bold text-sm flex items-center justify-center">{currencySymbol}</span>
|
||||||
@ -1325,7 +1327,7 @@ const handleMarkerDragEnd = async (lat, lng) => {
|
|||||||
className={`w-full pr-12 pl-4 py-3 border rounded-xl focus:outline-none focus:ring-2 focus:ring-amber-500 ${
|
className={`w-full pr-12 pl-4 py-3 border rounded-xl focus:outline-none focus:ring-2 focus:ring-amber-500 ${
|
||||||
errors.dailyPrice ? 'border-red-500' : 'border-gray-300'
|
errors.dailyPrice ? 'border-red-500' : 'border-gray-300'
|
||||||
}`}
|
}`}
|
||||||
placeholder="مثال: 50000"
|
placeholder={t('addProperty.dailyPricePlaceholder')}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
{errors.dailyPrice && (
|
{errors.dailyPrice && (
|
||||||
@ -1334,7 +1336,7 @@ const handleMarkerDragEnd = async (lat, lng) => {
|
|||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-sm font-medium text-gray-700 mb-2">
|
<label className="block text-sm font-medium text-gray-700 mb-2">
|
||||||
مبلغ التأمين ({currencySymbol})
|
{t('addProperty.depositLabel')} ({currencySymbol})
|
||||||
</label>
|
</label>
|
||||||
<div className="relative">
|
<div className="relative">
|
||||||
<span className="absolute right-3 top-1/2 transform -translate-y-1/2 w-5 h-5 text-gray-400 font-bold text-sm flex items-center justify-center">{currencySymbol}</span>
|
<span className="absolute right-3 top-1/2 transform -translate-y-1/2 w-5 h-5 text-gray-400 font-bold text-sm flex items-center justify-center">{currencySymbol}</span>
|
||||||
@ -1350,7 +1352,7 @@ const handleMarkerDragEnd = async (lat, lng) => {
|
|||||||
}}
|
}}
|
||||||
onKeyDown={(e) => { if (['-', 'e', 'E', '+'].includes(e.key)) e.preventDefault() }}
|
onKeyDown={(e) => { if (['-', 'e', 'E', '+'].includes(e.key)) e.preventDefault() }}
|
||||||
className="w-full pr-12 pl-4 py-3 border border-gray-300 rounded-xl focus:outline-none focus:ring-2 focus:ring-amber-500"
|
className="w-full pr-12 pl-4 py-3 border border-gray-300 rounded-xl focus:outline-none focus:ring-2 focus:ring-amber-500"
|
||||||
placeholder="مثال: 500000"
|
placeholder={t('addProperty.depositPlaceholder')}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -1367,7 +1369,7 @@ const handleMarkerDragEnd = async (lat, lng) => {
|
|||||||
>
|
>
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-sm font-medium text-gray-700 mb-2">
|
<label className="block text-sm font-medium text-gray-700 mb-2">
|
||||||
سعر الايجار الشهري ({currencySymbol}) <span className="text-red-500">*</span>
|
{t('addProperty.monthlyPriceLabel')} ({currencySymbol}) <span className="text-red-500">*</span>
|
||||||
</label>
|
</label>
|
||||||
<div className="relative">
|
<div className="relative">
|
||||||
<span className="absolute right-3 top-1/2 transform -translate-y-1/2 w-5 h-5 text-gray-400 font-bold text-sm flex items-center justify-center">{currencySymbol}</span>
|
<span className="absolute right-3 top-1/2 transform -translate-y-1/2 w-5 h-5 text-gray-400 font-bold text-sm flex items-center justify-center">{currencySymbol}</span>
|
||||||
@ -1385,7 +1387,7 @@ const handleMarkerDragEnd = async (lat, lng) => {
|
|||||||
className={`w-full pr-12 pl-4 py-3 border rounded-xl focus:outline-none focus:ring-2 focus:ring-amber-500 ${
|
className={`w-full pr-12 pl-4 py-3 border rounded-xl focus:outline-none focus:ring-2 focus:ring-amber-500 ${
|
||||||
errors.monthlyPrice ? 'border-red-500' : 'border-gray-300'
|
errors.monthlyPrice ? 'border-red-500' : 'border-gray-300'
|
||||||
}`}
|
}`}
|
||||||
placeholder="مثال: 1000000"
|
placeholder={t('addProperty.monthlyPricePlaceholder')}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
{errors.monthlyPrice && (
|
{errors.monthlyPrice && (
|
||||||
@ -1394,7 +1396,7 @@ const handleMarkerDragEnd = async (lat, lng) => {
|
|||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-sm font-medium text-gray-700 mb-2">
|
<label className="block text-sm font-medium text-gray-700 mb-2">
|
||||||
مبلغ التأمين ({currencySymbol})
|
{t('addProperty.depositLabel')} ({currencySymbol})
|
||||||
</label>
|
</label>
|
||||||
<div className="relative">
|
<div className="relative">
|
||||||
<span className="absolute right-3 top-1/2 transform -translate-y-1/2 w-5 h-5 text-gray-400 font-bold text-sm flex items-center justify-center">{currencySymbol}</span>
|
<span className="absolute right-3 top-1/2 transform -translate-y-1/2 w-5 h-5 text-gray-400 font-bold text-sm flex items-center justify-center">{currencySymbol}</span>
|
||||||
@ -1410,7 +1412,7 @@ const handleMarkerDragEnd = async (lat, lng) => {
|
|||||||
}}
|
}}
|
||||||
onKeyDown={(e) => { if (['-', 'e', 'E', '+'].includes(e.key)) e.preventDefault() }}
|
onKeyDown={(e) => { if (['-', 'e', 'E', '+'].includes(e.key)) e.preventDefault() }}
|
||||||
className="w-full pr-12 pl-4 py-3 border border-gray-300 rounded-xl focus:outline-none focus:ring-2 focus:ring-amber-500"
|
className="w-full pr-12 pl-4 py-3 border border-gray-300 rounded-xl focus:outline-none focus:ring-2 focus:ring-amber-500"
|
||||||
placeholder="مثال: 500000"
|
placeholder={t('addProperty.depositPlaceholder')}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -1427,7 +1429,7 @@ const handleMarkerDragEnd = async (lat, lng) => {
|
|||||||
>
|
>
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-sm font-medium text-gray-700 mb-2">
|
<label className="block text-sm font-medium text-gray-700 mb-2">
|
||||||
سعر الايجار اليومي ({currencySymbol}) <span className="text-red-500">*</span>
|
{t('addProperty.dailyPriceLabel')} ({currencySymbol}) <span className="text-red-500">*</span>
|
||||||
</label>
|
</label>
|
||||||
<div className="relative">
|
<div className="relative">
|
||||||
<span className="absolute right-3 top-1/2 transform -translate-y-1/2 w-5 h-5 text-gray-400 font-bold text-sm flex items-center justify-center">{currencySymbol}</span>
|
<span className="absolute right-3 top-1/2 transform -translate-y-1/2 w-5 h-5 text-gray-400 font-bold text-sm flex items-center justify-center">{currencySymbol}</span>
|
||||||
@ -1445,7 +1447,7 @@ const handleMarkerDragEnd = async (lat, lng) => {
|
|||||||
className={`w-full pr-12 pl-4 py-3 border rounded-xl focus:outline-none focus:ring-2 focus:ring-amber-500 ${
|
className={`w-full pr-12 pl-4 py-3 border rounded-xl focus:outline-none focus:ring-2 focus:ring-amber-500 ${
|
||||||
errors.dailyPrice ? 'border-red-500' : 'border-gray-300'
|
errors.dailyPrice ? 'border-red-500' : 'border-gray-300'
|
||||||
}`}
|
}`}
|
||||||
placeholder="مثال: 50000"
|
placeholder={t('addProperty.dailyPricePlaceholder')}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
{errors.dailyPrice && (
|
{errors.dailyPrice && (
|
||||||
@ -1454,7 +1456,7 @@ const handleMarkerDragEnd = async (lat, lng) => {
|
|||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-sm font-medium text-gray-700 mb-2">
|
<label className="block text-sm font-medium text-gray-700 mb-2">
|
||||||
سعر الايجار الشهري ({currencySymbol}) <span className="text-red-500">*</span>
|
{t('addProperty.monthlyPriceLabel')} ({currencySymbol}) <span className="text-red-500">*</span>
|
||||||
</label>
|
</label>
|
||||||
<div className="relative">
|
<div className="relative">
|
||||||
<span className="absolute right-3 top-1/2 transform -translate-y-1/2 w-5 h-5 text-gray-400 font-bold text-sm flex items-center justify-center">{currencySymbol}</span>
|
<span className="absolute right-3 top-1/2 transform -translate-y-1/2 w-5 h-5 text-gray-400 font-bold text-sm flex items-center justify-center">{currencySymbol}</span>
|
||||||
@ -1472,7 +1474,7 @@ const handleMarkerDragEnd = async (lat, lng) => {
|
|||||||
className={`w-full pr-12 pl-4 py-3 border rounded-xl focus:outline-none focus:ring-2 focus:ring-amber-500 ${
|
className={`w-full pr-12 pl-4 py-3 border rounded-xl focus:outline-none focus:ring-2 focus:ring-amber-500 ${
|
||||||
errors.monthlyPrice ? 'border-red-500' : 'border-gray-300'
|
errors.monthlyPrice ? 'border-red-500' : 'border-gray-300'
|
||||||
}`}
|
}`}
|
||||||
placeholder="مثال: 1000000"
|
placeholder={t('addProperty.monthlyPricePlaceholder')}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
{errors.monthlyPrice && (
|
{errors.monthlyPrice && (
|
||||||
@ -1481,7 +1483,7 @@ const handleMarkerDragEnd = async (lat, lng) => {
|
|||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-sm font-medium text-gray-700 mb-2">
|
<label className="block text-sm font-medium text-gray-700 mb-2">
|
||||||
مبلغ التأمين ({currencySymbol})
|
{t('addProperty.depositLabel')} ({currencySymbol})
|
||||||
</label>
|
</label>
|
||||||
<div className="relative">
|
<div className="relative">
|
||||||
<span className="absolute right-3 top-1/2 transform -translate-y-1/2 w-5 h-5 text-gray-400 font-bold text-sm flex items-center justify-center">{currencySymbol}</span>
|
<span className="absolute right-3 top-1/2 transform -translate-y-1/2 w-5 h-5 text-gray-400 font-bold text-sm flex items-center justify-center">{currencySymbol}</span>
|
||||||
@ -1497,7 +1499,7 @@ const handleMarkerDragEnd = async (lat, lng) => {
|
|||||||
}}
|
}}
|
||||||
onKeyDown={(e) => { if (['-', 'e', 'E', '+'].includes(e.key)) e.preventDefault() }}
|
onKeyDown={(e) => { if (['-', 'e', 'E', '+'].includes(e.key)) e.preventDefault() }}
|
||||||
className="w-full pr-12 pl-4 py-3 border border-gray-300 rounded-xl focus:outline-none focus:ring-2 focus:ring-amber-500"
|
className="w-full pr-12 pl-4 py-3 border border-gray-300 rounded-xl focus:outline-none focus:ring-2 focus:ring-amber-500"
|
||||||
placeholder="مثال: 500000"
|
placeholder={t('addProperty.depositPlaceholder')}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -1513,12 +1515,12 @@ const handleMarkerDragEnd = async (lat, lng) => {
|
|||||||
<div className="w-20 h-20 bg-amber-100 rounded-2xl flex items-center justify-center mx-auto mb-4">
|
<div className="w-20 h-20 bg-amber-100 rounded-2xl flex items-center justify-center mx-auto mb-4">
|
||||||
<MapPin className="w-10 h-10 text-amber-600" />
|
<MapPin className="w-10 h-10 text-amber-600" />
|
||||||
</div>
|
</div>
|
||||||
<h2 className="text-2xl font-bold text-gray-900 mb-2">الموقع والصور</h2>
|
<h2 className="text-2xl font-bold text-gray-900 mb-2">{t('addProperty.locationAndImagesTitle')}</h2>
|
||||||
<p className="text-gray-600">حدد موقع العقار وأضف الصور</p>
|
<p className="text-gray-600">{t('addProperty.locationAndImagesSubtitle')}</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<h3 className="text-lg font-bold text-gray-900 mb-4">حدد موقع العقار على الخريطة</h3>
|
<h3 className="text-lg font-bold text-gray-900 mb-4">{t('addProperty.selectLocationTitle')}</h3>
|
||||||
|
|
||||||
<div className="flex gap-2 mb-4">
|
<div className="flex gap-2 mb-4">
|
||||||
<div className="flex-1 relative">
|
<div className="flex-1 relative">
|
||||||
@ -1527,7 +1529,7 @@ const handleMarkerDragEnd = async (lat, lng) => {
|
|||||||
value={searchQuery}
|
value={searchQuery}
|
||||||
onChange={(e) => setSearchQuery(e.target.value)}
|
onChange={(e) => setSearchQuery(e.target.value)}
|
||||||
onKeyPress={(e) => e.key === 'Enter' && handleSearch()}
|
onKeyPress={(e) => e.key === 'Enter' && handleSearch()}
|
||||||
placeholder="ابحث عن عنوان..."
|
placeholder={t('addProperty.searchAddress')}
|
||||||
className="w-full px-4 py-3 border border-gray-300 rounded-xl focus:outline-none focus:ring-2 focus:ring-amber-500 pr-12"
|
className="w-full px-4 py-3 border border-gray-300 rounded-xl focus:outline-none focus:ring-2 focus:ring-amber-500 pr-12"
|
||||||
/>
|
/>
|
||||||
<Search className="absolute left-4 top-1/2 transform -translate-y-1/2 w-5 h-5 text-gray-400" />
|
<Search className="absolute left-4 top-1/2 transform -translate-y-1/2 w-5 h-5 text-gray-400" />
|
||||||
@ -1536,16 +1538,16 @@ const handleMarkerDragEnd = async (lat, lng) => {
|
|||||||
onClick={handleSearch}
|
onClick={handleSearch}
|
||||||
className="px-6 py-3 bg-amber-500 text-white rounded-xl hover:bg-amber-600 transition-colors"
|
className="px-6 py-3 bg-amber-500 text-white rounded-xl hover:bg-amber-600 transition-colors"
|
||||||
>
|
>
|
||||||
بحث
|
{t('addProperty.searchButton')}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="bg-amber-50 border border-amber-200 rounded-xl p-4 mb-4 text-sm text-amber-800 leading-relaxed">
|
<div className="bg-amber-50 border border-amber-200 rounded-xl p-4 mb-4 text-sm text-amber-800 leading-relaxed">
|
||||||
<Search className="w-4 h-4 inline ml-1" />
|
<Search className="w-4 h-4 inline ml-1" />
|
||||||
ابحث أولاً ثم اضغط على النتيجة لتحريك الخريطة و تثبيت العلامة .
|
{t('addProperty.searchHint')}
|
||||||
<br />
|
<br />
|
||||||
<MapPin className="w-4 h-4 inline ml-1" />
|
<MapPin className="w-4 h-4 inline ml-1" />
|
||||||
اضغط على الخريطة لتحديد موقع العقار
|
{t('addProperty.clickMapHint')}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="relative w-full h-96 rounded-xl overflow-hidden border-2 border-gray-200 mb-4">
|
<div className="relative w-full h-96 rounded-xl overflow-hidden border-2 border-gray-200 mb-4">
|
||||||
@ -1564,7 +1566,7 @@ const handleMarkerDragEnd = async (lat, lng) => {
|
|||||||
className="w-full mb-4 bg-green-500 text-white py-3 rounded-xl font-medium hover:bg-green-600 transition-colors flex items-center justify-center gap-2"
|
className="w-full mb-4 bg-green-500 text-white py-3 rounded-xl font-medium hover:bg-green-600 transition-colors flex items-center justify-center gap-2"
|
||||||
>
|
>
|
||||||
<CheckCircle className="w-5 h-5" />
|
<CheckCircle className="w-5 h-5" />
|
||||||
تأكيد هذا الموقع
|
{t('addProperty.confirmLocation')}
|
||||||
</button>
|
</button>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
@ -1572,7 +1574,7 @@ const handleMarkerDragEnd = async (lat, lng) => {
|
|||||||
<div className="bg-green-50 border border-green-200 rounded-xl p-4 mb-4">
|
<div className="bg-green-50 border border-green-200 rounded-xl p-4 mb-4">
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<CheckCircle className="w-5 h-5 text-green-600" />
|
<CheckCircle className="w-5 h-5 text-green-600" />
|
||||||
<span className="text-green-800 font-medium">تم تأكيد الموقع:</span>
|
<span className="text-green-800 font-medium">{t('addProperty.locationConfirmed')}:</span>
|
||||||
</div>
|
</div>
|
||||||
<p className="text-green-700 text-sm mt-2 line-clamp-2">{formData.address}</p>
|
<p className="text-green-700 text-sm mt-2 line-clamp-2">{formData.address}</p>
|
||||||
</div>
|
</div>
|
||||||
@ -1584,7 +1586,7 @@ const handleMarkerDragEnd = async (lat, lng) => {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<h3 className="text-lg font-bold text-gray-900 mb-4">صور العقار</h3>
|
<h3 className="text-lg font-bold text-gray-900 mb-4">{t('addProperty.imagesTitle')}</h3>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
onClick={() => fileInputRef.current?.click()}
|
onClick={() => fileInputRef.current?.click()}
|
||||||
@ -1602,16 +1604,16 @@ const handleMarkerDragEnd = async (lat, lng) => {
|
|||||||
/>
|
/>
|
||||||
|
|
||||||
<Upload className="w-12 h-12 text-gray-400 mx-auto mb-3" />
|
<Upload className="w-12 h-12 text-gray-400 mx-auto mb-3" />
|
||||||
<p className="text-gray-600 font-medium">اضغط لرفع الصور</p>
|
<p className="text-gray-600 font-medium">{t('addProperty.uploadImages')}</p>
|
||||||
<p className="text-xs text-gray-500 mt-2">
|
<p className="text-xs text-gray-500 mt-2">
|
||||||
JPEG, PNG, JPG • حتى 5MB • 800x600 بكسل • 2-5 صور
|
{t('addProperty.imageFormatHint')}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<p className="text-sm text-gray-500 mt-2 mb-4 text-center">
|
<p className="text-sm text-gray-500 mt-2 mb-4 text-center">
|
||||||
يرجى رفع عدة صور واضحة
|
{t('addProperty.uploadMultipleHintLine1')}
|
||||||
<br />
|
<br />
|
||||||
(اضغط على الرفع أكثر من مرة)
|
{t('addProperty.uploadMultipleHintLine2')}
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
{errors.images && (
|
{errors.images && (
|
||||||
@ -1649,8 +1651,7 @@ const handleMarkerDragEnd = async (lat, lng) => {
|
|||||||
<div className="border-t border-gray-200 pt-6 mt-8">
|
<div className="border-t border-gray-200 pt-6 mt-8">
|
||||||
<div className="bg-gray-50 rounded-2xl p-5 border border-gray-200">
|
<div className="bg-gray-50 rounded-2xl p-5 border border-gray-200">
|
||||||
<p className="text-sm text-gray-700 leading-relaxed mb-4">
|
<p className="text-sm text-gray-700 leading-relaxed mb-4">
|
||||||
أنا صاحب العقار أقر أنني مالك العقار أو مخول قانونياً بعرضه للإيجار أو البيع،
|
{t('addProperty.ownerAgreement')}
|
||||||
وأن جميع المعلومات المدخلة صحيحة، وأتحمل كامل المسؤولية عن أي معلومات غير صحيحة.
|
|
||||||
</p>
|
</p>
|
||||||
<label className="flex items-start gap-3 cursor-pointer">
|
<label className="flex items-start gap-3 cursor-pointer">
|
||||||
<input
|
<input
|
||||||
@ -1660,9 +1661,9 @@ const handleMarkerDragEnd = async (lat, lng) => {
|
|||||||
className="w-5 h-5 mt-0.5 text-amber-500 rounded shrink-0"
|
className="w-5 h-5 mt-0.5 text-amber-500 rounded shrink-0"
|
||||||
/>
|
/>
|
||||||
<span className="text-sm text-gray-600">
|
<span className="text-sm text-gray-600">
|
||||||
أوافق على{' '}
|
{t('addProperty.agreeToText')}{' '}
|
||||||
<Link href="/terms" target="_blank" className="text-amber-600 underline hover:text-amber-700">
|
<Link href="/terms" target="_blank" className="text-amber-600 underline hover:text-amber-700">
|
||||||
شروط الاستخدام
|
{t('addProperty.termsOfUseText')}
|
||||||
</Link>
|
</Link>
|
||||||
</span>
|
</span>
|
||||||
</label>
|
</label>
|
||||||
@ -1680,7 +1681,7 @@ const handleMarkerDragEnd = async (lat, lng) => {
|
|||||||
className="flex-1 py-3 px-4 bg-gray-100 text-gray-700 rounded-xl font-medium hover:bg-gray-200 transition-colors flex items-center justify-center gap-2"
|
className="flex-1 py-3 px-4 bg-gray-100 text-gray-700 rounded-xl font-medium hover:bg-gray-200 transition-colors flex items-center justify-center gap-2"
|
||||||
>
|
>
|
||||||
<ChevronRight className="w-5 h-5" />
|
<ChevronRight className="w-5 h-5" />
|
||||||
السابق
|
{t('addProperty.previous')}
|
||||||
</button>
|
</button>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
@ -1691,7 +1692,7 @@ const handleMarkerDragEnd = async (lat, lng) => {
|
|||||||
step === 1 ? 'w-full' : ''
|
step === 1 ? 'w-full' : ''
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
التالي
|
{t('addProperty.next')}
|
||||||
<ChevronLeft className="w-5 h-5" />
|
<ChevronLeft className="w-5 h-5" />
|
||||||
</button>
|
</button>
|
||||||
) : (
|
) : (
|
||||||
@ -1703,12 +1704,12 @@ const handleMarkerDragEnd = async (lat, lng) => {
|
|||||||
{isLoading ? (
|
{isLoading ? (
|
||||||
<>
|
<>
|
||||||
<Loader2 className="w-5 h-5 animate-spin" />
|
<Loader2 className="w-5 h-5 animate-spin" />
|
||||||
جاري الحفظ...
|
{t('addProperty.saving')}
|
||||||
</>
|
</>
|
||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
<Save className="w-5 h-5" />
|
<Save className="w-5 h-5" />
|
||||||
حفظ العقار
|
{t('addProperty.saveProperty')}
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@ -643,12 +643,12 @@
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
import { useState, useEffect, useCallback } from 'react';
|
import { useState, useEffect, useCallback } from 'react';
|
||||||
import { motion } from 'framer-motion';
|
import { motion } from 'framer-motion';
|
||||||
import { useRouter } from 'next/navigation';
|
import { useRouter } from 'next/navigation';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
import {
|
import {
|
||||||
Calendar,
|
Calendar,
|
||||||
Clock,
|
Clock,
|
||||||
@ -660,8 +660,6 @@ import {
|
|||||||
MapPin,
|
MapPin,
|
||||||
DollarSign,
|
DollarSign,
|
||||||
Home,
|
Home,
|
||||||
ArrowLeft,
|
|
||||||
RefreshCw,
|
|
||||||
Flag,
|
Flag,
|
||||||
} from 'lucide-react';
|
} from 'lucide-react';
|
||||||
import toast, { Toaster } from 'react-hot-toast';
|
import toast, { Toaster } from 'react-hot-toast';
|
||||||
@ -680,34 +678,37 @@ const STATUS_MAP = [
|
|||||||
'cancelled',
|
'cancelled',
|
||||||
];
|
];
|
||||||
|
|
||||||
|
const STATUS_I18N_KEYS = {
|
||||||
|
pending: 'bookingStatus.pending',
|
||||||
|
ownerConfirmed: 'bookingStatus.ownerConfirmed',
|
||||||
|
depositPaid: 'bookingStatus.depositPaid',
|
||||||
|
depositConfirmed: 'bookingStatus.depositConfirmed',
|
||||||
|
completed: 'bookingStatus.completed',
|
||||||
|
cancelled: 'bookingStatus.cancelled',
|
||||||
|
};
|
||||||
|
|
||||||
const STATUS_UI = {
|
const STATUS_UI = {
|
||||||
pending: {
|
pending: {
|
||||||
label: 'قيد الانتظار',
|
|
||||||
color: 'bg-yellow-100 text-yellow-800',
|
color: 'bg-yellow-100 text-yellow-800',
|
||||||
icon: Clock,
|
icon: Clock,
|
||||||
},
|
},
|
||||||
ownerConfirmed: {
|
ownerConfirmed: {
|
||||||
label: 'مؤكد',
|
|
||||||
color: 'bg-green-100 text-green-800',
|
color: 'bg-green-100 text-green-800',
|
||||||
icon: CheckCircle,
|
icon: CheckCircle,
|
||||||
},
|
},
|
||||||
depositPaid: {
|
depositPaid: {
|
||||||
label: 'تم دفع السلفة',
|
|
||||||
color: 'bg-indigo-100 text-indigo-800',
|
color: 'bg-indigo-100 text-indigo-800',
|
||||||
icon: DollarSign,
|
icon: DollarSign,
|
||||||
},
|
},
|
||||||
depositConfirmed: {
|
depositConfirmed: {
|
||||||
label: 'مؤكد نهائياً',
|
|
||||||
color: 'bg-green-100 text-green-800',
|
color: 'bg-green-100 text-green-800',
|
||||||
icon: CheckCircle,
|
icon: CheckCircle,
|
||||||
},
|
},
|
||||||
completed: {
|
completed: {
|
||||||
label: 'منتهي',
|
|
||||||
color: 'bg-blue-100 text-blue-800',
|
color: 'bg-blue-100 text-blue-800',
|
||||||
icon: CheckCircle,
|
icon: CheckCircle,
|
||||||
},
|
},
|
||||||
cancelled: {
|
cancelled: {
|
||||||
label: 'ملغي',
|
|
||||||
color: 'bg-gray-100 text-gray-800',
|
color: 'bg-gray-100 text-gray-800',
|
||||||
icon: XCircle,
|
icon: XCircle,
|
||||||
},
|
},
|
||||||
@ -715,7 +716,7 @@ const STATUS_UI = {
|
|||||||
|
|
||||||
const getStatusKey = (code) => STATUS_MAP[Number(code)] || 'pending';
|
const getStatusKey = (code) => STATUS_MAP[Number(code)] || 'pending';
|
||||||
|
|
||||||
const sLabel = (code) => STATUS_UI[getStatusKey(code)]?.label ?? String(code);
|
const sLabel = (code, t) => t(STATUS_I18N_KEYS[getStatusKey(code)]) || String(code);
|
||||||
|
|
||||||
const sColor = (code) =>
|
const sColor = (code) =>
|
||||||
STATUS_UI[getStatusKey(code)]?.color ?? 'bg-gray-100 text-gray-700';
|
STATUS_UI[getStatusKey(code)]?.color ?? 'bg-gray-100 text-gray-700';
|
||||||
@ -723,6 +724,7 @@ const sColor = (code) =>
|
|||||||
const sIcon = (code) => STATUS_UI[getStatusKey(code)]?.icon ?? Clock;
|
const sIcon = (code) => STATUS_UI[getStatusKey(code)]?.icon ?? Clock;
|
||||||
|
|
||||||
function StatusBadge({ code }) {
|
function StatusBadge({ code }) {
|
||||||
|
const { t } = useTranslation();
|
||||||
const Icon = sIcon(code);
|
const Icon = sIcon(code);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -731,7 +733,7 @@ function StatusBadge({ code }) {
|
|||||||
code
|
code
|
||||||
)}`}
|
)}`}
|
||||||
>
|
>
|
||||||
<Icon className="w-3 h-3" /> {sLabel(code)}
|
<Icon className="w-3 h-3" /> {sLabel(code, t)}
|
||||||
</span>
|
</span>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -839,10 +841,10 @@ async function reportReservation(reservationId, message) {
|
|||||||
const rid = Number(reservationId);
|
const rid = Number(reservationId);
|
||||||
|
|
||||||
if (!Number.isInteger(rid)) {
|
if (!Number.isInteger(rid)) {
|
||||||
throw new Error('رقم الحجز غير صالح');
|
throw new Error(t('invalidReservationId'));
|
||||||
}
|
}
|
||||||
if (!Number.isInteger(reporter)) {
|
if (!Number.isInteger(reporter)) {
|
||||||
throw new Error('تعذر تحديد المستخدم الحالي');
|
throw new Error(t('couldNotIdentifyUser'));
|
||||||
}
|
}
|
||||||
|
|
||||||
const token = getAuthToken();
|
const token = getAuthToken();
|
||||||
@ -860,7 +862,7 @@ async function reportReservation(reservationId, message) {
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (!res.ok) {
|
if (!res.ok) {
|
||||||
let errorMessage = 'فشل إرسال البلاغ';
|
let errorMessage = t('reportFailed');
|
||||||
try {
|
try {
|
||||||
const data = await res.json();
|
const data = await res.json();
|
||||||
errorMessage = data?.message || data?.title || errorMessage;
|
errorMessage = data?.message || data?.title || errorMessage;
|
||||||
@ -889,6 +891,7 @@ function OwnerCard({
|
|||||||
actionLoadingId,
|
actionLoadingId,
|
||||||
reportingId,
|
reportingId,
|
||||||
}) {
|
}) {
|
||||||
|
const { t } = useTranslation();
|
||||||
const p = r._prop;
|
const p = r._prop;
|
||||||
const imgs = pImgs(p);
|
const imgs = pImgs(p);
|
||||||
const img = imgs.length > 0 ? buildImageUrl(imgs[0]) : null;
|
const img = imgs.length > 0 ? buildImageUrl(imgs[0]) : null;
|
||||||
@ -926,21 +929,21 @@ function OwnerCard({
|
|||||||
<div className="text-lg font-bold text-amber-600">
|
<div className="text-lg font-bold text-amber-600">
|
||||||
{r.totalPrice?.toLocaleString() ?? '—'}
|
{r.totalPrice?.toLocaleString() ?? '—'}
|
||||||
</div>
|
</div>
|
||||||
<div className="text-xs text-gray-500">السعر الإجمالي</div>
|
<div className="text-xs text-gray-500">{t('totalPrice')}</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{(pBeds(p) || pBaths(p)) && (
|
{(pBeds(p) || pBaths(p)) && (
|
||||||
<div className="flex gap-3 mb-3 text-sm text-gray-600">
|
<div className="flex gap-3 mb-3 text-sm text-gray-600">
|
||||||
{pBeds(p) > 0 && <span>{pBeds(p)} غرف</span>}
|
{pBeds(p) > 0 && <span>{pBeds(p)} {t('reservationsBedrooms')}</span>}
|
||||||
{pBaths(p) > 0 && <span>{pBaths(p)} حمامات</span>}
|
{pBaths(p) > 0 && <span>{pBaths(p)} {t('reservationsBathrooms')}</span>}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<div className="grid grid-cols-2 gap-3 mb-4 text-center">
|
<div className="grid grid-cols-2 gap-3 mb-4 text-center">
|
||||||
<div className="bg-gray-50 p-2 rounded-lg">
|
<div className="bg-gray-50 p-2 rounded-lg">
|
||||||
<Calendar className="w-4 h-4 text-amber-500 mx-auto mb-1" />
|
<Calendar className="w-4 h-4 text-amber-500 mx-auto mb-1" />
|
||||||
<div className="text-xs text-gray-500">من</div>
|
<div className="text-xs text-gray-500">{t('from')}</div>
|
||||||
<div className="text-sm font-medium">
|
<div className="text-sm font-medium">
|
||||||
{r.startDate
|
{r.startDate
|
||||||
? new Date(r.startDate).toLocaleDateString('ar')
|
? new Date(r.startDate).toLocaleDateString('ar')
|
||||||
@ -950,7 +953,7 @@ function OwnerCard({
|
|||||||
|
|
||||||
<div className="bg-gray-50 p-2 rounded-lg">
|
<div className="bg-gray-50 p-2 rounded-lg">
|
||||||
<Calendar className="w-4 h-4 text-amber-500 mx-auto mb-1" />
|
<Calendar className="w-4 h-4 text-amber-500 mx-auto mb-1" />
|
||||||
<div className="text-xs text-gray-500">إلى</div>
|
<div className="text-xs text-gray-500">{t('to')}</div>
|
||||||
<div className="text-sm font-medium">
|
<div className="text-sm font-medium">
|
||||||
{r.endDate ? new Date(r.endDate).toLocaleDateString('ar') : '—'}
|
{r.endDate ? new Date(r.endDate).toLocaleDateString('ar') : '—'}
|
||||||
</div>
|
</div>
|
||||||
@ -968,7 +971,7 @@ function OwnerCard({
|
|||||||
className="flex-1 bg-gray-100 text-gray-700 py-2 rounded-xl text-sm font-medium hover:bg-gray-200 transition-colors flex items-center justify-center gap-2"
|
className="flex-1 bg-gray-100 text-gray-700 py-2 rounded-xl text-sm font-medium hover:bg-gray-200 transition-colors flex items-center justify-center gap-2"
|
||||||
>
|
>
|
||||||
<Eye className="w-4 h-4" />
|
<Eye className="w-4 h-4" />
|
||||||
التفاصيل
|
{t('details')}
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
{isPending && (
|
{isPending && (
|
||||||
@ -984,7 +987,7 @@ function OwnerCard({
|
|||||||
) : (
|
) : (
|
||||||
<CheckCircle className="w-4 h-4" />
|
<CheckCircle className="w-4 h-4" />
|
||||||
)}
|
)}
|
||||||
قبول
|
{t('accept')}
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<button
|
<button
|
||||||
@ -998,7 +1001,7 @@ function OwnerCard({
|
|||||||
) : (
|
) : (
|
||||||
<XCircle className="w-4 h-4" />
|
<XCircle className="w-4 h-4" />
|
||||||
)}
|
)}
|
||||||
رفض
|
{t('reject')}
|
||||||
</button>
|
</button>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
@ -1019,7 +1022,7 @@ function OwnerCard({
|
|||||||
) : (
|
) : (
|
||||||
<Flag className="w-4 h-4" />
|
<Flag className="w-4 h-4" />
|
||||||
)}
|
)}
|
||||||
{isReporting ? 'جاري الإبلاغ...' : 'إبلاغ'}
|
{isReporting ? t('reporting') : t('report')}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</motion.div>
|
</motion.div>
|
||||||
@ -1027,6 +1030,7 @@ function OwnerCard({
|
|||||||
}
|
}
|
||||||
|
|
||||||
function DetailsModal({ r, isOpen, onClose, onReport, reportingId }) {
|
function DetailsModal({ r, isOpen, onClose, onReport, reportingId }) {
|
||||||
|
const { t } = useTranslation();
|
||||||
if (!isOpen || !r) return null;
|
if (!isOpen || !r) return null;
|
||||||
|
|
||||||
const p = r._prop;
|
const p = r._prop;
|
||||||
@ -1049,7 +1053,7 @@ function DetailsModal({ r, isOpen, onClose, onReport, reportingId }) {
|
|||||||
>
|
>
|
||||||
<div className="sticky top-0 bg-gradient-to-r from-amber-500 to-amber-600 p-6 text-white">
|
<div className="sticky top-0 bg-gradient-to-r from-amber-500 to-amber-600 p-6 text-white">
|
||||||
<div className="flex justify-between items-center">
|
<div className="flex justify-between items-center">
|
||||||
<h2 className="text-xl font-bold">طلب حجز #{r.id}</h2>
|
<h2 className="text-xl font-bold">{t('reservationRequest', { id: r.id })}</h2>
|
||||||
|
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
@ -1066,11 +1070,11 @@ function DetailsModal({ r, isOpen, onClose, onReport, reportingId }) {
|
|||||||
<div className="bg-gray-50 p-4 rounded-xl">
|
<div className="bg-gray-50 p-4 rounded-xl">
|
||||||
<h3 className="font-bold text-gray-900 mb-3 flex items-center gap-2">
|
<h3 className="font-bold text-gray-900 mb-3 flex items-center gap-2">
|
||||||
<Home className="w-5 h-5 text-amber-500" />
|
<Home className="w-5 h-5 text-amber-500" />
|
||||||
معلومات العقار
|
{t('propertyInfo')}
|
||||||
</h3>
|
</h3>
|
||||||
|
|
||||||
<p>
|
<p>
|
||||||
<span className="text-gray-500">العنوان:</span>{' '}
|
<span className="text-gray-500">{t('address')}</span>{' '}
|
||||||
{pAddr(p) || '—'}
|
{pAddr(p) || '—'}
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
@ -1078,13 +1082,13 @@ function DetailsModal({ r, isOpen, onClose, onReport, reportingId }) {
|
|||||||
<div className="flex gap-3 mt-2">
|
<div className="flex gap-3 mt-2">
|
||||||
{pBeds(p) > 0 && (
|
{pBeds(p) > 0 && (
|
||||||
<span className="text-sm bg-white px-2 py-1 rounded-lg">
|
<span className="text-sm bg-white px-2 py-1 rounded-lg">
|
||||||
{pBeds(p)} غرف
|
{pBeds(p)} {t('reservationsBedrooms')}
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{pBaths(p) > 0 && (
|
{pBaths(p) > 0 && (
|
||||||
<span className="text-sm bg-white px-2 py-1 rounded-lg">
|
<span className="text-sm bg-white px-2 py-1 rounded-lg">
|
||||||
{pBaths(p)} حمامات
|
{pBaths(p)} {t('reservationsBathrooms')}
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
@ -1095,12 +1099,12 @@ function DetailsModal({ r, isOpen, onClose, onReport, reportingId }) {
|
|||||||
<div className="bg-gray-50 p-4 rounded-xl">
|
<div className="bg-gray-50 p-4 rounded-xl">
|
||||||
<h3 className="font-bold text-gray-900 mb-3 flex items-center gap-2">
|
<h3 className="font-bold text-gray-900 mb-3 flex items-center gap-2">
|
||||||
<Calendar className="w-5 h-5 text-amber-500" />
|
<Calendar className="w-5 h-5 text-amber-500" />
|
||||||
تفاصيل الحجز
|
{t('reservationDetails')}
|
||||||
</h3>
|
</h3>
|
||||||
|
|
||||||
<div className="grid grid-cols-2 gap-4">
|
<div className="grid grid-cols-2 gap-4">
|
||||||
<div>
|
<div>
|
||||||
<p className="text-gray-500">تاريخ البداية</p>
|
<p className="text-gray-500">{t('startDate')}</p>
|
||||||
<p className="font-medium">
|
<p className="font-medium">
|
||||||
{r.startDate
|
{r.startDate
|
||||||
? new Date(r.startDate).toLocaleDateString('ar')
|
? new Date(r.startDate).toLocaleDateString('ar')
|
||||||
@ -1109,19 +1113,19 @@ function DetailsModal({ r, isOpen, onClose, onReport, reportingId }) {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<p className="text-gray-500">تاريخ النهاية</p>
|
<p className="text-gray-500">{t('endDate')}</p>
|
||||||
<p className="font-medium">
|
<p className="font-medium">
|
||||||
{r.endDate ? new Date(r.endDate).toLocaleDateString('ar') : '—'}
|
{r.endDate ? new Date(r.endDate).toLocaleDateString('ar') : '—'}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<p className="text-gray-500">الحالة</p>
|
<p className="text-gray-500">{t('statusLabel')}</p>
|
||||||
<StatusBadge code={r.status} />
|
<StatusBadge code={r.status} />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<p className="text-gray-500">تاريخ الإنشاء</p>
|
<p className="text-gray-500">{t('createdAt')}</p>
|
||||||
<p className="font-medium">
|
<p className="font-medium">
|
||||||
{r.createdAt
|
{r.createdAt
|
||||||
? new Date(r.createdAt).toLocaleDateString('ar')
|
? new Date(r.createdAt).toLocaleDateString('ar')
|
||||||
@ -1134,11 +1138,11 @@ function DetailsModal({ r, isOpen, onClose, onReport, reportingId }) {
|
|||||||
<div className="bg-amber-50 p-4 rounded-xl">
|
<div className="bg-amber-50 p-4 rounded-xl">
|
||||||
<h3 className="font-bold text-amber-700 mb-3 flex items-center gap-2">
|
<h3 className="font-bold text-amber-700 mb-3 flex items-center gap-2">
|
||||||
<DollarSign className="w-5 h-5" />
|
<DollarSign className="w-5 h-5" />
|
||||||
المعلومات المالية
|
{t('financialInfo')}
|
||||||
</h3>
|
</h3>
|
||||||
|
|
||||||
<div className="flex justify-between font-bold">
|
<div className="flex justify-between font-bold">
|
||||||
<span className="text-gray-900">الإجمالي</span>
|
<span className="text-gray-900">{t('total')}</span>
|
||||||
<span className="text-amber-600 text-lg">
|
<span className="text-amber-600 text-lg">
|
||||||
{r.totalPrice?.toLocaleString() ?? '—'}
|
{r.totalPrice?.toLocaleString() ?? '—'}
|
||||||
</span>
|
</span>
|
||||||
@ -1151,6 +1155,7 @@ function DetailsModal({ r, isOpen, onClose, onReport, reportingId }) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function ReportDialog({ isOpen, reservation, onClose, onSubmit, submitting }) {
|
function ReportDialog({ isOpen, reservation, onClose, onSubmit, submitting }) {
|
||||||
|
const { t } = useTranslation();
|
||||||
const [message, setMessage] = useState('');
|
const [message, setMessage] = useState('');
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@ -1177,8 +1182,8 @@ function ReportDialog({ isOpen, reservation, onClose, onSubmit, submitting }) {
|
|||||||
<div className="bg-gradient-to-r from-red-500 to-red-600 p-6 text-white">
|
<div className="bg-gradient-to-r from-red-500 to-red-600 p-6 text-white">
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<div>
|
<div>
|
||||||
<h2 className="text-xl font-bold">الإبلاغ عن الحجز</h2>
|
<h2 className="text-xl font-bold">{t('reportReservation')}</h2>
|
||||||
<p className="text-red-100 text-sm mt-1">رقم الحجز: #{reservation.id}</p>
|
<p className="text-red-100 text-sm mt-1">{t('reservationId', { id: reservation.id })}</p>
|
||||||
</div>
|
</div>
|
||||||
<button onClick={onClose} className="rounded-full p-1 hover:bg-white/20">
|
<button onClick={onClose} className="rounded-full p-1 hover:bg-white/20">
|
||||||
<XCircle className="h-6 w-6" />
|
<XCircle className="h-6 w-6" />
|
||||||
@ -1188,13 +1193,13 @@ function ReportDialog({ isOpen, reservation, onClose, onSubmit, submitting }) {
|
|||||||
|
|
||||||
<div className="p-6">
|
<div className="p-6">
|
||||||
<p className="text-gray-700 mb-4 leading-7">
|
<p className="text-gray-700 mb-4 leading-7">
|
||||||
اخبر فريق الدعم بما حدث التفاصيل الواضحة تساعدنا على مراجعة هذا الحجز بشكل اسرع
|
{t('reportDescription')}
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<textarea
|
<textarea
|
||||||
value={message}
|
value={message}
|
||||||
onChange={(e) => setMessage(e.target.value)}
|
onChange={(e) => setMessage(e.target.value)}
|
||||||
placeholder="اكتب تفاصيل البلاغ هنا..."
|
placeholder={t('reportPlaceholder')}
|
||||||
rows={5}
|
rows={5}
|
||||||
className="w-full resize-none rounded-xl border border-gray-300 p-3 text-sm focus:outline-none focus:ring-2 focus:ring-red-500"
|
className="w-full resize-none rounded-xl border border-gray-300 p-3 text-sm focus:outline-none focus:ring-2 focus:ring-red-500"
|
||||||
/>
|
/>
|
||||||
@ -1208,14 +1213,14 @@ function ReportDialog({ isOpen, reservation, onClose, onSubmit, submitting }) {
|
|||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
{submitting ? <Loader2 className="h-4 w-4 animate-spin" /> : <Flag className="h-4 w-4" />}
|
{submitting ? <Loader2 className="h-4 w-4 animate-spin" /> : <Flag className="h-4 w-4" />}
|
||||||
{submitting ? 'جاري الإرسال...' : 'إرسال البلاغ'}
|
{submitting ? t('submitting') : t('submitReport')}
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
onClick={onClose}
|
onClick={onClose}
|
||||||
disabled={submitting}
|
disabled={submitting}
|
||||||
className="rounded-xl bg-gray-200 px-4 py-2.5 text-sm font-medium text-gray-700 hover:bg-gray-300 transition-colors disabled:cursor-not-allowed"
|
className="rounded-xl bg-gray-200 px-4 py-2.5 text-sm font-medium text-gray-700 hover:bg-gray-300 transition-colors disabled:cursor-not-allowed"
|
||||||
>
|
>
|
||||||
إلغاء
|
{t('cancel')}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -1225,6 +1230,7 @@ function ReportDialog({ isOpen, reservation, onClose, onSubmit, submitting }) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export default function OwnerReservationRequestsPage() {
|
export default function OwnerReservationRequestsPage() {
|
||||||
|
const { t, i18n } = useTranslation();
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
||||||
const [reservations, setReservations] = useState([]);
|
const [reservations, setReservations] = useState([]);
|
||||||
@ -1270,7 +1276,7 @@ export default function OwnerReservationRequestsPage() {
|
|||||||
setFiltered(enriched);
|
setFiltered(enriched);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error(err);
|
console.error(err);
|
||||||
toast.error('فشل تحميل طلبات الحجز');
|
toast.error(t('loadFailedReservations'));
|
||||||
setReservations([]);
|
setReservations([]);
|
||||||
setFiltered([]);
|
setFiltered([]);
|
||||||
} finally {
|
} finally {
|
||||||
@ -1321,18 +1327,18 @@ export default function OwnerReservationRequestsPage() {
|
|||||||
throw new Error(errorText || `HTTP ${res.status}`);
|
throw new Error(errorText || `HTTP ${res.status}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
toast.success('تم قبول الحجز بنجاح');
|
toast.success(t('acceptSuccess'));
|
||||||
await loadReservations();
|
await loadReservations();
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error(err);
|
console.error(err);
|
||||||
toast.error('فشل قبول الحجز');
|
toast.error(t('acceptFailed'));
|
||||||
} finally {
|
} finally {
|
||||||
setActionLoadingId(null);
|
setActionLoadingId(null);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleReject = async (r) => {
|
const handleReject = async (r) => {
|
||||||
if (!confirm('هل أنت متأكد من رفض هذا الحجز؟')) return;
|
if (!confirm(t('rejectConfirm'))) return;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
setActionLoadingId(r.id);
|
setActionLoadingId(r.id);
|
||||||
@ -1348,11 +1354,11 @@ export default function OwnerReservationRequestsPage() {
|
|||||||
throw new Error(errorText || `HTTP ${res.status}`);
|
throw new Error(errorText || `HTTP ${res.status}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
toast.success('تم رفض الحجز');
|
toast.success(t('rejectSuccess'));
|
||||||
await loadReservations();
|
await loadReservations();
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error(err);
|
console.error(err);
|
||||||
toast.error('فشل رفض الحجز');
|
toast.error(t('rejectFailed'));
|
||||||
} finally {
|
} finally {
|
||||||
setActionLoadingId(null);
|
setActionLoadingId(null);
|
||||||
}
|
}
|
||||||
@ -1372,11 +1378,11 @@ export default function OwnerReservationRequestsPage() {
|
|||||||
setReportingId(reportDialog.reservation.id);
|
setReportingId(reportDialog.reservation.id);
|
||||||
try {
|
try {
|
||||||
await reportReservation(reportDialog.reservation.id, message.trim() || null);
|
await reportReservation(reportDialog.reservation.id, message.trim() || null);
|
||||||
toast.success('تم إرسال البلاغ بنجاح');
|
toast.success(t('reportSuccess'));
|
||||||
closeReportDialog();
|
closeReportDialog();
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error(err);
|
console.error(err);
|
||||||
toast.error(err?.message || 'فشل إرسال البلاغ');
|
toast.error(err?.message || t('reportFailed'));
|
||||||
} finally {
|
} finally {
|
||||||
setReportingId(null);
|
setReportingId(null);
|
||||||
}
|
}
|
||||||
@ -1405,7 +1411,7 @@ export default function OwnerReservationRequestsPage() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
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.language === 'ar' ? 'rtl' : 'ltr'}>
|
||||||
<Toaster position="top-center" reverseOrder={false} />
|
<Toaster position="top-center" reverseOrder={false} />
|
||||||
|
|
||||||
<DetailsModal
|
<DetailsModal
|
||||||
@ -1429,30 +1435,11 @@ export default function OwnerReservationRequestsPage() {
|
|||||||
animate={{ opacity: 1, y: 0 }}
|
animate={{ opacity: 1, y: 0 }}
|
||||||
className="mb-8"
|
className="mb-8"
|
||||||
>
|
>
|
||||||
<button
|
<div className="mb-4">
|
||||||
type="button"
|
|
||||||
onClick={() => router.back()}
|
|
||||||
className="flex items-center gap-2 text-gray-600 hover:text-amber-600 mb-4"
|
|
||||||
>
|
|
||||||
<ArrowLeft className="w-5 h-5" />
|
|
||||||
الرجوع
|
|
||||||
</button>
|
|
||||||
|
|
||||||
<div className="flex items-center justify-between mb-2">
|
|
||||||
<div>
|
|
||||||
<h1 className="text-3xl font-bold text-gray-900">
|
<h1 className="text-3xl font-bold text-gray-900">
|
||||||
طلبات الحجز
|
{t('reservationsTitle')}
|
||||||
</h1>
|
</h1>
|
||||||
<p className="text-gray-600">لديك {reservations.length} طلب</p>
|
<p className="text-gray-600">{t('reservationsCount', { count: reservations.length })}</p>
|
||||||
</div>
|
|
||||||
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
onClick={loadReservations}
|
|
||||||
className="p-2 bg-white shadow rounded-xl hover:shadow-md transition-all"
|
|
||||||
>
|
|
||||||
<RefreshCw className="w-5 h-5 text-gray-600" />
|
|
||||||
</button>
|
|
||||||
</div>
|
</div>
|
||||||
</motion.div>
|
</motion.div>
|
||||||
|
|
||||||
@ -1471,7 +1458,7 @@ export default function OwnerReservationRequestsPage() {
|
|||||||
>
|
>
|
||||||
<div className="text-2xl font-bold text-amber-600">{c}</div>
|
<div className="text-2xl font-bold text-amber-600">{c}</div>
|
||||||
<div className="text-sm text-gray-600">
|
<div className="text-sm text-gray-600">
|
||||||
{s === 'all' ? 'الكل' : STATUS_UI[s]?.label || s}
|
{s === 'all' ? t('filterAll') : t(STATUS_I18N_KEYS[s]) || s}
|
||||||
</div>
|
</div>
|
||||||
</motion.div>
|
</motion.div>
|
||||||
))}
|
))}
|
||||||
@ -1482,7 +1469,7 @@ export default function OwnerReservationRequestsPage() {
|
|||||||
|
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
placeholder="ابحث بعنوان العقار أو رقم الحجز..."
|
placeholder={t('searchPlaceholderReservations')}
|
||||||
value={searchTerm}
|
value={searchTerm}
|
||||||
onChange={(e) => setSearchTerm(e.target.value)}
|
onChange={(e) => setSearchTerm(e.target.value)}
|
||||||
className="w-full pl-10 pr-4 py-3 border border-gray-300 rounded-xl focus:outline-none focus:ring-2 focus:ring-amber-500"
|
className="w-full pl-10 pr-4 py-3 border border-gray-300 rounded-xl focus:outline-none focus:ring-2 focus:ring-amber-500"
|
||||||
@ -1493,10 +1480,10 @@ export default function OwnerReservationRequestsPage() {
|
|||||||
<div className="bg-white rounded-2xl p-12 text-center border-2 border-dashed border-gray-300">
|
<div className="bg-white rounded-2xl p-12 text-center border-2 border-dashed border-gray-300">
|
||||||
<Calendar className="w-12 h-12 text-amber-600 mx-auto mb-4" />
|
<Calendar className="w-12 h-12 text-amber-600 mx-auto mb-4" />
|
||||||
<h3 className="text-xl font-bold text-gray-900 mb-2">
|
<h3 className="text-xl font-bold text-gray-900 mb-2">
|
||||||
لا توجد طلبات
|
{t('noReservations')}
|
||||||
</h3>
|
</h3>
|
||||||
<p className="text-gray-600">
|
<p className="text-gray-600">
|
||||||
لم يتم استلام أي طلبات حجز حتى الآن
|
{t('noReservationsHint')}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
|
|||||||
175
app/page.js
175
app/page.js
@ -32,8 +32,9 @@ import Image from 'next/image';
|
|||||||
import { getRentProperties, getSaleProperties } from './utils/api';
|
import { getRentProperties, getSaleProperties } from './utils/api';
|
||||||
import { BuildingTypeKeys, PropertyStatusKeys, extractCity } from './enums';
|
import { BuildingTypeKeys, PropertyStatusKeys, extractCity } from './enums';
|
||||||
import AuthService from './services/AuthService';
|
import AuthService from './services/AuthService';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
function mapApiProperty(item, index) {
|
function mapApiProperty(t, item, index) {
|
||||||
const info = item.propertyInformation || {};
|
const info = item.propertyInformation || {};
|
||||||
|
|
||||||
const dailyPrice = item.dailyRent ?? 0;
|
const dailyPrice = item.dailyRent ?? 0;
|
||||||
@ -48,11 +49,11 @@ function mapApiProperty(item, index) {
|
|||||||
const status = PropertyStatusKeys[info.status] ?? PropertyStatusKeys[item.status] ?? 'available';
|
const status = PropertyStatusKeys[info.status] ?? PropertyStatusKeys[item.status] ?? 'available';
|
||||||
|
|
||||||
const features = [];
|
const features = [];
|
||||||
if (item.isSmokeAllow) features.push('يسمح بالتدخين');
|
if (item.isSmokeAllow) features.push(t('smoke-allowed'));
|
||||||
if (item.isVisitorAllow) features.push('يسمح بالزوار');
|
if (item.isVisitorAllow) features.push(t('visitors-allowed'));
|
||||||
if (item.specializedFor) features.push('متخصص');
|
if (item.specializedFor) features.push(t('specialized'));
|
||||||
if (info.numberOfBedRooms) features.push(`${info.numberOfBedRooms} غرف نوم`);
|
if (info.numberOfBedRooms) features.push(`${info.numberOfBedRooms} ${t('rooms')}`);
|
||||||
if (info.numberOfBathRooms) features.push(`${info.numberOfBathRooms} حمامات`);
|
if (info.numberOfBathRooms) features.push(`${info.numberOfBathRooms} ${t('bathrooms')}`);
|
||||||
|
|
||||||
const apiBase = typeof window !== 'undefined' ? (process.env.NEXT_PUBLIC_API_URL || 'https://45.93.137.91.nip.io/api') : '';
|
const apiBase = typeof window !== 'undefined' ? (process.env.NEXT_PUBLIC_API_URL || 'https://45.93.137.91.nip.io/api') : '';
|
||||||
const rawImages = Array.isArray(info.images) ? info.images : [];
|
const rawImages = Array.isArray(info.images) ? info.images : [];
|
||||||
@ -68,7 +69,7 @@ function mapApiProperty(item, index) {
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
id: item.id ?? index + 1,
|
id: item.id ?? index + 1,
|
||||||
title: info.address || `عقار #${item.id || index + 1}`,
|
title: info.address || t('propertyWithId', { id: item.id || index + 1 }),
|
||||||
description: info.description || '',
|
description: info.description || '',
|
||||||
type: propType,
|
type: propType,
|
||||||
price: price,
|
price: price,
|
||||||
@ -76,7 +77,7 @@ function mapApiProperty(item, index) {
|
|||||||
priceUnit,
|
priceUnit,
|
||||||
listingType: isRentListing ? 'rent' : 'sale',
|
listingType: isRentListing ? 'rent' : 'sale',
|
||||||
location: {
|
location: {
|
||||||
city: extractCity(info.address) || 'دمشق',
|
city: extractCity(info.address) || 'damascus',
|
||||||
district: info.address || '',
|
district: info.address || '',
|
||||||
address: info.address || '',
|
address: info.address || '',
|
||||||
lat: parseFloat(info.cordsX) || 0,
|
lat: parseFloat(info.cordsX) || 0,
|
||||||
@ -103,6 +104,7 @@ function mapApiProperty(item, index) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export default function HomePage() {
|
export default function HomePage() {
|
||||||
|
const { t } = useTranslation();
|
||||||
const mapSectionRef = useRef(null);
|
const mapSectionRef = useRef(null);
|
||||||
const [searchFilters, setSearchFilters] = useState(null);
|
const [searchFilters, setSearchFilters] = useState(null);
|
||||||
const [showMap, setShowMap] = useState(false);
|
const [showMap, setShowMap] = useState(false);
|
||||||
@ -143,8 +145,8 @@ export default function HomePage() {
|
|||||||
const rentList = Array.isArray(rentData) ? rentData : [];
|
const rentList = Array.isArray(rentData) ? rentData : [];
|
||||||
const saleList = Array.isArray(saleData) ? saleData : [];
|
const saleList = Array.isArray(saleData) ? saleData : [];
|
||||||
|
|
||||||
const mappedRent = rentList.map((p, i) => mapApiProperty(p, i));
|
const mappedRent = rentList.map((p, i) => mapApiProperty(t, p, i));
|
||||||
const mappedSale = saleList.map((p, i) => mapApiProperty(p, rentList.length + i));
|
const mappedSale = saleList.map((p, i) => mapApiProperty(t, p, rentList.length + i));
|
||||||
|
|
||||||
setRentProperties(mappedRent);
|
setRentProperties(mappedRent);
|
||||||
setSaleProperties(mappedSale);
|
setSaleProperties(mappedSale);
|
||||||
@ -297,7 +299,7 @@ export default function HomePage() {
|
|||||||
visible: { opacity: 1, y: 0 }
|
visible: { opacity: 1, y: 0 }
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
إيجاد منزلك الجديد<br />
|
{t("heroTitleLine1")}<br />
|
||||||
<motion.span
|
<motion.span
|
||||||
className="text-amber-300"
|
className="text-amber-300"
|
||||||
animate={{
|
animate={{
|
||||||
@ -309,7 +311,7 @@ export default function HomePage() {
|
|||||||
ease: "easeInOut"
|
ease: "easeInOut"
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
أصبح سهلاً
|
{t("heroTitleLine2")}
|
||||||
</motion.span>
|
</motion.span>
|
||||||
</motion.h1>
|
</motion.h1>
|
||||||
<motion.p
|
<motion.p
|
||||||
@ -319,7 +321,7 @@ export default function HomePage() {
|
|||||||
visible: { opacity: 1, y: 0 }
|
visible: { opacity: 1, y: 0 }
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
نوفر قوائم عقارات عالية الجودة لمساعدتك في إيجاد المنزل المثالي
|
{t("heroSubtitle")}
|
||||||
</motion.p>
|
</motion.p>
|
||||||
</motion.div>
|
</motion.div>
|
||||||
|
|
||||||
@ -333,17 +335,17 @@ export default function HomePage() {
|
|||||||
className="bg-white/10 backdrop-blur-lg rounded-2xl p-8 text-center border border-white/20"
|
className="bg-white/10 backdrop-blur-lg rounded-2xl p-8 text-center border border-white/20"
|
||||||
>
|
>
|
||||||
<h2 className="text-2xl font-bold text-amber-50 mb-2">
|
<h2 className="text-2xl font-bold text-amber-50 mb-2">
|
||||||
مرحباً {user?.name}!
|
{t("ownerGreeting")} {user?.name}!
|
||||||
</h2>
|
</h2>
|
||||||
<p className="text-amber-50/80 mb-4">
|
<p className="text-amber-50/80 mb-4">
|
||||||
يمكنك إدارة عقاراتك من خلال لوحة التحكم الخاصة بك
|
{t("ownerDescription")}
|
||||||
</p>
|
</p>
|
||||||
<Link
|
<Link
|
||||||
href="/owner/properties"
|
href="/owner/properties"
|
||||||
className="inline-flex items-center gap-2 bg-amber-500 text-white px-6 py-3 rounded-xl font-medium hover:bg-amber-600 transition-colors"
|
className="inline-flex items-center gap-2 bg-amber-500 text-white px-6 py-3 rounded-xl font-medium hover:bg-amber-600 transition-colors"
|
||||||
>
|
>
|
||||||
<Building className="w-5 h-5" />
|
<Building className="w-5 h-5" />
|
||||||
إدارة عقاراتي
|
{t("manageMyProperties")}
|
||||||
</Link>
|
</Link>
|
||||||
</motion.div>
|
</motion.div>
|
||||||
)}
|
)}
|
||||||
@ -373,7 +375,7 @@ export default function HomePage() {
|
|||||||
)}
|
)}
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
{!isOwner && (
|
{!isOwner && (<>
|
||||||
<AnimatePresence mode="wait">
|
<AnimatePresence mode="wait">
|
||||||
{showMap && (
|
{showMap && (
|
||||||
<motion.section
|
<motion.section
|
||||||
@ -407,7 +409,7 @@ export default function HomePage() {
|
|||||||
>
|
>
|
||||||
<div className="flex items-center justify-center gap-4 mb-2">
|
<div className="flex items-center justify-center gap-4 mb-2">
|
||||||
<h2 className="text-3xl font-bold text-gray-900">
|
<h2 className="text-3xl font-bold text-gray-900">
|
||||||
{filteredProperties.length > 0 ? 'نتائج البحث' : 'لا توجد نتائج'}
|
{filteredProperties.length > 0 ? t('searchResults') : t('no-properties')}
|
||||||
</h2>
|
</h2>
|
||||||
<motion.button
|
<motion.button
|
||||||
onClick={resetSearch}
|
onClick={resetSearch}
|
||||||
@ -418,17 +420,17 @@ export default function HomePage() {
|
|||||||
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M10 19l-7-7m0 0l7-7m-7 7h18" />
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M10 19l-7-7m0 0l7-7m-7 7h18" />
|
||||||
</svg>
|
</svg>
|
||||||
بحث جديد
|
{t('newSearch')}
|
||||||
</motion.button>
|
</motion.button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{filteredProperties.length > 0 ? (
|
{filteredProperties.length > 0 ? (
|
||||||
<p className="text-gray-600">
|
<p className="text-gray-600">
|
||||||
تم العثور على {filteredProperties.length} عقار يطابق معايير البحث
|
{t('foundPropertiesCount', { count: filteredProperties.length })}
|
||||||
</p>
|
</p>
|
||||||
) : (
|
) : (
|
||||||
<p className="text-gray-600">
|
<p className="text-gray-600">
|
||||||
لا توجد عقارات تطابق معايير البحث. جرب تغيير الفلاتر.
|
{t('noPropertiesMatchFilters')}
|
||||||
</p>
|
</p>
|
||||||
)}
|
)}
|
||||||
</motion.div>
|
</motion.div>
|
||||||
@ -456,88 +458,33 @@ export default function HomePage() {
|
|||||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9.172 16.172a4 4 0 015.656 0M9 10h.01M15 10h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9.172 16.172a4 4 0 015.656 0M9 10h.01M15 10h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
|
||||||
</svg>
|
</svg>
|
||||||
</div>
|
</div>
|
||||||
<h3 className="text-xl font-bold text-gray-700 mb-2">لا توجد نتائج</h3>
|
<h3 className="text-xl font-bold text-gray-700 mb-2">{t('no-properties')}</h3>
|
||||||
<p className="text-gray-500">حاول تغيير معايير البحث</p>
|
<p className="text-gray-500">{t('try-changing-search-criteria')}</p>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</motion.div>
|
</motion.div>
|
||||||
|
|
||||||
{filteredProperties.length > 0 && searchFilters && (
|
|
||||||
<motion.div
|
|
||||||
initial={{ opacity: 0, y: 20 }}
|
|
||||||
animate={{ opacity: 1, y: 0 }}
|
|
||||||
transition={{ delay: 0.5 }}
|
|
||||||
className="mt-6 flex flex-wrap gap-3 justify-center"
|
|
||||||
>
|
|
||||||
<div className="bg-white px-4 py-2 rounded-full shadow-sm border border-gray-200 text-sm">
|
|
||||||
<span className="text-gray-600">المدينة: </span>
|
|
||||||
<span className="font-bold text-gray-900">
|
|
||||||
{searchFilters.city === 'all' ? 'جميع المدن' : searchFilters.city}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
<div className="bg-white px-4 py-2 rounded-full shadow-sm border border-gray-200 text-sm">
|
|
||||||
<span className="text-gray-600">نوع العقار: </span>
|
|
||||||
<span className="font-bold text-gray-900">
|
|
||||||
{searchFilters.propertyType === 'all' ? 'الكل' :
|
|
||||||
searchFilters.propertyType === 'apartment' ? 'شقة' :
|
|
||||||
searchFilters.propertyType === 'villa' ? 'فيلا' : 'بيت'}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
<div className="bg-white px-4 py-2 rounded-full shadow-sm border border-gray-200 text-sm">
|
|
||||||
<span className="text-gray-600">نطاق السعر: </span>
|
|
||||||
<span className="font-bold text-gray-900">
|
|
||||||
{searchFilters.priceRange === 'all' ? 'جميع الأسعار' :
|
|
||||||
searchFilters.priceRange === '0-500' ? 'أقل من 50$' :
|
|
||||||
searchFilters.priceRange === '500-1000' ? '50$ - 100$' :
|
|
||||||
searchFilters.priceRange === '1000-2000' ? '100$ - 200$' :
|
|
||||||
searchFilters.priceRange === '2000-3000' ? '200$ - 300$' : 'أكثر من 300$'}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
<div className="bg-white px-4 py-2 rounded-full shadow-sm border border-gray-200 text-sm">
|
|
||||||
<span className="text-gray-600">مصدر العرض: </span>
|
|
||||||
<span className="font-bold text-gray-900">
|
|
||||||
{searchFilters.ownerSource === 'all' ? 'الكل' :
|
|
||||||
searchFilters.ownerSource === 'owner' ? 'من المالك' : 'من مكتب عقاري'}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
<div className="bg-white px-4 py-2 rounded-full shadow-sm border border-gray-200 text-sm">
|
|
||||||
<span className="text-gray-600">نوع الإيجار: </span>
|
|
||||||
<span className="font-bold text-gray-900">
|
|
||||||
{searchFilters.rentPeriod === 'all' ? 'الكل' :
|
|
||||||
searchFilters.rentPeriod === 'daily' ? 'إيجار يومي' : 'إيجار شهري'}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
{searchFilters.availableToday && (
|
|
||||||
<div className="bg-white px-4 py-2 rounded-full shadow-sm border border-gray-200 text-sm">
|
|
||||||
<span className="font-bold text-gray-900">فقط المتاحة من اليوم</span>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</motion.div>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
</motion.section>
|
</motion.section>
|
||||||
)}
|
)}
|
||||||
</AnimatePresence>
|
</AnimatePresence>
|
||||||
)}
|
</>)}
|
||||||
|
|
||||||
<section className="py-20 bg-gradient-to-b from-white to-gray-50">
|
|
||||||
<div className="container mx-auto px-4">
|
|
||||||
<motion.div
|
<motion.div
|
||||||
className="text-center mb-12"
|
|
||||||
initial={{ opacity: 0, y: 20 }}
|
initial={{ opacity: 0, y: 20 }}
|
||||||
whileInView={{ opacity: 1, y: 0 }}
|
whileInView={{ opacity: 1, y: 0 }}
|
||||||
viewport={{ once: true }}
|
viewport={{ once: true }}
|
||||||
transition={{ duration: 0.6 }}
|
transition={{ duration: 0.6 }}
|
||||||
|
className="text-center mb-12"
|
||||||
>
|
>
|
||||||
<h2 className="text-3xl md:text-4xl font-bold text-gray-900 mb-4 tracking-tight">
|
<h2 className="text-3xl md:text-4xl font-bold text-gray-900 mb-4 tracking-tight">
|
||||||
لماذا تختار سويت هوم؟
|
{t('whyChooseUsTitle')}
|
||||||
</h2>
|
</h2>
|
||||||
<p className="text-gray-600 max-w-2xl mx-auto text-lg">
|
<p className="text-gray-600 max-w-2xl mx-auto text-lg">
|
||||||
نجعل عملية إيجاد منزلك المثالي سهلة وسريعة
|
{t('whyChooseUsSubtitle')}
|
||||||
</p>
|
</p>
|
||||||
</motion.div>
|
</motion.div>
|
||||||
|
|
||||||
<div className="grid grid-cols-1 md:grid-cols-3 gap-6">
|
<div className="grid grid-cols-1 md:grid-cols-3 gap-6 mb-12">
|
||||||
<motion.div
|
<motion.div
|
||||||
className="group bg-white p-6 rounded-xl shadow-sm hover:shadow-md transition-all duration-300 border border-gray-100"
|
className="group bg-white p-6 rounded-xl shadow-sm hover:shadow-md transition-all duration-300 border border-gray-100"
|
||||||
initial={{ opacity: 0, y: 20 }}
|
initial={{ opacity: 0, y: 20 }}
|
||||||
@ -551,12 +498,11 @@ export default function HomePage() {
|
|||||||
<ShieldCheck className="w-6 h-6 text-amber-600" />
|
<ShieldCheck className="w-6 h-6 text-amber-600" />
|
||||||
</div>
|
</div>
|
||||||
<h3 className="text-lg font-bold text-gray-900">
|
<h3 className="text-lg font-bold text-gray-900">
|
||||||
قوائم موثوقة
|
{t('feature1Title')}
|
||||||
</h3>
|
</h3>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<p className="text-gray-600 text-sm leading-relaxed">
|
<p className="text-gray-600 text-sm leading-relaxed">
|
||||||
كل عقار يتم التحقق منه بدقة لضمان الدقة والجودة.
|
{t('feature1Description')}
|
||||||
</p>
|
</p>
|
||||||
</motion.div>
|
</motion.div>
|
||||||
|
|
||||||
@ -573,12 +519,11 @@ export default function HomePage() {
|
|||||||
<Lock className="w-6 h-6 text-blue-600" />
|
<Lock className="w-6 h-6 text-blue-600" />
|
||||||
</div>
|
</div>
|
||||||
<h3 className="text-lg font-bold text-gray-900">
|
<h3 className="text-lg font-bold text-gray-900">
|
||||||
عمليات آمنة
|
{t('feature2Title')}
|
||||||
</h3>
|
</h3>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<p className="text-gray-600 text-sm leading-relaxed">
|
<p className="text-gray-600 text-sm leading-relaxed">
|
||||||
سلامتك هي أولويتنا. نوفر معاملات آمنة ونحمي معلوماتك الشخصية.
|
{t('feature2Description')}
|
||||||
</p>
|
</p>
|
||||||
</motion.div>
|
</motion.div>
|
||||||
|
|
||||||
@ -595,17 +540,61 @@ export default function HomePage() {
|
|||||||
<Zap className="w-6 h-6 text-green-600" />
|
<Zap className="w-6 h-6 text-green-600" />
|
||||||
</div>
|
</div>
|
||||||
<h3 className="text-lg font-bold text-gray-900">
|
<h3 className="text-lg font-bold text-gray-900">
|
||||||
نتائج سريعة
|
{t('feature3Title')}
|
||||||
</h3>
|
</h3>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<p className="text-gray-600 text-sm leading-relaxed">
|
<p className="text-gray-600 text-sm leading-relaxed">
|
||||||
اعثر على منزلك المثالي في دقائق باستخدام خوارزميات البحث والمطابقة المتقدمة لدينا.
|
{t('feature3Description')}
|
||||||
</p>
|
</p>
|
||||||
</motion.div>
|
</motion.div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{filteredProperties.length > 0 && searchFilters && (
|
||||||
|
<motion.div
|
||||||
|
initial={{ opacity: 0, y: 20 }}
|
||||||
|
animate={{ opacity: 1, y: 0 }}
|
||||||
|
transition={{ delay: 0.4, type: "spring" }}
|
||||||
|
className="flex flex-wrap items-center justify-center gap-3"
|
||||||
|
>
|
||||||
|
<div className="bg-white px-4 py-2 rounded-full shadow-sm border border-gray-200 text-sm">
|
||||||
|
<span className="text-gray-600">{t('filterPropertyTypeLabel')}</span>
|
||||||
|
<span className="font-bold text-gray-900">
|
||||||
|
{searchFilters.propertyType === 'all' ? t('all') :
|
||||||
|
searchFilters.propertyType === 'apartment' ? t('buildingType.apartment') :
|
||||||
|
searchFilters.propertyType === 'villa' ? t('buildingType.villa') : t('house')}
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
<div className="bg-white px-4 py-2 rounded-full shadow-sm border border-gray-200 text-sm">
|
||||||
|
<span className="text-gray-600">{t('filterPriceRangeLabel')}</span>
|
||||||
|
<span className="font-bold text-gray-900">
|
||||||
|
{searchFilters.priceRange === 'all' ? t('allPrices') :
|
||||||
|
searchFilters.priceRange === '0-500' ? t('priceRangeUnder500') :
|
||||||
|
searchFilters.priceRange === '500-1000' ? t('priceRange50to100') :
|
||||||
|
searchFilters.priceRange === '1000-2000' ? t('priceRange100to200') :
|
||||||
|
searchFilters.priceRange === '2000-3000' ? t('priceRange200to300') : t('priceRangeOver300')}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div className="bg-white px-4 py-2 rounded-full shadow-sm border border-gray-200 text-sm">
|
||||||
|
<span className="text-gray-600">{t('filterSourceLabel')}</span>
|
||||||
|
<span className="font-bold text-gray-900">
|
||||||
|
{searchFilters.ownerSource === 'all' ? t('all') :
|
||||||
|
searchFilters.ownerSource === 'owner' ? t('fromOwner') : t('fromAgency')}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div className="bg-white px-4 py-2 rounded-full shadow-sm border border-gray-200 text-sm">
|
||||||
|
<span className="text-gray-600">{t('filterRentTypeLabel')}</span>
|
||||||
|
<span className="font-bold text-gray-900">
|
||||||
|
{searchFilters.rentPeriod === 'all' ? t('all') :
|
||||||
|
searchFilters.rentPeriod === 'daily' ? t('dailyRent') : t('monthlyRent')}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
{searchFilters.availableToday && (
|
||||||
|
<div className="bg-white px-4 py-2 rounded-full shadow-sm border border-gray-200 text-sm">
|
||||||
|
<span className="font-bold text-gray-900">{t('availableFromToday')}</span>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</motion.div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -1,8 +1,9 @@
|
|||||||
'use client';
|
"use client";
|
||||||
|
|
||||||
import { useEffect, useState, useCallback } from 'react';
|
import { useEffect, useState, useCallback } from 'react';
|
||||||
import Link from 'next/link';
|
import Link from 'next/link';
|
||||||
import { motion } from 'framer-motion';
|
import { motion } from 'framer-motion';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
import {
|
import {
|
||||||
Home,
|
Home,
|
||||||
MapPin,
|
MapPin,
|
||||||
@ -66,7 +67,7 @@ const PAYMENT_METHODS = [
|
|||||||
desc: 'سيتم تفعيل الدفع الإلكتروني والتحويل لاحقاً',
|
desc: 'سيتم تفعيل الدفع الإلكتروني والتحويل لاحقاً',
|
||||||
icon: CreditCard,
|
icon: CreditCard,
|
||||||
active: false,
|
active: false,
|
||||||
},
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
function formatCurrency(v, sign = 'ل.س') {
|
function formatCurrency(v, sign = 'ل.س') {
|
||||||
@ -81,6 +82,7 @@ function formatDate(date) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export default function PaymentsPage() {
|
export default function PaymentsPage() {
|
||||||
|
const { t } = 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);
|
||||||
@ -121,7 +123,7 @@ export default function PaymentsPage() {
|
|||||||
setReservations(mapped);
|
setReservations(mapped);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error(err);
|
console.error(err);
|
||||||
toast.error('فشل تحميل المدفوعات');
|
toast.error(t('payments.loadingTransactions'));
|
||||||
} finally {
|
} finally {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
}
|
}
|
||||||
@ -141,10 +143,10 @@ export default function PaymentsPage() {
|
|||||||
setPayingId(reservation.id);
|
setPayingId(reservation.id);
|
||||||
try {
|
try {
|
||||||
await payDeposit({ reservationId: reservation.id });
|
await payDeposit({ reservationId: reservation.id });
|
||||||
toast.success('تم دفع السلفة بنجاح!');
|
toast.success(t('payments.depositPaidSuccess'));
|
||||||
loadReservations();
|
loadReservations();
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
toast.error(err?.message || 'فشل عملية الدفع');
|
toast.error(err?.message || t('payments.paymentFailed'));
|
||||||
} finally {
|
} finally {
|
||||||
setPayingId(null);
|
setPayingId(null);
|
||||||
}
|
}
|
||||||
@ -171,16 +173,16 @@ export default function PaymentsPage() {
|
|||||||
<div className="w-20 h-20 bg-amber-100 rounded-full flex items-center justify-center mx-auto mb-6">
|
<div className="w-20 h-20 bg-amber-100 rounded-full flex items-center justify-center mx-auto mb-6">
|
||||||
<Lock className="w-10 h-10 text-amber-600" />
|
<Lock className="w-10 h-10 text-amber-600" />
|
||||||
</div>
|
</div>
|
||||||
<h2 className="text-2xl font-bold text-gray-900 mb-3">المدفوعات</h2>
|
<h2 className="text-2xl font-bold text-gray-900 mb-3">{t('payments.title')}</h2>
|
||||||
<p className="text-gray-600 leading-relaxed mb-8">
|
<p className="text-gray-600 leading-relaxed mb-8">
|
||||||
دفعاتك مرتبطة بحاسبك لذلك يرجى تسجيل الدخول أولاً
|
{t('payments.loginRequiredDesc')}
|
||||||
</p>
|
</p>
|
||||||
<Link
|
<Link
|
||||||
href="/login"
|
href="/login"
|
||||||
className="inline-flex items-center gap-2 bg-amber-500 hover:bg-amber-600 text-white px-8 py-3 rounded-2xl text-lg font-semibold transition shadow-lg shadow-amber-200"
|
className="inline-flex items-center gap-2 bg-amber-500 hover:bg-amber-600 text-white px-8 py-3 rounded-2xl text-lg font-semibold transition shadow-lg shadow-amber-200"
|
||||||
>
|
>
|
||||||
<LogIn className="w-5 h-5" />
|
<LogIn className="w-5 h-5" />
|
||||||
تسجيل الدخول
|
{t('login')}
|
||||||
</Link>
|
</Link>
|
||||||
</motion.div>
|
</motion.div>
|
||||||
</div>
|
</div>
|
||||||
@ -199,15 +201,15 @@ export default function PaymentsPage() {
|
|||||||
animate={{ opacity: 1, y: 0 }}
|
animate={{ opacity: 1, y: 0 }}
|
||||||
className="mb-8"
|
className="mb-8"
|
||||||
>
|
>
|
||||||
<h1 className="text-3xl font-bold text-gray-900 mb-2">المدفوعات</h1>
|
<h1 className="text-3xl font-bold text-gray-900 mb-2">{t('payments.title')}</h1>
|
||||||
<p className="text-gray-600">إدارة مدفوعات الحجوزات والدفعات المقدمة</p>
|
<p className="text-gray-600">{t('payments.description')}</p>
|
||||||
</motion.div>
|
</motion.div>
|
||||||
|
|
||||||
{payables.length > 0 && (
|
{payables.length > 0 && (
|
||||||
<div className="space-y-6 mb-10">
|
<div className="space-y-6 mb-10">
|
||||||
<h2 className="text-xl font-bold text-gray-800 flex items-center gap-2">
|
<h2 className="text-xl font-bold text-gray-800 flex items-center gap-2">
|
||||||
<Wallet className="w-5 h-5 text-amber-500" />
|
<Wallet className="w-5 h-5 text-amber-500" />
|
||||||
المدفوعات المطلوبة
|
{t('payments.payablesTitle')}
|
||||||
</h2>
|
</h2>
|
||||||
{payables.map((r, i) => (
|
{payables.map((r, i) => (
|
||||||
<PaymentCard
|
<PaymentCard
|
||||||
@ -226,7 +228,7 @@ export default function PaymentsPage() {
|
|||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
<h2 className="text-xl font-bold text-gray-800 flex items-center gap-2">
|
<h2 className="text-xl font-bold text-gray-800 flex items-center gap-2">
|
||||||
<Clock className="w-5 h-5 text-gray-500" />
|
<Clock className="w-5 h-5 text-gray-500" />
|
||||||
الحجوزات السابقة
|
{t('payments.previousReservationsTitle')}
|
||||||
</h2>
|
</h2>
|
||||||
{others.map((r, i) => {
|
{others.map((r, i) => {
|
||||||
const statusKey = STATUS_MAP[r.status] || 'pending';
|
const statusKey = STATUS_MAP[r.status] || 'pending';
|
||||||
@ -263,23 +265,24 @@ export default function PaymentsPage() {
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{reservations.length === 0 && (
|
{reservations.length === 0 && (
|
||||||
<motion.div
|
<motion.div
|
||||||
initial={{ opacity: 0, y: 20 }}
|
initial={{ opacity: 0, y: 20 }}
|
||||||
animate={{ opacity: 1, y: 0 }}
|
animate={{ opacity: 1, y: 0 }}
|
||||||
className="bg-white rounded-2xl p-12 text-center border-2 border-dashed border-gray-300"
|
className="bg-white rounded-2xl p-12 text-center border-2 border-dashed border-gray-300"
|
||||||
>
|
>
|
||||||
<CreditCard className="w-16 h-16 text-gray-300 mx-auto mb-4" />
|
<CreditCard className="w-16 h-16 text-gray-300 mx-auto mb-4" />
|
||||||
<h3 className="text-xl font-bold text-gray-700 mb-2">لا توجد معاملات مالية</h3>
|
<h3 className="text-xl font-bold text-gray-700 mb-2">{t('payments.noTransactions')}</h3>
|
||||||
<p className="text-gray-500">ستظهر هنا مدفوعاتك للحجوزات</p>
|
<p className="text-gray-500">{t('payments.noTransactionsDesc')}</p>
|
||||||
</motion.div>
|
</motion.div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function PaymentCard({ reservation, payingId, selectedPayment, onSelectPayment, onPay }) {
|
// eslint-disable-next-line react/prop-types
|
||||||
|
function PaymentCard({ reservation, payingId, selectedPayment, onSelectPayment, onPay }) {
|
||||||
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);
|
||||||
@ -325,12 +328,12 @@ function PaymentCard({ reservation, payingId, selectedPayment, onSelectPayment,
|
|||||||
{/* Deposit amount */}
|
{/* Deposit amount */}
|
||||||
<div className="px-6 py-5 border-b border-gray-100">
|
<div className="px-6 py-5 border-b border-gray-100">
|
||||||
<div className="text-center">
|
<div className="text-center">
|
||||||
<p className="text-sm text-gray-500 mb-1">مبلغ التأمين (الرعبون)</p>
|
<p className="text-sm text-gray-500 mb-1">{t('payments.depositAmount')}</p>
|
||||||
<p className="text-4xl font-bold text-amber-600">
|
<p className="text-4xl font-bold text-amber-600">
|
||||||
{formatCurrency(amount, r.currencySign)}
|
{formatCurrency(amount, r.currencySign)}
|
||||||
</p>
|
</p>
|
||||||
<p className="text-xs text-gray-400 mt-2">
|
<p className="text-xs text-gray-400 mt-2">
|
||||||
يتم دفع التأمين إلى المنصة وليس إلى المالك
|
{t('payments.depositPaidToPlatform')}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -340,14 +343,14 @@ function PaymentCard({ reservation, payingId, selectedPayment, onSelectPayment,
|
|||||||
<div className="flex items-start gap-3">
|
<div className="flex items-start gap-3">
|
||||||
<Info className="w-5 h-5 text-amber-600 shrink-0 mt-0.5" />
|
<Info className="w-5 h-5 text-amber-600 shrink-0 mt-0.5" />
|
||||||
<p className="text-sm text-amber-800 leading-relaxed">
|
<p className="text-sm text-amber-800 leading-relaxed">
|
||||||
الدفع النقدي فقط هو المتاح. سيتم تفعيل الدفع الإلكتروني والتحويل لاحقاً.
|
{t('payments.cashOnlyNotice')}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Payment method options */}
|
{/* Payment method options */}
|
||||||
<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">طريقة الدفع</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) => {
|
{PAYMENT_METHODS.map((method) => {
|
||||||
const isSelected = selectedPayment === method.id;
|
const isSelected = selectedPayment === method.id;
|
||||||
@ -380,7 +383,7 @@ function PaymentCard({ reservation, payingId, selectedPayment, onSelectPayment,
|
|||||||
</p>
|
</p>
|
||||||
{!method.active && (
|
{!method.active && (
|
||||||
<span className="inline-block mt-1 text-[10px] font-medium text-gray-400 bg-gray-200 px-2 py-0.5 rounded-full">
|
<span className="inline-block mt-1 text-[10px] font-medium text-gray-400 bg-gray-200 px-2 py-0.5 rounded-full">
|
||||||
غير متاح
|
{t('validation.notAvailable', { defaultValue: 'غير متاح' })}
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
@ -408,12 +411,12 @@ function PaymentCard({ reservation, payingId, selectedPayment, onSelectPayment,
|
|||||||
{payingId === r.id ? (
|
{payingId === r.id ? (
|
||||||
<>
|
<>
|
||||||
<Loader2 className="w-5 h-5 animate-spin" />
|
<Loader2 className="w-5 h-5 animate-spin" />
|
||||||
جاري الدفع...
|
{t('payments.processingPayment')}
|
||||||
</>
|
</>
|
||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
<Banknote className="w-5 h-5" />
|
<Banknote className="w-5 h-5" />
|
||||||
دفع التأمين ({formatCurrency(amount, r.currencySign)})
|
{t('payments.payButton', { amount: formatCurrency(amount, r.currencySign) })}
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</motion.button>
|
</motion.button>
|
||||||
@ -421,13 +424,13 @@ function PaymentCard({ reservation, payingId, selectedPayment, onSelectPayment,
|
|||||||
|
|
||||||
{/* 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
|
||||||
type="button"
|
type="button"
|
||||||
onClick={() => setShowCashDialog(true)}
|
onClick={() => setShowCashDialog(true)}
|
||||||
className="w-full bg-white border-2 border-amber-200 hover:border-amber-400 text-amber-700 font-bold py-3 px-6 rounded-2xl text-base transition-all flex items-center justify-center gap-2"
|
className="w-full bg-white border-2 border-amber-200 hover:border-amber-400 text-amber-700 font-bold py-3 px-6 rounded-2xl text-base transition-all flex items-center justify-center gap-2"
|
||||||
>
|
>
|
||||||
<Building className="w-5 h-5" />
|
<Building className="w-5 h-5" />
|
||||||
سأدفع نقداً
|
{t('payments.payCashButton')}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -442,15 +445,15 @@ function PaymentCard({ reservation, payingId, selectedPayment, onSelectPayment,
|
|||||||
<div className="w-16 h-16 bg-amber-100 rounded-full flex items-center justify-center mx-auto mb-5">
|
<div className="w-16 h-16 bg-amber-100 rounded-full flex items-center justify-center mx-auto mb-5">
|
||||||
<Clock className="w-8 h-8 text-amber-600" />
|
<Clock className="w-8 h-8 text-amber-600" />
|
||||||
</div>
|
</div>
|
||||||
<h3 className="text-xl font-bold text-gray-900 mb-3">الدفع النقدي قيد الانتظار</h3>
|
<h3 className="text-xl font-bold text-gray-900 mb-3">{t('payments.cashPendingDialogTitle')}</h3>
|
||||||
<p className="text-gray-600 leading-relaxed mb-6">
|
<p className="text-gray-600 leading-relaxed mb-6">
|
||||||
اذهب إلى موقع المنصة وادفع العربون.
|
{t('payments.cashPendingDialogDesc1')}
|
||||||
<br />
|
<br />
|
||||||
ستقوم المنصة بتأكيد الدفع بعد استلام المبلغ.
|
{t('payments.cashPendingDialogDesc2')}
|
||||||
</p>
|
</p>
|
||||||
<div className="bg-gray-50 rounded-2xl p-4 mb-6 text-right">
|
<div className="bg-gray-50 rounded-2xl p-4 mb-6 text-right">
|
||||||
<p className="text-sm font-bold text-gray-800 mb-1">موقع المنصة</p>
|
<p className="text-sm font-bold text-gray-800 mb-1">{t('payments.platformOfficeLabel')}</p>
|
||||||
<p className="text-sm text-gray-600">مكتب المنصة: أبو رمانة، شارع المالكي، دمشق</p>
|
<p className="text-sm text-gray-600">{t('payments.platformOfficeFullAddress')}</p>
|
||||||
<p className="text-sm text-gray-600 flex items-center gap-1 mt-1" dir="ltr">
|
<p className="text-sm text-gray-600 flex items-center gap-1 mt-1" dir="ltr">
|
||||||
<Phone className="w-3.5 h-3.5" />
|
<Phone className="w-3.5 h-3.5" />
|
||||||
+963567823411
|
+963567823411
|
||||||
@ -462,13 +465,13 @@ function PaymentCard({ reservation, payingId, selectedPayment, onSelectPayment,
|
|||||||
onClick={() => setShowCashDialog(false)}
|
onClick={() => setShowCashDialog(false)}
|
||||||
className="w-full px-5 py-3 rounded-xl border border-gray-300 text-gray-700 hover:bg-gray-100 transition-colors font-medium"
|
className="w-full px-5 py-3 rounded-xl border border-gray-300 text-gray-700 hover:bg-gray-100 transition-colors font-medium"
|
||||||
>
|
>
|
||||||
إغلاق
|
{t('payments.closeButton')}
|
||||||
</button>
|
</button>
|
||||||
<Link
|
<Link
|
||||||
href="/reservations"
|
href="/reservations"
|
||||||
className="w-full px-5 py-3 rounded-xl bg-amber-500 text-white font-semibold text-center hover:bg-amber-600 transition-colors"
|
className="w-full px-5 py-3 rounded-xl bg-amber-500 text-white font-semibold text-center hover:bg-amber-600 transition-colors"
|
||||||
>
|
>
|
||||||
حجوزاتي
|
{t('payments.myReservationsLink')}
|
||||||
</Link>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
</motion.div>
|
</motion.div>
|
||||||
@ -480,9 +483,9 @@ function PaymentCard({ reservation, payingId, selectedPayment, onSelectPayment,
|
|||||||
<div className="flex items-start gap-3">
|
<div className="flex items-start gap-3">
|
||||||
<Landmark className="w-5 h-5 text-amber-600 shrink-0 mt-0.5" />
|
<Landmark className="w-5 h-5 text-amber-600 shrink-0 mt-0.5" />
|
||||||
<div>
|
<div>
|
||||||
<p className="text-sm font-bold text-gray-800 mb-1">موقع الدفع النقدي</p>
|
<p className="text-sm font-bold text-gray-800 mb-1">{t('payments.cashPaymentLocationLabel')}</p>
|
||||||
<p className="text-sm text-gray-600 leading-relaxed">
|
<p className="text-sm text-gray-600 leading-relaxed">
|
||||||
مكتب المنصة: أبو رمانة، شارع المالكي، دمشق
|
{t('payments.platformOfficeFullAddress')}
|
||||||
</p>
|
</p>
|
||||||
<p className="text-sm text-gray-600 flex items-center gap-1 mt-1">
|
<p className="text-sm text-gray-600 flex items-center gap-1 mt-1">
|
||||||
<Phone className="w-3.5 h-3.5" />
|
<Phone className="w-3.5 h-3.5" />
|
||||||
|
|||||||
@ -4,6 +4,7 @@ import { useState, useEffect } from "react";
|
|||||||
import { motion } from "framer-motion";
|
import { motion } from "framer-motion";
|
||||||
import { useRouter } from "next/navigation";
|
import { useRouter } from "next/navigation";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
import {
|
import {
|
||||||
User,
|
User,
|
||||||
Mail,
|
Mail,
|
||||||
@ -24,6 +25,7 @@ import AuthService from "../services/AuthService";
|
|||||||
import { getCustomerByUserId, getOwnerByUserId } from "../utils/api";
|
import { getCustomerByUserId, getOwnerByUserId } from "../utils/api";
|
||||||
|
|
||||||
export default function ProfilePage() {
|
export default function ProfilePage() {
|
||||||
|
const { t, i18n } = useTranslation();
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const [user, setUser] = useState(null);
|
const [user, setUser] = useState(null);
|
||||||
const [isLoading, setIsLoading] = useState(true);
|
const [isLoading, setIsLoading] = useState(true);
|
||||||
@ -76,11 +78,11 @@ export default function ProfilePage() {
|
|||||||
bio: profile.bio || "",
|
bio: profile.bio || "",
|
||||||
location: profile.address || profile.location || "",
|
location: profile.address || profile.location || "",
|
||||||
joinedDate: profile.createdAt
|
joinedDate: profile.createdAt
|
||||||
? new Date(profile.createdAt).toLocaleDateString("ar-SA", {
|
? new Date(profile.createdAt).toLocaleDateString(i18n.language === 'ar' ? 'ar-SA' : 'en-US', {
|
||||||
month: "long",
|
month: "long",
|
||||||
year: "numeric",
|
year: "numeric",
|
||||||
})
|
})
|
||||||
: new Date().toLocaleDateString("ar-SA", {
|
: new Date().toLocaleDateString(i18n.language === 'ar' ? 'ar-SA' : 'en-US', {
|
||||||
month: "long",
|
month: "long",
|
||||||
year: "numeric",
|
year: "numeric",
|
||||||
}),
|
}),
|
||||||
@ -107,7 +109,7 @@ export default function ProfilePage() {
|
|||||||
whatsapp: "",
|
whatsapp: "",
|
||||||
bio: "",
|
bio: "",
|
||||||
location: "",
|
location: "",
|
||||||
joinedDate: new Date().toLocaleDateString("ar-SA", {
|
joinedDate: new Date().toLocaleDateString(i18n.language === 'ar' ? 'ar-SA' : 'en-US', {
|
||||||
month: "long",
|
month: "long",
|
||||||
year: "numeric",
|
year: "numeric",
|
||||||
}),
|
}),
|
||||||
@ -144,7 +146,7 @@ export default function ProfilePage() {
|
|||||||
setFormData(updatedData);
|
setFormData(updatedData);
|
||||||
localStorage.setItem("userProfile", JSON.stringify(updatedData));
|
localStorage.setItem("userProfile", JSON.stringify(updatedData));
|
||||||
setEditingBio(false);
|
setEditingBio(false);
|
||||||
toast.success("تم تحديث نبذة عني بنجاح");
|
toast.success(t('bio-updated-success'));
|
||||||
};
|
};
|
||||||
|
|
||||||
const fadeInUp = {
|
const fadeInUp = {
|
||||||
@ -158,14 +160,14 @@ export default function ProfilePage() {
|
|||||||
<div className="min-h-screen bg-gray-50 flex items-center justify-center">
|
<div className="min-h-screen bg-gray-50 flex items-center justify-center">
|
||||||
<div className="text-center">
|
<div className="text-center">
|
||||||
<Loader2 className="w-12 h-12 text-amber-500 animate-spin mx-auto mb-4" />
|
<Loader2 className="w-12 h-12 text-amber-500 animate-spin mx-auto mb-4" />
|
||||||
<p className="text-gray-600">جاري تحميل الملف الشخصي...</p>
|
<p className="text-gray-600">{t('loading-profile')}</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="min-h-screen bg-gray-50 py-8">
|
<div dir={i18n.language === 'ar' ? 'rtl' : 'ltr'} className="min-h-screen bg-gray-50 py-8">
|
||||||
<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">
|
||||||
@ -179,7 +181,7 @@ export default function ProfilePage() {
|
|||||||
className="flex items-center gap-2 text-gray-600 hover:text-amber-600 transition-colors"
|
className="flex items-center gap-2 text-gray-600 hover:text-amber-600 transition-colors"
|
||||||
>
|
>
|
||||||
<ArrowLeft className="w-5 h-5" />
|
<ArrowLeft className="w-5 h-5" />
|
||||||
<span>العودة للرئيسية</span>
|
<span>{t('backToHome')}</span>
|
||||||
</Link>
|
</Link>
|
||||||
</motion.div>
|
</motion.div>
|
||||||
|
|
||||||
@ -229,12 +231,12 @@ export default function ProfilePage() {
|
|||||||
|
|
||||||
<div className="flex items-center justify-center gap-2 text-gray-500 mt-2">
|
<div className="flex items-center justify-center gap-2 text-gray-500 mt-2">
|
||||||
<MapPin className="w-4 h-4" />
|
<MapPin className="w-4 h-4" />
|
||||||
<span>{formData.location || "الموقع غير محدد"}</span>
|
<span>{formData.location || t('addressNotSpecified')}</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex items-center justify-center gap-2 text-gray-500 mt-1">
|
<div className="flex items-center justify-center gap-2 text-gray-500 mt-1">
|
||||||
<Calendar className="w-4 h-4" />
|
<Calendar className="w-4 h-4" />
|
||||||
<span>عضو منذ {formData.joinedDate}</span>
|
<span> {t('member-since')} {formData.joinedDate}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -242,7 +244,7 @@ export default function ProfilePage() {
|
|||||||
<div className="bg-gray-50 p-4 rounded-xl group">
|
<div className="bg-gray-50 p-4 rounded-xl group">
|
||||||
<div className="flex justify-between items-start mb-2">
|
<div className="flex justify-between items-start mb-2">
|
||||||
<label className="text-sm font-medium text-gray-600">
|
<label className="text-sm font-medium text-gray-600">
|
||||||
البريد الإلكتروني
|
{t('emailLabel')}
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center gap-2 text-gray-900">
|
<div className="flex items-center gap-2 text-gray-900">
|
||||||
@ -254,41 +256,41 @@ export default function ProfilePage() {
|
|||||||
<div className="bg-gray-50 p-4 rounded-xl group">
|
<div className="bg-gray-50 p-4 rounded-xl group">
|
||||||
<div className="flex justify-between items-start mb-2">
|
<div className="flex justify-between items-start mb-2">
|
||||||
<label className="text-sm font-medium text-gray-600">
|
<label className="text-sm font-medium text-gray-600">
|
||||||
رقم الهاتف
|
{t('phoneLabel')}
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center gap-2 text-gray-900">
|
<div className="flex items-center gap-2 text-gray-900">
|
||||||
<Phone className="w-5 h-5 text-gray-400" />
|
<Phone className="w-5 h-5 text-gray-400" />
|
||||||
<span>{formData.phone || "غير محدد"}</span>
|
<span>{formData.phone || t('not-specified')}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="bg-gray-50 p-4 rounded-xl group">
|
<div className="bg-gray-50 p-4 rounded-xl group">
|
||||||
<div className="flex justify-between items-start mb-2">
|
<div className="flex justify-between items-start mb-2">
|
||||||
<label className="text-sm font-medium text-gray-600">
|
<label className="text-sm font-medium text-gray-600">
|
||||||
رقم الواتساب
|
{t('whatsapp-label')}
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center gap-2 text-gray-900">
|
<div className="flex items-center gap-2 text-gray-900">
|
||||||
<MessageCircle className="w-5 h-5 text-gray-400" />
|
<MessageCircle className="w-5 h-5 text-gray-400" />
|
||||||
<span>{formData.whatsapp || "غير محدد"}</span>
|
<span>{formData.whatsapp || t('not-specified')}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="bg-gray-50 p-4 rounded-xl">
|
<div className="bg-gray-50 p-4 rounded-xl">
|
||||||
<label className="block text-sm font-medium text-gray-600 mb-2">
|
<label className="block text-sm font-medium text-gray-600 mb-2">
|
||||||
نوع الحساب
|
{t('account-type')}
|
||||||
</label>
|
</label>
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
{user?.role === "owner" ? (
|
{user?.role === "owner" ? (
|
||||||
<>
|
<>
|
||||||
<Building className="w-5 h-5 text-amber-500" />
|
<Building className="w-5 h-5 text-amber-500" />
|
||||||
<span className="text-gray-900">مالك عقار</span>
|
<span className="text-gray-900">{t('property-owner')}</span>
|
||||||
</>
|
</>
|
||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
<Home className="w-5 h-5 text-blue-500" />
|
<Home className="w-5 h-5 text-blue-500" />
|
||||||
<span className="text-gray-900">مستأجر</span>
|
<span className="text-gray-900">{t('customer')}</span>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
@ -348,15 +350,15 @@ export default function ProfilePage() {
|
|||||||
<div className="grid grid-cols-3 gap-4 mt-6">
|
<div className="grid grid-cols-3 gap-4 mt-6">
|
||||||
<div className="bg-amber-50 p-4 rounded-xl text-center">
|
<div className="bg-amber-50 p-4 rounded-xl text-center">
|
||||||
<div className="text-2xl font-bold text-amber-600">12</div>
|
<div className="text-2xl font-bold text-amber-600">12</div>
|
||||||
<div className="text-xs text-gray-600">عقارات</div>
|
<div className="text-xs text-gray-600">{t('propertiesListed')}</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="bg-blue-50 p-4 rounded-xl text-center">
|
<div className="bg-blue-50 p-4 rounded-xl text-center">
|
||||||
<div className="text-2xl font-bold text-blue-600">8</div>
|
<div className="text-2xl font-bold text-blue-600">8</div>
|
||||||
<div className="text-xs text-gray-600">حجوزات نشطة</div>
|
<div className="text-xs text-gray-600">{t('citiesCovered')}</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="bg-green-50 p-4 rounded-xl text-center">
|
<div className="bg-green-50 p-4 rounded-xl text-center">
|
||||||
<div className="text-2xl font-bold text-green-600">4.8</div>
|
<div className="text-2xl font-bold text-green-600">4.8</div>
|
||||||
<div className="text-xs text-gray-600">تقييم</div>
|
<div className="text-xs text-gray-600">{t('customerSatisfaction')}</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@ -35,9 +35,10 @@ import { getRentProperties, getSaleProperties } from '../utils/api';
|
|||||||
import { useFavorites } from '@/app/contexts/FavoritesContext';
|
import { useFavorites } from '@/app/contexts/FavoritesContext';
|
||||||
import AuthService from '@/app/services/AuthService';
|
import AuthService from '@/app/services/AuthService';
|
||||||
import toast, { Toaster } from 'react-hot-toast';
|
import toast, { Toaster } from 'react-hot-toast';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
// Map API data to UI format
|
// Map API data to UI format
|
||||||
function mapApiProperty(item, index) {
|
function mapApiProperty(t, item, index) {
|
||||||
const info = item.propertyInformation || {};
|
const info = item.propertyInformation || {};
|
||||||
|
|
||||||
const dailyPrice = item.dailyRent ?? item.monthlyRent ?? item.price ?? 0;
|
const dailyPrice = item.dailyRent ?? item.monthlyRent ?? item.price ?? 0;
|
||||||
@ -50,11 +51,11 @@ function mapApiProperty(item, index) {
|
|||||||
const status = statusMap[info.status] ?? statusMap[item.status] ?? 'available';
|
const status = statusMap[info.status] ?? statusMap[item.status] ?? 'available';
|
||||||
|
|
||||||
const features = [];
|
const features = [];
|
||||||
if (item.isSmokeAllow) features.push('يسمح بالتدخين');
|
if (item.isSmokeAllow) features.push(t('smoke-allowed'));
|
||||||
if (item.isVisitorAllow) features.push('يسمح بالزوار');
|
if (item.isVisitorAllow) features.push(t('visitors-allowed'));
|
||||||
if (item.specializedFor) features.push('متخصص');
|
if (item.specializedFor) features.push(t('specialized'));
|
||||||
if (info.numberOfBedRooms) features.push(`${info.numberOfBedRooms} غرف نوم`);
|
if (info.numberOfBedRooms) features.push(`${info.numberOfBedRooms} ${t('bedrooms')}`);
|
||||||
if (info.numberOfBathRooms) features.push(`${info.numberOfBathRooms} حمامات`);
|
if (info.numberOfBathRooms) features.push(`${info.numberOfBathRooms} ${t('bathrooms')}`);
|
||||||
|
|
||||||
// Extract images from API and build full URLs
|
// Extract images from API and build full URLs
|
||||||
const apiBase = typeof window !== 'undefined' ? (process.env.NEXT_PUBLIC_API_URL || 'https://45.93.137.91.nip.io/api') : '';
|
const apiBase = typeof window !== 'undefined' ? (process.env.NEXT_PUBLIC_API_URL || 'https://45.93.137.91.nip.io/api') : '';
|
||||||
@ -65,13 +66,13 @@ function mapApiProperty(item, index) {
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
id: item.id ?? index + 1,
|
id: item.id ?? index + 1,
|
||||||
title: info.address || `عقار #${item.id || index + 1}`,
|
title: info.address || `${t('property')} #${item.id || index + 1}`,
|
||||||
description: info.description || '',
|
description: info.description || '',
|
||||||
type: propType,
|
type: propType,
|
||||||
price: dailyPrice,
|
price: dailyPrice,
|
||||||
priceUnit: 'daily',
|
priceUnit: 'daily',
|
||||||
location: {
|
location: {
|
||||||
city: extractCity(info.address) || 'دمشق',
|
city: extractCity(info.address) || 'damascus',
|
||||||
district: info.address || '',
|
district: info.address || '',
|
||||||
},
|
},
|
||||||
bedrooms: info.numberOfBedRooms || 0,
|
bedrooms: info.numberOfBedRooms || 0,
|
||||||
@ -88,9 +89,23 @@ function mapApiProperty(item, index) {
|
|||||||
|
|
||||||
function extractCity(address) {
|
function extractCity(address) {
|
||||||
if (!address) return '';
|
if (!address) return '';
|
||||||
const cities = ['دمشق', 'حلب', 'حمص', 'اللاذقية', 'درعا', 'طرطوس', 'السويداء', 'دير الزور', 'الرقة', 'إدلب', 'الحسكة', 'القامشلي', 'ريف دمشق'];
|
const cityMap = {
|
||||||
for (const city of cities) {
|
'دمشق': 'damascus',
|
||||||
if (address.includes(city)) return city;
|
'حلب': 'aleppo',
|
||||||
|
'حمص': 'homs',
|
||||||
|
'اللاذقية': 'latakia',
|
||||||
|
'درعا': 'daraa',
|
||||||
|
'طرطوس': 'tartous',
|
||||||
|
'السويداء': 'suweida',
|
||||||
|
'دير الزور': 'deirEzzor',
|
||||||
|
'الرقة': 'raqqa',
|
||||||
|
'إدلب': 'idlib',
|
||||||
|
'الحسكة': 'hasakah',
|
||||||
|
'القامشلي': 'qamishli',
|
||||||
|
'ريف دمشق': 'ruralDamascus'
|
||||||
|
};
|
||||||
|
for (const [arabic, key] of Object.entries(cityMap)) {
|
||||||
|
if (address.includes(arabic)) return key;
|
||||||
}
|
}
|
||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
@ -98,6 +113,7 @@ function extractCity(address) {
|
|||||||
// API-only — no fallback data
|
// API-only — no fallback data
|
||||||
|
|
||||||
const PropertyCard = ({ property, viewMode = 'grid', onLoginRequired }) => {
|
const PropertyCard = ({ property, viewMode = 'grid', onLoginRequired }) => {
|
||||||
|
const { t, i18n } = useTranslation();
|
||||||
const { isFavorite: checkFavorite, addFavorite, removeFavorite } = useFavorites();
|
const { isFavorite: checkFavorite, addFavorite, removeFavorite } = useFavorites();
|
||||||
const [favLoading, setFavLoading] = useState(false);
|
const [favLoading, setFavLoading] = useState(false);
|
||||||
const [currentImage, setCurrentImage] = useState(0);
|
const [currentImage, setCurrentImage] = useState(0);
|
||||||
@ -118,7 +134,7 @@ const PropertyCard = ({ property, viewMode = 'grid', onLoginRequired }) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const formatCurrency = (amount) => {
|
const formatCurrency = (amount) => {
|
||||||
return amount?.toLocaleString() + ' ل.س';
|
return amount?.toLocaleString() + ' ' + t('syp-symbol');
|
||||||
};
|
};
|
||||||
|
|
||||||
const getPropertyTypeIcon = (type) => {
|
const getPropertyTypeIcon = (type) => {
|
||||||
@ -133,10 +149,10 @@ const PropertyCard = ({ property, viewMode = 'grid', onLoginRequired }) => {
|
|||||||
|
|
||||||
const getPropertyTypeLabel = (type) => {
|
const getPropertyTypeLabel = (type) => {
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case 'villa': return 'فيلا';
|
case 'villa': return t('buildingType.villa');
|
||||||
case 'apartment': return 'شقة';
|
case 'apartment': return t('buildingType.apartment');
|
||||||
case 'house': return 'بيت';
|
case 'house': return t('house');
|
||||||
case 'studio': return 'استوديو';
|
case 'studio': return t('buildingType.studio');
|
||||||
default: return type;
|
default: return type;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -187,33 +203,33 @@ const PropertyCard = ({ property, viewMode = 'grid', onLoginRequired }) => {
|
|||||||
{getPropertyTypeLabel(property.type)}
|
{getPropertyTypeLabel(property.type)}
|
||||||
</span>
|
</span>
|
||||||
<span className={`px-2 py-1 rounded-lg text-xs font-medium ${property.status === 'available' ? 'bg-gray-800 text-white' : 'bg-gray-200 text-gray-600'}`}>
|
<span className={`px-2 py-1 rounded-lg text-xs font-medium ${property.status === 'available' ? 'bg-gray-800 text-white' : 'bg-gray-200 text-gray-600'}`}>
|
||||||
{property.status === 'available' ? 'متاح' : 'محجوز'}
|
{property.status === 'available' ? t('available') : t('booked')}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<h3 className="text-xl font-bold text-gray-900 mb-1">{property.title}</h3>
|
<h3 className="text-xl font-bold text-gray-900 mb-1">{property.title}</h3>
|
||||||
<div className="flex items-center gap-1 text-gray-500 text-sm mb-3">
|
<div className="flex items-center gap-1 text-gray-500 text-sm mb-3">
|
||||||
<MapPin className="w-4 h-4" />
|
<MapPin className="w-4 h-4" />
|
||||||
{property.location.city}، {property.location.district}
|
{t('city.' + property.location.city)}، {property.location.district}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="text-left">
|
<div className="text-left">
|
||||||
<div className="text-2xl font-bold text-gray-900">{formatCurrency(property.price)}</div>
|
<div className="text-2xl font-bold text-gray-900">{formatCurrency(property.price)}</div>
|
||||||
<div className="text-xs text-gray-500">/{property.priceUnit === 'daily' ? 'يوم' : 'شهر'}</div>
|
<div className="text-xs text-gray-500">/{property.priceUnit === 'daily' ? t('day') : t('month')}</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex flex-wrap gap-4 mb-4">
|
<div className="flex flex-wrap gap-4 mb-4">
|
||||||
<div className="flex items-center gap-1 text-gray-600">
|
<div className="flex items-center gap-1 text-gray-600">
|
||||||
<Bed className="w-4 h-4" />
|
<Bed className="w-4 h-4" />
|
||||||
<span>{property.bedrooms} غرف</span>
|
<span>{property.bedrooms} {t('rooms')}</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center gap-1 text-gray-600">
|
<div className="flex items-center gap-1 text-gray-600">
|
||||||
<Bath className="w-4 h-4" />
|
<Bath className="w-4 h-4" />
|
||||||
<span>{property.bathrooms} حمامات</span>
|
<span>{property.bathrooms} {t('bathrooms')}</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center gap-1 text-gray-600">
|
<div className="flex items-center gap-1 text-gray-600">
|
||||||
<Square className="w-4 h-4" />
|
<Square className="w-4 h-4" />
|
||||||
<span>{property.area} م²</span>
|
<span>{property.area} {t('sqm')}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -224,7 +240,7 @@ const PropertyCard = ({ property, viewMode = 'grid', onLoginRequired }) => {
|
|||||||
href={`/property/${property.id}`}
|
href={`/property/${property.id}`}
|
||||||
className="flex-1 bg-gray-800 text-white py-3 rounded-xl font-medium hover:bg-gray-900 transition-colors text-center"
|
className="flex-1 bg-gray-800 text-white py-3 rounded-xl font-medium hover:bg-gray-900 transition-colors text-center"
|
||||||
>
|
>
|
||||||
عرض التفاصيل
|
{t('viewDetails')}
|
||||||
</Link>
|
</Link>
|
||||||
<button className="px-4 bg-gray-100 text-gray-700 py-3 rounded-xl font-medium hover:bg-gray-200 transition-colors flex items-center gap-2">
|
<button className="px-4 bg-gray-100 text-gray-700 py-3 rounded-xl font-medium hover:bg-gray-200 transition-colors flex items-center gap-2">
|
||||||
<Phone className="w-4 h-4" />
|
<Phone className="w-4 h-4" />
|
||||||
@ -269,18 +285,18 @@ const PropertyCard = ({ property, viewMode = 'grid', onLoginRequired }) => {
|
|||||||
{getPropertyTypeLabel(property.type)}
|
{getPropertyTypeLabel(property.type)}
|
||||||
</span>
|
</span>
|
||||||
{property.status === 'available' && (
|
{property.status === 'available' && (
|
||||||
<span className="px-2 py-1 bg-gray-800 text-white rounded-lg text-xs font-medium">متاح</span>
|
<span className="px-2 py-1 bg-gray-800 text-white rounded-lg text-xs font-medium">{t('available')}</span>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<h3 className="font-bold text-gray-900 mb-1 line-clamp-1">{property.title}</h3>
|
<h3 className="font-bold text-gray-900 mb-1 line-clamp-1">{property.title}</h3>
|
||||||
<div className="flex items-center gap-1 text-gray-500 text-xs mb-2">
|
<div className="flex items-center gap-1 text-gray-500 text-xs mb-2">
|
||||||
<MapPin className="w-3 h-3" />
|
<MapPin className="w-3 h-3" />
|
||||||
<span className="line-clamp-1">{property.location.city}، {property.location.district}</span>
|
<span className="line-clamp-1">{t('city.' + property.location.city)}، {property.location.district}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="text-left">
|
<div className="text-left">
|
||||||
<div className="text-xl font-bold text-gray-900">{formatCurrency(property.price)}</div>
|
<div className="text-xl font-bold text-gray-900">{formatCurrency(property.price)}</div>
|
||||||
<div className="text-xs text-gray-500">/{property.priceUnit === 'daily' ? 'يوم' : 'شهر'}</div>
|
<div className="text-xs text-gray-500">/{property.priceUnit === 'daily' ? t('day') : t('month')}</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -296,7 +312,7 @@ const PropertyCard = ({ property, viewMode = 'grid', onLoginRequired }) => {
|
|||||||
</div>
|
</div>
|
||||||
<div className="flex items-center gap-1">
|
<div className="flex items-center gap-1">
|
||||||
<Square className="w-4 h-4" />
|
<Square className="w-4 h-4" />
|
||||||
<span>{property.area}م²</span>
|
<span>{property.area}{t('sqm')}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{property.rating > 0 && (
|
{property.rating > 0 && (
|
||||||
@ -311,7 +327,7 @@ const PropertyCard = ({ property, viewMode = 'grid', onLoginRequired }) => {
|
|||||||
href={`/property/${property.id}`}
|
href={`/property/${property.id}`}
|
||||||
className="block w-full bg-gray-800 text-white py-3 rounded-xl font-medium hover:bg-gray-900 transition-colors text-center"
|
className="block w-full bg-gray-800 text-white py-3 rounded-xl font-medium hover:bg-gray-900 transition-colors text-center"
|
||||||
>
|
>
|
||||||
عرض التفاصيل
|
{t('viewDetails')}
|
||||||
</Link>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
</motion.div>
|
</motion.div>
|
||||||
@ -319,31 +335,40 @@ const PropertyCard = ({ property, viewMode = 'grid', onLoginRequired }) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const FilterBar = ({ filters, onFilterChange }) => {
|
const FilterBar = ({ filters, onFilterChange }) => {
|
||||||
|
const { t, i18n } = useTranslation();
|
||||||
const [showFilters, setShowFilters] = useState(false);
|
const [showFilters, setShowFilters] = useState(false);
|
||||||
|
|
||||||
const propertyTypes = [
|
const propertyTypes = [
|
||||||
{ id: 'all', label: 'الكل' },
|
{ id: 'all', label: t('all') },
|
||||||
{ id: 'apartment', label: 'شقة', icon: Building2 },
|
{ id: 'apartment', label: t('buildingType.apartment'), icon: Building2 },
|
||||||
{ id: 'villa', label: 'فيلا', icon: Home },
|
{ id: 'villa', label: t('buildingType.villa'), icon: Home },
|
||||||
{ id: 'house', label: 'بيت', icon: Home },
|
{ id: 'house', label: t('house'), icon: Home },
|
||||||
];
|
];
|
||||||
|
|
||||||
const priceRanges = [
|
const priceRanges = [
|
||||||
{ id: 'all', label: 'جميع الأسعار' },
|
{ id: 'all', label: t('allPrices') },
|
||||||
{ id: '0-500000', label: 'أقل من 500,000' },
|
{ id: '0-500000', label: t('price-range-less-than-500k') },
|
||||||
{ id: '500000-1000000', label: '500,000 - 1,000,000' },
|
{ id: '500000-1000000', label: t('price-range-500k-to-1m') },
|
||||||
{ id: '1000000-2000000', label: '1,000,000 - 2,000,000' },
|
{ id: '1000000-2000000', label: t('price-range-1m-to-2m') },
|
||||||
{ id: '2000000-5000000', label: '2,000,000 - 5,000,000' },
|
{ id: '2000000-5000000', label: t('price-range-2m-to-5m') },
|
||||||
{ id: '5000000+', label: 'أكثر من 5,000,000' }
|
{ id: '5000000+', label: t('price-range-more-than-5m') }
|
||||||
];
|
];
|
||||||
|
|
||||||
const cities = [
|
const cities = [
|
||||||
{ id: 'all', label: 'جميع المدن' },
|
{ id: 'all', label: t('allCities') },
|
||||||
{ id: 'دمشق', label: 'دمشق' },
|
{ id: 'damascus', label: t('city.damascus') },
|
||||||
{ id: 'حلب', label: 'حلب' },
|
{ id: 'aleppo', label: t('city.aleppo') },
|
||||||
{ id: 'حمص', label: 'حمص' },
|
{ id: 'homs', label: t('city.homs') },
|
||||||
{ id: 'اللاذقية', label: 'اللاذقية' },
|
{ id: 'latakia', label: t('city.latakia') },
|
||||||
{ id: 'درعا', label: 'درعا' }
|
{ id: 'daraa', label: t('city.daraa') },
|
||||||
|
{ id: 'tartous', label: t('city.tartous') },
|
||||||
|
{ id: 'suweida', label: t('city.suweida') },
|
||||||
|
{ id: 'deirEzzor', label: t('city.deirEzzor') },
|
||||||
|
{ id: 'raqqa', label: t('city.raqqa') },
|
||||||
|
{ id: 'idlib', label: t('city.idlib') },
|
||||||
|
{ id: 'hasakah', label: t('city.hasakah') },
|
||||||
|
{ id: 'qamishli', label: t('city.qamishli') },
|
||||||
|
{ id: 'ruralDamascus', label: t('city.ruralDamascus') }
|
||||||
];
|
];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -353,7 +378,7 @@ const FilterBar = ({ filters, onFilterChange }) => {
|
|||||||
<Search className="absolute right-3 top-1/2 transform -translate-y-1/2 w-5 h-5 text-gray-400" />
|
<Search className="absolute right-3 top-1/2 transform -translate-y-1/2 w-5 h-5 text-gray-400" />
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
placeholder="ابحث عن عقار..."
|
placeholder={t('search-property-placeholder')}
|
||||||
className="w-full pr-12 px-4 py-3 border border-gray-200 rounded-xl focus:ring-2 focus:ring-gray-300 focus:border-gray-300 transition-all"
|
className="w-full pr-12 px-4 py-3 border border-gray-200 rounded-xl focus:ring-2 focus:ring-gray-300 focus:border-gray-300 transition-all"
|
||||||
value={filters.search}
|
value={filters.search}
|
||||||
onChange={(e) => onFilterChange({ ...filters, search: e.target.value })}
|
onChange={(e) => onFilterChange({ ...filters, search: e.target.value })}
|
||||||
@ -364,7 +389,7 @@ const FilterBar = ({ filters, onFilterChange }) => {
|
|||||||
className="px-6 py-3 bg-gray-100 rounded-xl font-medium hover:bg-gray-200 transition-colors flex items-center gap-2 text-gray-700"
|
className="px-6 py-3 bg-gray-100 rounded-xl font-medium hover:bg-gray-200 transition-colors flex items-center gap-2 text-gray-700"
|
||||||
>
|
>
|
||||||
<Filter className="w-5 h-5" />
|
<Filter className="w-5 h-5" />
|
||||||
فلاتر متقدمة
|
{t('advanced-filters')}
|
||||||
<ChevronDown className={`w-4 h-4 transition-transform ${showFilters ? 'rotate-180' : ''}`} />
|
<ChevronDown className={`w-4 h-4 transition-transform ${showFilters ? 'rotate-180' : ''}`} />
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
@ -379,7 +404,7 @@ const FilterBar = ({ filters, onFilterChange }) => {
|
|||||||
>
|
>
|
||||||
<div className="grid grid-cols-1 md:grid-cols-3 gap-4 pt-4 border-t border-gray-100">
|
<div className="grid grid-cols-1 md:grid-cols-3 gap-4 pt-4 border-t border-gray-100">
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-sm font-medium text-gray-700 mb-2">نوع العقار</label>
|
<label className="block text-sm font-medium text-gray-700 mb-2">{t('property-type')}</label>
|
||||||
<div className="flex flex-wrap gap-2">
|
<div className="flex flex-wrap gap-2">
|
||||||
{propertyTypes.map((type) => {
|
{propertyTypes.map((type) => {
|
||||||
const Icon = type.icon;
|
const Icon = type.icon;
|
||||||
@ -398,7 +423,7 @@ const FilterBar = ({ filters, onFilterChange }) => {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-sm font-medium text-gray-700 mb-2">المدينة</label>
|
<label className="block text-sm font-medium text-gray-700 mb-2">{t('city')}</label>
|
||||||
<select
|
<select
|
||||||
value={filters.city}
|
value={filters.city}
|
||||||
onChange={(e) => onFilterChange({ ...filters, city: e.target.value })}
|
onChange={(e) => onFilterChange({ ...filters, city: e.target.value })}
|
||||||
@ -411,7 +436,7 @@ const FilterBar = ({ filters, onFilterChange }) => {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-sm font-medium text-gray-700 mb-2">نطاق السعر</label>
|
<label className="block text-sm font-medium text-gray-700 mb-2">{t('price-range')}</label>
|
||||||
<select
|
<select
|
||||||
value={filters.priceRange}
|
value={filters.priceRange}
|
||||||
onChange={(e) => onFilterChange({ ...filters, priceRange: e.target.value })}
|
onChange={(e) => onFilterChange({ ...filters, priceRange: e.target.value })}
|
||||||
@ -424,13 +449,13 @@ const FilterBar = ({ filters, onFilterChange }) => {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-sm font-medium text-gray-700 mb-2">غرف النوم</label>
|
<label className="block text-sm font-medium text-gray-700 mb-2">{t('bedrooms')}</label>
|
||||||
<select
|
<select
|
||||||
value={filters.bedrooms}
|
value={filters.bedrooms}
|
||||||
onChange={(e) => onFilterChange({ ...filters, bedrooms: e.target.value })}
|
onChange={(e) => onFilterChange({ ...filters, bedrooms: e.target.value })}
|
||||||
className="w-full px-4 py-2 border border-gray-200 rounded-xl focus:ring-2 focus:ring-gray-300 focus:border-gray-300"
|
className="w-full px-4 py-2 border border-gray-200 rounded-xl focus:ring-2 focus:ring-gray-300 focus:border-gray-300"
|
||||||
>
|
>
|
||||||
<option value="all">جميع الأعداد</option>
|
<option value="all">{t('all-numbers')}</option>
|
||||||
<option value="1">1+</option>
|
<option value="1">1+</option>
|
||||||
<option value="2">2+</option>
|
<option value="2">2+</option>
|
||||||
<option value="3">3+</option>
|
<option value="3">3+</option>
|
||||||
@ -440,18 +465,18 @@ const FilterBar = ({ filters, onFilterChange }) => {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-sm font-medium text-gray-700 mb-2">المساحة (م²)</label>
|
<label className="block text-sm font-medium text-gray-700 mb-2">{t('area-sqm')}</label>
|
||||||
<div className="flex gap-2">
|
<div className="flex gap-2">
|
||||||
<input
|
<input
|
||||||
type="number"
|
type="number"
|
||||||
placeholder="من"
|
placeholder={t('from')}
|
||||||
className="w-full px-4 py-2 border border-gray-200 rounded-xl focus:ring-2 focus:ring-gray-300 focus:border-gray-300"
|
className="w-full px-4 py-2 border border-gray-200 rounded-xl focus:ring-2 focus:ring-gray-300 focus:border-gray-300"
|
||||||
value={filters.minArea}
|
value={filters.minArea}
|
||||||
onChange={(e) => onFilterChange({ ...filters, minArea: e.target.value })}
|
onChange={(e) => onFilterChange({ ...filters, minArea: e.target.value })}
|
||||||
/>
|
/>
|
||||||
<input
|
<input
|
||||||
type="number"
|
type="number"
|
||||||
placeholder="إلى"
|
placeholder={t('to')}
|
||||||
className="w-full px-4 py-2 border border-gray-200 rounded-xl focus:ring-2 focus:ring-gray-300 focus:border-gray-300"
|
className="w-full px-4 py-2 border border-gray-200 rounded-xl focus:ring-2 focus:ring-gray-300 focus:border-gray-300"
|
||||||
value={filters.maxArea}
|
value={filters.maxArea}
|
||||||
onChange={(e) => onFilterChange({ ...filters, maxArea: e.target.value })}
|
onChange={(e) => onFilterChange({ ...filters, maxArea: e.target.value })}
|
||||||
@ -474,13 +499,13 @@ const FilterBar = ({ filters, onFilterChange }) => {
|
|||||||
})}
|
})}
|
||||||
className="px-6 py-2 bg-gray-100 rounded-xl font-medium hover:bg-gray-200 transition-colors text-gray-700"
|
className="px-6 py-2 bg-gray-100 rounded-xl font-medium hover:bg-gray-200 transition-colors text-gray-700"
|
||||||
>
|
>
|
||||||
إعادة تعيين
|
{t('reset')}
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
onClick={() => setShowFilters(false)}
|
onClick={() => setShowFilters(false)}
|
||||||
className="px-6 py-2 bg-gray-800 text-white rounded-xl font-medium hover:bg-gray-900 transition-colors"
|
className="px-6 py-2 bg-gray-800 text-white rounded-xl font-medium hover:bg-gray-900 transition-colors"
|
||||||
>
|
>
|
||||||
تطبيق الفلاتر
|
{t('apply-filters')}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</motion.div>
|
</motion.div>
|
||||||
@ -491,6 +516,7 @@ const FilterBar = ({ filters, onFilterChange }) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export default function PropertiesPage() {
|
export default function PropertiesPage() {
|
||||||
|
const { t, i18n } = useTranslation();
|
||||||
const [purposeTab, setPurposeTab] = useState('rent');
|
const [purposeTab, setPurposeTab] = useState('rent');
|
||||||
const [viewMode, setViewMode] = useState('grid');
|
const [viewMode, setViewMode] = useState('grid');
|
||||||
const [sortBy, setSortBy] = useState('newest');
|
const [sortBy, setSortBy] = useState('newest');
|
||||||
@ -520,8 +546,8 @@ export default function PropertiesPage() {
|
|||||||
const saleList = Array.isArray(saleData) ? saleData : [];
|
const saleList = Array.isArray(saleData) ? saleData : [];
|
||||||
|
|
||||||
const mapped = [
|
const mapped = [
|
||||||
...rentList.map((p, i) => ({ ...mapApiProperty(p, i), purpose: 'rent' })),
|
...rentList.map((p, i) => ({ ...mapApiProperty(t, p, i), purpose: 'rent' })),
|
||||||
...saleList.map((p, i) => ({ ...mapApiProperty(p, rentList.length + i), purpose: 'sale' })),
|
...saleList.map((p, i) => ({ ...mapApiProperty(t, p, rentList.length + i), purpose: 'sale' })),
|
||||||
];
|
];
|
||||||
|
|
||||||
if (mapped.length > 0) {
|
if (mapped.length > 0) {
|
||||||
@ -575,26 +601,26 @@ export default function PropertiesPage() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="min-h-screen bg-gray-50 py-8">
|
<div dir={i18n.language === 'ar' ? 'rtl' : 'ltr'} className="min-h-screen bg-gray-50 py-8">
|
||||||
<div className="container mx-auto px-4">
|
<div className="container mx-auto px-4">
|
||||||
<motion.div
|
<motion.div
|
||||||
initial={{ opacity: 0, y: -20 }}
|
initial={{ opacity: 0, y: -20 }}
|
||||||
animate={{ opacity: 1, y: 0 }}
|
animate={{ opacity: 1, y: 0 }}
|
||||||
className="text-center mb-8"
|
className="text-center mb-8"
|
||||||
>
|
>
|
||||||
<h1 className="text-4xl font-bold text-gray-900 mb-2">{purposeTab === 'rent' ? 'عقارات للإيجار' : 'عقارات للبيع'}</h1>
|
<h1 className="text-4xl font-bold text-gray-900 mb-2">{purposeTab === 'rent' ? t('properties-for-rent') : t('properties-for-sale')}</h1>
|
||||||
<p className="text-gray-500">أفضل العقارات في سوريا</p>
|
<p className="text-gray-500">{t('best-properties-in-syria')}</p>
|
||||||
|
|
||||||
{/* Purpose Toggle */}
|
{/* Purpose Toggle */}
|
||||||
<div className="flex justify-center mt-4">
|
<div className="flex justify-center mt-4">
|
||||||
<div className="inline-flex bg-gray-100 rounded-xl p-1">
|
<div className="inline-flex bg-gray-100 rounded-xl p-1">
|
||||||
<button onClick={() => setPurposeTab('rent')}
|
<button onClick={() => setPurposeTab('rent')}
|
||||||
className={`px-6 py-2 rounded-lg text-sm font-medium transition-all ${purposeTab === 'rent' ? 'bg-white text-amber-600 shadow-sm' : 'text-gray-500 hover:text-gray-700'}`}>
|
className={`px-6 py-2 rounded-lg text-sm font-medium transition-all ${purposeTab === 'rent' ? 'bg-white text-amber-600 shadow-sm' : 'text-gray-500 hover:text-gray-700'}`}>
|
||||||
للإيجار
|
{t('for-rent')}
|
||||||
</button>
|
</button>
|
||||||
<button onClick={() => setPurposeTab('sale')}
|
<button onClick={() => setPurposeTab('sale')}
|
||||||
className={`px-6 py-2 rounded-lg text-sm font-medium transition-all ${purposeTab === 'sale' ? 'bg-white text-blue-600 shadow-sm' : 'text-gray-500 hover:text-gray-700'}`}>
|
className={`px-6 py-2 rounded-lg text-sm font-medium transition-all ${purposeTab === 'sale' ? 'bg-white text-blue-600 shadow-sm' : 'text-gray-500 hover:text-gray-700'}`}>
|
||||||
للبيع
|
{t('for-sale')}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -609,7 +635,7 @@ export default function PropertiesPage() {
|
|||||||
|
|
||||||
<div className="flex justify-between items-center my-6">
|
<div className="flex justify-between items-center my-6">
|
||||||
<div className="text-gray-600">
|
<div className="text-gray-600">
|
||||||
<span className="font-bold text-gray-900">{filteredProperties.length}</span> عقار متاح
|
<span className="font-bold text-gray-900">{filteredProperties.length}</span> {t('property-available')}
|
||||||
</div>
|
</div>
|
||||||
<div className="flex gap-3">
|
<div className="flex gap-3">
|
||||||
<select
|
<select
|
||||||
@ -617,10 +643,10 @@ export default function PropertiesPage() {
|
|||||||
onChange={(e) => setSortBy(e.target.value)}
|
onChange={(e) => setSortBy(e.target.value)}
|
||||||
className="px-4 py-2 border border-gray-200 rounded-xl focus:ring-2 focus:ring-gray-300 focus:border-gray-300 text-gray-700"
|
className="px-4 py-2 border border-gray-200 rounded-xl focus:ring-2 focus:ring-gray-300 focus:border-gray-300 text-gray-700"
|
||||||
>
|
>
|
||||||
<option value="newest">الأحدث</option>
|
<option value="newest">{t('newest')}</option>
|
||||||
<option value="price_asc">السعر: من الأقل</option>
|
<option value="price_asc">{t('price-low-to-high')}</option>
|
||||||
<option value="price_desc">السعر: من الأعلى</option>
|
<option value="price_desc">{t('price-high-to-low')}</option>
|
||||||
<option value="rating">التقييم</option>
|
<option value="rating">{t('rating')}</option>
|
||||||
</select>
|
</select>
|
||||||
<div className="flex gap-2">
|
<div className="flex gap-2">
|
||||||
<button
|
<button
|
||||||
@ -657,8 +683,8 @@ export default function PropertiesPage() {
|
|||||||
<div className="w-24 h-24 bg-gray-100 rounded-full flex items-center justify-center mx-auto mb-4">
|
<div className="w-24 h-24 bg-gray-100 rounded-full flex items-center justify-center mx-auto mb-4">
|
||||||
<Home className="w-12 h-12 text-gray-400" />
|
<Home className="w-12 h-12 text-gray-400" />
|
||||||
</div>
|
</div>
|
||||||
<h3 className="text-xl font-bold text-gray-700 mb-2">لا توجد عقارات</h3>
|
<h3 className="text-xl font-bold text-gray-700 mb-2">{t('no-properties')}</h3>
|
||||||
<p className="text-gray-500">جرب تغيير معايير البحث</p>
|
<p className="text-gray-500">{t('try-changing-search-criteria')}</p>
|
||||||
</motion.div>
|
</motion.div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
@ -674,20 +700,20 @@ export default function PropertiesPage() {
|
|||||||
<div className="w-14 h-14 bg-amber-100 rounded-full flex items-center justify-center mx-auto mb-4">
|
<div className="w-14 h-14 bg-amber-100 rounded-full flex items-center justify-center mx-auto mb-4">
|
||||||
<Heart className="w-7 h-7 text-amber-600" />
|
<Heart className="w-7 h-7 text-amber-600" />
|
||||||
</div>
|
</div>
|
||||||
<h3 className="text-xl font-bold text-gray-900 mb-2">تسجيل الدخول مطلوب</h3>
|
<h3 className="text-xl font-bold text-gray-900 mb-2">{t('login-required-title')}</h3>
|
||||||
<p className="text-gray-500 mb-6">يجب تسجيل الدخول لإضافة العقارات إلى المفضلة</p>
|
<p className="text-gray-500 mb-6">{t('login-required-favorites-desc')}</p>
|
||||||
<div className="flex gap-3">
|
<div className="flex gap-3">
|
||||||
<button
|
<button
|
||||||
onClick={() => setShowLoginDialog(false)}
|
onClick={() => setShowLoginDialog(false)}
|
||||||
className="flex-1 py-3 border border-gray-200 rounded-xl font-medium text-gray-600 hover:bg-gray-50 transition-colors"
|
className="flex-1 py-3 border border-gray-200 rounded-xl font-medium text-gray-600 hover:bg-gray-50 transition-colors"
|
||||||
>
|
>
|
||||||
إلغاء
|
{t('cancel')}
|
||||||
</button>
|
</button>
|
||||||
<Link
|
<Link
|
||||||
href="/login"
|
href="/login"
|
||||||
className="flex-1 py-3 bg-amber-500 text-white rounded-xl font-medium hover:bg-amber-600 transition-colors text-center"
|
className="flex-1 py-3 bg-amber-500 text-white rounded-xl font-medium hover:bg-amber-600 transition-colors text-center"
|
||||||
>
|
>
|
||||||
تسجيل الدخول
|
{t('login')}
|
||||||
</Link>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
</motion.div>
|
</motion.div>
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { useState, useEffect, useMemo, useRef } from "react";
|
import { useState, useEffect, useMemo, useRef } from "react";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
import { motion, AnimatePresence } from "framer-motion";
|
import { motion, AnimatePresence } from "framer-motion";
|
||||||
import toast, { Toaster } from "react-hot-toast";
|
import toast, { Toaster } from "react-hot-toast";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
@ -149,62 +150,9 @@ function buildImageUrl(img) {
|
|||||||
return `${apiBase}${img.startsWith("/") ? "" : "/Pictures/"}${img}`;
|
return `${apiBase}${img.startsWith("/") ? "" : "/Pictures/"}${img}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
const serviceLabels = {
|
const svcKey = (k) => "propertyService." + k.charAt(0).toLowerCase() + k.slice(1);
|
||||||
Electricity: "كهرباء",
|
const termKey = (k) => "propertyTerm." + k.charAt(0).toLowerCase() + k.slice(1);
|
||||||
Internet: "إنترنت",
|
const proxKey = (k) => "proximity." + k.toLowerCase();
|
||||||
Heating: "تدفئة",
|
|
||||||
Water: "ماء",
|
|
||||||
Pool: "مسبح",
|
|
||||||
PrivateGarden: "حديقة خاصة",
|
|
||||||
Parking: "موقف سيارات",
|
|
||||||
Security247: "حراسة 24 س",
|
|
||||||
CentralHeating: "تدفئة مركزية",
|
|
||||||
CentralAirConditioning: "تكييف مركزي",
|
|
||||||
EquippedKitchen: "مطبخ مجهز",
|
|
||||||
MaidsRoom: "غرفة خادمة",
|
|
||||||
Elevator: "مصعد",
|
|
||||||
Gym: "نادي رياضي",
|
|
||||||
Sauna: "ساونا",
|
|
||||||
Jacuzzi: "جاكوزي",
|
|
||||||
Balcony: "بلكونة",
|
|
||||||
Rooftop: "سطح",
|
|
||||||
Furnished: "مفروش",
|
|
||||||
AirConditioning: "تكييف",
|
|
||||||
SatelliteTV: "تلفاز",
|
|
||||||
Fireplace: "مدفأة",
|
|
||||||
StudyRoom: "غرفة دراسة",
|
|
||||||
Storage: "مستودع",
|
|
||||||
Laundry: "غرفة غسيل",
|
|
||||||
SmartHome: "منزل ذكي",
|
|
||||||
};
|
|
||||||
|
|
||||||
const termLabels = {
|
|
||||||
NoSmoking: "ممنوع التدخين",
|
|
||||||
NoAnimals: "ممنوع الحيوانات الأليفة",
|
|
||||||
NoParties: "ممنوع الحفلات",
|
|
||||||
NoAlcohol: "ممنوع الكحول",
|
|
||||||
SuitableForChildren: "مناسب للأطفال",
|
|
||||||
SuitableForFamilies: "مناسب للعائلات",
|
|
||||||
SuitableForStudents: "مناسب للطلاب",
|
|
||||||
SuitableForElderly: "مناسب لكبار السن",
|
|
||||||
OnlyFemales: "إناث فقط",
|
|
||||||
OnlyMales: "ذكور فقط",
|
|
||||||
};
|
|
||||||
|
|
||||||
const proximityLabels = {
|
|
||||||
School: "مدرسة",
|
|
||||||
Hospital: "مستشفى",
|
|
||||||
Restaurant: "مطعم",
|
|
||||||
University: "جامعة",
|
|
||||||
Park: "حديقة",
|
|
||||||
Mall: "مركز تسوق",
|
|
||||||
Supermarket: "سوبر ماركت",
|
|
||||||
Pharmacy: "صيدلية",
|
|
||||||
Mosque: "مسجد",
|
|
||||||
Bank: "بنك",
|
|
||||||
Airport: "مطار",
|
|
||||||
BusStation: "موقف باص",
|
|
||||||
};
|
|
||||||
|
|
||||||
function mapApiDetail(item) {
|
function mapApiDetail(item) {
|
||||||
if (!item) return null;
|
if (!item) return null;
|
||||||
@ -231,30 +179,10 @@ function mapApiDetail(item) {
|
|||||||
BuildingTypeKeys[info.buildingType] ??
|
BuildingTypeKeys[info.buildingType] ??
|
||||||
BuildingTypeKeys[item.type] ??
|
BuildingTypeKeys[item.type] ??
|
||||||
"apartment";
|
"apartment";
|
||||||
const typeLabel =
|
|
||||||
{
|
|
||||||
apartment: "شقة",
|
|
||||||
villa: "فيلا",
|
|
||||||
house: "بيت",
|
|
||||||
sweet: "سويت",
|
|
||||||
studio: "استوديو",
|
|
||||||
office: "مكتب",
|
|
||||||
shop: "متجر",
|
|
||||||
warehouse: "مستودع",
|
|
||||||
farms: "مزرعة",
|
|
||||||
room: "غرفة ضمن شقة",
|
|
||||||
}[propType] || "عقار";
|
|
||||||
const status =
|
const status =
|
||||||
PropertyStatusKeys[info.status] ??
|
PropertyStatusKeys[info.status] ??
|
||||||
PropertyStatusKeys[item.status] ??
|
PropertyStatusKeys[item.status] ??
|
||||||
"available";
|
"available";
|
||||||
const statusLabel =
|
|
||||||
{
|
|
||||||
available: "متاح",
|
|
||||||
booked: "محجوز",
|
|
||||||
notAvailable: "غير متاح",
|
|
||||||
maintenance: "صيانة",
|
|
||||||
}[status] || "متاح";
|
|
||||||
|
|
||||||
const rawImages = Array.isArray(info.images) ? info.images : [];
|
const rawImages = Array.isArray(info.images) ? info.images : [];
|
||||||
const photosFallback = Array.isArray(details.photos) ? details.photos : [];
|
const photosFallback = Array.isArray(details.photos) ? details.photos : [];
|
||||||
@ -312,10 +240,10 @@ function mapApiDetail(item) {
|
|||||||
return {
|
return {
|
||||||
id: item.id,
|
id: item.id,
|
||||||
propertyInformationId: info.id,
|
propertyInformationId: info.id,
|
||||||
title: details.description || info.address || `عقار #${item.id}`,
|
title: details.description || info.address || "",
|
||||||
description: details.description || info.description || "",
|
description: details.description || info.description || "",
|
||||||
type: propType,
|
type: propType,
|
||||||
typeLabel,
|
typeLabel: propType,
|
||||||
price,
|
price,
|
||||||
priceUnit,
|
priceUnit,
|
||||||
priceDisplay: { daily: dailyPrice, monthly: monthlyPrice, sale: salePrice },
|
priceDisplay: { daily: dailyPrice, monthly: monthlyPrice, sale: salePrice },
|
||||||
@ -324,7 +252,7 @@ function mapApiDetail(item) {
|
|||||||
propertyCondition,
|
propertyCondition,
|
||||||
furnished,
|
furnished,
|
||||||
location: {
|
location: {
|
||||||
city: extractCity(info.address) || "دمشق",
|
city: extractCity(info.address) || "damascus",
|
||||||
address: info.address || "",
|
address: info.address || "",
|
||||||
lat: parseFloat(info.cordsX) || 0,
|
lat: parseFloat(info.cordsX) || 0,
|
||||||
lng: parseFloat(info.cordsY) || 0,
|
lng: parseFloat(info.cordsY) || 0,
|
||||||
@ -337,7 +265,7 @@ function mapApiDetail(item) {
|
|||||||
balconies,
|
balconies,
|
||||||
images,
|
images,
|
||||||
status,
|
status,
|
||||||
statusLabel,
|
statusLabel: status,
|
||||||
services,
|
services,
|
||||||
terms,
|
terms,
|
||||||
proximity,
|
proximity,
|
||||||
@ -355,6 +283,7 @@ function mapApiDetail(item) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export default function PropertyDetailsPage() {
|
export default function PropertyDetailsPage() {
|
||||||
|
const { t, i18n } = useTranslation();
|
||||||
const params = useParams();
|
const params = useParams();
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const { isFavorite, addFavorite, removeFavorite } = useFavorites();
|
const { isFavorite, addFavorite, removeFavorite } = useFavorites();
|
||||||
@ -483,7 +412,7 @@ export default function PropertyDetailsPage() {
|
|||||||
setContactInfo(info);
|
setContactInfo(info);
|
||||||
setShowContact(true);
|
setShowContact(true);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
toast.error("فشل تحميل معلومات الاتصال");
|
toast.error(t("contactLoadFailed"));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -501,7 +430,7 @@ export default function PropertyDetailsPage() {
|
|||||||
await addFavorite(property.id);
|
await addFavorite(property.id);
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
toast.error("حدث خطأ");
|
toast.error(t("errorOccurred"));
|
||||||
} finally {
|
} finally {
|
||||||
setFavLoading(false);
|
setFavLoading(false);
|
||||||
}
|
}
|
||||||
@ -513,7 +442,7 @@ export default function PropertyDetailsPage() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (!bookingDates.start || !bookingDates.end) {
|
if (!bookingDates.start || !bookingDates.end) {
|
||||||
setBookingError("يرجى تحديد تاريخ البداية والنهاية");
|
setBookingError(t("validationDateRequired"));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
setBookingLoading(true);
|
setBookingLoading(true);
|
||||||
@ -525,29 +454,23 @@ export default function PropertyDetailsPage() {
|
|||||||
bookingDates.end,
|
bookingDates.end,
|
||||||
);
|
);
|
||||||
setBookingSuccess(true);
|
setBookingSuccess(true);
|
||||||
toast.success("تم إرسال طلب الحجز بنجاح");
|
toast.success(t("bookingSentSuccess"));
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
setBookingError(err.message || "فشل الحجز");
|
setBookingError(err.message || t("bookingFailed"));
|
||||||
} finally {
|
} finally {
|
||||||
setBookingLoading(false);
|
setBookingLoading(false);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const MONTHS_AR = [
|
const MONTH_KEYS = [
|
||||||
"يناير",
|
"month.january", "month.february", "month.march", "month.april",
|
||||||
"فبراير",
|
"month.may", "month.june", "month.july", "month.august",
|
||||||
"مارس",
|
"month.september", "month.october", "month.november", "month.december",
|
||||||
"أبريل",
|
];
|
||||||
"مايو",
|
const DAY_KEYS = [
|
||||||
"يونيو",
|
"dayAbbr.sun", "dayAbbr.mon", "dayAbbr.tue", "dayAbbr.wed",
|
||||||
"يوليو",
|
"dayAbbr.thu", "dayAbbr.fri", "dayAbbr.sat",
|
||||||
"أغسطس",
|
|
||||||
"سبتمبر",
|
|
||||||
"أكتوبر",
|
|
||||||
"نوفمبر",
|
|
||||||
"ديسمبر",
|
|
||||||
];
|
];
|
||||||
const DAYS_AR = ["ح", "ن", "ث", "ر", "خ", "ج", "س"];
|
|
||||||
|
|
||||||
const availableDatesSet = useMemo(() => {
|
const availableDatesSet = useMemo(() => {
|
||||||
const dates = new Set();
|
const dates = new Set();
|
||||||
@ -592,7 +515,7 @@ export default function PropertyDetailsPage() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (!selectedStart || !selectedEnd) {
|
if (!selectedStart || !selectedEnd) {
|
||||||
setBookingError("يرجى تحديد تاريخ البداية والنهاية");
|
setBookingError(t("validationDateRequired"));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
setBookingLoading(true);
|
setBookingLoading(true);
|
||||||
@ -603,9 +526,9 @@ export default function PropertyDetailsPage() {
|
|||||||
const endDate = new Date(selectedEnd + "T00:00:00.000").toISOString();
|
const endDate = new Date(selectedEnd + "T00:00:00.000").toISOString();
|
||||||
await bookReservation(propInfoId, startDate, endDate);
|
await bookReservation(propInfoId, startDate, endDate);
|
||||||
setBookingSuccess(true);
|
setBookingSuccess(true);
|
||||||
toast.success("تم إرسال طلب الحجز بنجاح");
|
toast.success(t("bookingSentSuccess"));
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
setBookingError(err.message || "فشل الحجز");
|
setBookingError(err.message || t("bookingFailed"));
|
||||||
} finally {
|
} finally {
|
||||||
setBookingLoading(false);
|
setBookingLoading(false);
|
||||||
}
|
}
|
||||||
@ -636,7 +559,7 @@ export default function PropertyDetailsPage() {
|
|||||||
<div className="min-h-screen bg-gray-50 flex items-center justify-center">
|
<div className="min-h-screen bg-gray-50 flex items-center justify-center">
|
||||||
<div className="text-center">
|
<div className="text-center">
|
||||||
<Loader2 className="w-12 h-12 text-amber-500 animate-spin mx-auto mb-4" />
|
<Loader2 className="w-12 h-12 text-amber-500 animate-spin mx-auto mb-4" />
|
||||||
<p className="text-gray-600">جاري تحميل العقار...</p>
|
<p className="text-gray-600">{t("loadingProperty")}</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
@ -650,15 +573,15 @@ export default function PropertyDetailsPage() {
|
|||||||
<Home className="w-12 h-12 text-amber-600" />
|
<Home className="w-12 h-12 text-amber-600" />
|
||||||
</div>
|
</div>
|
||||||
<h2 className="text-2xl font-bold text-gray-900 mb-2">
|
<h2 className="text-2xl font-bold text-gray-900 mb-2">
|
||||||
العقار غير موجود
|
{t("propertyNotFound")}
|
||||||
</h2>
|
</h2>
|
||||||
<p className="text-gray-600 mb-6">لم يتم العثور على العقار المطلوب</p>
|
<p className="text-gray-600 mb-6">{t("propertyNotFoundDesc")}</p>
|
||||||
<Link
|
<Link
|
||||||
href="/properties"
|
href="/properties"
|
||||||
className="inline-flex items-center gap-2 bg-amber-500 text-white px-6 py-3 rounded-xl font-medium hover:bg-amber-600"
|
className="inline-flex items-center gap-2 bg-amber-500 text-white px-6 py-3 rounded-xl font-medium hover:bg-amber-600"
|
||||||
>
|
>
|
||||||
<ArrowLeft className="w-5 h-5" />
|
<ArrowLeft className="w-5 h-5" />
|
||||||
العودة إلى العقارات
|
{t("backToProperties")}
|
||||||
</Link>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -679,7 +602,7 @@ export default function PropertyDetailsPage() {
|
|||||||
: "daily";
|
: "daily";
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="min-h-screen bg-gray-50" dir="rtl">
|
<div className="min-h-screen bg-gray-50" dir={i18n.language === 'ar' ? 'rtl' : 'ltr'}>
|
||||||
<Toaster position="top-center" reverseOrder={false} />
|
<Toaster position="top-center" reverseOrder={false} />
|
||||||
|
|
||||||
<div className="container mx-auto px-4 py-6">
|
<div className="container mx-auto px-4 py-6">
|
||||||
@ -693,7 +616,7 @@ export default function PropertyDetailsPage() {
|
|||||||
className="inline-flex items-center gap-2 text-gray-600 hover:text-amber-600 transition-colors"
|
className="inline-flex items-center gap-2 text-gray-600 hover:text-amber-600 transition-colors"
|
||||||
>
|
>
|
||||||
<ArrowLeft className="w-5 h-5" />
|
<ArrowLeft className="w-5 h-5" />
|
||||||
العودة إلى العقارات
|
{t("backToProperties")}
|
||||||
</Link>
|
</Link>
|
||||||
</motion.div>
|
</motion.div>
|
||||||
|
|
||||||
@ -723,7 +646,7 @@ export default function PropertyDetailsPage() {
|
|||||||
>
|
>
|
||||||
<div className="text-center">
|
<div className="text-center">
|
||||||
<ImageIcon className="w-20 h-20 text-gray-500 mx-auto mb-2" />
|
<ImageIcon className="w-20 h-20 text-gray-500 mx-auto mb-2" />
|
||||||
<p className="text-gray-400 text-sm">لا توجد صور</p>
|
<p className="text-gray-400 text-sm">{t("noImages")}</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
@ -731,7 +654,7 @@ export default function PropertyDetailsPage() {
|
|||||||
{isMostRequested && (
|
{isMostRequested && (
|
||||||
<div className="absolute top-4 right-4 z-10">
|
<div className="absolute top-4 right-4 z-10">
|
||||||
<span className="bg-gradient-to-l from-amber-500 to-amber-600 text-white px-3 py-1.5 rounded-full text-xs font-bold shadow-lg flex items-center gap-1">
|
<span className="bg-gradient-to-l from-amber-500 to-amber-600 text-white px-3 py-1.5 rounded-full text-xs font-bold shadow-lg flex items-center gap-1">
|
||||||
<Star className="w-3 h-3 fill-white" /> الأكثر طلباً
|
<Star className="w-3 h-3 fill-white" /> {t("mostRequested")}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
@ -765,10 +688,10 @@ export default function PropertyDetailsPage() {
|
|||||||
|
|
||||||
<div className="absolute bottom-4 right-4 flex gap-2 z-10">
|
<div className="absolute bottom-4 right-4 flex gap-2 z-10">
|
||||||
<span className="bg-black/70 text-white px-3 py-1 rounded-full text-sm font-medium backdrop-blur-sm">
|
<span className="bg-black/70 text-white px-3 py-1 rounded-full text-sm font-medium backdrop-blur-sm">
|
||||||
{property.statusLabel}
|
{t("propertyStatus." + property.statusLabel)}
|
||||||
</span>
|
</span>
|
||||||
<span className="bg-black/70 text-white px-3 py-1 rounded-full text-sm font-medium backdrop-blur-sm">
|
<span className="bg-black/70 text-white px-3 py-1 rounded-full text-sm font-medium backdrop-blur-sm">
|
||||||
{property.typeLabel}
|
{t("buildingType." + property.typeLabel)}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="absolute bottom-4 left-4 bg-black/70 text-white px-3 py-1 rounded-full text-xs backdrop-blur-sm z-10">
|
<div className="absolute bottom-4 left-4 bg-black/70 text-white px-3 py-1 rounded-full text-xs backdrop-blur-sm z-10">
|
||||||
@ -808,21 +731,21 @@ export default function PropertyDetailsPage() {
|
|||||||
<div>
|
<div>
|
||||||
<div className="flex items-center gap-1.5 mb-1 flex-wrap">
|
<div className="flex items-center gap-1.5 mb-1 flex-wrap">
|
||||||
<span className="px-2 py-0.5 bg-amber-100 text-amber-800 rounded-full text-xs">
|
<span className="px-2 py-0.5 bg-amber-100 text-amber-800 rounded-full text-xs">
|
||||||
{property.typeLabel}
|
{t("buildingType." + property.typeLabel)}
|
||||||
</span>
|
</span>
|
||||||
<span
|
<span
|
||||||
className={`px-2 py-0.5 rounded-full text-xs ${property.status === "available" ? "bg-green-100 text-green-800" : "bg-yellow-100 text-yellow-800"}`}
|
className={`px-2 py-0.5 rounded-full text-xs ${property.status === "available" ? "bg-green-100 text-green-800" : "bg-yellow-100 text-yellow-800"}`}
|
||||||
>
|
>
|
||||||
{property.statusLabel}
|
{t("propertyStatus." + property.statusLabel)}
|
||||||
</span>
|
</span>
|
||||||
{property.isRent && property.displayType && (
|
{property.isRent && property.displayType && (
|
||||||
<span className="px-2 py-0.5 bg-blue-100 text-blue-800 rounded-full text-xs">
|
<span className="px-2 py-0.5 bg-blue-100 text-blue-800 rounded-full text-xs">
|
||||||
{(() => {
|
{(() => {
|
||||||
const dt = property.displayType.toLowerCase();
|
const dt = property.displayType.toLowerCase();
|
||||||
if (dt === "both" || dt.includes("both"))
|
if (dt === "both" || dt.includes("both"))
|
||||||
return "يومي وشهري";
|
return t("dailyAndMonthly");
|
||||||
if (dt.includes("daily")) return "يومي";
|
if (dt.includes("daily")) return t("rentType.daily");
|
||||||
if (dt.includes("monthly")) return "شهري";
|
if (dt.includes("monthly")) return t("rentType.monthly");
|
||||||
return dt;
|
return dt;
|
||||||
})()}
|
})()}
|
||||||
</span>
|
</span>
|
||||||
@ -830,16 +753,16 @@ export default function PropertyDetailsPage() {
|
|||||||
<span
|
<span
|
||||||
className={`px-2 py-0.5 rounded-full text-xs ${property.furnished ? "bg-purple-100 text-purple-800" : "bg-gray-100 text-gray-600"}`}
|
className={`px-2 py-0.5 rounded-full text-xs ${property.furnished ? "bg-purple-100 text-purple-800" : "bg-gray-100 text-gray-600"}`}
|
||||||
>
|
>
|
||||||
{property.furnished ? "مفروش" : "غير مفروش"}
|
{property.furnished ? t("rentCondition.furnished") : t("rentCondition.unfurnished")}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<h1 className="text-xl font-bold text-gray-900">
|
<h1 className="text-xl font-bold text-gray-900">
|
||||||
{property.title}
|
{property.title || t("propertyWithId", { id: property.id })}
|
||||||
</h1>
|
</h1>
|
||||||
<div className="flex items-center gap-1 text-gray-500 text-xs mt-0.5">
|
<div className="flex items-center gap-1 text-gray-500 text-xs mt-0.5">
|
||||||
<MapPin className="w-3 h-3" />
|
<MapPin className="w-3 h-3" />
|
||||||
<span>
|
<span>
|
||||||
{property.location.address || property.location.city}
|
{property.location.address || t("city." + property.location.city)}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -863,7 +786,7 @@ export default function PropertyDetailsPage() {
|
|||||||
<span className="text-2xl font-bold text-amber-600">
|
<span className="text-2xl font-bold text-amber-600">
|
||||||
{formatCurrency(property.priceDisplay.monthly)}
|
{formatCurrency(property.priceDisplay.monthly)}
|
||||||
</span>
|
</span>
|
||||||
<span className="text-gray-500 mr-1">ل.س / شهرياً</span>
|
<span className="text-gray-500 mr-1">{t("sypPerMonth")}</span>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{property.priceDisplay.daily > 0 && (
|
{property.priceDisplay.daily > 0 && (
|
||||||
@ -871,13 +794,13 @@ export default function PropertyDetailsPage() {
|
|||||||
<span className="text-2xl font-bold text-amber-600">
|
<span className="text-2xl font-bold text-amber-600">
|
||||||
{formatCurrency(property.priceDisplay.daily)}
|
{formatCurrency(property.priceDisplay.daily)}
|
||||||
</span>
|
</span>
|
||||||
<span className="text-gray-500 mr-1">ل.س / يومياً</span>
|
<span className="text-gray-500 mr-1">{t("sypPerDay")}</span>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{property.deposit > 0 && (
|
{property.deposit > 0 && (
|
||||||
<div className="text-sm text-gray-500">
|
<div className="text-sm text-gray-500">
|
||||||
<span className="font-medium">تأمين:</span>{" "}
|
<span className="font-medium">{t("depositLabel")}</span>{" "}
|
||||||
{formatCurrency(property.deposit)} ل.س
|
{formatCurrency(property.deposit)} {t("syp")}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
@ -886,8 +809,8 @@ export default function PropertyDetailsPage() {
|
|||||||
<span className="text-2xl font-bold text-blue-600">
|
<span className="text-2xl font-bold text-blue-600">
|
||||||
{formatCurrency(property.price)}
|
{formatCurrency(property.price)}
|
||||||
</span>
|
</span>
|
||||||
<span className="text-gray-500 mr-1">ل.س</span>
|
<span className="text-gray-500 mr-1">{t("syp")}</span>
|
||||||
<span className="text-sm text-gray-400 mr-2">للبيع</span>
|
<span className="text-sm text-gray-400 mr-2">{t("forSale")}</span>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
@ -900,7 +823,7 @@ export default function PropertyDetailsPage() {
|
|||||||
<div className="font-bold text-gray-900 text-sm">
|
<div className="font-bold text-gray-900 text-sm">
|
||||||
{property.bedrooms}
|
{property.bedrooms}
|
||||||
</div>
|
</div>
|
||||||
<div className="text-[10px] text-gray-500">غرف نوم</div>
|
<div className="text-[10px] text-gray-500">{t("bedrooms")}</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{property.bathrooms > 0 && (
|
{property.bathrooms > 0 && (
|
||||||
@ -909,7 +832,7 @@ export default function PropertyDetailsPage() {
|
|||||||
<div className="font-bold text-gray-900 text-sm">
|
<div className="font-bold text-gray-900 text-sm">
|
||||||
{property.bathrooms}
|
{property.bathrooms}
|
||||||
</div>
|
</div>
|
||||||
<div className="text-[10px] text-gray-500">حمامات</div>
|
<div className="text-[10px] text-gray-500">{t("bathrooms")}</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{property.area > 0 && (
|
{property.area > 0 && (
|
||||||
@ -918,7 +841,7 @@ export default function PropertyDetailsPage() {
|
|||||||
<div className="font-bold text-gray-900 text-sm">
|
<div className="font-bold text-gray-900 text-sm">
|
||||||
{property.area}
|
{property.area}
|
||||||
</div>
|
</div>
|
||||||
<div className="text-[10px] text-gray-500">م²</div>
|
<div className="text-[10px] text-gray-500">{t("sqm")}</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{property.floor > 0 && (
|
{property.floor > 0 && (
|
||||||
@ -927,7 +850,7 @@ export default function PropertyDetailsPage() {
|
|||||||
<div className="font-bold text-gray-900 text-sm">
|
<div className="font-bold text-gray-900 text-sm">
|
||||||
{property.floor}
|
{property.floor}
|
||||||
</div>
|
</div>
|
||||||
<div className="text-[10px] text-gray-500">طابق</div>
|
<div className="text-[10px] text-gray-500">{t("floor")}</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{property.salons > 0 && (
|
{property.salons > 0 && (
|
||||||
@ -936,7 +859,7 @@ export default function PropertyDetailsPage() {
|
|||||||
<div className="font-bold text-gray-900 text-sm">
|
<div className="font-bold text-gray-900 text-sm">
|
||||||
{property.salons}
|
{property.salons}
|
||||||
</div>
|
</div>
|
||||||
<div className="text-[10px] text-gray-500">صالونات</div>
|
<div className="text-[10px] text-gray-500">{t("salons")}</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{property.balconies > 0 && (
|
{property.balconies > 0 && (
|
||||||
@ -945,7 +868,7 @@ export default function PropertyDetailsPage() {
|
|||||||
<div className="font-bold text-gray-900 text-sm">
|
<div className="font-bold text-gray-900 text-sm">
|
||||||
{property.balconies}
|
{property.balconies}
|
||||||
</div>
|
</div>
|
||||||
<div className="text-[10px] text-gray-500">بلكونات</div>
|
<div className="text-[10px] text-gray-500">{t("balconies")}</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{avgRating !== null && avgRating > 0 && (
|
{avgRating !== null && avgRating > 0 && (
|
||||||
@ -954,7 +877,7 @@ export default function PropertyDetailsPage() {
|
|||||||
<div className="font-bold text-gray-900 text-sm">
|
<div className="font-bold text-gray-900 text-sm">
|
||||||
{avgRating.toFixed(1)}
|
{avgRating.toFixed(1)}
|
||||||
</div>
|
</div>
|
||||||
<div className="text-[10px] text-gray-500">التقييم</div>
|
<div className="text-[10px] text-gray-500">{t("rateAndReview")}</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{property.bookedCount > 0 && (
|
{property.bookedCount > 0 && (
|
||||||
@ -963,7 +886,7 @@ export default function PropertyDetailsPage() {
|
|||||||
<div className="font-bold text-gray-900 text-sm">
|
<div className="font-bold text-gray-900 text-sm">
|
||||||
{property.bookedCount}
|
{property.bookedCount}
|
||||||
</div>
|
</div>
|
||||||
<div className="text-[10px] text-gray-500">حجوزات</div>
|
<div className="text-[10px] text-gray-500">{t("bookingCount")}</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
@ -972,7 +895,7 @@ export default function PropertyDetailsPage() {
|
|||||||
{property.description && (
|
{property.description && (
|
||||||
<div className="mb-4">
|
<div className="mb-4">
|
||||||
<h3 className="font-bold text-gray-900 mb-1 text-sm">
|
<h3 className="font-bold text-gray-900 mb-1 text-sm">
|
||||||
الوصف
|
{t("description")}
|
||||||
</h3>
|
</h3>
|
||||||
<p className="text-gray-600 text-sm leading-relaxed">
|
<p className="text-gray-600 text-sm leading-relaxed">
|
||||||
{property.description}
|
{property.description}
|
||||||
@ -984,17 +907,17 @@ export default function PropertyDetailsPage() {
|
|||||||
<div className="flex flex-wrap gap-1.5 mb-4">
|
<div className="flex flex-wrap gap-1.5 mb-4">
|
||||||
{property.isSmokeAllow && (
|
{property.isSmokeAllow && (
|
||||||
<span className="px-2 py-0.5 bg-gray-100 text-gray-700 rounded-full text-xs border flex items-center gap-1">
|
<span className="px-2 py-0.5 bg-gray-100 text-gray-700 rounded-full text-xs border flex items-center gap-1">
|
||||||
<Wind className="w-3 h-3" /> يسمح بالتدخين
|
<Wind className="w-3 h-3" /> {t("smokingAllowed")}
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
{!property.isSmokeAllow && (
|
{!property.isSmokeAllow && (
|
||||||
<span className="px-2 py-0.5 bg-gray-100 text-gray-700 rounded-full text-xs border flex items-center gap-1">
|
<span className="px-2 py-0.5 bg-gray-100 text-gray-700 rounded-full text-xs border flex items-center gap-1">
|
||||||
<Ban className="w-3 h-3" /> ممنوع التدخين
|
<Ban className="w-3 h-3" /> {t("propertyTerm.noSmoking")}
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
{property.isVisitorAllow && (
|
{property.isVisitorAllow && (
|
||||||
<span className="px-2 py-0.5 bg-gray-100 text-gray-700 rounded-full text-xs border flex items-center gap-1">
|
<span className="px-2 py-0.5 bg-gray-100 text-gray-700 rounded-full text-xs border flex items-center gap-1">
|
||||||
<Users className="w-3 h-3" /> يسمح بالزوار
|
<Users className="w-3 h-3" /> {t("visitorsAllowed")}
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
{property.specializedFor && (
|
{property.specializedFor && (
|
||||||
@ -1011,7 +934,7 @@ export default function PropertyDetailsPage() {
|
|||||||
: Object.keys(property.services).length > 0) && (
|
: Object.keys(property.services).length > 0) && (
|
||||||
<div className="mb-4">
|
<div className="mb-4">
|
||||||
<h3 className="font-bold text-gray-900 mb-2 text-sm">
|
<h3 className="font-bold text-gray-900 mb-2 text-sm">
|
||||||
الخدمات
|
{t("services")}
|
||||||
</h3>
|
</h3>
|
||||||
<div className="flex flex-wrap gap-1.5">
|
<div className="flex flex-wrap gap-1.5">
|
||||||
{Array.isArray(property.services)
|
{Array.isArray(property.services)
|
||||||
@ -1020,7 +943,7 @@ export default function PropertyDetailsPage() {
|
|||||||
key={i}
|
key={i}
|
||||||
className="px-3 py-1 bg-green-50 text-green-700 rounded-full text-sm border border-green-200 flex items-center gap-1"
|
className="px-3 py-1 bg-green-50 text-green-700 rounded-full text-sm border border-green-200 flex items-center gap-1"
|
||||||
>
|
>
|
||||||
{serviceLabels[svc] || svc}
|
{t(svcKey(svc))}
|
||||||
</span>
|
</span>
|
||||||
))
|
))
|
||||||
: Object.entries(property.services).map(
|
: Object.entries(property.services).map(
|
||||||
@ -1037,7 +960,7 @@ export default function PropertyDetailsPage() {
|
|||||||
key={key}
|
key={key}
|
||||||
className="px-3 py-1 bg-green-50 text-green-700 rounded-full text-sm border border-green-200 flex items-center gap-1"
|
className="px-3 py-1 bg-green-50 text-green-700 rounded-full text-sm border border-green-200 flex items-center gap-1"
|
||||||
>
|
>
|
||||||
{serviceLabels[key] || key}
|
{t(svcKey(key))}
|
||||||
{detail && (
|
{detail && (
|
||||||
<span className="text-green-400">
|
<span className="text-green-400">
|
||||||
· {detail}
|
· {detail}
|
||||||
@ -1056,7 +979,7 @@ export default function PropertyDetailsPage() {
|
|||||||
<div className="mb-4 bg-blue-50 rounded-xl p-3">
|
<div className="mb-4 bg-blue-50 rounded-xl p-3">
|
||||||
<h3 className="font-bold text-gray-900 mb-2 text-sm flex items-center gap-2">
|
<h3 className="font-bold text-gray-900 mb-2 text-sm flex items-center gap-2">
|
||||||
<Info className="w-4 h-4 text-blue-500" />
|
<Info className="w-4 h-4 text-blue-500" />
|
||||||
تفاصيل الغرفة
|
{t("roomDetails")}
|
||||||
</h3>
|
</h3>
|
||||||
<div className="grid grid-cols-2 md:grid-cols-3 gap-2">
|
<div className="grid grid-cols-2 md:grid-cols-3 gap-2">
|
||||||
{(() => {
|
{(() => {
|
||||||
@ -1064,117 +987,117 @@ export default function PropertyDetailsPage() {
|
|||||||
const items = [];
|
const items = [];
|
||||||
if (rd.areaType)
|
if (rd.areaType)
|
||||||
items.push({
|
items.push({
|
||||||
label: "نوع المساحة",
|
label: t("roomDetail.areaType"),
|
||||||
value:
|
value:
|
||||||
rd.areaType === "private room"
|
rd.areaType === "private room"
|
||||||
? "غرفة خاصة"
|
? t("roomDetail.privateRoom")
|
||||||
: rd.areaType === "shared room"
|
: rd.areaType === "shared room"
|
||||||
? "غرفة مشتركة"
|
? t("roomDetail.sharedRoom")
|
||||||
: rd.areaType,
|
: rd.areaType,
|
||||||
});
|
});
|
||||||
if (rd.peopleAllowed)
|
if (rd.peopleAllowed)
|
||||||
items.push({
|
items.push({
|
||||||
label: "عدد الأشخاص",
|
label: t("roomDetail.numberOfPeople"),
|
||||||
value: rd.peopleAllowed,
|
value: rd.peopleAllowed,
|
||||||
});
|
});
|
||||||
if (rd.furnitureDetails || rd.furniture)
|
if (rd.furnitureDetails || rd.furniture)
|
||||||
items.push({
|
items.push({
|
||||||
label: "الأثاث",
|
label: t("roomDetail.furniture"),
|
||||||
value: rd.furnitureDetails || rd.furniture,
|
value: rd.furnitureDetails || rd.furniture,
|
||||||
});
|
});
|
||||||
if (rd.entranceType)
|
if (rd.entranceType)
|
||||||
items.push({
|
items.push({
|
||||||
label: "نوع المدخل",
|
label: t("roomDetail.entranceType"),
|
||||||
value:
|
value:
|
||||||
rd.entranceType === "shared entrance"
|
rd.entranceType === "shared entrance"
|
||||||
? "مدخل مشترك"
|
? t("roomDetail.sharedEntrance")
|
||||||
: rd.entranceType === "independent entrance"
|
: rd.entranceType === "independent entrance"
|
||||||
? "مدخل مستقل"
|
? t("roomDetail.independentEntrance")
|
||||||
: rd.entranceType,
|
: rd.entranceType,
|
||||||
});
|
});
|
||||||
if (rd.bathroomType)
|
if (rd.bathroomType)
|
||||||
items.push({
|
items.push({
|
||||||
label: "الحمام",
|
label: t("roomDetail.bathroom"),
|
||||||
value:
|
value:
|
||||||
rd.bathroomType === "room specific"
|
rd.bathroomType === "room specific"
|
||||||
? "خاص بالغرفة"
|
? t("roomDetail.bathroomPrivate")
|
||||||
: rd.bathroomType === "shared"
|
: rd.bathroomType === "shared"
|
||||||
? "مشترك"
|
? t("roomDetail.bathroomShared")
|
||||||
: rd.bathroomType,
|
: rd.bathroomType,
|
||||||
});
|
});
|
||||||
if (rd.kitchenType)
|
if (rd.kitchenType)
|
||||||
items.push({
|
items.push({
|
||||||
label: "المطبخ",
|
label: t("roomDetail.kitchen"),
|
||||||
value:
|
value:
|
||||||
rd.kitchenType === "shared"
|
rd.kitchenType === "shared"
|
||||||
? "مشترك"
|
? t("roomDetail.kitchenShared")
|
||||||
: rd.kitchenType === "not available"
|
: rd.kitchenType === "not available"
|
||||||
? "غير متوفر"
|
? t("roomDetail.kitchenNotAvailable")
|
||||||
: rd.kitchenType,
|
: rd.kitchenType,
|
||||||
});
|
});
|
||||||
if (rd.homeResidentsCount ?? rd.residents)
|
if (rd.homeResidentsCount ?? rd.residents)
|
||||||
items.push({
|
items.push({
|
||||||
label: "عدد السكان",
|
label: t("roomDetail.residents"),
|
||||||
value: rd.homeResidentsCount ?? rd.residents,
|
value: rd.homeResidentsCount ?? rd.residents,
|
||||||
});
|
});
|
||||||
if (rd.currentPopulationGender)
|
if (rd.currentPopulationGender)
|
||||||
items.push({
|
items.push({
|
||||||
label: "جنس السكان",
|
label: t("roomDetail.gender"),
|
||||||
value:
|
value:
|
||||||
rd.currentPopulationGender === "men"
|
rd.currentPopulationGender === "men"
|
||||||
? "رجال"
|
? t("roomDetail.men")
|
||||||
: rd.currentPopulationGender === "women"
|
: rd.currentPopulationGender === "women"
|
||||||
? "نساء"
|
? t("roomDetail.women")
|
||||||
: rd.currentPopulationGender === "family"
|
: rd.currentPopulationGender === "family"
|
||||||
? "عائلة"
|
? t("roomDetail.family")
|
||||||
: rd.currentPopulationGender,
|
: rd.currentPopulationGender,
|
||||||
});
|
});
|
||||||
if (rd.dedicatedTo)
|
if (rd.dedicatedTo)
|
||||||
items.push({
|
items.push({
|
||||||
label: "مخصص لـ",
|
label: t("roomDetail.dedicatedTo"),
|
||||||
value:
|
value:
|
||||||
rd.dedicatedTo === "men only"
|
rd.dedicatedTo === "men only"
|
||||||
? "رجال فقط"
|
? t("roomDetail.menOnly")
|
||||||
: rd.dedicatedTo === "women only"
|
: rd.dedicatedTo === "women only"
|
||||||
? "نساء فقط"
|
? t("roomDetail.womenOnly")
|
||||||
: rd.dedicatedTo === "families only"
|
: rd.dedicatedTo === "families only"
|
||||||
? "عائلات فقط"
|
? t("roomDetail.familiesOnly")
|
||||||
: rd.dedicatedTo === "everyone"
|
: rd.dedicatedTo === "everyone"
|
||||||
? "الجميع"
|
? t("roomDetail.everyone")
|
||||||
: rd.dedicatedTo,
|
: rd.dedicatedTo,
|
||||||
});
|
});
|
||||||
if (rd.hasRestrictedOwnerAreas !== undefined)
|
if (rd.hasRestrictedOwnerAreas !== undefined)
|
||||||
items.push({
|
items.push({
|
||||||
label: "مناطق ممنوعة",
|
label: t("roomDetail.restrictedAreas"),
|
||||||
value: rd.hasRestrictedOwnerAreas ? "نعم" : "لا",
|
value: rd.hasRestrictedOwnerAreas ? t("roomDetail.yes") : t("roomDetail.no"),
|
||||||
});
|
});
|
||||||
if (rd.hasChildren !== undefined)
|
if (rd.hasChildren !== undefined)
|
||||||
items.push({
|
items.push({
|
||||||
label: "أطفال",
|
label: t("roomDetail.children"),
|
||||||
value: rd.hasChildren ? "مسموح" : "غير مسموح",
|
value: rd.hasChildren ? t("roomDetail.allowed") : t("roomDetail.notAllowed"),
|
||||||
});
|
});
|
||||||
if (rd.hasPets !== undefined)
|
if (rd.hasPets !== undefined)
|
||||||
items.push({
|
items.push({
|
||||||
label: "حيوانات أليفة",
|
label: t("roomDetail.pets"),
|
||||||
value: rd.hasPets ? "مسموح" : "غير مسموح",
|
value: rd.hasPets ? t("roomDetail.allowed") : t("roomDetail.notAllowed"),
|
||||||
});
|
});
|
||||||
if (rd.languageDialect)
|
if (rd.languageDialect)
|
||||||
items.push({
|
items.push({
|
||||||
label: "اللغة",
|
label: t("roomDetail.language"),
|
||||||
value: rd.languageDialect,
|
value: rd.languageDialect,
|
||||||
});
|
});
|
||||||
if (rd.visitorsAllowed !== undefined)
|
if (rd.visitorsAllowed !== undefined)
|
||||||
items.push({
|
items.push({
|
||||||
label: "الزوار",
|
label: t("roomDetail.visitors"),
|
||||||
value: rd.visitorsAllowed ? "مسموح" : "ممنوع",
|
value: rd.visitorsAllowed ? t("roomDetail.allowed") : t("roomDetail.forbidden"),
|
||||||
});
|
});
|
||||||
if (rd.quietTimesEnabled ?? rd.quietTimes)
|
if (rd.quietTimesEnabled ?? rd.quietTimes)
|
||||||
items.push({
|
items.push({
|
||||||
label: "أوقات الهدوء",
|
label: t("roomDetail.quietHours"),
|
||||||
value:
|
value:
|
||||||
rd.quietTimesDetails ||
|
rd.quietTimesDetails ||
|
||||||
rd.quietTimes ||
|
rd.quietTimes ||
|
||||||
(rd.quietTimesEnabled ? "مفعلة" : ""),
|
(rd.quietTimesEnabled ? t("roomDetail.quietHoursEnabled") : ""),
|
||||||
});
|
});
|
||||||
return items.map((item, i) => (
|
return items.map((item, i) => (
|
||||||
<div
|
<div
|
||||||
@ -1198,7 +1121,7 @@ export default function PropertyDetailsPage() {
|
|||||||
{Object.keys(property.proximity).length > 0 && (
|
{Object.keys(property.proximity).length > 0 && (
|
||||||
<div className="mb-4">
|
<div className="mb-4">
|
||||||
<h3 className="font-bold text-gray-900 mb-2 text-sm">
|
<h3 className="font-bold text-gray-900 mb-2 text-sm">
|
||||||
القرب من الخدمات
|
{t("proximityToServices")}
|
||||||
</h3>
|
</h3>
|
||||||
<div className="grid grid-cols-2 md:grid-cols-3 gap-1.5">
|
<div className="grid grid-cols-2 md:grid-cols-3 gap-1.5">
|
||||||
{Object.entries(property.proximity).map(([key, val]) => {
|
{Object.entries(property.proximity).map(([key, val]) => {
|
||||||
@ -1239,10 +1162,10 @@ export default function PropertyDetailsPage() {
|
|||||||
)}
|
)}
|
||||||
<div>
|
<div>
|
||||||
<div className="text-xs font-medium text-gray-900">
|
<div className="text-xs font-medium text-gray-900">
|
||||||
{proximityLabels[key] || key}
|
{t(proxKey(key))}
|
||||||
</div>
|
</div>
|
||||||
<div className="text-[10px] text-gray-500">
|
<div className="text-[10px] text-gray-500">
|
||||||
{dist} {typeof dist === "number" ? "كم" : ""}
|
{dist} {typeof dist === "number" ? t("km") : ""}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -1256,7 +1179,7 @@ export default function PropertyDetailsPage() {
|
|||||||
{Object.keys(property.terms).length > 0 && (
|
{Object.keys(property.terms).length > 0 && (
|
||||||
<div className="mb-4">
|
<div className="mb-4">
|
||||||
<h3 className="font-bold text-gray-900 mb-2 text-sm">
|
<h3 className="font-bold text-gray-900 mb-2 text-sm">
|
||||||
الشروط
|
{t("terms")}
|
||||||
</h3>
|
</h3>
|
||||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-1.5">
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-1.5">
|
||||||
{Object.entries(property.terms).map(([key, val]) => {
|
{Object.entries(property.terms).map(([key, val]) => {
|
||||||
@ -1272,7 +1195,7 @@ export default function PropertyDetailsPage() {
|
|||||||
<Check className="w-3.5 h-3.5 text-green-500 flex-shrink-0" />
|
<Check className="w-3.5 h-3.5 text-green-500 flex-shrink-0" />
|
||||||
)}
|
)}
|
||||||
<span className="text-xs text-gray-700">
|
<span className="text-xs text-gray-700">
|
||||||
{termLabels[key] || key}
|
{t(termKey(key))}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
@ -1298,7 +1221,7 @@ export default function PropertyDetailsPage() {
|
|||||||
</div>
|
</div>
|
||||||
<div className="p-3 bg-amber-50 text-center text-sm text-amber-700 flex items-center justify-center gap-2">
|
<div className="p-3 bg-amber-50 text-center text-sm text-amber-700 flex items-center justify-center gap-2">
|
||||||
<Info className="w-4 h-4" />
|
<Info className="w-4 h-4" />
|
||||||
<span>موقع تقريبي — يظهر الموقع الدقيق بعد تأكيد الحجز</span>
|
<span>{t("approximateLocation")}</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="p-3 border-t border-gray-100">
|
<div className="p-3 border-t border-gray-100">
|
||||||
<a
|
<a
|
||||||
@ -1308,7 +1231,7 @@ export default function PropertyDetailsPage() {
|
|||||||
className="flex items-center justify-center gap-2 text-blue-600 hover:text-blue-700 font-medium text-sm"
|
className="flex items-center justify-center gap-2 text-blue-600 hover:text-blue-700 font-medium text-sm"
|
||||||
>
|
>
|
||||||
<ExternalLink className="w-4 h-4" />
|
<ExternalLink className="w-4 h-4" />
|
||||||
فتح في Google Maps
|
{t("openInGoogleMaps")}
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</motion.div>
|
</motion.div>
|
||||||
@ -1340,10 +1263,10 @@ export default function PropertyDetailsPage() {
|
|||||||
<Home className="w-7 h-7 text-gray-400" />
|
<Home className="w-7 h-7 text-gray-400" />
|
||||||
</div>
|
</div>
|
||||||
<h4 className="font-bold text-gray-700 text-sm mb-1">
|
<h4 className="font-bold text-gray-700 text-sm mb-1">
|
||||||
هذا عقارك
|
{t("thisIsYourProperty")}
|
||||||
</h4>
|
</h4>
|
||||||
<p className="text-xs text-gray-500">
|
<p className="text-xs text-gray-500">
|
||||||
لا يمكنك حجز عقارك الخاص
|
{t("cannotBookOwnProperty")}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
) : bookingSuccess ? (
|
) : bookingSuccess ? (
|
||||||
@ -1352,10 +1275,10 @@ export default function PropertyDetailsPage() {
|
|||||||
<Check className="w-7 h-7 text-green-600" />
|
<Check className="w-7 h-7 text-green-600" />
|
||||||
</div>
|
</div>
|
||||||
<h4 className="font-bold text-green-700 text-sm mb-1">
|
<h4 className="font-bold text-green-700 text-sm mb-1">
|
||||||
تم إرسال طلب الحجز
|
{t("bookingRequestSent")}
|
||||||
</h4>
|
</h4>
|
||||||
<p className="text-xs text-gray-500">
|
<p className="text-xs text-gray-500">
|
||||||
سيتم مراجعة طلبك من قبل المالك
|
{t("bookingRequestWillBeReviewed")}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
@ -1368,10 +1291,10 @@ export default function PropertyDetailsPage() {
|
|||||||
className={`p-2.5 rounded-xl text-center border-2 transition-all ${effectivePricingMode === "daily" ? "border-amber-500 bg-amber-50" : "border-gray-200 hover:border-gray-300"}`}
|
className={`p-2.5 rounded-xl text-center border-2 transition-all ${effectivePricingMode === "daily" ? "border-amber-500 bg-amber-50" : "border-gray-200 hover:border-gray-300"}`}
|
||||||
>
|
>
|
||||||
<div className="text-xs font-bold text-gray-900">
|
<div className="text-xs font-bold text-gray-900">
|
||||||
إيجار يومي
|
{t("dailyRentLabel")}
|
||||||
</div>
|
</div>
|
||||||
<div className="text-sm font-bold text-amber-600">
|
<div className="text-sm font-bold text-amber-600">
|
||||||
{formatCurrency(property.priceDisplay.daily)} ل.س
|
{formatCurrency(property.priceDisplay.daily)} {t("syp")}
|
||||||
</div>
|
</div>
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
@ -1379,10 +1302,10 @@ export default function PropertyDetailsPage() {
|
|||||||
className={`p-2.5 rounded-xl text-center border-2 transition-all ${effectivePricingMode === "monthly" ? "border-amber-500 bg-amber-50" : "border-gray-200 hover:border-gray-300"}`}
|
className={`p-2.5 rounded-xl text-center border-2 transition-all ${effectivePricingMode === "monthly" ? "border-amber-500 bg-amber-50" : "border-gray-200 hover:border-gray-300"}`}
|
||||||
>
|
>
|
||||||
<div className="text-xs font-bold text-gray-900">
|
<div className="text-xs font-bold text-gray-900">
|
||||||
إيجار شهري
|
{t("monthlyRentLabel")}
|
||||||
</div>
|
</div>
|
||||||
<div className="text-sm font-bold text-amber-600">
|
<div className="text-sm font-bold text-amber-600">
|
||||||
{formatCurrency(property.priceDisplay.monthly)} ل.س
|
{formatCurrency(property.priceDisplay.monthly)} {t("syp")}
|
||||||
</div>
|
</div>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
@ -1396,7 +1319,7 @@ export default function PropertyDetailsPage() {
|
|||||||
<div
|
<div
|
||||||
className={`w-1.5 h-1.5 rounded-full ${bookingStep === "entry" ? "bg-amber-500" : "bg-gray-400"}`}
|
className={`w-1.5 h-1.5 rounded-full ${bookingStep === "entry" ? "bg-amber-500" : "bg-gray-400"}`}
|
||||||
/>
|
/>
|
||||||
تحديد تاريخ البداية
|
{t("selectStartDate")}
|
||||||
</div>
|
</div>
|
||||||
<div className="text-gray-300 text-xs">←</div>
|
<div className="text-gray-300 text-xs">←</div>
|
||||||
<div
|
<div
|
||||||
@ -1405,7 +1328,7 @@ export default function PropertyDetailsPage() {
|
|||||||
<div
|
<div
|
||||||
className={`w-1.5 h-1.5 rounded-full ${bookingStep === "exit" ? "bg-amber-500" : "bg-gray-400"}`}
|
className={`w-1.5 h-1.5 rounded-full ${bookingStep === "exit" ? "bg-amber-500" : "bg-gray-400"}`}
|
||||||
/>
|
/>
|
||||||
تحديد تاريخ النهاية
|
{t("selectEndDate")}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -1420,7 +1343,7 @@ export default function PropertyDetailsPage() {
|
|||||||
<ChevronRight className="w-4 h-4 text-gray-600" />
|
<ChevronRight className="w-4 h-4 text-gray-600" />
|
||||||
</button>
|
</button>
|
||||||
<span className="text-sm font-bold text-gray-900">
|
<span className="text-sm font-bold text-gray-900">
|
||||||
{MONTHS_AR[calendarMonth]} {calendarYear}
|
{t(MONTH_KEYS[calendarMonth])} {calendarYear}
|
||||||
</span>
|
</span>
|
||||||
<button
|
<button
|
||||||
onClick={() => navigateMonth(1)}
|
onClick={() => navigateMonth(1)}
|
||||||
@ -1431,12 +1354,12 @@ export default function PropertyDetailsPage() {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="grid grid-cols-7 mb-1">
|
<div className="grid grid-cols-7 mb-1">
|
||||||
{DAYS_AR.map((d, i) => (
|
{DAY_KEYS.map((dk, i) => (
|
||||||
<div
|
<div
|
||||||
key={i}
|
key={i}
|
||||||
className="text-center text-[10px] text-gray-400 font-medium py-1"
|
className="text-center text-[10px] text-gray-400 font-medium py-1"
|
||||||
>
|
>
|
||||||
{d}
|
{t(dk)}
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
@ -1509,7 +1432,7 @@ export default function PropertyDetailsPage() {
|
|||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div className="grid grid-cols-3 gap-2">
|
<div className="grid grid-cols-3 gap-2">
|
||||||
{MONTHS_AR.map((name, idx) => {
|
{MONTH_KEYS.map((mk, idx) => {
|
||||||
const monthStr = `${calendarYear}-${String(idx + 1).padStart(2, "0")}`;
|
const monthStr = `${calendarYear}-${String(idx + 1).padStart(2, "0")}`;
|
||||||
const isSelStart =
|
const isSelStart =
|
||||||
selectedStart &&
|
selectedStart &&
|
||||||
@ -1548,7 +1471,7 @@ export default function PropertyDetailsPage() {
|
|||||||
}}
|
}}
|
||||||
className={`p-2.5 rounded-xl text-center text-xs font-medium transition-all border ${isSelStart || isSelEnd ? "bg-amber-500 text-white border-amber-500 shadow-sm" : inRange ? "bg-amber-100 text-amber-800 border-amber-200" : "bg-white text-gray-700 border-gray-200 hover:border-amber-300"}`}
|
className={`p-2.5 rounded-xl text-center text-xs font-medium transition-all border ${isSelStart || isSelEnd ? "bg-amber-500 text-white border-amber-500 shadow-sm" : inRange ? "bg-amber-100 text-amber-800 border-amber-200" : "bg-white text-gray-700 border-gray-200 hover:border-amber-300"}`}
|
||||||
>
|
>
|
||||||
{name}
|
{t(mk)}
|
||||||
</button>
|
</button>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
@ -1560,7 +1483,7 @@ export default function PropertyDetailsPage() {
|
|||||||
{selectedStart && (
|
{selectedStart && (
|
||||||
<div className="bg-gray-50 rounded-xl p-3 mb-3 space-y-1.5">
|
<div className="bg-gray-50 rounded-xl p-3 mb-3 space-y-1.5">
|
||||||
<div className="flex justify-between text-xs">
|
<div className="flex justify-between text-xs">
|
||||||
<span className="text-gray-500">تاريخ البداية</span>
|
<span className="text-gray-500">{t("startDateLabel")}</span>
|
||||||
<span className="font-medium text-gray-900">
|
<span className="font-medium text-gray-900">
|
||||||
{selectedStart}
|
{selectedStart}
|
||||||
</span>
|
</span>
|
||||||
@ -1569,7 +1492,7 @@ export default function PropertyDetailsPage() {
|
|||||||
<>
|
<>
|
||||||
<div className="flex justify-between text-xs">
|
<div className="flex justify-between text-xs">
|
||||||
<span className="text-gray-500">
|
<span className="text-gray-500">
|
||||||
تاريخ النهاية
|
{t("endDateLabel")}
|
||||||
</span>
|
</span>
|
||||||
<span className="font-medium text-gray-900">
|
<span className="font-medium text-gray-900">
|
||||||
{selectedEnd}
|
{selectedEnd}
|
||||||
@ -1579,8 +1502,8 @@ export default function PropertyDetailsPage() {
|
|||||||
<div className="flex justify-between text-xs">
|
<div className="flex justify-between text-xs">
|
||||||
<span className="text-gray-500">
|
<span className="text-gray-500">
|
||||||
{effectivePricingMode === "daily"
|
{effectivePricingMode === "daily"
|
||||||
? "عدد الأيام"
|
? t("numberOfDays")
|
||||||
: "عدد الأشهر"}
|
: t("numberOfMonths")}
|
||||||
</span>
|
</span>
|
||||||
<span className="font-medium text-gray-900">
|
<span className="font-medium text-gray-900">
|
||||||
{effectivePricingMode === "daily"
|
{effectivePricingMode === "daily"
|
||||||
@ -1601,7 +1524,7 @@ export default function PropertyDetailsPage() {
|
|||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex justify-between text-xs font-bold">
|
<div className="flex justify-between text-xs font-bold">
|
||||||
<span className="text-gray-700">المجموع</span>
|
<span className="text-gray-700">{t("total")}</span>
|
||||||
<span className="text-amber-600">
|
<span className="text-amber-600">
|
||||||
{formatCurrency(
|
{formatCurrency(
|
||||||
effectivePricingMode === "daily"
|
effectivePricingMode === "daily"
|
||||||
@ -1623,14 +1546,14 @@ export default function PropertyDetailsPage() {
|
|||||||
1) *
|
1) *
|
||||||
property.priceDisplay.monthly,
|
property.priceDisplay.monthly,
|
||||||
)}{" "}
|
)}{" "}
|
||||||
ل.س
|
{t("syp")}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
{property.deposit > 0 && (
|
{property.deposit > 0 && (
|
||||||
<div className="flex justify-between text-xs">
|
<div className="flex justify-between text-xs">
|
||||||
<span className="text-gray-500">تأمين</span>
|
<span className="text-gray-500">{t("deposit")}</span>
|
||||||
<span className="font-medium text-gray-900">
|
<span className="font-medium text-gray-900">
|
||||||
{formatCurrency(property.deposit)} ل.س
|
{formatCurrency(property.deposit)} {t("syp")}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
@ -1657,7 +1580,7 @@ export default function PropertyDetailsPage() {
|
|||||||
) : (
|
) : (
|
||||||
<Calendar className="w-4 h-4" />
|
<Calendar className="w-4 h-4" />
|
||||||
)}
|
)}
|
||||||
{bookingLoading ? "جاري الحجز..." : "تأكيد الحجز"}
|
{bookingLoading ? t("bookingInProgress") : t("confirmBooking")}
|
||||||
</button>
|
</button>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
@ -1669,7 +1592,7 @@ export default function PropertyDetailsPage() {
|
|||||||
<motion.div initial={{ opacity: 0, x: 20 }} animate={{ opacity: 1, x: 0 }} className="bg-white rounded-2xl p-5 shadow-sm border border-gray-200">
|
<motion.div initial={{ opacity: 0, x: 20 }} animate={{ opacity: 1, x: 0 }} className="bg-white rounded-2xl p-5 shadow-sm border border-gray-200">
|
||||||
<div className="flex items-center gap-2 mb-3">
|
<div className="flex items-center gap-2 mb-3">
|
||||||
<Phone className="w-4 h-4 text-amber-500" />
|
<Phone className="w-4 h-4 text-amber-500" />
|
||||||
<h3 className="font-bold text-gray-900">معلومات المالك</h3>
|
<h3 className="font-bold text-gray-900">{t("ownerInfo")}</h3>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{showContact && contactInfo ? (
|
{showContact && contactInfo ? (
|
||||||
@ -1690,7 +1613,7 @@ export default function PropertyDetailsPage() {
|
|||||||
<button onClick={fetchContactInfo}
|
<button onClick={fetchContactInfo}
|
||||||
className="w-full bg-gray-800 hover:bg-gray-900 text-white py-2.5 rounded-xl font-medium text-sm transition-colors flex items-center justify-center gap-2">
|
className="w-full bg-gray-800 hover:bg-gray-900 text-white py-2.5 rounded-xl font-medium text-sm transition-colors flex items-center justify-center gap-2">
|
||||||
<Phone className="w-4 h-4" />
|
<Phone className="w-4 h-4" />
|
||||||
عرض معلومات الاتصال
|
{t("showContactInfo")}
|
||||||
</button>
|
</button>
|
||||||
)}
|
)}
|
||||||
</motion.div>
|
</motion.div>
|
||||||
@ -1712,27 +1635,27 @@ export default function PropertyDetailsPage() {
|
|||||||
<div className="w-16 h-16 bg-amber-100 rounded-full flex items-center justify-center mx-auto mb-4">
|
<div className="w-16 h-16 bg-amber-100 rounded-full flex items-center justify-center mx-auto mb-4">
|
||||||
<LogIn className="w-8 h-8 text-amber-600" />
|
<LogIn className="w-8 h-8 text-amber-600" />
|
||||||
</div>
|
</div>
|
||||||
<h3 className="text-xl font-bold mb-2">تسجيل الدخول مطلوب</h3>
|
<h3 className="text-xl font-bold mb-2">{t("loginRequiredTitle")}</h3>
|
||||||
<p className="text-gray-500 mb-4">
|
<p className="text-gray-500 mb-4">
|
||||||
للحجز أو إضافة المفضلة، سجل دخولك.
|
{t("loginRequiredDesc")}
|
||||||
</p>
|
</p>
|
||||||
<Link
|
<Link
|
||||||
href="/login"
|
href="/login"
|
||||||
className="block w-full bg-amber-500 text-white py-3 rounded-xl font-medium mb-2 hover:bg-amber-600"
|
className="block w-full bg-amber-500 text-white py-3 rounded-xl font-medium mb-2 hover:bg-amber-600"
|
||||||
>
|
>
|
||||||
تسجيل الدخول
|
{t("login")}
|
||||||
</Link>
|
</Link>
|
||||||
<Link
|
<Link
|
||||||
href="/auth/choose-role"
|
href="/auth/choose-role"
|
||||||
className="block w-full bg-gray-100 py-3 rounded-xl font-medium hover:bg-gray-200"
|
className="block w-full bg-gray-100 py-3 rounded-xl font-medium hover:bg-gray-200"
|
||||||
>
|
>
|
||||||
إنشاء حساب
|
{t("createAccount")}
|
||||||
</Link>
|
</Link>
|
||||||
<button
|
<button
|
||||||
onClick={() => setShowLoginDialog(false)}
|
onClick={() => setShowLoginDialog(false)}
|
||||||
className="mt-3 text-gray-400 hover:text-gray-600"
|
className="mt-3 text-gray-400 hover:text-gray-600"
|
||||||
>
|
>
|
||||||
إلغاء
|
{t("cancel")}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -31,7 +31,7 @@ function mapProperty(item) {
|
|||||||
const priceUnit = item.monthlyRent || item.MonthlyRent ? 'monthly' : 'daily';
|
const priceUnit = item.monthlyRent || item.MonthlyRent ? 'monthly' : 'daily';
|
||||||
const buildingType = info.buildingType ?? info.BuildingType ?? 0;
|
const buildingType = info.buildingType ?? info.BuildingType ?? 0;
|
||||||
const type = { 0: 'apartment', 1: 'villa', 2: 'house' }[buildingType] || 'apartment';
|
const type = { 0: 'apartment', 1: 'villa', 2: 'house' }[buildingType] || 'apartment';
|
||||||
const typeLabel = { 0: 'شقة', 1: 'فيلا', 2: 'بيت' }[buildingType] || 'عقار';
|
const typeLabel = { 0: 'apartment', 1: 'villa', 2: 'house' }[buildingType] || 'apartment';
|
||||||
const address = info.address || info.Address || '';
|
const address = info.address || info.Address || '';
|
||||||
const bedrooms = info.numberOfBedRooms || info.NumberOfBedRooms || 0;
|
const bedrooms = info.numberOfBedRooms || info.NumberOfBedRooms || 0;
|
||||||
const bathrooms = info.numberOfBathRooms || info.NumberOfBathRooms || 0;
|
const bathrooms = info.numberOfBathRooms || info.NumberOfBathRooms || 0;
|
||||||
|
|||||||
@ -5,6 +5,7 @@ import { motion, AnimatePresence } from 'framer-motion';
|
|||||||
import { useRouter } from 'next/navigation';
|
import { useRouter } from 'next/navigation';
|
||||||
import Link from 'next/link';
|
import Link from 'next/link';
|
||||||
import Image from 'next/image';
|
import Image from 'next/image';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
import {
|
import {
|
||||||
User, Mail, Phone, Lock, Eye, EyeOff, MessageCircle,
|
User, Mail, Phone, Lock, Eye, EyeOff, MessageCircle,
|
||||||
Camera, X, CheckCircle, XCircle, ArrowLeft,
|
Camera, X, CheckCircle, XCircle, ArrowLeft,
|
||||||
@ -12,16 +13,10 @@ import {
|
|||||||
BadgeCheck, Briefcase, Calendar
|
BadgeCheck, Briefcase, Calendar
|
||||||
} from 'lucide-react';
|
} from 'lucide-react';
|
||||||
import toast, { Toaster } from 'react-hot-toast';
|
import toast, { Toaster } from 'react-hot-toast';
|
||||||
import {
|
|
||||||
registerRealEstateAgent,
|
|
||||||
loginWithEmail,
|
|
||||||
sendEmailOTP,
|
|
||||||
verifyEmail
|
|
||||||
} from '../../utils/api';
|
|
||||||
import AuthService from '../../services/AuthService';
|
|
||||||
|
|
||||||
export default function AgentRegisterPage() {
|
export default function AgentRegisterPage() {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
const { t } = useTranslation();
|
||||||
const [step, setStep] = useState(1);
|
const [step, setStep] = useState(1);
|
||||||
const [showOtpModal, setShowOtpModal] = useState(false);
|
const [showOtpModal, setShowOtpModal] = useState(false);
|
||||||
const [showPassword, setShowPassword] = useState(false);
|
const [showPassword, setShowPassword] = useState(false);
|
||||||
@ -68,12 +63,12 @@ export default function AgentRegisterPage() {
|
|||||||
if (!file) return;
|
if (!file) return;
|
||||||
|
|
||||||
if (!file.type.startsWith('image/')) {
|
if (!file.type.startsWith('image/')) {
|
||||||
toast.error('الرجاء اختيار صورة صالحة');
|
toast.error(t('register.selectValidImage'));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (file.size > 5 * 1024 * 1024) {
|
if (file.size > 5 * 1024 * 1024) {
|
||||||
toast.error('حجم الصورة يجب أن يكون أقل من 5 ميجابايت');
|
toast.error(t('register.imageSizeLimit'));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -84,7 +79,7 @@ export default function AgentRegisterPage() {
|
|||||||
reader.readAsDataURL(file);
|
reader.readAsDataURL(file);
|
||||||
setIdImages((prev) => ({ ...prev, [side]: file }));
|
setIdImages((prev) => ({ ...prev, [side]: file }));
|
||||||
|
|
||||||
toast.success('تم رفع الصورة بنجاح', {
|
toast.success(t('register.imageUploadSuccess'), {
|
||||||
style: { background: '#dcfce7', color: '#166534' }
|
style: { background: '#dcfce7', color: '#166534' }
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
@ -92,20 +87,20 @@ export default function AgentRegisterPage() {
|
|||||||
const validateStep1 = () => {
|
const validateStep1 = () => {
|
||||||
const newErrors = {};
|
const newErrors = {};
|
||||||
|
|
||||||
if (!formData.firstName) newErrors.firstName = 'الاسم الأول مطلوب';
|
if (!formData.firstName) newErrors.firstName = t('register.firstNameRequired');
|
||||||
if (!formData.lastName) newErrors.lastName = 'اسم العائلة مطلوب';
|
if (!formData.lastName) newErrors.lastName = t('register.lastNameRequired');
|
||||||
|
|
||||||
if (!formData.email) newErrors.email = 'البريد الإلكتروني مطلوب';
|
if (!formData.email) newErrors.email = t('register.emailRequired');
|
||||||
else if (!validateEmail(formData.email)) newErrors.email = 'البريد الإلكتروني غير صالح';
|
else if (!validateEmail(formData.email)) newErrors.email = t('register.emailInvalid');
|
||||||
|
|
||||||
if (!formData.phone) newErrors.phone = 'رقم الهاتف مطلوب';
|
if (!formData.phone) newErrors.phone = t('register.phoneRequired');
|
||||||
else if (!validatePhone(formData.phone)) newErrors.phone = 'رقم الهاتف غير صالح (يجب أن يبدأ 09 أو 05)';
|
else if (!validatePhone(formData.phone)) newErrors.phone = t('register.phoneInvalid') + ` (${t('register.phoneMustStartWith')} 09/05)`;
|
||||||
|
|
||||||
if (!formData.password) newErrors.password = 'كلمة المرور مطلوبة';
|
if (!formData.password) newErrors.password = t('register.passwordRequired');
|
||||||
else if (formData.password.length < 6) newErrors.password = 'كلمة المرور يجب أن تكون 6 أحرف على الأقل';
|
else if (formData.password.length < 6) newErrors.password = t('validation.passwordMinLength');
|
||||||
|
|
||||||
if (!formData.confirmPassword) newErrors.confirmPassword = 'تأكيد كلمة المرور مطلوب';
|
if (!formData.confirmPassword) newErrors.confirmPassword = t('register.confirmPasswordRequired');
|
||||||
else if (formData.password !== formData.confirmPassword) newErrors.confirmPassword = 'كلمات المرور غير متطابقة';
|
else if (formData.password !== formData.confirmPassword) newErrors.confirmPassword = t('validation.passwordsMatch');
|
||||||
|
|
||||||
setErrors(newErrors);
|
setErrors(newErrors);
|
||||||
return Object.keys(newErrors).length === 0;
|
return Object.keys(newErrors).length === 0;
|
||||||
@ -114,13 +109,13 @@ export default function AgentRegisterPage() {
|
|||||||
const validateStep2 = () => {
|
const validateStep2 = () => {
|
||||||
const newErrors = {};
|
const newErrors = {};
|
||||||
|
|
||||||
if (!formData.nationalNumber) newErrors.nationalNumber = 'الرقم الوطني مطلوب';
|
if (!formData.nationalNumber) newErrors.nationalNumber = t('register.nationalNumberRequired');
|
||||||
|
|
||||||
if (!formData.whatsapp) newErrors.whatsapp = 'رقم الواتساب مطلوب';
|
if (!formData.whatsapp) newErrors.whatsapp = t('register.whatsappRequired');
|
||||||
else if (!validatePhone(formData.whatsapp)) newErrors.whatsapp = 'رقم الواتساب غير صالح (يجب أن يبدأ 09 أو 05)';
|
else if (!validatePhone(formData.whatsapp)) newErrors.whatsapp = t('register.phoneInvalid') + ` (${t('register.phoneMustStartWith')} 09/05)`;
|
||||||
|
|
||||||
if (!idImages.front) newErrors.front = 'صورة الوجه الأمامي للهوية مطلوبة';
|
if (!idImages.front) newErrors.front = t('register.frontIdRequired');
|
||||||
if (!idImages.back) newErrors.back = 'صورة الوجه الخلفي للهوية مطلوبة';
|
if (!idImages.back) newErrors.back = t('register.backIdRequired');
|
||||||
|
|
||||||
setErrors(newErrors);
|
setErrors(newErrors);
|
||||||
return Object.keys(newErrors).length === 0;
|
return Object.keys(newErrors).length === 0;
|
||||||
@ -129,9 +124,9 @@ export default function AgentRegisterPage() {
|
|||||||
const validateStep3 = () => {
|
const validateStep3 = () => {
|
||||||
const newErrors = {};
|
const newErrors = {};
|
||||||
|
|
||||||
if (!formData.agencyAddress) newErrors.agencyAddress = 'عنوان الوكالة مطلوب';
|
if (!formData.agencyAddress) newErrors.agencyAddress = t('register.agent.agencyAddressRequired');
|
||||||
if (!formData.licenseNumber) newErrors.licenseNumber = 'رقم الترخيص مطلوب';
|
if (!formData.licenseNumber) newErrors.licenseNumber = t('register.agent.licenseNumberRequired');
|
||||||
if (!idImages.license) newErrors.license = 'صورة الترخيص مطلوبة';
|
if (!idImages.license) newErrors.license = t('register.agent.licenseImageRequired');
|
||||||
|
|
||||||
setErrors(newErrors);
|
setErrors(newErrors);
|
||||||
return Object.keys(newErrors).length === 0;
|
return Object.keys(newErrors).length === 0;
|
||||||
@ -148,14 +143,13 @@ export default function AgentRegisterPage() {
|
|||||||
setStep(4);
|
setStep(4);
|
||||||
window.scrollTo({ top: 0, behavior: 'smooth' });
|
window.scrollTo({ top: 0, behavior: 'smooth' });
|
||||||
} else {
|
} else {
|
||||||
toast.error('يرجى تصحيح الأخطاء في النموذج');
|
toast.error(t('validation.correctErrors'));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const buildFormData = () => {
|
const buildFormData = () => {
|
||||||
const fd = new FormData();
|
const fd = new FormData();
|
||||||
|
|
||||||
// مطابق للـ API
|
|
||||||
fd.append('Address', formData.agencyAddress);
|
fd.append('Address', formData.agencyAddress);
|
||||||
fd.append('LicenseNumber', formData.licenseNumber);
|
fd.append('LicenseNumber', formData.licenseNumber);
|
||||||
|
|
||||||
@ -169,7 +163,6 @@ export default function AgentRegisterPage() {
|
|||||||
fd.append('Owner.WhatsAppNumber', formData.whatsapp);
|
fd.append('Owner.WhatsAppNumber', formData.whatsapp);
|
||||||
fd.append('Owner.Language', '0');
|
fd.append('Owner.Language', '0');
|
||||||
|
|
||||||
// مطابق لأسماء الحقول الظاهرة في swagger
|
|
||||||
if (idImages.front) fd.append('FrontIdCarImage', idImages.front);
|
if (idImages.front) fd.append('FrontIdCarImage', idImages.front);
|
||||||
if (idImages.back) fd.append('RearIdCarImage', idImages.back);
|
if (idImages.back) fd.append('RearIdCarImage', idImages.back);
|
||||||
if (idImages.license) fd.append('license', idImages.license);
|
if (idImages.license) fd.append('license', idImages.license);
|
||||||
@ -181,21 +174,20 @@ export default function AgentRegisterPage() {
|
|||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
|
||||||
if (!formData.agreeTerms) {
|
if (!formData.agreeTerms) {
|
||||||
toast.error('يجب الموافقة على الشروط والأحكام');
|
toast.error(t('validation.agreeToTerms'));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
setIsLoading(true);
|
setIsLoading(true);
|
||||||
try {
|
try {
|
||||||
const fd = buildFormData();
|
const fd = buildFormData();
|
||||||
|
|
||||||
const res = await registerRealEstateAgent(fd);
|
const res = await registerRealEstateAgent(fd);
|
||||||
|
|
||||||
if (res?.ok || res?.status === 200) {
|
if (res?.ok || res?.status === 200) {
|
||||||
const tempToken = res?.data;
|
const tempToken = res?.data;
|
||||||
if (tempToken) AuthService.addToken(tempToken);
|
if (tempToken) AuthService.addToken(tempToken);
|
||||||
|
|
||||||
toast.success(res?.message || 'تم إنشاء الحساب! يرجى التحقق من بريدك الإلكتروني', {
|
toast.success(res?.message || t('register.accountCreated'), {
|
||||||
duration: 4000
|
duration: 4000
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -205,7 +197,7 @@ export default function AgentRegisterPage() {
|
|||||||
const otpToken = loginRes?.data;
|
const otpToken = loginRes?.data;
|
||||||
if (otpToken) AuthService.addToken(otpToken);
|
if (otpToken) AuthService.addToken(otpToken);
|
||||||
|
|
||||||
toast(loginRes?.message || 'تم إرسال رمز التحقق إلى بريدك الإلكتروني', {
|
toast(loginRes?.message || t('register.otpSentEmail'), {
|
||||||
icon: '📧'
|
icon: '📧'
|
||||||
});
|
});
|
||||||
setShowOtpModal(true);
|
setShowOtpModal(true);
|
||||||
@ -213,17 +205,17 @@ export default function AgentRegisterPage() {
|
|||||||
const loginToken = loginRes?.data;
|
const loginToken = loginRes?.data;
|
||||||
if (loginToken) AuthService.addToken(loginToken);
|
if (loginToken) AuthService.addToken(loginToken);
|
||||||
|
|
||||||
toast.success(loginRes?.message || 'تم تسجيل الدخول بنجاح!');
|
toast.success(loginRes?.message || t('register.loginSuccess'));
|
||||||
router.push('/');
|
router.push('/');
|
||||||
} else {
|
} else {
|
||||||
toast.success('تم إنشاء الحساب بنجاح! يرجى تسجيل الدخول');
|
toast.success(t('register.accountCreatedLogin'));
|
||||||
setTimeout(() => router.push('/login'), 1500);
|
setTimeout(() => router.push('/login'), 1500);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
toast.error(res?.message || res?.data?.message || 'فشل في إنشاء الحساب');
|
toast.error(res?.message || res?.data?.message || t('register.accountCreationFailed'));
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
toast.error(err?.message || 'حدث خطأ أثناء التسجيل');
|
toast.error(err?.message || t('register.registrationError'));
|
||||||
} finally {
|
} finally {
|
||||||
setIsLoading(false);
|
setIsLoading(false);
|
||||||
}
|
}
|
||||||
@ -231,7 +223,7 @@ export default function AgentRegisterPage() {
|
|||||||
|
|
||||||
const handleVerifyOTP = async () => {
|
const handleVerifyOTP = async () => {
|
||||||
if (!otpCode || otpCode.length < 4) {
|
if (!otpCode || otpCode.length < 4) {
|
||||||
toast.error('يرجى إدخال رمز التحقق');
|
toast.error(t('register.otpRequired'));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -241,14 +233,14 @@ export default function AgentRegisterPage() {
|
|||||||
|
|
||||||
if (res?.status === 200) {
|
if (res?.status === 200) {
|
||||||
AuthService.deleteToken();
|
AuthService.deleteToken();
|
||||||
toast.success(res?.message || 'تم التحقق من البريد الإلكتروني بنجاح!');
|
toast.success(res?.message || t('register.emailVerified'));
|
||||||
setShowOtpModal(false);
|
setShowOtpModal(false);
|
||||||
setTimeout(() => router.push('/login'), 1500);
|
setTimeout(() => router.push('/login'), 1500);
|
||||||
} else {
|
} else {
|
||||||
toast.error(res?.message || res?.data?.message || 'رمز التحقق غير صحيح');
|
toast.error(res?.message || res?.data?.message || t('accountVerification.otpInvalid'));
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
toast.error(err?.message || 'حدث خطأ أثناء التحقق');
|
toast.error(err?.message || t('register.verificationError'));
|
||||||
} finally {
|
} finally {
|
||||||
setIsLoading(false);
|
setIsLoading(false);
|
||||||
}
|
}
|
||||||
@ -258,9 +250,9 @@ export default function AgentRegisterPage() {
|
|||||||
setIsLoading(true);
|
setIsLoading(true);
|
||||||
try {
|
try {
|
||||||
await sendEmailOTP();
|
await sendEmailOTP();
|
||||||
toast.success('تم إرسال رمز تحقق جديد');
|
toast.success(t('register.newOtpSent'));
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
toast.error('فشل في إرسال الرمز');
|
toast.error(t('register.resendOtpFailed'));
|
||||||
} finally {
|
} finally {
|
||||||
setIsLoading(false);
|
setIsLoading(false);
|
||||||
}
|
}
|
||||||
@ -330,17 +322,17 @@ export default function AgentRegisterPage() {
|
|||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const stepTitles = [
|
const stepTitles = [
|
||||||
'المعلومات الأساسية',
|
t('register.agent.step1Title'),
|
||||||
'معلومات الهوية',
|
t('register.agent.step2Title'),
|
||||||
'معلومات الوكالة',
|
t('register.agent.step3Title'),
|
||||||
'تأكيد التسجيل'
|
t('register.agent.step4Title')
|
||||||
];
|
];
|
||||||
|
|
||||||
const stepDescriptions = [
|
const stepDescriptions = [
|
||||||
'أدخل معلوماتك الأساسية',
|
t('register.agent.step1Desc'),
|
||||||
'يرجى رفع صور الهوية للتحقق',
|
t('register.agent.step2Desc'),
|
||||||
'أدخل بيانات الوكالة والترخيص',
|
t('register.agent.step3Desc'),
|
||||||
'راجع معلوماتك قبل الإرسال'
|
t('register.agent.step4Desc')
|
||||||
];
|
];
|
||||||
|
|
||||||
const renderStepFields = () => {
|
const renderStepFields = () => {
|
||||||
@ -351,7 +343,7 @@ export default function AgentRegisterPage() {
|
|||||||
<motion.div variants={fadeInUp} className="grid grid-cols-2 gap-3">
|
<motion.div variants={fadeInUp} className="grid grid-cols-2 gap-3">
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-sm font-medium text-gray-300 mb-2">
|
<label className="block text-sm font-medium text-gray-300 mb-2">
|
||||||
الاسم الأول <span className="text-red-500">*</span>
|
{t('register.firstName')} <span className="text-red-500">*</span>
|
||||||
</label>
|
</label>
|
||||||
<div className="relative group">
|
<div className="relative group">
|
||||||
<div className="absolute inset-y-0 right-0 pr-3 flex items-center pointer-events-none">
|
<div className="absolute inset-y-0 right-0 pr-3 flex items-center pointer-events-none">
|
||||||
@ -373,7 +365,7 @@ export default function AgentRegisterPage() {
|
|||||||
className={`w-full pr-12 pl-4 py-3 bg-white/5 border rounded-xl focus:outline-none focus:ring-2 focus:ring-purple-500 focus:border-transparent text-white placeholder-gray-500 transition-all ${
|
className={`w-full pr-12 pl-4 py-3 bg-white/5 border rounded-xl focus:outline-none focus:ring-2 focus:ring-purple-500 focus:border-transparent text-white placeholder-gray-500 transition-all ${
|
||||||
errors.firstName ? 'border-red-500' : 'border-gray-700'
|
errors.firstName ? 'border-red-500' : 'border-gray-700'
|
||||||
}`}
|
}`}
|
||||||
placeholder="الاسم الأول"
|
placeholder={t('register.firstName')}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
{errors.firstName && (
|
{errors.firstName && (
|
||||||
@ -383,7 +375,7 @@ export default function AgentRegisterPage() {
|
|||||||
|
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-sm font-medium text-gray-300 mb-2">
|
<label className="block text-sm font-medium text-gray-300 mb-2">
|
||||||
اسم العائلة <span className="text-red-500">*</span>
|
{t('register.lastName')} <span className="text-red-500">*</span>
|
||||||
</label>
|
</label>
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
@ -395,7 +387,7 @@ export default function AgentRegisterPage() {
|
|||||||
className={`w-full px-4 py-3 bg-white/5 border rounded-xl focus:outline-none focus:ring-2 focus:ring-purple-500 focus:border-transparent text-white placeholder-gray-500 transition-all ${
|
className={`w-full px-4 py-3 bg-white/5 border rounded-xl focus:outline-none focus:ring-2 focus:ring-purple-500 focus:border-transparent text-white placeholder-gray-500 transition-all ${
|
||||||
errors.lastName ? 'border-red-500' : 'border-gray-700'
|
errors.lastName ? 'border-red-500' : 'border-gray-700'
|
||||||
}`}
|
}`}
|
||||||
placeholder="اسم العائلة"
|
placeholder={t('register.lastName')}
|
||||||
/>
|
/>
|
||||||
{errors.lastName && (
|
{errors.lastName && (
|
||||||
<p className="text-red-500 text-sm mt-1">{errors.lastName}</p>
|
<p className="text-red-500 text-sm mt-1">{errors.lastName}</p>
|
||||||
@ -405,7 +397,7 @@ export default function AgentRegisterPage() {
|
|||||||
|
|
||||||
<motion.div variants={fadeInUp}>
|
<motion.div variants={fadeInUp}>
|
||||||
<label className="block text-sm font-medium text-gray-300 mb-2">
|
<label className="block text-sm font-medium text-gray-300 mb-2">
|
||||||
البريد الإلكتروني <span className="text-red-500">*</span>
|
{t('register.email')} <span className="text-red-500">*</span>
|
||||||
</label>
|
</label>
|
||||||
<div className="relative group">
|
<div className="relative group">
|
||||||
<div className="absolute inset-y-0 right-0 pr-3 flex items-center pointer-events-none">
|
<div className="absolute inset-y-0 right-0 pr-3 flex items-center pointer-events-none">
|
||||||
@ -427,7 +419,7 @@ export default function AgentRegisterPage() {
|
|||||||
className={`w-full pr-12 pl-4 py-3 bg-white/5 border rounded-xl focus:outline-none focus:ring-2 focus:ring-purple-500 focus:border-transparent text-white placeholder-gray-500 transition-all ${
|
className={`w-full pr-12 pl-4 py-3 bg-white/5 border rounded-xl focus:outline-none focus:ring-2 focus:ring-purple-500 focus:border-transparent text-white placeholder-gray-500 transition-all ${
|
||||||
errors.email ? 'border-red-500' : 'border-gray-700'
|
errors.email ? 'border-red-500' : 'border-gray-700'
|
||||||
}`}
|
}`}
|
||||||
placeholder="أدخل بريدك الإلكتروني"
|
placeholder={t('register.emailPlaceholder')}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
{errors.email && <p className="text-red-500 text-sm mt-1">{errors.email}</p>}
|
{errors.email && <p className="text-red-500 text-sm mt-1">{errors.email}</p>}
|
||||||
@ -435,7 +427,7 @@ export default function AgentRegisterPage() {
|
|||||||
|
|
||||||
<motion.div variants={fadeInUp}>
|
<motion.div variants={fadeInUp}>
|
||||||
<label className="block text-sm font-medium text-gray-300 mb-2">
|
<label className="block text-sm font-medium text-gray-300 mb-2">
|
||||||
رقم الهاتف <span className="text-red-500">*</span>
|
{t('register.phone')} <span className="text-red-500">*</span>
|
||||||
</label>
|
</label>
|
||||||
<div className="relative group">
|
<div className="relative group">
|
||||||
<div className="absolute inset-y-0 right-0 pr-3 flex items-center pointer-events-none">
|
<div className="absolute inset-y-0 right-0 pr-3 flex items-center pointer-events-none">
|
||||||
@ -457,7 +449,7 @@ export default function AgentRegisterPage() {
|
|||||||
className={`w-full pr-12 pl-4 py-3 bg-white/5 border rounded-xl focus:outline-none focus:ring-2 focus:ring-purple-500 focus:border-transparent text-white placeholder-gray-500 transition-all ${
|
className={`w-full pr-12 pl-4 py-3 bg-white/5 border rounded-xl focus:outline-none focus:ring-2 focus:ring-purple-500 focus:border-transparent text-white placeholder-gray-500 transition-all ${
|
||||||
errors.phone ? 'border-red-500' : 'border-gray-700'
|
errors.phone ? 'border-red-500' : 'border-gray-700'
|
||||||
}`}
|
}`}
|
||||||
placeholder="أدخل رقم هاتفك"
|
placeholder={t('register.phonePlaceholder')}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
{errors.phone && <p className="text-red-500 text-sm mt-1">{errors.phone}</p>}
|
{errors.phone && <p className="text-red-500 text-sm mt-1">{errors.phone}</p>}
|
||||||
@ -465,7 +457,7 @@ export default function AgentRegisterPage() {
|
|||||||
|
|
||||||
<motion.div variants={fadeInUp}>
|
<motion.div variants={fadeInUp}>
|
||||||
<label className="block text-sm font-medium text-gray-300 mb-2">
|
<label className="block text-sm font-medium text-gray-300 mb-2">
|
||||||
كلمة المرور <span className="text-red-500">*</span>
|
{t('register.password')} <span className="text-red-500">*</span>
|
||||||
</label>
|
</label>
|
||||||
<div className="relative group">
|
<div className="relative group">
|
||||||
<div className="absolute inset-y-0 right-0 pr-3 flex items-center pointer-events-none">
|
<div className="absolute inset-y-0 right-0 pr-3 flex items-center pointer-events-none">
|
||||||
@ -487,7 +479,7 @@ export default function AgentRegisterPage() {
|
|||||||
className={`w-full pr-12 pl-12 py-3 bg-white/5 border rounded-xl focus:outline-none focus:ring-2 focus:ring-purple-500 focus:border-transparent text-white placeholder-gray-500 transition-all ${
|
className={`w-full pr-12 pl-12 py-3 bg-white/5 border rounded-xl focus:outline-none focus:ring-2 focus:ring-purple-500 focus:border-transparent text-white placeholder-gray-500 transition-all ${
|
||||||
errors.password ? 'border-red-500' : 'border-gray-700'
|
errors.password ? 'border-red-500' : 'border-gray-700'
|
||||||
}`}
|
}`}
|
||||||
placeholder="أدخل كلمة المرور"
|
placeholder={t('register.passwordPlaceholder')}
|
||||||
/>
|
/>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
@ -506,7 +498,7 @@ export default function AgentRegisterPage() {
|
|||||||
|
|
||||||
<motion.div variants={fadeInUp}>
|
<motion.div variants={fadeInUp}>
|
||||||
<label className="block text-sm font-medium text-gray-300 mb-2">
|
<label className="block text-sm font-medium text-gray-300 mb-2">
|
||||||
تأكيد كلمة المرور <span className="text-red-500">*</span>
|
{t('register.confirmPassword')} <span className="text-red-500">*</span>
|
||||||
</label>
|
</label>
|
||||||
<div className="relative group">
|
<div className="relative group">
|
||||||
<div className="absolute inset-y-0 right-0 pr-3 flex items-center pointer-events-none">
|
<div className="absolute inset-y-0 right-0 pr-3 flex items-center pointer-events-none">
|
||||||
@ -528,7 +520,7 @@ export default function AgentRegisterPage() {
|
|||||||
className={`w-full pr-12 pl-12 py-3 bg-white/5 border rounded-xl focus:outline-none focus:ring-2 focus:ring-purple-500 focus:border-transparent text-white placeholder-gray-500 transition-all ${
|
className={`w-full pr-12 pl-12 py-3 bg-white/5 border rounded-xl focus:outline-none focus:ring-2 focus:ring-purple-500 focus:border-transparent text-white placeholder-gray-500 transition-all ${
|
||||||
errors.confirmPassword ? 'border-red-500' : 'border-gray-700'
|
errors.confirmPassword ? 'border-red-500' : 'border-gray-700'
|
||||||
}`}
|
}`}
|
||||||
placeholder="أعد إدخال كلمة المرور"
|
placeholder={t('register.confirmPasswordPlaceholder')}
|
||||||
/>
|
/>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
@ -563,7 +555,7 @@ export default function AgentRegisterPage() {
|
|||||||
<>
|
<>
|
||||||
<motion.div variants={fadeInUp}>
|
<motion.div variants={fadeInUp}>
|
||||||
<label className="block text-sm font-medium text-gray-300 mb-2">
|
<label className="block text-sm font-medium text-gray-300 mb-2">
|
||||||
الرقم الوطني <span className="text-red-500">*</span>
|
{t('register.nationalNumber')} <span className="text-red-500">*</span>
|
||||||
</label>
|
</label>
|
||||||
<div className="relative group">
|
<div className="relative group">
|
||||||
<div className="absolute inset-y-0 right-0 pr-3 flex items-center pointer-events-none">
|
<div className="absolute inset-y-0 right-0 pr-3 flex items-center pointer-events-none">
|
||||||
@ -585,7 +577,7 @@ export default function AgentRegisterPage() {
|
|||||||
className={`w-full pr-12 pl-4 py-3 bg-white/5 border rounded-xl focus:outline-none focus:ring-2 focus:ring-purple-500 focus:border-transparent text-white placeholder-gray-500 transition-all ${
|
className={`w-full pr-12 pl-4 py-3 bg-white/5 border rounded-xl focus:outline-none focus:ring-2 focus:ring-purple-500 focus:border-transparent text-white placeholder-gray-500 transition-all ${
|
||||||
errors.nationalNumber ? 'border-red-500' : 'border-gray-700'
|
errors.nationalNumber ? 'border-red-500' : 'border-gray-700'
|
||||||
}`}
|
}`}
|
||||||
placeholder="أدخل الرقم الوطني"
|
placeholder={t('register.nationalNumberPlaceholder')}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
{errors.nationalNumber && (
|
{errors.nationalNumber && (
|
||||||
@ -595,7 +587,7 @@ export default function AgentRegisterPage() {
|
|||||||
|
|
||||||
<motion.div variants={fadeInUp}>
|
<motion.div variants={fadeInUp}>
|
||||||
<label className="block text-sm font-medium text-gray-300 mb-2">
|
<label className="block text-sm font-medium text-gray-300 mb-2">
|
||||||
رقم الواتساب <span className="text-red-500">*</span>
|
{t('register.whatsapp')} <span className="text-red-500">*</span>
|
||||||
</label>
|
</label>
|
||||||
<div className="relative group">
|
<div className="relative group">
|
||||||
<div className="absolute inset-y-0 right-0 pr-3 flex items-center pointer-events-none">
|
<div className="absolute inset-y-0 right-0 pr-3 flex items-center pointer-events-none">
|
||||||
@ -617,7 +609,7 @@ export default function AgentRegisterPage() {
|
|||||||
className={`w-full pr-12 pl-4 py-3 bg-white/5 border rounded-xl focus:outline-none focus:ring-2 focus:ring-purple-500 focus:border-transparent text-white placeholder-gray-500 transition-all ${
|
className={`w-full pr-12 pl-4 py-3 bg-white/5 border rounded-xl focus:outline-none focus:ring-2 focus:ring-purple-500 focus:border-transparent text-white placeholder-gray-500 transition-all ${
|
||||||
errors.whatsapp ? 'border-red-500' : 'border-gray-700'
|
errors.whatsapp ? 'border-red-500' : 'border-gray-700'
|
||||||
}`}
|
}`}
|
||||||
placeholder="أدخل رقم الواتساب"
|
placeholder={t('register.whatsappPlaceholder')}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
{errors.whatsapp && <p className="text-red-500 text-sm mt-1">{errors.whatsapp}</p>}
|
{errors.whatsapp && <p className="text-red-500 text-sm mt-1">{errors.whatsapp}</p>}
|
||||||
@ -625,7 +617,7 @@ export default function AgentRegisterPage() {
|
|||||||
|
|
||||||
<motion.div variants={fadeInUp}>
|
<motion.div variants={fadeInUp}>
|
||||||
<label className="block text-sm font-medium text-gray-300 mb-2">
|
<label className="block text-sm font-medium text-gray-300 mb-2">
|
||||||
صورة الهوية - الوجه الأمامي <span className="text-red-500">*</span>
|
{t('register.agent.frontIdLabel')} <span className="text-red-500">*</span>
|
||||||
</label>
|
</label>
|
||||||
<div
|
<div
|
||||||
onClick={() => fileInputFrontRef.current?.click()}
|
onClick={() => fileInputFrontRef.current?.click()}
|
||||||
@ -649,7 +641,7 @@ export default function AgentRegisterPage() {
|
|||||||
<div className="relative">
|
<div className="relative">
|
||||||
<Image
|
<Image
|
||||||
src={idImagePreviews.front}
|
src={idImagePreviews.front}
|
||||||
alt="Front ID"
|
alt={t('register.uploadFrontAlt')}
|
||||||
width={200}
|
width={200}
|
||||||
height={120}
|
height={120}
|
||||||
className="mx-auto rounded-lg object-cover"
|
className="mx-auto rounded-lg object-cover"
|
||||||
@ -669,8 +661,8 @@ export default function AgentRegisterPage() {
|
|||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
<Camera className="w-12 h-12 text-gray-500 mx-auto mb-3" />
|
<Camera className="w-12 h-12 text-gray-500 mx-auto mb-3" />
|
||||||
<p className="text-gray-400">اضغط لرفع الصورة</p>
|
<p className="text-gray-400">{t('register.agent.clickUploadImage')}</p>
|
||||||
<p className="text-xs text-gray-500 mt-2">JPEG, PNG, JPG • حتى 5MB</p>
|
<p className="text-xs text-gray-500 mt-2">JPEG, PNG, JPG • {t('register.upTo5MB')}</p>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
@ -679,7 +671,7 @@ export default function AgentRegisterPage() {
|
|||||||
|
|
||||||
<motion.div variants={fadeInUp}>
|
<motion.div variants={fadeInUp}>
|
||||||
<label className="block text-sm font-medium text-gray-300 mb-2">
|
<label className="block text-sm font-medium text-gray-300 mb-2">
|
||||||
صورة الهوية - الوجه الخلفي <span className="text-red-500">*</span>
|
{t('register.agent.backIdLabel')} <span className="text-red-500">*</span>
|
||||||
</label>
|
</label>
|
||||||
<div
|
<div
|
||||||
onClick={() => fileInputBackRef.current?.click()}
|
onClick={() => fileInputBackRef.current?.click()}
|
||||||
@ -703,7 +695,7 @@ export default function AgentRegisterPage() {
|
|||||||
<div className="relative">
|
<div className="relative">
|
||||||
<Image
|
<Image
|
||||||
src={idImagePreviews.back}
|
src={idImagePreviews.back}
|
||||||
alt="Back ID"
|
alt={t('register.uploadBackAlt')}
|
||||||
width={200}
|
width={200}
|
||||||
height={120}
|
height={120}
|
||||||
className="mx-auto rounded-lg object-cover"
|
className="mx-auto rounded-lg object-cover"
|
||||||
@ -723,8 +715,8 @@ export default function AgentRegisterPage() {
|
|||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
<Camera className="w-12 h-12 text-gray-500 mx-auto mb-3" />
|
<Camera className="w-12 h-12 text-gray-500 mx-auto mb-3" />
|
||||||
<p className="text-gray-400">اضغط لرفع الصورة</p>
|
<p className="text-gray-400">{t('register.agent.clickUploadImage')}</p>
|
||||||
<p className="text-xs text-gray-500 mt-2">JPEG, PNG, JPG • حتى 5MB</p>
|
<p className="text-xs text-gray-500 mt-2">JPEG, PNG, JPG • {t('register.upTo5MB')}</p>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
@ -738,7 +730,7 @@ export default function AgentRegisterPage() {
|
|||||||
<>
|
<>
|
||||||
<motion.div variants={fadeInUp}>
|
<motion.div variants={fadeInUp}>
|
||||||
<label className="block text-sm font-medium text-gray-300 mb-2">
|
<label className="block text-sm font-medium text-gray-300 mb-2">
|
||||||
عنوان الوكالة <span className="text-red-500">*</span>
|
{t('register.agent.agencyAddressLabel')} <span className="text-red-500">*</span>
|
||||||
</label>
|
</label>
|
||||||
<div className="relative group">
|
<div className="relative group">
|
||||||
<div className="absolute inset-y-0 right-0 pr-3 flex items-center pointer-events-none">
|
<div className="absolute inset-y-0 right-0 pr-3 flex items-center pointer-events-none">
|
||||||
@ -760,7 +752,7 @@ export default function AgentRegisterPage() {
|
|||||||
className={`w-full pr-12 pl-4 py-3 bg-white/5 border rounded-xl focus:outline-none focus:ring-2 focus:ring-purple-500 focus:border-transparent text-white placeholder-gray-500 transition-all ${
|
className={`w-full pr-12 pl-4 py-3 bg-white/5 border rounded-xl focus:outline-none focus:ring-2 focus:ring-purple-500 focus:border-transparent text-white placeholder-gray-500 transition-all ${
|
||||||
errors.agencyAddress ? 'border-red-500' : 'border-gray-700'
|
errors.agencyAddress ? 'border-red-500' : 'border-gray-700'
|
||||||
}`}
|
}`}
|
||||||
placeholder="أدخل عنوان الوكالة"
|
placeholder={t('register.agent.agencyAddressPlaceholder')}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
{errors.agencyAddress && (
|
{errors.agencyAddress && (
|
||||||
@ -770,7 +762,7 @@ export default function AgentRegisterPage() {
|
|||||||
|
|
||||||
<motion.div variants={fadeInUp}>
|
<motion.div variants={fadeInUp}>
|
||||||
<label className="block text-sm font-medium text-gray-300 mb-2">
|
<label className="block text-sm font-medium text-gray-300 mb-2">
|
||||||
رقم الترخيص <span className="text-red-500">*</span>
|
{t('register.agent.licenseNumberLabel')} <span className="text-red-500">*</span>
|
||||||
</label>
|
</label>
|
||||||
<div className="relative group">
|
<div className="relative group">
|
||||||
<div className="absolute inset-y-0 right-0 pr-3 flex items-center pointer-events-none">
|
<div className="absolute inset-y-0 right-0 pr-3 flex items-center pointer-events-none">
|
||||||
@ -792,7 +784,7 @@ export default function AgentRegisterPage() {
|
|||||||
className={`w-full pr-12 pl-4 py-3 bg-white/5 border rounded-xl focus:outline-none focus:ring-2 focus:ring-purple-500 focus:border-transparent text-white placeholder-gray-500 transition-all ${
|
className={`w-full pr-12 pl-4 py-3 bg-white/5 border rounded-xl focus:outline-none focus:ring-2 focus:ring-purple-500 focus:border-transparent text-white placeholder-gray-500 transition-all ${
|
||||||
errors.licenseNumber ? 'border-red-500' : 'border-gray-700'
|
errors.licenseNumber ? 'border-red-500' : 'border-gray-700'
|
||||||
}`}
|
}`}
|
||||||
placeholder="أدخل رقم الترخيص العقاري"
|
placeholder={t('register.agent.licenseNumberPlaceholder')}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
{errors.licenseNumber && (
|
{errors.licenseNumber && (
|
||||||
@ -802,7 +794,7 @@ export default function AgentRegisterPage() {
|
|||||||
|
|
||||||
<motion.div variants={fadeInUp}>
|
<motion.div variants={fadeInUp}>
|
||||||
<label className="block text-sm font-medium text-gray-300 mb-2">
|
<label className="block text-sm font-medium text-gray-300 mb-2">
|
||||||
صورة الترخيص <span className="text-red-500">*</span>
|
{t('register.agent.licenseImageLabel')} <span className="text-red-500">*</span>
|
||||||
</label>
|
</label>
|
||||||
<div
|
<div
|
||||||
onClick={() => fileInputLicenseStep3Ref.current?.click()}
|
onClick={() => fileInputLicenseStep3Ref.current?.click()}
|
||||||
@ -826,7 +818,7 @@ export default function AgentRegisterPage() {
|
|||||||
<div className="relative">
|
<div className="relative">
|
||||||
<Image
|
<Image
|
||||||
src={idImagePreviews.license}
|
src={idImagePreviews.license}
|
||||||
alt="License"
|
alt={t('register.uploadLicenseAlt')}
|
||||||
width={200}
|
width={200}
|
||||||
height={120}
|
height={120}
|
||||||
className="mx-auto rounded-lg object-cover"
|
className="mx-auto rounded-lg object-cover"
|
||||||
@ -846,8 +838,8 @@ export default function AgentRegisterPage() {
|
|||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
<Camera className="w-12 h-12 text-gray-500 mx-auto mb-3" />
|
<Camera className="w-12 h-12 text-gray-500 mx-auto mb-3" />
|
||||||
<p className="text-gray-400">اضغط لرفع صورة الترخيص</p>
|
<p className="text-gray-400">{t('register.agent.clickUploadLicense')}</p>
|
||||||
<p className="text-xs text-gray-500 mt-2">JPEG, PNG, JPG • حتى 5MB</p>
|
<p className="text-xs text-gray-500 mt-2">JPEG, PNG, JPG • {t('register.upTo5MB')}</p>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
@ -862,23 +854,23 @@ export default function AgentRegisterPage() {
|
|||||||
<div className="bg-white/5 rounded-xl p-4 border border-gray-700">
|
<div className="bg-white/5 rounded-xl p-4 border border-gray-700">
|
||||||
<h3 className="text-lg font-semibold text-white mb-3 flex items-center gap-2">
|
<h3 className="text-lg font-semibold text-white mb-3 flex items-center gap-2">
|
||||||
<User className="w-5 h-5 text-purple-400" />
|
<User className="w-5 h-5 text-purple-400" />
|
||||||
المعلومات الأساسية
|
{t('register.agent.reviewBasicInfo')}
|
||||||
</h3>
|
</h3>
|
||||||
<div className="grid grid-cols-2 gap-3 text-sm">
|
<div className="grid grid-cols-2 gap-3 text-sm">
|
||||||
<div>
|
<div>
|
||||||
<span className="text-gray-400">الاسم الأول:</span>{' '}
|
<span className="text-gray-400">{t('register.agent.reviewFirstName')}</span>{' '}
|
||||||
<span className="text-white">{formData.firstName}</span>
|
<span className="text-white">{formData.firstName}</span>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<span className="text-gray-400">اسم العائلة:</span>{' '}
|
<span className="text-gray-400">{t('register.agent.reviewLastName')}</span>{' '}
|
||||||
<span className="text-white">{formData.lastName}</span>
|
<span className="text-white">{formData.lastName}</span>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<span className="text-gray-400">البريد الإلكتروني:</span>{' '}
|
<span className="text-gray-400">{t('register.email')}:</span>{' '}
|
||||||
<span className="text-white">{formData.email}</span>
|
<span className="text-white">{formData.email}</span>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<span className="text-gray-400">رقم الهاتف:</span>{' '}
|
<span className="text-gray-400">{t('register.phone')}:</span>{' '}
|
||||||
<span className="text-white">{formData.phone}</span>
|
<span className="text-white">{formData.phone}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -887,15 +879,15 @@ export default function AgentRegisterPage() {
|
|||||||
<div className="bg-white/5 rounded-xl p-4 border border-gray-700">
|
<div className="bg-white/5 rounded-xl p-4 border border-gray-700">
|
||||||
<h3 className="text-lg font-semibold text-white mb-3 flex items-center gap-2">
|
<h3 className="text-lg font-semibold text-white mb-3 flex items-center gap-2">
|
||||||
<FileText className="w-5 h-5 text-purple-400" />
|
<FileText className="w-5 h-5 text-purple-400" />
|
||||||
معلومات الهوية
|
{t('register.agent.reviewIdInfo')}
|
||||||
</h3>
|
</h3>
|
||||||
<div className="grid grid-cols-2 gap-3 text-sm">
|
<div className="grid grid-cols-2 gap-3 text-sm">
|
||||||
<div>
|
<div>
|
||||||
<span className="text-gray-400">الرقم الوطني:</span>{' '}
|
<span className="text-gray-400">{t('register.nationalNumber')}:</span>{' '}
|
||||||
<span className="text-white">{formData.nationalNumber}</span>
|
<span className="text-white">{formData.nationalNumber}</span>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<span className="text-gray-400">رقم الواتساب:</span>{' '}
|
<span className="text-gray-400">{t('register.whatsapp')}:</span>{' '}
|
||||||
<span className="text-white">{formData.whatsapp}</span>
|
<span className="text-white">{formData.whatsapp}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -905,24 +897,24 @@ export default function AgentRegisterPage() {
|
|||||||
<div className="text-center">
|
<div className="text-center">
|
||||||
<Image
|
<Image
|
||||||
src={idImagePreviews.front}
|
src={idImagePreviews.front}
|
||||||
alt="Front ID"
|
alt={t('register.uploadFrontAlt')}
|
||||||
width={100}
|
width={100}
|
||||||
height={60}
|
height={60}
|
||||||
className="rounded-lg object-cover mx-auto"
|
className="rounded-lg object-cover mx-auto"
|
||||||
/>
|
/>
|
||||||
<p className="text-xs text-gray-400 mt-1">الوجه الأمامي</p>
|
<p className="text-xs text-gray-400 mt-1">{t('register.agent.reviewFront')}</p>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{idImagePreviews.back && (
|
{idImagePreviews.back && (
|
||||||
<div className="text-center">
|
<div className="text-center">
|
||||||
<Image
|
<Image
|
||||||
src={idImagePreviews.back}
|
src={idImagePreviews.back}
|
||||||
alt="Back ID"
|
alt={t('register.uploadBackAlt')}
|
||||||
width={100}
|
width={100}
|
||||||
height={60}
|
height={60}
|
||||||
className="rounded-lg object-cover mx-auto"
|
className="rounded-lg object-cover mx-auto"
|
||||||
/>
|
/>
|
||||||
<p className="text-xs text-gray-400 mt-1">الوجه الخلفي</p>
|
<p className="text-xs text-gray-400 mt-1">{t('register.agent.reviewBack')}</p>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
@ -931,15 +923,15 @@ export default function AgentRegisterPage() {
|
|||||||
<div className="bg-white/5 rounded-xl p-4 border border-gray-700">
|
<div className="bg-white/5 rounded-xl p-4 border border-gray-700">
|
||||||
<h3 className="text-lg font-semibold text-white mb-3 flex items-center gap-2">
|
<h3 className="text-lg font-semibold text-white mb-3 flex items-center gap-2">
|
||||||
<Briefcase className="w-5 h-5 text-purple-400" />
|
<Briefcase className="w-5 h-5 text-purple-400" />
|
||||||
معلومات الوكالة
|
{t('register.agent.reviewAgencyInfo')}
|
||||||
</h3>
|
</h3>
|
||||||
<div className="grid grid-cols-2 gap-3 text-sm">
|
<div className="grid grid-cols-2 gap-3 text-sm">
|
||||||
<div>
|
<div>
|
||||||
<span className="text-gray-400">عنوان الوكالة:</span>{' '}
|
<span className="text-gray-400">{t('register.agent.reviewAgencyAddress')}:</span>{' '}
|
||||||
<span className="text-white">{formData.agencyAddress}</span>
|
<span className="text-white">{formData.agencyAddress}</span>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<span className="text-gray-400">رقم الترخيص:</span>{' '}
|
<span className="text-gray-400">{t('register.agent.reviewLicenseNumber')}:</span>{' '}
|
||||||
<span className="text-white">{formData.licenseNumber}</span>
|
<span className="text-white">{formData.licenseNumber}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -948,12 +940,12 @@ export default function AgentRegisterPage() {
|
|||||||
<div className="text-center mt-3">
|
<div className="text-center mt-3">
|
||||||
<Image
|
<Image
|
||||||
src={idImagePreviews.license}
|
src={idImagePreviews.license}
|
||||||
alt="License"
|
alt={t('register.uploadLicenseAlt')}
|
||||||
width={100}
|
width={100}
|
||||||
height={60}
|
height={60}
|
||||||
className="rounded-lg object-cover mx-auto"
|
className="rounded-lg object-cover mx-auto"
|
||||||
/>
|
/>
|
||||||
<p className="text-xs text-gray-400 mt-1">صورة الترخيص</p>
|
<p className="text-xs text-gray-400 mt-1">{t('register.agent.reviewLicense')}</p>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
@ -968,13 +960,13 @@ export default function AgentRegisterPage() {
|
|||||||
required
|
required
|
||||||
/>
|
/>
|
||||||
<label htmlFor="terms" className="text-sm text-gray-300">
|
<label htmlFor="terms" className="text-sm text-gray-300">
|
||||||
أوافق على{' '}
|
{t('register.agreeTerms')}{' '}
|
||||||
<Link href="/terms" className="text-purple-400 hover:text-purple-300">
|
<Link href="/terms" className="text-purple-400 hover:text-purple-300">
|
||||||
شروط الاستخدام
|
{t('register.termsOfUse')}
|
||||||
</Link>{' '}
|
</Link>{' '}
|
||||||
و{' '}
|
{t('register.and')}{' '}
|
||||||
<Link href="/privacy" className="text-purple-400 hover:text-purple-300">
|
<Link href="/privacy" className="text-purple-400 hover:text-purple-300">
|
||||||
سياسة الخصوصية
|
{t('register.privacyPolicy')}
|
||||||
</Link>
|
</Link>
|
||||||
</label>
|
</label>
|
||||||
</motion.div>
|
</motion.div>
|
||||||
@ -1006,9 +998,9 @@ export default function AgentRegisterPage() {
|
|||||||
<motion.div whileHover={{ x: -5 }}>
|
<motion.div whileHover={{ x: -5 }}>
|
||||||
<ArrowLeft className="w-4 h-4" />
|
<ArrowLeft className="w-4 h-4" />
|
||||||
</motion.div>
|
</motion.div>
|
||||||
<span>العودة</span>
|
<span>{t('forgotPassword.backToLogin')}</span>
|
||||||
</Link>
|
</Link>
|
||||||
<span className="text-sm text-gray-400">خطوة {step} من 4</span>
|
<span className="text-sm text-gray-400">{t('register.stepOf4', { step: step })}</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex gap-2">
|
<div className="flex gap-2">
|
||||||
{[1, 2, 3, 4].map((s) => (
|
{[1, 2, 3, 4].map((s) => (
|
||||||
@ -1072,7 +1064,7 @@ export default function AgentRegisterPage() {
|
|||||||
}}
|
}}
|
||||||
className="flex-1 py-3 px-4 bg-white/5 border border-gray-700 rounded-xl text-gray-300 hover:bg-white/10 transition-colors"
|
className="flex-1 py-3 px-4 bg-white/5 border border-gray-700 rounded-xl text-gray-300 hover:bg-white/10 transition-colors"
|
||||||
>
|
>
|
||||||
السابق
|
{t('register.previous')}
|
||||||
</button>
|
</button>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
@ -1081,7 +1073,7 @@ export default function AgentRegisterPage() {
|
|||||||
type="submit"
|
type="submit"
|
||||||
className="flex-1 bg-gradient-to-r from-purple-500 to-purple-600 text-white py-3 px-4 rounded-xl font-medium hover:from-purple-600 hover:to-purple-700 transition-all"
|
className="flex-1 bg-gradient-to-r from-purple-500 to-purple-600 text-white py-3 px-4 rounded-xl font-medium hover:from-purple-600 hover:to-purple-700 transition-all"
|
||||||
>
|
>
|
||||||
التالي
|
{t('register.next')}
|
||||||
</button>
|
</button>
|
||||||
) : (
|
) : (
|
||||||
<button
|
<button
|
||||||
@ -1092,10 +1084,10 @@ export default function AgentRegisterPage() {
|
|||||||
{isLoading ? (
|
{isLoading ? (
|
||||||
<div className="flex items-center justify-center gap-2">
|
<div className="flex items-center justify-center gap-2">
|
||||||
<Loader2 className="w-5 h-5 animate-spin" />
|
<Loader2 className="w-5 h-5 animate-spin" />
|
||||||
<span>جاري التسجيل...</span>
|
<span>{t('register.registering')}</span>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
'إنشاء حساب وكيل'
|
t('register.agent.createAccount')
|
||||||
)}
|
)}
|
||||||
</button>
|
</button>
|
||||||
)}
|
)}
|
||||||
@ -1123,14 +1115,14 @@ export default function AgentRegisterPage() {
|
|||||||
<div className="w-16 h-16 bg-purple-500/20 rounded-full flex items-center justify-center mx-auto mb-3">
|
<div className="w-16 h-16 bg-purple-500/20 rounded-full flex items-center justify-center mx-auto mb-3">
|
||||||
<Shield className="w-8 h-8 text-purple-500" />
|
<Shield className="w-8 h-8 text-purple-500" />
|
||||||
</div>
|
</div>
|
||||||
<h2 className="text-xl font-bold text-white">التحقق من البريد</h2>
|
<h2 className="text-xl font-bold text-white">{t('register.otpTitle')}</h2>
|
||||||
<p className="text-gray-400 text-sm mt-1">تم إرسال رمز التحقق إلى</p>
|
<p className="text-gray-400 text-sm mt-1">{t('register.otpSentTo')}</p>
|
||||||
<p className="text-purple-400 font-medium text-sm">{formData.email}</p>
|
<p className="text-purple-400 font-medium text-sm">{formData.email}</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="mb-6">
|
<div className="mb-6">
|
||||||
<label className="block text-sm font-medium text-gray-300 mb-2">
|
<label className="block text-sm font-medium text-gray-300 mb-2">
|
||||||
رمز التحقق
|
{t('register.otpLabel')}
|
||||||
</label>
|
</label>
|
||||||
<div className="relative">
|
<div className="relative">
|
||||||
<div className="absolute inset-y-0 right-0 pr-3 flex items-center pointer-events-none">
|
<div className="absolute inset-y-0 right-0 pr-3 flex items-center pointer-events-none">
|
||||||
@ -1156,10 +1148,10 @@ export default function AgentRegisterPage() {
|
|||||||
{isLoading ? (
|
{isLoading ? (
|
||||||
<>
|
<>
|
||||||
<Loader2 className="w-5 h-5 animate-spin" />
|
<Loader2 className="w-5 h-5 animate-spin" />
|
||||||
<span>جاري التحقق...</span>
|
<span>{t('register.verifying')}</span>
|
||||||
</>
|
</>
|
||||||
) : (
|
) : (
|
||||||
'تحقق'
|
t('register.verify')
|
||||||
)}
|
)}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
@ -1169,7 +1161,7 @@ export default function AgentRegisterPage() {
|
|||||||
disabled={isLoading}
|
disabled={isLoading}
|
||||||
className="w-full text-center text-purple-400 hover:text-purple-300 text-sm mt-3 disabled:opacity-50"
|
className="w-full text-center text-purple-400 hover:text-purple-300 text-sm mt-3 disabled:opacity-50"
|
||||||
>
|
>
|
||||||
إعادة إرسال الرمز
|
{t('register.resendOtp')}
|
||||||
</button>
|
</button>
|
||||||
</motion.div>
|
</motion.div>
|
||||||
</motion.div>
|
</motion.div>
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@ -5,6 +5,7 @@ import { useState, useEffect, useMemo } from 'react';
|
|||||||
import { motion, AnimatePresence } from 'framer-motion';
|
import { motion, AnimatePresence } from 'framer-motion';
|
||||||
import { useRouter } from 'next/navigation';
|
import { useRouter } from 'next/navigation';
|
||||||
import Link from 'next/link';
|
import Link from 'next/link';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
import {
|
import {
|
||||||
User, Mail, Phone, Lock, Eye, EyeOff,
|
User, Mail, Phone, Lock, Eye, EyeOff,
|
||||||
CheckCircle, XCircle, ArrowLeft, Home, Loader2,
|
CheckCircle, XCircle, ArrowLeft, Home, Loader2,
|
||||||
@ -17,7 +18,8 @@ import { CustomerType, CustomerTypeLabels } from '../../enums';
|
|||||||
|
|
||||||
export default function TenantRegisterPage() {
|
export default function TenantRegisterPage() {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const [step, setStep] = useState(1); // 1=form, 2=agree terms (no images)
|
const { t } = useTranslation();
|
||||||
|
const [step, setStep] = useState(1);
|
||||||
const [showOtpModal, setShowOtpModal] = useState(false);
|
const [showOtpModal, setShowOtpModal] = useState(false);
|
||||||
const [showPassword, setShowPassword] = useState(false);
|
const [showPassword, setShowPassword] = useState(false);
|
||||||
const [showConfirmPassword, setShowConfirmPassword] = useState(false);
|
const [showConfirmPassword, setShowConfirmPassword] = useState(false);
|
||||||
@ -45,18 +47,18 @@ export default function TenantRegisterPage() {
|
|||||||
|
|
||||||
const validateStep1 = () => {
|
const validateStep1 = () => {
|
||||||
const newErrors = {};
|
const newErrors = {};
|
||||||
if (!formData.firstName) newErrors.firstName = 'الاسم الأول مطلوب';
|
if (!formData.firstName) newErrors.firstName = t('register.firstNameRequired');
|
||||||
if (!formData.lastName) newErrors.lastName = 'اسم العائلة مطلوب';
|
if (!formData.lastName) newErrors.lastName = t('register.lastNameRequired');
|
||||||
if (!formData.email) newErrors.email = 'البريد الإلكتروني مطلوب';
|
if (!formData.email) newErrors.email = t('register.emailRequired');
|
||||||
else if (!validateEmail(formData.email)) newErrors.email = 'البريد الإلكتروني غير صالح';
|
else if (!validateEmail(formData.email)) newErrors.email = t('register.emailInvalid');
|
||||||
if (!formData.phone) newErrors.phone = 'رقم الهاتف مطلوب';
|
if (!formData.phone) newErrors.phone = t('register.phoneRequired');
|
||||||
else if (!validatePhone(formData.phone)) newErrors.phone = 'رقم الهاتف غير صالح (يجب أن يبدأ 09 أو 05)';
|
else if (!validatePhone(formData.phone)) newErrors.phone = t('register.phoneInvalid') + ` (${t('register.phoneMustStartWith')} 09/05)`;
|
||||||
if (!formData.password) newErrors.password = 'كلمة المرور مطلوبة';
|
if (!formData.password) newErrors.password = t('register.passwordRequired');
|
||||||
else if (formData.password.length < 6) newErrors.password = 'كلمة المرور يجب أن تكون 6 أحرف على الأقل';
|
else if (formData.password.length < 6) newErrors.password = t('validation.passwordMinLength');
|
||||||
if (!formData.whatsapp) newErrors.whatsapp = 'رقم الواتساب مطلوب';
|
if (!formData.whatsapp) newErrors.whatsapp = t('register.whatsappRequired');
|
||||||
if (!formData.phone2 || formData.phone2.length !== 7) newErrors.phone2 = 'رقم الهاتف يجب أن يكون 7 أرقام';
|
if (!formData.phone2 || formData.phone2.length !== 7) newErrors.phone2 = t('validation.phone7Digits');
|
||||||
if (!formData.nationalNumber) newErrors.nationalNumber = 'الرقم الوطني مطلوب';
|
if (!formData.nationalNumber) newErrors.nationalNumber = t('register.nationalNumberRequired');
|
||||||
if (formData.password !== formData.confirmPassword) newErrors.confirmPassword = 'كلمات المرور غير متطابقة';
|
if (formData.password !== formData.confirmPassword) newErrors.confirmPassword = t('validation.passwordsMatch');
|
||||||
setErrors(newErrors);
|
setErrors(newErrors);
|
||||||
return Object.keys(newErrors).length === 0;
|
return Object.keys(newErrors).length === 0;
|
||||||
};
|
};
|
||||||
@ -66,7 +68,7 @@ export default function TenantRegisterPage() {
|
|||||||
setStep(2);
|
setStep(2);
|
||||||
window.scrollTo({ top: 0, behavior: 'smooth' });
|
window.scrollTo({ top: 0, behavior: 'smooth' });
|
||||||
} else {
|
} else {
|
||||||
toast.error('يرجى تصحيح الأخطاء في النموذج');
|
toast.error(t('validation.correctErrors'));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -74,7 +76,7 @@ export default function TenantRegisterPage() {
|
|||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
|
||||||
if (!formData.agreeTerms) {
|
if (!formData.agreeTerms) {
|
||||||
toast.error('يجب الموافقة على الشروط والأحكام');
|
toast.error(t('validation.agreeToTerms'));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -93,7 +95,6 @@ export default function TenantRegisterPage() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// لا توجد صور للمستأجر
|
|
||||||
const res = await addCustomer(payload, null, null);
|
const res = await addCustomer(payload, null, null);
|
||||||
|
|
||||||
if (res.status === 200 || res.ok) {
|
if (res.status === 200 || res.ok) {
|
||||||
@ -103,26 +104,26 @@ export default function TenantRegisterPage() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const apiMessage = res.message || res.data?.message;
|
const apiMessage = res.message || res.data?.message;
|
||||||
toast.success(apiMessage || 'تم إنشاء الحساب! يرجى التحقق من بريدك الإلكتروني', { duration: 4000 });
|
toast.success(apiMessage || t('register.accountCreated'), { duration: 4000 });
|
||||||
|
|
||||||
const loginRes = await loginWithEmail(formData.email, formData.password);
|
const loginRes = await loginWithEmail(formData.email, formData.password);
|
||||||
if (loginRes.status === 206) {
|
if (loginRes.status === 206) {
|
||||||
const otpToken = loginRes.data;
|
const otpToken = loginRes.data;
|
||||||
if (otpToken) AuthService.addToken(otpToken);
|
if (otpToken) AuthService.addToken(otpToken);
|
||||||
toast(loginRes.message || 'تم إرسال رمز التحقق إلى بريدك الإلكتروني', { icon: '📧' });
|
toast(loginRes.message || t('register.otpSentEmail'), { icon: '📧' });
|
||||||
setShowOtpModal(true);
|
setShowOtpModal(true);
|
||||||
} else if (loginRes.status === 200) {
|
} else if (loginRes.status === 200) {
|
||||||
const loginToken = loginRes.data;
|
const loginToken = loginRes.data;
|
||||||
if (loginToken) AuthService.addToken(loginToken);
|
if (loginToken) AuthService.addToken(loginToken);
|
||||||
toast.success(loginRes.message || 'تم تسجيل الدخول بنجاح!');
|
toast.success(loginRes.message || t('register.loginSuccess'));
|
||||||
router.push('/');
|
router.push('/');
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
const errMsg = res.message || res.data?.message || 'فشل في إنشاء الحساب';
|
const errMsg = res.message || res.data?.message || t('register.accountCreationFailed');
|
||||||
toast.error(errMsg);
|
toast.error(errMsg);
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
toast.error(err.message || 'حدث خطأ أثناء التسجيل');
|
toast.error(err.message || t('register.registrationError'));
|
||||||
} finally {
|
} finally {
|
||||||
setIsLoading(false);
|
setIsLoading(false);
|
||||||
}
|
}
|
||||||
@ -130,7 +131,7 @@ export default function TenantRegisterPage() {
|
|||||||
|
|
||||||
const handleVerifyOTP = async () => {
|
const handleVerifyOTP = async () => {
|
||||||
if (!otpCode || otpCode.length < 4) {
|
if (!otpCode || otpCode.length < 4) {
|
||||||
toast.error('يرجى إدخال رمز التحقق');
|
toast.error(t('register.otpRequired'));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
setIsLoading(true);
|
setIsLoading(true);
|
||||||
@ -138,14 +139,14 @@ export default function TenantRegisterPage() {
|
|||||||
const res = await verifyEmail(otpCode);
|
const res = await verifyEmail(otpCode);
|
||||||
if (res.status === 200) {
|
if (res.status === 200) {
|
||||||
AuthService.deleteToken();
|
AuthService.deleteToken();
|
||||||
toast.success(res.message || 'تم التحقق من البريد الإلكتروني بنجاح!');
|
toast.success(res.message || t('register.emailVerified'));
|
||||||
setShowOtpModal(false);
|
setShowOtpModal(false);
|
||||||
setTimeout(() => router.push('/login'), 1500);
|
setTimeout(() => router.push('/login'), 1500);
|
||||||
} else {
|
} else {
|
||||||
toast.error(res.message || res.data?.message || 'رمز التحقق غير صحيح');
|
toast.error(res.message || res.data?.message || t('accountVerification.otpInvalid'));
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
toast.error(err.message || 'حدث خطأ أثناء التحقق');
|
toast.error(err.message || t('register.verificationError'));
|
||||||
} finally {
|
} finally {
|
||||||
setIsLoading(false);
|
setIsLoading(false);
|
||||||
}
|
}
|
||||||
@ -155,9 +156,9 @@ export default function TenantRegisterPage() {
|
|||||||
setIsLoading(true);
|
setIsLoading(true);
|
||||||
try {
|
try {
|
||||||
await sendEmailOTP();
|
await sendEmailOTP();
|
||||||
toast.success('تم إرسال رمز تحقق جديد');
|
toast.success(t('register.newOtpSent'));
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
toast.error('فشل في إرسال الرمز');
|
toast.error(t('register.resendOtpFailed'));
|
||||||
} finally {
|
} finally {
|
||||||
setIsLoading(false);
|
setIsLoading(false);
|
||||||
}
|
}
|
||||||
@ -212,7 +213,7 @@ export default function TenantRegisterPage() {
|
|||||||
<motion.div initial={{ opacity: 0, x: -20 }} animate={{ opacity: 1, x: 0 }} className="mb-8">
|
<motion.div initial={{ opacity: 0, x: -20 }} animate={{ opacity: 1, x: 0 }} className="mb-8">
|
||||||
<Link href="/auth/choose-role" className="flex items-center gap-2 text-gray-400 hover:text-white transition-colors group">
|
<Link href="/auth/choose-role" className="flex items-center gap-2 text-gray-400 hover:text-white transition-colors group">
|
||||||
<motion.div whileHover={{ x: -5 }}><ArrowLeft className="w-4 h-4" /></motion.div>
|
<motion.div whileHover={{ x: -5 }}><ArrowLeft className="w-4 h-4" /></motion.div>
|
||||||
<span>العودة</span>
|
<span>{t('forgotPassword.backToLogin')}</span>
|
||||||
</Link>
|
</Link>
|
||||||
</motion.div>
|
</motion.div>
|
||||||
|
|
||||||
@ -229,8 +230,8 @@ export default function TenantRegisterPage() {
|
|||||||
<motion.div animate={{ rotate: [0, 10, -10, 0] }} transition={{ duration: 2, repeat: Infinity }} className="w-20 h-20 mx-auto mb-4 bg-white/20 rounded-2xl flex items-center justify-center backdrop-blur-sm">
|
<motion.div animate={{ rotate: [0, 10, -10, 0] }} transition={{ duration: 2, repeat: Infinity }} className="w-20 h-20 mx-auto mb-4 bg-white/20 rounded-2xl flex items-center justify-center backdrop-blur-sm">
|
||||||
<Home className="w-10 h-10 text-white" />
|
<Home className="w-10 h-10 text-white" />
|
||||||
</motion.div>
|
</motion.div>
|
||||||
<h1 className="text-3xl font-bold text-white mb-2">{step === 1 ? 'إنشاء حساب مستأجر' : 'الموافقة على الشروط'}</h1>
|
<h1 className="text-3xl font-bold text-white mb-2">{step === 1 ? t('register.tenant.step1Title') : t('register.tenant.step2Title')}</h1>
|
||||||
<p className="text-blue-100">{step === 1 ? 'انضم إلينا وابحث عن منزل أحلامك' : 'يرجى الموافقة على الشروط لإكمال التسجيل'}</p>
|
<p className="text-blue-100">{step === 1 ? t('register.tenant.step1Desc') : t('register.tenant.step2Desc')}</p>
|
||||||
</motion.div>
|
</motion.div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -240,91 +241,91 @@ export default function TenantRegisterPage() {
|
|||||||
<>
|
<>
|
||||||
<motion.div variants={fadeInUp} className="grid grid-cols-2 gap-3">
|
<motion.div variants={fadeInUp} className="grid grid-cols-2 gap-3">
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-sm font-medium text-gray-300 mb-2">الاسم الأول <span className="text-red-500">*</span></label>
|
<label className="block text-sm font-medium text-gray-300 mb-2">{t('register.firstName')} <span className="text-red-500">*</span></label>
|
||||||
<div className="relative group">
|
<div className="relative group">
|
||||||
<div className="absolute inset-y-0 right-0 pr-3 flex items-center pointer-events-none">
|
<div className="absolute inset-y-0 right-0 pr-3 flex items-center pointer-events-none">
|
||||||
<User className={`w-5 h-5 ${errors.firstName ? 'text-red-500' : 'text-gray-400 group-focus-within:text-blue-500'}`} />
|
<User className={`w-5 h-5 ${errors.firstName ? 'text-red-500' : 'text-gray-400 group-focus-within:text-blue-500'}`} />
|
||||||
</div>
|
</div>
|
||||||
<input type="text" value={formData.firstName} onChange={(e) => { setFormData({...formData, firstName: e.target.value}); setErrors({...errors, firstName: null}); }} className={`w-full pr-12 pl-4 py-3 bg-white/5 border rounded-xl focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent text-white placeholder-gray-500 transition-all ${errors.firstName ? 'border-red-500' : 'border-gray-700'}`} placeholder="الاسم الأول" />
|
<input type="text" value={formData.firstName} onChange={(e) => { setFormData({...formData, firstName: e.target.value}); setErrors({...errors, firstName: null}); }} className={`w-full pr-12 pl-4 py-3 bg-white/5 border rounded-xl focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent text-white placeholder-gray-500 transition-all ${errors.firstName ? 'border-red-500' : 'border-gray-700'}`} placeholder={t('register.firstName')} />
|
||||||
</div>
|
</div>
|
||||||
{errors.firstName && <p className="text-red-500 text-sm mt-1">{errors.firstName}</p>}
|
{errors.firstName && <p className="text-red-500 text-sm mt-1">{errors.firstName}</p>}
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-sm font-medium text-gray-300 mb-2">اسم العائلة <span className="text-red-500">*</span></label>
|
<label className="block text-sm font-medium text-gray-300 mb-2">{t('register.lastName')} <span className="text-red-500">*</span></label>
|
||||||
<input type="text" value={formData.lastName} onChange={(e) => { setFormData({...formData, lastName: e.target.value}); setErrors({...errors, lastName: null}); }} className={`w-full px-4 py-3 bg-white/5 border rounded-xl focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent text-white placeholder-gray-500 transition-all ${errors.lastName ? 'border-red-500' : 'border-gray-700'}`} placeholder="اسم العائلة" />
|
<input type="text" value={formData.lastName} onChange={(e) => { setFormData({...formData, lastName: e.target.value}); setErrors({...errors, lastName: null}); }} className={`w-full px-4 py-3 bg-white/5 border rounded-xl focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent text-white placeholder-gray-500 transition-all ${errors.lastName ? 'border-red-500' : 'border-gray-700'}`} placeholder={t('register.lastName')} />
|
||||||
{errors.lastName && <p className="text-red-500 text-sm mt-1">{errors.lastName}</p>}
|
{errors.lastName && <p className="text-red-500 text-sm mt-1">{errors.lastName}</p>}
|
||||||
</div>
|
</div>
|
||||||
</motion.div>
|
</motion.div>
|
||||||
|
|
||||||
<motion.div variants={fadeInUp}>
|
<motion.div variants={fadeInUp}>
|
||||||
<label className="block text-sm font-medium text-gray-300 mb-2">البريد الإلكتروني <span className="text-red-500">*</span></label>
|
<label className="block text-sm font-medium text-gray-300 mb-2">{t('register.email')} <span className="text-red-500">*</span></label>
|
||||||
<div className="relative group">
|
<div className="relative group">
|
||||||
<div className="absolute inset-y-0 right-0 pr-3 flex items-center pointer-events-none">
|
<div className="absolute inset-y-0 right-0 pr-3 flex items-center pointer-events-none">
|
||||||
<Mail className={`w-5 h-5 ${errors.email ? 'text-red-500' : 'text-gray-400 group-focus-within:text-blue-500'}`} />
|
<Mail className={`w-5 h-5 ${errors.email ? 'text-red-500' : 'text-gray-400 group-focus-within:text-blue-500'}`} />
|
||||||
</div>
|
</div>
|
||||||
<input type="email" value={formData.email} onChange={(e) => { setFormData({...formData, email: e.target.value}); setErrors({...errors, email: null}); }} className={`w-full pr-12 pl-4 py-3 bg-white/5 border rounded-xl focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent text-white placeholder-gray-500 transition-all ${errors.email ? 'border-red-500' : 'border-gray-700'}`} placeholder="أدخل بريدك الإلكتروني" />
|
<input type="email" value={formData.email} onChange={(e) => { setFormData({...formData, email: e.target.value}); setErrors({...errors, email: null}); }} className={`w-full pr-12 pl-4 py-3 bg-white/5 border rounded-xl focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent text-white placeholder-gray-500 transition-all ${errors.email ? 'border-red-500' : 'border-gray-700'}`} placeholder={t('register.emailPlaceholder')} />
|
||||||
</div>
|
</div>
|
||||||
{errors.email && <p className="text-red-500 text-sm mt-1">{errors.email}</p>}
|
{errors.email && <p className="text-red-500 text-sm mt-1">{errors.email}</p>}
|
||||||
</motion.div>
|
</motion.div>
|
||||||
|
|
||||||
<motion.div variants={fadeInUp}>
|
<motion.div variants={fadeInUp}>
|
||||||
<label className="block text-sm font-medium text-gray-300 mb-2">رقم الهاتف <span className="text-red-500">*</span></label>
|
<label className="block text-sm font-medium text-gray-300 mb-2">{t('register.phone')} <span className="text-red-500">*</span></label>
|
||||||
<div className="relative group">
|
<div className="relative group">
|
||||||
<div className="absolute inset-y-0 right-0 pr-3 flex items-center pointer-events-none">
|
<div className="absolute inset-y-0 right-0 pr-3 flex items-center pointer-events-none">
|
||||||
<Phone className={`w-5 h-5 ${errors.phone ? 'text-red-500' : 'text-gray-400 group-focus-within:text-blue-500'}`} />
|
<Phone className={`w-5 h-5 ${errors.phone ? 'text-red-500' : 'text-gray-400 group-focus-within:text-blue-500'}`} />
|
||||||
</div>
|
</div>
|
||||||
<input type="tel" value={formData.phone} onChange={(e) => { setFormData({...formData, phone: e.target.value}); setErrors({...errors, phone: null}); }} className={`w-full pr-12 pl-4 py-3 bg-white/5 border rounded-xl focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent text-white placeholder-gray-500 transition-all ${errors.phone ? 'border-red-500' : 'border-gray-700'}`} placeholder="أدخل رقم هاتفك" />
|
<input type="tel" value={formData.phone} onChange={(e) => { setFormData({...formData, phone: e.target.value}); setErrors({...errors, phone: null}); }} className={`w-full pr-12 pl-4 py-3 bg-white/5 border rounded-xl focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent text-white placeholder-gray-500 transition-all ${errors.phone ? 'border-red-500' : 'border-gray-700'}`} placeholder={t('register.phonePlaceholder')} />
|
||||||
</div>
|
</div>
|
||||||
{errors.phone && <p className="text-red-500 text-sm mt-1">{errors.phone}</p>}
|
{errors.phone && <p className="text-red-500 text-sm mt-1">{errors.phone}</p>}
|
||||||
</motion.div>
|
</motion.div>
|
||||||
|
|
||||||
<motion.div variants={fadeInUp}>
|
<motion.div variants={fadeInUp}>
|
||||||
<label className="block text-sm font-medium text-gray-300 mb-2">رقم الواتساب <span className="text-red-500">*</span></label>
|
<label className="block text-sm font-medium text-gray-300 mb-2">{t('register.whatsapp')} <span className="text-red-500">*</span></label>
|
||||||
<div className="relative group">
|
<div className="relative group">
|
||||||
<div className="absolute inset-y-0 right-0 pr-3 flex items-center pointer-events-none">
|
<div className="absolute inset-y-0 right-0 pr-3 flex items-center pointer-events-none">
|
||||||
<Phone className={`w-5 h-5 ${errors.whatsapp ? 'text-red-500' : 'text-gray-400 group-focus-within:text-blue-500'}`} />
|
<Phone className={`w-5 h-5 ${errors.whatsapp ? 'text-red-500' : 'text-gray-400 group-focus-within:text-blue-500'}`} />
|
||||||
</div>
|
</div>
|
||||||
<input type="tel" value={formData.whatsapp} onChange={(e) => { setFormData({...formData, whatsapp: e.target.value}); setErrors({...errors, whatsapp: null}); }} className={`w-full pr-12 pl-4 py-3 bg-white/5 border rounded-xl focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent text-white placeholder-gray-500 transition-all ${errors.whatsapp ? 'border-red-500' : 'border-gray-700'}`} placeholder="أدخل رقم الواتساب" />
|
<input type="tel" value={formData.whatsapp} onChange={(e) => { setFormData({...formData, whatsapp: e.target.value}); setErrors({...errors, whatsapp: null}); }} className={`w-full pr-12 pl-4 py-3 bg-white/5 border rounded-xl focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent text-white placeholder-gray-500 transition-all ${errors.whatsapp ? 'border-red-500' : 'border-gray-700'}`} placeholder={t('register.whatsappPlaceholder')} />
|
||||||
</div>
|
</div>
|
||||||
{errors.whatsapp && <p className="text-red-500 text-sm mt-1">{errors.whatsapp}</p>}
|
{errors.whatsapp && <p className="text-red-500 text-sm mt-1">{errors.whatsapp}</p>}
|
||||||
</motion.div>
|
</motion.div>
|
||||||
|
|
||||||
<motion.div variants={fadeInUp}>
|
<motion.div variants={fadeInUp}>
|
||||||
<label className="block text-sm font-medium text-gray-300 mb-2">رقم الهاتف (7 أرقام) <span className="text-red-500">*</span></label>
|
<label className="block text-sm font-medium text-gray-300 mb-2">{t('register.phoneSecondary')} <span className="text-red-500">*</span></label>
|
||||||
<div className="relative group">
|
<div className="relative group">
|
||||||
<div className="absolute inset-y-0 right-0 pr-3 flex items-center pointer-events-none">
|
<div className="absolute inset-y-0 right-0 pr-3 flex items-center pointer-events-none">
|
||||||
<Phone className={`w-5 h-5 ${errors.phone2 ? 'text-red-500' : 'text-gray-400 group-focus-within:text-blue-500'}`} />
|
<Phone className={`w-5 h-5 ${errors.phone2 ? 'text-red-500' : 'text-gray-400 group-focus-within:text-blue-500'}`} />
|
||||||
</div>
|
</div>
|
||||||
<input type="tel" value={formData.phone2} onChange={(e) => { setFormData({...formData, phone2: e.target.value}); setErrors({...errors, phone2: null}); }} className={`w-full pr-12 pl-4 py-3 bg-white/5 border rounded-xl focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent text-white placeholder-gray-500 transition-all ${errors.phone2 ? 'border-red-500' : 'border-gray-700'}`} placeholder="أدخل رقم الهاتف" maxLength={7} />
|
<input type="tel" value={formData.phone2} onChange={(e) => { setFormData({...formData, phone2: e.target.value}); setErrors({...errors, phone2: null}); }} className={`w-full pr-12 pl-4 py-3 bg-white/5 border rounded-xl focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent text-white placeholder-gray-500 transition-all ${errors.phone2 ? 'border-red-500' : 'border-gray-700'}`} placeholder={t('register.phoneSecondaryPlaceholder')} maxLength={7} />
|
||||||
</div>
|
</div>
|
||||||
{errors.phone2 && <p className="text-red-500 text-sm mt-1">{errors.phone2}</p>}
|
{errors.phone2 && <p className="text-red-500 text-sm mt-1">{errors.phone2}</p>}
|
||||||
</motion.div>
|
</motion.div>
|
||||||
|
|
||||||
<motion.div variants={fadeInUp}>
|
<motion.div variants={fadeInUp}>
|
||||||
<label className="block text-sm font-medium text-gray-300 mb-2">الرقم الوطني <span className="text-red-500">*</span></label>
|
<label className="block text-sm font-medium text-gray-300 mb-2">{t('register.nationalNumber')} <span className="text-red-500">*</span></label>
|
||||||
<div className="relative group">
|
<div className="relative group">
|
||||||
<div className="absolute inset-y-0 right-0 pr-3 flex items-center pointer-events-none">
|
<div className="absolute inset-y-0 right-0 pr-3 flex items-center pointer-events-none">
|
||||||
<User className={`w-5 h-5 ${errors.nationalNumber ? 'text-red-500' : 'text-gray-400 group-focus-within:text-blue-500'}`} />
|
<User className={`w-5 h-5 ${errors.nationalNumber ? 'text-red-500' : 'text-gray-400 group-focus-within:text-blue-500'}`} />
|
||||||
</div>
|
</div>
|
||||||
<input type="text" value={formData.nationalNumber} onChange={(e) => { setFormData({...formData, nationalNumber: e.target.value}); setErrors({...errors, nationalNumber: null}); }} className={`w-full pr-12 pl-4 py-3 bg-white/5 border rounded-xl focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent text-white placeholder-gray-500 transition-all ${errors.nationalNumber ? 'border-red-500' : 'border-gray-700'}`} placeholder="أدخل الرقم الوطني" />
|
<input type="text" value={formData.nationalNumber} onChange={(e) => { setFormData({...formData, nationalNumber: e.target.value}); setErrors({...errors, nationalNumber: null}); }} className={`w-full pr-12 pl-4 py-3 bg-white/5 border rounded-xl focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent text-white placeholder-gray-500 transition-all ${errors.nationalNumber ? 'border-red-500' : 'border-gray-700'}`} placeholder={t('register.nationalNumberPlaceholder')} />
|
||||||
</div>
|
</div>
|
||||||
{errors.nationalNumber && <p className="text-red-500 text-sm mt-1">{errors.nationalNumber}</p>}
|
{errors.nationalNumber && <p className="text-red-500 text-sm mt-1">{errors.nationalNumber}</p>}
|
||||||
</motion.div>
|
</motion.div>
|
||||||
|
|
||||||
<motion.div variants={fadeInUp}>
|
<motion.div variants={fadeInUp}>
|
||||||
<label className="block text-sm font-medium text-gray-300 mb-2">نوع العميل <span className="text-red-500">*</span></label>
|
<label className="block text-sm font-medium text-gray-300 mb-2">{t('register.customerType')} <span className="text-red-500">*</span></label>
|
||||||
<select value={formData.customerType} onChange={(e) => setFormData({...formData, customerType: e.target.value})} className="w-full py-3 px-4 bg-white/5 border border-gray-700 rounded-xl focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent text-white appearance-none cursor-pointer">
|
<select value={formData.customerType} onChange={(e) => setFormData({...formData, customerType: e.target.value})} className="w-full py-3 px-4 bg-white/5 border border-gray-700 rounded-xl focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent text-white appearance-none cursor-pointer">
|
||||||
{Object.entries(CustomerTypeLabels).map(([value, label]) => (<option key={value} value={value} className="bg-gray-900 text-white">{label}</option>))}
|
{Object.entries(CustomerTypeLabels).map(([value, label]) => (<option key={value} value={value} className="bg-gray-900 text-white">{label}</option>))}
|
||||||
</select>
|
</select>
|
||||||
</motion.div>
|
</motion.div>
|
||||||
|
|
||||||
<motion.div variants={fadeInUp}>
|
<motion.div variants={fadeInUp}>
|
||||||
<label className="block text-sm font-medium text-gray-300 mb-2">كلمة المرور <span className="text-red-500">*</span></label>
|
<label className="block text-sm font-medium text-gray-300 mb-2">{t('register.password')} <span className="text-red-500">*</span></label>
|
||||||
<div className="relative group">
|
<div className="relative group">
|
||||||
<div className="absolute inset-y-0 right-0 pr-3 flex items-center pointer-events-none">
|
<div className="absolute inset-y-0 right-0 pr-3 flex items-center pointer-events-none">
|
||||||
<Lock className={`w-5 h-5 ${errors.password ? 'text-red-500' : 'text-gray-400 group-focus-within:text-blue-500'}`} />
|
<Lock className={`w-5 h-5 ${errors.password ? 'text-red-500' : 'text-gray-400 group-focus-within:text-blue-500'}`} />
|
||||||
</div>
|
</div>
|
||||||
<input type={showPassword ? "text" : "password"} value={formData.password} onChange={(e) => { setFormData({...formData, password: e.target.value}); setErrors({...errors, password: null}); }} className={`w-full pr-12 pl-12 py-3 bg-white/5 border rounded-xl focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent text-white placeholder-gray-500 transition-all ${errors.password ? 'border-red-500' : 'border-gray-700'}`} placeholder="أدخل كلمة المرور" />
|
<input type={showPassword ? "text" : "password"} value={formData.password} onChange={(e) => { setFormData({...formData, password: e.target.value}); setErrors({...errors, password: null}); }} className={`w-full pr-12 pl-12 py-3 bg-white/5 border rounded-xl focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent text-white placeholder-gray-500 transition-all ${errors.password ? 'border-red-500' : 'border-gray-700'}`} placeholder={t('register.passwordPlaceholder')} />
|
||||||
<button type="button" onClick={() => setShowPassword(!showPassword)} className="absolute inset-y-0 left-0 pl-3 flex items-center">
|
<button type="button" onClick={() => setShowPassword(!showPassword)} className="absolute inset-y-0 left-0 pl-3 flex items-center">
|
||||||
{showPassword ? <EyeOff className="w-5 h-5 text-gray-400" /> : <Eye className="w-5 h-5 text-gray-400" />}
|
{showPassword ? <EyeOff className="w-5 h-5 text-gray-400" /> : <Eye className="w-5 h-5 text-gray-400" />}
|
||||||
</button>
|
</button>
|
||||||
@ -333,12 +334,12 @@ export default function TenantRegisterPage() {
|
|||||||
</motion.div>
|
</motion.div>
|
||||||
|
|
||||||
<motion.div variants={fadeInUp}>
|
<motion.div variants={fadeInUp}>
|
||||||
<label className="block text-sm font-medium text-gray-300 mb-2">تأكيد كلمة المرور <span className="text-red-500">*</span></label>
|
<label className="block text-sm font-medium text-gray-300 mb-2">{t('register.confirmPassword')} <span className="text-red-500">*</span></label>
|
||||||
<div className="relative group">
|
<div className="relative group">
|
||||||
<div className="absolute inset-y-0 right-0 pr-3 flex items-center pointer-events-none">
|
<div className="absolute inset-y-0 right-0 pr-3 flex items-center pointer-events-none">
|
||||||
<Lock className={`w-5 h-5 ${errors.confirmPassword ? 'text-red-500' : 'text-gray-400 group-focus-within:text-blue-500'}`} />
|
<Lock className={`w-5 h-5 ${errors.confirmPassword ? 'text-red-500' : 'text-gray-400 group-focus-within:text-blue-500'}`} />
|
||||||
</div>
|
</div>
|
||||||
<input type={showConfirmPassword ? "text" : "password"} value={formData.confirmPassword} onChange={(e) => { setFormData({...formData, confirmPassword: e.target.value}); setErrors({...errors, confirmPassword: null}); }} className={`w-full pr-12 pl-12 py-3 bg-white/5 border rounded-xl focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent text-white placeholder-gray-500 transition-all ${errors.confirmPassword ? 'border-red-500' : 'border-gray-700'}`} placeholder="أعد إدخال كلمة المرور" />
|
<input type={showConfirmPassword ? "text" : "password"} value={formData.confirmPassword} onChange={(e) => { setFormData({...formData, confirmPassword: e.target.value}); setErrors({...errors, confirmPassword: null}); }} className={`w-full pr-12 pl-12 py-3 bg-white/5 border rounded-xl focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent text-white placeholder-gray-500 transition-all ${errors.confirmPassword ? 'border-red-500' : 'border-gray-700'}`} placeholder={t('register.confirmPasswordPlaceholder')} />
|
||||||
<button type="button" onClick={() => setShowConfirmPassword(!showConfirmPassword)} className="absolute inset-y-0 left-0 pl-3 flex items-center">
|
<button type="button" onClick={() => setShowConfirmPassword(!showConfirmPassword)} className="absolute inset-y-0 left-0 pl-3 flex items-center">
|
||||||
{showConfirmPassword ? <EyeOff className="w-5 h-5 text-gray-400" /> : <Eye className="w-5 h-5 text-gray-400" />}
|
{showConfirmPassword ? <EyeOff className="w-5 h-5 text-gray-400" /> : <Eye className="w-5 h-5 text-gray-400" />}
|
||||||
</button>
|
</button>
|
||||||
@ -354,7 +355,7 @@ export default function TenantRegisterPage() {
|
|||||||
<motion.div variants={fadeInUp} className="flex items-center gap-2">
|
<motion.div variants={fadeInUp} className="flex items-center gap-2">
|
||||||
<input type="checkbox" id="terms" checked={formData.agreeTerms} onChange={(e) => setFormData({...formData, agreeTerms: e.target.checked})} className="w-4 h-4 rounded border-gray-600 bg-white/5 text-blue-500 focus:ring-blue-500" required />
|
<input type="checkbox" id="terms" checked={formData.agreeTerms} onChange={(e) => setFormData({...formData, agreeTerms: e.target.checked})} className="w-4 h-4 rounded border-gray-600 bg-white/5 text-blue-500 focus:ring-blue-500" required />
|
||||||
<label htmlFor="terms" className="text-sm text-gray-300">
|
<label htmlFor="terms" className="text-sm text-gray-300">
|
||||||
أوافق على <Link href="/terms" className="text-blue-400 hover:text-blue-300">شروط الاستخدام</Link> و <Link href="/privacy" className="text-blue-400 hover:text-blue-300">سياسة الخصوصية</Link>
|
{t('register.agreeTerms')} <Link href="/terms" className="text-blue-400 hover:text-blue-300">{t('register.termsOfUse')}</Link> {t('register.and')} <Link href="/privacy" className="text-blue-400 hover:text-blue-300">{t('register.privacyPolicy')}</Link>
|
||||||
</label>
|
</label>
|
||||||
</motion.div>
|
</motion.div>
|
||||||
</>
|
</>
|
||||||
@ -363,14 +364,14 @@ export default function TenantRegisterPage() {
|
|||||||
<motion.div variants={fadeInUp} className="flex gap-3 pt-4">
|
<motion.div variants={fadeInUp} className="flex gap-3 pt-4">
|
||||||
{step === 1 ? (
|
{step === 1 ? (
|
||||||
<>
|
<>
|
||||||
<button type="button" onClick={() => router.push('/auth/choose-role')} className="flex-1 py-3 px-4 bg-white/5 border border-gray-700 rounded-xl text-gray-300 hover:bg-white/10 transition-colors">إلغاء</button>
|
<button type="button" onClick={() => router.push('/auth/choose-role')} className="flex-1 py-3 px-4 bg-white/5 border border-gray-700 rounded-xl text-gray-300 hover:bg-white/10 transition-colors">{t('register.cancel')}</button>
|
||||||
<button type="submit" className="flex-1 bg-gradient-to-r from-blue-500 to-blue-600 text-white py-3 px-4 rounded-xl font-medium hover:from-blue-600 hover:to-blue-700 transition-all">التالي</button>
|
<button type="submit" className="flex-1 bg-gradient-to-r from-blue-500 to-blue-600 text-white py-3 px-4 rounded-xl font-medium hover:from-blue-600 hover:to-blue-700 transition-all">{t('register.next')}</button>
|
||||||
</>
|
</>
|
||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
<button type="button" onClick={() => setStep(1)} className="flex-1 py-3 px-4 bg-white/5 border border-gray-700 rounded-xl text-gray-300 hover:bg-white/10 transition-colors">السابق</button>
|
<button type="button" onClick={() => setStep(1)} className="flex-1 py-3 px-4 bg-white/5 border border-gray-700 rounded-xl text-gray-300 hover:bg-white/10 transition-colors">{t('register.previous')}</button>
|
||||||
<button type="submit" disabled={isLoading || !formData.agreeTerms} className="flex-1 bg-gradient-to-r from-blue-500 to-blue-600 text-white py-3 px-4 rounded-xl font-medium hover:from-blue-600 hover:to-blue-700 transition-all disabled:opacity-50 disabled:cursor-not-allowed">
|
<button type="submit" disabled={isLoading || !formData.agreeTerms} className="flex-1 bg-gradient-to-r from-blue-500 to-blue-600 text-white py-3 px-4 rounded-xl font-medium hover:from-blue-600 hover:to-blue-700 transition-all disabled:opacity-50 disabled:cursor-not-allowed">
|
||||||
{isLoading ? (<div className="flex items-center justify-center gap-2"><Loader2 className="w-5 h-5 animate-spin" /><span>جاري التسجيل...</span></div>) : 'إنشاء حساب'}
|
{isLoading ? (<div className="flex items-center justify-center gap-2"><Loader2 className="w-5 h-5 animate-spin" /><span>{t('register.registering')}</span></div>) : t('register.createAccount')}
|
||||||
</button>
|
</button>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
@ -378,8 +379,7 @@ export default function TenantRegisterPage() {
|
|||||||
|
|
||||||
{step === 1 && (
|
{step === 1 && (
|
||||||
<motion.p variants={fadeInUp} className="text-center text-gray-400 mt-4">
|
<motion.p variants={fadeInUp} className="text-center text-gray-400 mt-4">
|
||||||
لديك حساب بالفعل؟{' '}
|
{t('register.haveAccount')} <Link href="/login" className="text-blue-400 hover:text-blue-300 font-medium transition-colors">{t('register.loginLink')}</Link>
|
||||||
<Link href="/login" className="text-blue-400 hover:text-blue-300 font-medium transition-colors">تسجيل الدخول</Link>
|
|
||||||
</motion.p>
|
</motion.p>
|
||||||
)}
|
)}
|
||||||
</motion.form>
|
</motion.form>
|
||||||
@ -393,12 +393,12 @@ export default function TenantRegisterPage() {
|
|||||||
<motion.div initial={{ scale: 0.9, y: 20 }} animate={{ scale: 1, y: 0 }} exit={{ scale: 0.9, y: 20 }} className="bg-gray-900 border border-white/10 rounded-2xl w-full max-w-md p-6 shadow-2xl">
|
<motion.div initial={{ scale: 0.9, y: 20 }} animate={{ scale: 1, y: 0 }} exit={{ scale: 0.9, y: 20 }} className="bg-gray-900 border border-white/10 rounded-2xl w-full max-w-md p-6 shadow-2xl">
|
||||||
<div className="text-center mb-6">
|
<div className="text-center mb-6">
|
||||||
<div className="w-16 h-16 bg-blue-500/20 rounded-full flex items-center justify-center mx-auto mb-3"><Shield className="w-8 h-8 text-blue-500" /></div>
|
<div className="w-16 h-16 bg-blue-500/20 rounded-full flex items-center justify-center mx-auto mb-3"><Shield className="w-8 h-8 text-blue-500" /></div>
|
||||||
<h2 className="text-xl font-bold text-white">التحقق من البريد</h2>
|
<h2 className="text-xl font-bold text-white">{t('register.otpTitle')}</h2>
|
||||||
<p className="text-gray-400 text-sm mt-1">تم إرسال رمز التحقق إلى</p>
|
<p className="text-gray-400 text-sm mt-1">{t('register.otpSentTo')}</p>
|
||||||
<p className="text-blue-400 font-medium text-sm">{formData.email}</p>
|
<p className="text-blue-400 font-medium text-sm">{formData.email}</p>
|
||||||
</div>
|
</div>
|
||||||
<div className="mb-6">
|
<div className="mb-6">
|
||||||
<label className="block text-sm font-medium text-gray-300 mb-2">رمز التحقق</label>
|
<label className="block text-sm font-medium text-gray-300 mb-2">{t('register.otpLabel')}</label>
|
||||||
<div className="relative">
|
<div className="relative">
|
||||||
<div className="absolute inset-y-0 right-0 pr-3 flex items-center pointer-events-none"><KeyRound className="w-5 h-5 text-gray-400" /></div>
|
<div className="absolute inset-y-0 right-0 pr-3 flex items-center pointer-events-none"><KeyRound className="w-5 h-5 text-gray-400" /></div>
|
||||||
<input type="text" value={otpCode} maxLength={6} onChange={(e) => setOtpCode(e.target.value)} className="w-full pr-12 pl-4 py-3 bg-white/5 border border-gray-700 rounded-xl focus:outline-none focus:ring-2 focus:ring-blue-500 text-white text-center tracking-[0.5em] text-xl" placeholder="------" />
|
<input type="text" value={otpCode} maxLength={6} onChange={(e) => setOtpCode(e.target.value)} className="w-full pr-12 pl-4 py-3 bg-white/5 border border-gray-700 rounded-xl focus:outline-none focus:ring-2 focus:ring-blue-500 text-white text-center tracking-[0.5em] text-xl" placeholder="------" />
|
||||||
@ -406,10 +406,10 @@ export default function TenantRegisterPage() {
|
|||||||
</div>
|
</div>
|
||||||
<div className="flex gap-3">
|
<div className="flex gap-3">
|
||||||
<button onClick={handleVerifyOTP} disabled={isLoading || !otpCode} className="flex-1 bg-gradient-to-r from-blue-500 to-blue-600 text-white py-3 rounded-xl font-medium hover:from-blue-600 hover:to-blue-700 disabled:opacity-50 disabled:cursor-not-allowed flex items-center justify-center gap-2">
|
<button onClick={handleVerifyOTP} disabled={isLoading || !otpCode} className="flex-1 bg-gradient-to-r from-blue-500 to-blue-600 text-white py-3 rounded-xl font-medium hover:from-blue-600 hover:to-blue-700 disabled:opacity-50 disabled:cursor-not-allowed flex items-center justify-center gap-2">
|
||||||
{isLoading ? <><Loader2 className="w-5 h-5 animate-spin" /><span>جاري التحقق...</span></> : 'تحقق'}
|
{isLoading ? <><Loader2 className="w-5 h-5 animate-spin" /><span>{t('register.verifying')}</span></> : t('register.verify')}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<button onClick={handleResendOTP} disabled={isLoading} className="w-full text-center text-blue-400 hover:text-blue-300 text-sm mt-3 disabled:opacity-50">إعادة إرسال الرمز</button>
|
<button onClick={handleResendOTP} disabled={isLoading} className="w-full text-center text-blue-400 hover:text-blue-300 text-sm mt-3 disabled:opacity-50">{t('register.resendOtp')}</button>
|
||||||
</motion.div>
|
</motion.div>
|
||||||
</motion.div>
|
</motion.div>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@ -3,6 +3,7 @@
|
|||||||
import { useState, useEffect } from 'react';
|
import { useState, useEffect } from 'react';
|
||||||
import { motion, AnimatePresence } from 'framer-motion';
|
import { motion, AnimatePresence } from 'framer-motion';
|
||||||
import { useRouter } from 'next/navigation';
|
import { useRouter } from 'next/navigation';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
import toast, { Toaster } from 'react-hot-toast';
|
import toast, { Toaster } from 'react-hot-toast';
|
||||||
import {
|
import {
|
||||||
FileText,
|
FileText,
|
||||||
@ -19,13 +20,8 @@ import {
|
|||||||
import { submitReport, submitReservationReport, submitSaleReport } from '../utils/api';
|
import { submitReport, submitReservationReport, submitSaleReport } from '../utils/api';
|
||||||
import AuthService from '../services/AuthService';
|
import AuthService from '../services/AuthService';
|
||||||
|
|
||||||
const tabs = [
|
|
||||||
{ id: 'general', label: 'تقرير عام', icon: FileText },
|
|
||||||
{ id: 'reservation', label: 'تقرير حجز', icon: Calendar },
|
|
||||||
{ id: 'sale', label: 'تقرير بيع', icon: DollarSign },
|
|
||||||
];
|
|
||||||
|
|
||||||
export default function ReportsPage() {
|
export default function ReportsPage() {
|
||||||
|
const { t } = useTranslation();
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const [activeTab, setActiveTab] = useState('general');
|
const [activeTab, setActiveTab] = useState('general');
|
||||||
const [isLoading, setIsLoading] = useState(false);
|
const [isLoading, setIsLoading] = useState(false);
|
||||||
@ -37,6 +33,12 @@ export default function ReportsPage() {
|
|||||||
}
|
}
|
||||||
}, [router]);
|
}, [router]);
|
||||||
|
|
||||||
|
const tabs = [
|
||||||
|
{ id: 'general', label: t('reports.generalReport'), icon: FileText },
|
||||||
|
{ id: 'reservation', label: t('reports.reservationReport'), icon: Calendar },
|
||||||
|
{ id: 'sale', label: t('reports.saleReport'), icon: DollarSign },
|
||||||
|
];
|
||||||
|
|
||||||
const [generalForm, setGeneralForm] = useState({ subject: '', body: '' });
|
const [generalForm, setGeneralForm] = useState({ subject: '', body: '' });
|
||||||
const [reservationForm, setReservationForm] = useState({ reservationId: '', message: '', reporter: 'customer' });
|
const [reservationForm, setReservationForm] = useState({ reservationId: '', message: '', reporter: 'customer' });
|
||||||
const [saleForm, setSaleForm] = useState({ saleId: '', message: '', reporter: 'buyer' });
|
const [saleForm, setSaleForm] = useState({ saleId: '', message: '', reporter: 'buyer' });
|
||||||
@ -45,24 +47,24 @@ export default function ReportsPage() {
|
|||||||
|
|
||||||
const validateGeneral = () => {
|
const validateGeneral = () => {
|
||||||
const e = {};
|
const e = {};
|
||||||
if (!generalForm.subject.trim()) e.subject = 'الموضوع مطلوب';
|
if (!generalForm.subject.trim()) e.subject = t('reports.subjectRequired');
|
||||||
if (!generalForm.body.trim()) e.body = 'الوصف مطلوب';
|
if (!generalForm.body.trim()) e.body = t('reports.descriptionRequired');
|
||||||
setErrors(e);
|
setErrors(e);
|
||||||
return Object.keys(e).length === 0;
|
return Object.keys(e).length === 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
const validateReservation = () => {
|
const validateReservation = () => {
|
||||||
const e = {};
|
const e = {};
|
||||||
if (!reservationForm.reservationId.trim()) e.reservationId = 'رقم الحجز مطلوب';
|
if (!reservationForm.reservationId.trim()) e.reservationId = t('reports.reservationIdRequired');
|
||||||
if (!reservationForm.message.trim()) e.message = 'الرسالة مطلوبة';
|
if (!reservationForm.message.trim()) e.message = t('reports.messageRequired');
|
||||||
setErrors(e);
|
setErrors(e);
|
||||||
return Object.keys(e).length === 0;
|
return Object.keys(e).length === 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
const validateSale = () => {
|
const validateSale = () => {
|
||||||
const e = {};
|
const e = {};
|
||||||
if (!saleForm.saleId.trim()) e.saleId = 'رقم البيع مطلوب';
|
if (!saleForm.saleId.trim()) e.saleId = t('reports.saleIdRequired');
|
||||||
if (!saleForm.message.trim()) e.message = 'الرسالة مطلوبة';
|
if (!saleForm.message.trim()) e.message = t('reports.messageRequired');
|
||||||
setErrors(e);
|
setErrors(e);
|
||||||
return Object.keys(e).length === 0;
|
return Object.keys(e).length === 0;
|
||||||
};
|
};
|
||||||
@ -74,13 +76,13 @@ export default function ReportsPage() {
|
|||||||
try {
|
try {
|
||||||
await submitReport(generalForm.subject, generalForm.body);
|
await submitReport(generalForm.subject, generalForm.body);
|
||||||
setIsSuccess(true);
|
setIsSuccess(true);
|
||||||
toast.success('تم إرسال التقرير بنجاح!', {
|
toast.success(t('reports.generalSuccess'), {
|
||||||
style: { background: '#dcfce7', color: '#166534' },
|
style: { background: '#dcfce7', color: '#166534' },
|
||||||
});
|
});
|
||||||
setGeneralForm({ subject: '', body: '' });
|
setGeneralForm({ subject: '', body: '' });
|
||||||
setTimeout(() => setIsSuccess(false), 2000);
|
setTimeout(() => setIsSuccess(false), 2000);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
toast.error(err.message || 'فشل إرسال التقرير', {
|
toast.error(err.message || t('reports.generalFailed'), {
|
||||||
style: { background: '#fee2e2', color: '#991b1b' },
|
style: { background: '#fee2e2', color: '#991b1b' },
|
||||||
});
|
});
|
||||||
} finally {
|
} finally {
|
||||||
@ -95,13 +97,13 @@ export default function ReportsPage() {
|
|||||||
try {
|
try {
|
||||||
await submitReservationReport(reservationForm);
|
await submitReservationReport(reservationForm);
|
||||||
setIsSuccess(true);
|
setIsSuccess(true);
|
||||||
toast.success('تم إرسال تقرير الحجز!', {
|
toast.success(t('reports.reservationSuccess'), {
|
||||||
style: { background: '#dcfce7', color: '#166534' },
|
style: { background: '#dcfce7', color: '#166534' },
|
||||||
});
|
});
|
||||||
setReservationForm({ reservationId: '', message: '', reporter: 'customer' });
|
setReservationForm({ reservationId: '', message: '', reporter: 'customer' });
|
||||||
setTimeout(() => setIsSuccess(false), 2000);
|
setTimeout(() => setIsSuccess(false), 2000);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
toast.error(err.message || 'فشل إرسال التقرير', {
|
toast.error(err.message || t('reports.reservationFailed'), {
|
||||||
style: { background: '#fee2e2', color: '#991b1b' },
|
style: { background: '#fee2e2', color: '#991b1b' },
|
||||||
});
|
});
|
||||||
} finally {
|
} finally {
|
||||||
@ -116,13 +118,13 @@ export default function ReportsPage() {
|
|||||||
try {
|
try {
|
||||||
await submitSaleReport(saleForm);
|
await submitSaleReport(saleForm);
|
||||||
setIsSuccess(true);
|
setIsSuccess(true);
|
||||||
toast.success('تم إرسال تقرير البيع!', {
|
toast.success(t('reports.saleSuccess'), {
|
||||||
style: { background: '#dcfce7', color: '#166534' },
|
style: { background: '#dcfce7', color: '#166534' },
|
||||||
});
|
});
|
||||||
setSaleForm({ saleId: '', message: '', reporter: 'buyer' });
|
setSaleForm({ saleId: '', message: '', reporter: 'buyer' });
|
||||||
setTimeout(() => setIsSuccess(false), 2000);
|
setTimeout(() => setIsSuccess(false), 2000);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
toast.error(err.message || 'فشل إرسال التقرير', {
|
toast.error(err.message || t('reports.saleFailed'), {
|
||||||
style: { background: '#fee2e2', color: '#991b1b' },
|
style: { background: '#fee2e2', color: '#991b1b' },
|
||||||
});
|
});
|
||||||
} finally {
|
} finally {
|
||||||
@ -134,7 +136,7 @@ export default function ReportsPage() {
|
|||||||
general: (
|
general: (
|
||||||
<form onSubmit={handleGeneralSubmit} className="space-y-6">
|
<form onSubmit={handleGeneralSubmit} className="space-y-6">
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-sm font-medium text-gray-700 mb-2">الموضوع</label>
|
<label className="block text-sm font-medium text-gray-700 mb-2">{t('reports.subjectLabel')}</label>
|
||||||
<div className="relative group">
|
<div className="relative group">
|
||||||
<div className="absolute inset-y-0 right-0 pr-3 flex items-center pointer-events-none">
|
<div className="absolute inset-y-0 right-0 pr-3 flex items-center pointer-events-none">
|
||||||
<FileText className={`w-5 h-5 ${errors.subject ? 'text-red-500' : 'text-gray-400 group-focus-within:text-amber-500'}`} />
|
<FileText className={`w-5 h-5 ${errors.subject ? 'text-red-500' : 'text-gray-400 group-focus-within:text-amber-500'}`} />
|
||||||
@ -144,19 +146,19 @@ export default function ReportsPage() {
|
|||||||
value={generalForm.subject}
|
value={generalForm.subject}
|
||||||
onChange={(e) => { setGeneralForm({ ...generalForm, subject: e.target.value }); if (errors.subject) setErrors({ ...errors, subject: null }); }}
|
onChange={(e) => { setGeneralForm({ ...generalForm, subject: e.target.value }); if (errors.subject) setErrors({ ...errors, subject: null }); }}
|
||||||
className={`w-full pr-12 pl-4 py-3 border rounded-xl focus:outline-none focus:ring-2 focus:ring-amber-500 focus:border-transparent transition-all ${errors.subject ? 'border-red-500' : 'border-gray-300'}`}
|
className={`w-full pr-12 pl-4 py-3 border rounded-xl focus:outline-none focus:ring-2 focus:ring-amber-500 focus:border-transparent transition-all ${errors.subject ? 'border-red-500' : 'border-gray-300'}`}
|
||||||
placeholder="أدخل موضوع التقرير"
|
placeholder={t('reports.subjectPlaceholder')}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
{errors.subject && <p className="text-red-500 text-sm mt-1">{errors.subject}</p>}
|
{errors.subject && <p className="text-red-500 text-sm mt-1">{errors.subject}</p>}
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-sm font-medium text-gray-700 mb-2">الوصف</label>
|
<label className="block text-sm font-medium text-gray-700 mb-2">{t('reports.descriptionLabel')}</label>
|
||||||
<textarea
|
<textarea
|
||||||
value={generalForm.body}
|
value={generalForm.body}
|
||||||
onChange={(e) => { setGeneralForm({ ...generalForm, body: e.target.value }); if (errors.body) setErrors({ ...errors, body: null }); }}
|
onChange={(e) => { setGeneralForm({ ...generalForm, body: e.target.value }); if (errors.body) setErrors({ ...errors, body: null }); }}
|
||||||
rows={5}
|
rows={5}
|
||||||
className={`w-full px-4 py-3 border rounded-xl focus:outline-none focus:ring-2 focus:ring-amber-500 focus:border-transparent transition-all resize-none ${errors.body ? 'border-red-500' : 'border-gray-300'}`}
|
className={`w-full px-4 py-3 border rounded-xl focus:outline-none focus:ring-2 focus:ring-amber-500 focus:border-transparent transition-all resize-none ${errors.body ? 'border-red-500' : 'border-gray-300'}`}
|
||||||
placeholder="اشرح التفاصيل..."
|
placeholder={t('reports.descriptionPlaceholder')}
|
||||||
/>
|
/>
|
||||||
{errors.body && <p className="text-red-500 text-sm mt-1">{errors.body}</p>}
|
{errors.body && <p className="text-red-500 text-sm mt-1">{errors.body}</p>}
|
||||||
</div>
|
</div>
|
||||||
@ -168,11 +170,11 @@ export default function ReportsPage() {
|
|||||||
whileTap={{ scale: 0.98 }}
|
whileTap={{ scale: 0.98 }}
|
||||||
>
|
>
|
||||||
{isLoading ? (
|
{isLoading ? (
|
||||||
<><Loader2 className="w-5 h-5 animate-spin" /> جاري الإرسال...</>
|
<><Loader2 className="w-5 h-5 animate-spin" /> {t('reports.sending')}</>
|
||||||
) : isSuccess ? (
|
) : isSuccess ? (
|
||||||
<><CheckCircle className="w-5 h-5" /> تم الإرسال!</>
|
<><CheckCircle className="w-5 h-5" /> {t('reports.sentSuccess')}</>
|
||||||
) : (
|
) : (
|
||||||
<><Send className="w-5 h-5" /> إرسال التقرير</>
|
<><Send className="w-5 h-5" /> {t('reports.submitReport')}</>
|
||||||
)}
|
)}
|
||||||
</motion.button>
|
</motion.button>
|
||||||
</form>
|
</form>
|
||||||
@ -180,7 +182,7 @@ export default function ReportsPage() {
|
|||||||
reservation: (
|
reservation: (
|
||||||
<form onSubmit={handleReservationSubmit} className="space-y-6">
|
<form onSubmit={handleReservationSubmit} className="space-y-6">
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-sm font-medium text-gray-700 mb-2">رقم الحجز</label>
|
<label className="block text-sm font-medium text-gray-700 mb-2">{t('reports.reservationIdLabel')}</label>
|
||||||
<div className="relative group">
|
<div className="relative group">
|
||||||
<div className="absolute inset-y-0 right-0 pr-3 flex items-center pointer-events-none">
|
<div className="absolute inset-y-0 right-0 pr-3 flex items-center pointer-events-none">
|
||||||
<Hash className={`w-5 h-5 ${errors.reservationId ? 'text-red-500' : 'text-gray-400 group-focus-within:text-amber-500'}`} />
|
<Hash className={`w-5 h-5 ${errors.reservationId ? 'text-red-500' : 'text-gray-400 group-focus-within:text-amber-500'}`} />
|
||||||
@ -190,24 +192,24 @@ export default function ReportsPage() {
|
|||||||
value={reservationForm.reservationId}
|
value={reservationForm.reservationId}
|
||||||
onChange={(e) => { setReservationForm({ ...reservationForm, reservationId: e.target.value }); if (errors.reservationId) setErrors({ ...errors, reservationId: null }); }}
|
onChange={(e) => { setReservationForm({ ...reservationForm, reservationId: e.target.value }); if (errors.reservationId) setErrors({ ...errors, reservationId: null }); }}
|
||||||
className={`w-full pr-12 pl-4 py-3 border rounded-xl focus:outline-none focus:ring-2 focus:ring-amber-500 focus:border-transparent transition-all ${errors.reservationId ? 'border-red-500' : 'border-gray-300'}`}
|
className={`w-full pr-12 pl-4 py-3 border rounded-xl focus:outline-none focus:ring-2 focus:ring-amber-500 focus:border-transparent transition-all ${errors.reservationId ? 'border-red-500' : 'border-gray-300'}`}
|
||||||
placeholder="أدخل رقم الحجز"
|
placeholder={t('reports.reservationIdPlaceholder')}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
{errors.reservationId && <p className="text-red-500 text-sm mt-1">{errors.reservationId}</p>}
|
{errors.reservationId && <p className="text-red-500 text-sm mt-1">{errors.reservationId}</p>}
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-sm font-medium text-gray-700 mb-2">الرسالة</label>
|
<label className="block text-sm font-medium text-gray-700 mb-2">{t('message')}</label>
|
||||||
<textarea
|
<textarea
|
||||||
value={reservationForm.message}
|
value={reservationForm.message}
|
||||||
onChange={(e) => { setReservationForm({ ...reservationForm, message: e.target.value }); if (errors.message) setErrors({ ...errors, message: null }); }}
|
onChange={(e) => { setReservationForm({ ...reservationForm, message: e.target.value }); if (errors.message) setErrors({ ...errors, message: null }); }}
|
||||||
rows={4}
|
rows={4}
|
||||||
className={`w-full px-4 py-3 border rounded-xl focus:outline-none focus:ring-2 focus:ring-amber-500 focus:border-transparent transition-all resize-none ${errors.message ? 'border-red-500' : 'border-gray-300'}`}
|
className={`w-full px-4 py-3 border rounded-xl focus:outline-none focus:ring-2 focus:ring-amber-500 focus:border-transparent transition-all resize-none ${errors.message ? 'border-red-500' : 'border-gray-300'}`}
|
||||||
placeholder="اشرح المشكلة..."
|
placeholder={t('reports.messagePlaceholder')}
|
||||||
/>
|
/>
|
||||||
{errors.message && <p className="text-red-500 text-sm mt-1">{errors.message}</p>}
|
{errors.message && <p className="text-red-500 text-sm mt-1">{errors.message}</p>}
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-sm font-medium text-gray-700 mb-2">المبلغ</label>
|
<label className="block text-sm font-medium text-gray-700 mb-2">{t('reports.reporterLabel')}</label>
|
||||||
<div className="flex gap-3">
|
<div className="flex gap-3">
|
||||||
{['customer', 'owner'].map((val) => (
|
{['customer', 'owner'].map((val) => (
|
||||||
<button
|
<button
|
||||||
@ -221,7 +223,7 @@ export default function ReportsPage() {
|
|||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
<User className="w-4 h-4" />
|
<User className="w-4 h-4" />
|
||||||
{val === 'customer' ? 'مستأجر' : 'مالك'}
|
{val === 'customer' ? t('customer') : t('owner')}
|
||||||
</button>
|
</button>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
@ -234,11 +236,11 @@ export default function ReportsPage() {
|
|||||||
whileTap={{ scale: 0.98 }}
|
whileTap={{ scale: 0.98 }}
|
||||||
>
|
>
|
||||||
{isLoading ? (
|
{isLoading ? (
|
||||||
<><Loader2 className="w-5 h-5 animate-spin" /> جاري الإرسال...</>
|
<><Loader2 className="w-5 h-5 animate-spin" /> {t('reports.sending')}</>
|
||||||
) : isSuccess ? (
|
) : isSuccess ? (
|
||||||
<><CheckCircle className="w-5 h-5" /> تم الإرسال!</>
|
<><CheckCircle className="w-5 h-5" /> {t('reports.sentSuccess')}</>
|
||||||
) : (
|
) : (
|
||||||
<><Send className="w-5 h-5" /> إرسال التقرير</>
|
<><Send className="w-5 h-5" /> {t('reports.submitReport')}</>
|
||||||
)}
|
)}
|
||||||
</motion.button>
|
</motion.button>
|
||||||
</form>
|
</form>
|
||||||
@ -246,7 +248,7 @@ export default function ReportsPage() {
|
|||||||
sale: (
|
sale: (
|
||||||
<form onSubmit={handleSaleSubmit} className="space-y-6">
|
<form onSubmit={handleSaleSubmit} className="space-y-6">
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-sm font-medium text-gray-700 mb-2">رقم البيع</label>
|
<label className="block text-sm font-medium text-gray-700 mb-2">{t('reports.saleIdLabel')}</label>
|
||||||
<div className="relative group">
|
<div className="relative group">
|
||||||
<div className="absolute inset-y-0 right-0 pr-3 flex items-center pointer-events-none">
|
<div className="absolute inset-y-0 right-0 pr-3 flex items-center pointer-events-none">
|
||||||
<Hash className={`w-5 h-5 ${errors.saleId ? 'text-red-500' : 'text-gray-400 group-focus-within:text-amber-500'}`} />
|
<Hash className={`w-5 h-5 ${errors.saleId ? 'text-red-500' : 'text-gray-400 group-focus-within:text-amber-500'}`} />
|
||||||
@ -256,24 +258,24 @@ export default function ReportsPage() {
|
|||||||
value={saleForm.saleId}
|
value={saleForm.saleId}
|
||||||
onChange={(e) => { setSaleForm({ ...saleForm, saleId: e.target.value }); if (errors.saleId) setErrors({ ...errors, saleId: null }); }}
|
onChange={(e) => { setSaleForm({ ...saleForm, saleId: e.target.value }); if (errors.saleId) setErrors({ ...errors, saleId: null }); }}
|
||||||
className={`w-full pr-12 pl-4 py-3 border rounded-xl focus:outline-none focus:ring-2 focus:ring-amber-500 focus:border-transparent transition-all ${errors.saleId ? 'border-red-500' : 'border-gray-300'}`}
|
className={`w-full pr-12 pl-4 py-3 border rounded-xl focus:outline-none focus:ring-2 focus:ring-amber-500 focus:border-transparent transition-all ${errors.saleId ? 'border-red-500' : 'border-gray-300'}`}
|
||||||
placeholder="أدخل رقم البيع"
|
placeholder={t('reports.saleIdPlaceholder')}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
{errors.saleId && <p className="text-red-500 text-sm mt-1">{errors.saleId}</p>}
|
{errors.saleId && <p className="text-red-500 text-sm mt-1">{errors.saleId}</p>}
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-sm font-medium text-gray-700 mb-2">الرسالة</label>
|
<label className="block text-sm font-medium text-gray-700 mb-2">{t('message')}</label>
|
||||||
<textarea
|
<textarea
|
||||||
value={saleForm.message}
|
value={saleForm.message}
|
||||||
onChange={(e) => { setSaleForm({ ...saleForm, message: e.target.value }); if (errors.message) setErrors({ ...errors, message: null }); }}
|
onChange={(e) => { setSaleForm({ ...saleForm, message: e.target.value }); if (errors.message) setErrors({ ...errors, message: null }); }}
|
||||||
rows={4}
|
rows={4}
|
||||||
className={`w-full px-4 py-3 border rounded-xl focus:outline-none focus:ring-2 focus:ring-amber-500 focus:border-transparent transition-all resize-none ${errors.message ? 'border-red-500' : 'border-gray-300'}`}
|
className={`w-full px-4 py-3 border rounded-xl focus:outline-none focus:ring-2 focus:ring-amber-500 focus:border-transparent transition-all resize-none ${errors.message ? 'border-red-500' : 'border-gray-300'}`}
|
||||||
placeholder="اشرح المشكلة..."
|
placeholder={t('reports.messagePlaceholder')}
|
||||||
/>
|
/>
|
||||||
{errors.message && <p className="text-red-500 text-sm mt-1">{errors.message}</p>}
|
{errors.message && <p className="text-red-500 text-sm mt-1">{errors.message}</p>}
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-sm font-medium text-gray-700 mb-2">المبلغ</label>
|
<label className="block text-sm font-medium text-gray-700 mb-2">{t('reports.reporterLabel')}</label>
|
||||||
<div className="flex gap-3">
|
<div className="flex gap-3">
|
||||||
{['buyer', 'seller'].map((val) => (
|
{['buyer', 'seller'].map((val) => (
|
||||||
<button
|
<button
|
||||||
@ -287,7 +289,7 @@ export default function ReportsPage() {
|
|||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
<User className="w-4 h-4" />
|
<User className="w-4 h-4" />
|
||||||
{val === 'buyer' ? 'مشتري' : 'بائع'}
|
{val === 'buyer' ? t('buyer') : t('seller')}
|
||||||
</button>
|
</button>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
@ -300,11 +302,11 @@ export default function ReportsPage() {
|
|||||||
whileTap={{ scale: 0.98 }}
|
whileTap={{ scale: 0.98 }}
|
||||||
>
|
>
|
||||||
{isLoading ? (
|
{isLoading ? (
|
||||||
<><Loader2 className="w-5 h-5 animate-spin" /> جاري الإرسال...</>
|
<><Loader2 className="w-5 h-5 animate-spin" /> {t('reports.sending')}</>
|
||||||
) : isSuccess ? (
|
) : isSuccess ? (
|
||||||
<><CheckCircle className="w-5 h-5" /> تم الإرسال!</>
|
<><CheckCircle className="w-5 h-5" /> {t('reports.sentSuccess')}</>
|
||||||
) : (
|
) : (
|
||||||
<><Send className="w-5 h-5" /> إرسال التقرير</>
|
<><Send className="w-5 h-5" /> {t('reports.submitReport')}</>
|
||||||
)}
|
)}
|
||||||
</motion.button>
|
</motion.button>
|
||||||
</form>
|
</form>
|
||||||
@ -320,8 +322,8 @@ export default function ReportsPage() {
|
|||||||
animate={{ opacity: 1, y: 0 }}
|
animate={{ opacity: 1, y: 0 }}
|
||||||
className="mb-8"
|
className="mb-8"
|
||||||
>
|
>
|
||||||
<h1 className="text-3xl font-bold text-gray-900 mb-2">الإبلاغ عن مشكلة</h1>
|
<h1 className="text-3xl font-bold text-gray-900 mb-2">{t('reports.pageTitle')}</h1>
|
||||||
<p className="text-gray-600">أخبرنا عن المشكلة التي تواجهها وسنقوم بمعالجتها</p>
|
<p className="text-gray-600">{t('reports.pageDescription')}</p>
|
||||||
</motion.div>
|
</motion.div>
|
||||||
|
|
||||||
<div className="bg-white rounded-2xl shadow-sm border border-gray-200 overflow-hidden">
|
<div className="bg-white rounded-2xl shadow-sm border border-gray-200 overflow-hidden">
|
||||||
|
|||||||
@ -396,6 +396,7 @@ import { useState, useEffect, useCallback } from 'react';
|
|||||||
import Link from 'next/link';
|
import Link from 'next/link';
|
||||||
import { motion } from 'framer-motion';
|
import { motion } from 'framer-motion';
|
||||||
import { useRouter } from 'next/navigation';
|
import { useRouter } from 'next/navigation';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
import {
|
import {
|
||||||
Calendar,
|
Calendar,
|
||||||
Clock,
|
Clock,
|
||||||
@ -432,50 +433,49 @@ const API_BASE = process.env.NEXT_PUBLIC_API_URL || 'https://45.93.137.91.nip.io
|
|||||||
const STATUS_MAP = ['pending','ownerConfirmed','depositPaid','depositConfirmed','completed','cancelled'];
|
const STATUS_MAP = ['pending','ownerConfirmed','depositPaid','depositConfirmed','completed','cancelled'];
|
||||||
|
|
||||||
const STATUS_UI = {
|
const STATUS_UI = {
|
||||||
pending: { label: 'قيد الانتظار', color: 'bg-yellow-100 text-yellow-800', icon: Clock },
|
pending: { color: 'bg-yellow-100 text-yellow-800', icon: Clock },
|
||||||
ownerConfirmed: { label: 'مؤكد من المالك', color: 'bg-blue-100 text-blue-800', icon: CheckCircle },
|
ownerConfirmed: { color: 'bg-blue-100 text-blue-800', icon: CheckCircle },
|
||||||
depositPaid: { label: 'تم دفع السلفة', color: 'bg-indigo-100 text-indigo-800', icon: DollarSign },
|
depositPaid: { color: 'bg-indigo-100 text-indigo-800', icon: DollarSign },
|
||||||
depositConfirmed: { label: 'مؤكد', color: 'bg-green-100 text-green-800', icon: CheckCircle },
|
depositConfirmed: { color: 'bg-green-100 text-green-800', icon: CheckCircle },
|
||||||
completed: { label: 'منتهي', color: 'bg-green-100 text-green-800', icon: CheckCircle },
|
completed: { color: 'bg-green-100 text-green-800', icon: CheckCircle },
|
||||||
cancelled: { label: 'ملغي', color: 'bg-gray-100 text-gray-800', icon: XCircle },
|
cancelled: { color: 'bg-gray-100 text-gray-800', icon: XCircle },
|
||||||
};
|
};
|
||||||
|
|
||||||
const PAYMENT_METHODS = [
|
const PAYMENT_METHODS = [
|
||||||
{
|
{
|
||||||
id: 'cash',
|
id: 'cash',
|
||||||
label: 'الدفع النقدي',
|
labelKey: 'payment.cash',
|
||||||
desc: 'ادفع الآن عبر شام كاش مع تأكيد تلقائي للحجز',
|
descKey: 'payment.cashDesc',
|
||||||
icon: Banknote,
|
icon: Banknote,
|
||||||
active: true,
|
active: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'office',
|
id: 'office',
|
||||||
label: 'نقداً',
|
labelKey: 'payment.office',
|
||||||
desc: 'ادفع في مكتب المنصة. يتم التأكيد لاحقاً',
|
descKey: 'payment.officeDesc',
|
||||||
icon: Building,
|
icon: Building,
|
||||||
active: true,
|
active: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'transfer',
|
id: 'transfer',
|
||||||
label: 'تحويل داخلي',
|
labelKey: 'payment.transfer',
|
||||||
desc: 'حول من حساب داخلي ثم أرسل المرجع',
|
descKey: 'payment.transferDesc',
|
||||||
icon: ArrowLeftRight,
|
icon: ArrowLeftRight,
|
||||||
active: false,
|
active: false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'electronic',
|
id: 'electronic',
|
||||||
label: 'دفع إلكتروني',
|
labelKey: 'payment.electronic',
|
||||||
desc: 'سيتم تفعيل الدفع الإلكتروني والتحويل لاحقاً',
|
descKey: 'payment.electronicDesc',
|
||||||
icon: CreditCard,
|
icon: CreditCard,
|
||||||
active: false,
|
active: false,
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
function statusLabel(code) { return STATUS_UI[STATUS_MAP[code]]?.label ?? String(code); }
|
|
||||||
function statusColor(code) { return STATUS_UI[STATUS_MAP[code]]?.color ?? 'bg-gray-100 text-gray-700'; }
|
function statusColor(code) { return STATUS_UI[STATUS_MAP[code]]?.color ?? 'bg-gray-100 text-gray-700'; }
|
||||||
function statusIcon(code) { return STATUS_UI[STATUS_MAP[code]]?.icon ?? Clock; }
|
function statusIcon(code) { return STATUS_UI[STATUS_MAP[code]]?.icon ?? Clock; }
|
||||||
|
|
||||||
function formatCurrency(v, sign = 'ل.س') {
|
function formatCurrency(v, sign = '') {
|
||||||
return `${sign} ${Number(v ?? 0).toLocaleString()}`;
|
return `${sign} ${Number(v ?? 0).toLocaleString()}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -487,10 +487,12 @@ function formatDate(date) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function StatusBadge({ code }) {
|
function StatusBadge({ code }) {
|
||||||
|
const { t } = useTranslation();
|
||||||
const Icon = statusIcon(code);
|
const Icon = statusIcon(code);
|
||||||
|
const key = STATUS_MAP[code] || 'pending';
|
||||||
return (
|
return (
|
||||||
<span className={`inline-flex items-center gap-1 px-2 py-1 rounded-lg text-xs font-medium ${statusColor(code)}`}>
|
<span className={`inline-flex items-center gap-1 px-2 py-1 rounded-lg text-xs font-medium ${statusColor(code)}`}>
|
||||||
<Icon className="w-3 h-3" /> {statusLabel(code)}
|
<Icon className="w-3 h-3" /> {t(`bookingStatus.${key}`)}
|
||||||
</span>
|
</span>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -563,10 +565,10 @@ async function reportReservation(reservationId, message) {
|
|||||||
const rid = Number(reservationId);
|
const rid = Number(reservationId);
|
||||||
|
|
||||||
if (!Number.isInteger(rid)) {
|
if (!Number.isInteger(rid)) {
|
||||||
throw new Error('رقم الحجز غير صالح');
|
throw new Error('Invalid reservation ID');
|
||||||
}
|
}
|
||||||
if (!Number.isInteger(reporter)) {
|
if (!Number.isInteger(reporter)) {
|
||||||
throw new Error('تعذر تحديد المستخدم الحالي');
|
throw new Error('Could not identify current user');
|
||||||
}
|
}
|
||||||
|
|
||||||
const token = getAuthToken();
|
const token = getAuthToken();
|
||||||
@ -584,7 +586,7 @@ async function reportReservation(reservationId, message) {
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (!res.ok) {
|
if (!res.ok) {
|
||||||
let errorMessage = 'فشل إرسال البلاغ';
|
let errorMessage = 'Failed to submit report';
|
||||||
try {
|
try {
|
||||||
const data = await res.json();
|
const data = await res.json();
|
||||||
errorMessage = data?.message || data?.title || errorMessage;
|
errorMessage = data?.message || data?.title || errorMessage;
|
||||||
@ -640,6 +642,7 @@ function formatWindowDuration(str) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function CountdownTimer({ deadline }) {
|
function CountdownTimer({ deadline }) {
|
||||||
|
const { t } = useTranslation();
|
||||||
const [remaining, setRemaining] = useState(deadline ? Math.max(0, deadline - Date.now()) : 0);
|
const [remaining, setRemaining] = useState(deadline ? Math.max(0, deadline - Date.now()) : 0);
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!deadline) return;
|
if (!deadline) return;
|
||||||
@ -648,7 +651,7 @@ function CountdownTimer({ deadline }) {
|
|||||||
const id = setInterval(tick, 1000);
|
const id = setInterval(tick, 1000);
|
||||||
return () => clearInterval(id);
|
return () => clearInterval(id);
|
||||||
}, [deadline]);
|
}, [deadline]);
|
||||||
if (remaining <= 0) return <span className="text-red-500 text-sm font-medium">انتهت المهلة</span>;
|
if (remaining <= 0) return <span className="text-red-500 text-sm font-medium">{t('timeExpired')}</span>;
|
||||||
const h = Math.floor(remaining / 3600000);
|
const h = Math.floor(remaining / 3600000);
|
||||||
const m = Math.floor((remaining % 3600000) / 60000);
|
const m = Math.floor((remaining % 3600000) / 60000);
|
||||||
const s = Math.floor((remaining % 60000) / 1000);
|
const s = Math.floor((remaining % 60000) / 1000);
|
||||||
@ -665,6 +668,7 @@ function PaymentDialog({
|
|||||||
onConfirmPay,
|
onConfirmPay,
|
||||||
payingId,
|
payingId,
|
||||||
}) {
|
}) {
|
||||||
|
const { t } = useTranslation();
|
||||||
const [showCashDialog, setShowCashDialog] = useState(false);
|
const [showCashDialog, setShowCashDialog] = useState(false);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@ -674,7 +678,7 @@ function PaymentDialog({
|
|||||||
if (!isOpen || !reservation) return null;
|
if (!isOpen || !reservation) return null;
|
||||||
|
|
||||||
const amount = reservation.depositAmount || reservation.totalPrice || 0;
|
const amount = reservation.depositAmount || reservation.totalPrice || 0;
|
||||||
const currencySign = reservation.currencySign || 'ل.س';
|
const currencySign = reservation.currencySign || t('sypSymbol');
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
@ -720,12 +724,12 @@ function PaymentDialog({
|
|||||||
|
|
||||||
<div className="px-6 py-5 border-b border-gray-100">
|
<div className="px-6 py-5 border-b border-gray-100">
|
||||||
<div className="text-center">
|
<div className="text-center">
|
||||||
<p className="text-sm text-gray-500 mb-1">مبلغ التأمين (الرعبون)</p>
|
<p className="text-sm text-gray-500 mb-1">{t('depositAmount')}</p>
|
||||||
<p className="text-4xl font-bold text-amber-600">
|
<p className="text-4xl font-bold text-amber-600">
|
||||||
{formatCurrency(amount, currencySign)}
|
{formatCurrency(amount, currencySign)}
|
||||||
</p>
|
</p>
|
||||||
<p className="text-xs text-gray-400 mt-2">
|
<p className="text-xs text-gray-400 mt-2">
|
||||||
يتم دفع التأمين إلى المنصة وليس إلى المالك
|
{t('depositPaidToPlatform')}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -734,13 +738,13 @@ function PaymentDialog({
|
|||||||
<div className="flex items-start gap-3">
|
<div className="flex items-start gap-3">
|
||||||
<Info className="w-5 h-5 text-amber-600 shrink-0 mt-0.5" />
|
<Info className="w-5 h-5 text-amber-600 shrink-0 mt-0.5" />
|
||||||
<p className="text-sm text-amber-800 leading-relaxed">
|
<p className="text-sm text-amber-800 leading-relaxed">
|
||||||
الدفع النقدي فقط هو المتاح. سيتم تفعيل الدفع الإلكتروني والتحويل لاحقاً.
|
{t('cashOnlyNotice')}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<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">طريقة الدفع</p>
|
<p className="text-sm font-bold text-gray-700 mb-3">{t('paymentMethod')}</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) => {
|
{PAYMENT_METHODS.map((method) => {
|
||||||
const isSelected = selectedPayment === method.id;
|
const isSelected = selectedPayment === method.id;
|
||||||
@ -767,14 +771,14 @@ function PaymentDialog({
|
|||||||
</div>
|
</div>
|
||||||
<div className="min-w-0 flex-1">
|
<div className="min-w-0 flex-1">
|
||||||
<p className={`text-sm font-bold ${isSelected ? 'text-amber-900' : 'text-gray-800'}`}>
|
<p className={`text-sm font-bold ${isSelected ? 'text-amber-900' : 'text-gray-800'}`}>
|
||||||
{method.label}
|
{t(method.labelKey)}
|
||||||
</p>
|
</p>
|
||||||
<p className="text-xs text-gray-500 mt-0.5 leading-relaxed">
|
<p className="text-xs text-gray-500 mt-0.5 leading-relaxed">
|
||||||
{method.desc}
|
{t(method.descKey)}
|
||||||
</p>
|
</p>
|
||||||
{!method.active && (
|
{!method.active && (
|
||||||
<span className="inline-block mt-1 text-[10px] font-medium text-gray-400 bg-gray-200 px-2 py-0.5 rounded-full">
|
<span className="inline-block mt-1 text-[10px] font-medium text-gray-400 bg-gray-200 px-2 py-0.5 rounded-full">
|
||||||
غير متاح
|
{t('notAvailable')}
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
@ -801,12 +805,12 @@ function PaymentDialog({
|
|||||||
{payingId === reservation.id ? (
|
{payingId === reservation.id ? (
|
||||||
<>
|
<>
|
||||||
<Loader2 className="w-5 h-5 animate-spin" />
|
<Loader2 className="w-5 h-5 animate-spin" />
|
||||||
جاري الدفع...
|
{t('processingPayment')}
|
||||||
</>
|
</>
|
||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
<Banknote className="w-5 h-5" />
|
<Banknote className="w-5 h-5" />
|
||||||
دفع التأمين ({formatCurrency(amount, currencySign)})
|
{t('payDeposit', { amount: formatCurrency(amount, currencySign) })}
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</motion.button>
|
</motion.button>
|
||||||
@ -819,7 +823,7 @@ function PaymentDialog({
|
|||||||
className="w-full bg-white border-2 border-amber-200 hover:border-amber-400 text-amber-700 font-bold py-3 px-6 rounded-2xl text-base transition-all flex items-center justify-center gap-2"
|
className="w-full bg-white border-2 border-amber-200 hover:border-amber-400 text-amber-700 font-bold py-3 px-6 rounded-2xl text-base transition-all flex items-center justify-center gap-2"
|
||||||
>
|
>
|
||||||
<Building className="w-5 h-5" />
|
<Building className="w-5 h-5" />
|
||||||
سأدفع نقداً
|
{t('payCash')}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -827,9 +831,9 @@ function PaymentDialog({
|
|||||||
<div className="flex items-start gap-3">
|
<div className="flex items-start gap-3">
|
||||||
<Landmark className="w-5 h-5 text-amber-600 shrink-0 mt-0.5" />
|
<Landmark className="w-5 h-5 text-amber-600 shrink-0 mt-0.5" />
|
||||||
<div>
|
<div>
|
||||||
<p className="text-sm font-bold text-gray-800 mb-1">موقع الدفع النقدي</p>
|
<p className="text-sm font-bold text-gray-800 mb-1">{t('cashPaymentLocation')}</p>
|
||||||
<p className="text-sm text-gray-600 leading-relaxed">
|
<p className="text-sm text-gray-600 leading-relaxed">
|
||||||
مكتب المنصة: أبو رمانة، شارع المالكي، دمشق
|
{t('platformOfficeAddress')}
|
||||||
</p>
|
</p>
|
||||||
<p className="text-sm text-gray-600 flex items-center gap-1 mt-1">
|
<p className="text-sm text-gray-600 flex items-center gap-1 mt-1">
|
||||||
<Phone className="w-3.5 h-3.5" />
|
<Phone className="w-3.5 h-3.5" />
|
||||||
@ -845,7 +849,7 @@ function PaymentDialog({
|
|||||||
onClick={onClose}
|
onClick={onClose}
|
||||||
className="px-5 py-2.5 rounded-xl border border-gray-300 text-gray-700 hover:bg-gray-100 transition-colors font-medium"
|
className="px-5 py-2.5 rounded-xl border border-gray-300 text-gray-700 hover:bg-gray-100 transition-colors font-medium"
|
||||||
>
|
>
|
||||||
إغلاق
|
{t('close')}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</motion.div>
|
</motion.div>
|
||||||
@ -861,15 +865,13 @@ function PaymentDialog({
|
|||||||
<div className="w-16 h-16 bg-amber-100 rounded-full flex items-center justify-center mx-auto mb-5">
|
<div className="w-16 h-16 bg-amber-100 rounded-full flex items-center justify-center mx-auto mb-5">
|
||||||
<Clock className="w-8 h-8 text-amber-600" />
|
<Clock className="w-8 h-8 text-amber-600" />
|
||||||
</div>
|
</div>
|
||||||
<h3 className="text-xl font-bold text-gray-900 mb-3">الدفع النقدي قيد الانتظار</h3>
|
<h3 className="text-xl font-bold text-gray-900 mb-3">{t('cashPaymentPending')}</h3>
|
||||||
<p className="text-gray-600 leading-relaxed mb-6">
|
<p className="text-gray-600 leading-relaxed mb-6">
|
||||||
اذهب إلى موقع المنصة وادفع العربون.
|
{t('cashPaymentInstructions')}
|
||||||
<br />
|
|
||||||
ستقوم المنصة بتأكيد الدفع بعد استلام المبلغ.
|
|
||||||
</p>
|
</p>
|
||||||
<div className="bg-gray-50 rounded-2xl p-4 mb-6 text-right">
|
<div className="bg-gray-50 rounded-2xl p-4 mb-6 text-right">
|
||||||
<p className="text-sm font-bold text-gray-800 mb-1">موقع المنصة</p>
|
<p className="text-sm font-bold text-gray-800 mb-1">{t('platformLocation')}</p>
|
||||||
<p className="text-sm text-gray-600">مكتب المنصة: أبو رمانة، شارع المالكي، دمشق</p>
|
<p className="text-sm text-gray-600">{t('platformOfficeAddress')}</p>
|
||||||
<p className="text-sm text-gray-600 flex items-center gap-1 mt-1" dir="ltr">
|
<p className="text-sm text-gray-600 flex items-center gap-1 mt-1" dir="ltr">
|
||||||
<Phone className="w-3.5 h-3.5" />
|
<Phone className="w-3.5 h-3.5" />
|
||||||
+963567823411
|
+963567823411
|
||||||
@ -881,13 +883,13 @@ function PaymentDialog({
|
|||||||
onClick={() => setShowCashDialog(false)}
|
onClick={() => setShowCashDialog(false)}
|
||||||
className="w-full px-5 py-3 rounded-xl border border-gray-300 text-gray-700 hover:bg-gray-100 transition-colors font-medium"
|
className="w-full px-5 py-3 rounded-xl border border-gray-300 text-gray-700 hover:bg-gray-100 transition-colors font-medium"
|
||||||
>
|
>
|
||||||
إغلاق
|
{t('close')}
|
||||||
</button>
|
</button>
|
||||||
<Link
|
<Link
|
||||||
href="/reservations"
|
href="/reservations"
|
||||||
className="w-full px-5 py-3 rounded-xl bg-amber-500 text-white font-semibold text-center hover:bg-amber-600 transition-colors"
|
className="w-full px-5 py-3 rounded-xl bg-amber-500 text-white font-semibold text-center hover:bg-amber-600 transition-colors"
|
||||||
>
|
>
|
||||||
حجوزاتي
|
{t('myBookings')}
|
||||||
</Link>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
</motion.div>
|
</motion.div>
|
||||||
@ -898,6 +900,7 @@ function PaymentDialog({
|
|||||||
}
|
}
|
||||||
|
|
||||||
function ReportDialog({ isOpen, reservation, onClose, onSubmit, submitting }) {
|
function ReportDialog({ isOpen, reservation, onClose, onSubmit, submitting }) {
|
||||||
|
const { t } = useTranslation();
|
||||||
const [message, setMessage] = useState('');
|
const [message, setMessage] = useState('');
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@ -924,8 +927,8 @@ function ReportDialog({ isOpen, reservation, onClose, onSubmit, submitting }) {
|
|||||||
<div className="bg-gradient-to-r from-red-500 to-red-600 p-6 text-white">
|
<div className="bg-gradient-to-r from-red-500 to-red-600 p-6 text-white">
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<div>
|
<div>
|
||||||
<h2 className="text-xl font-bold">الإبلاغ عن الحجز</h2>
|
<h2 className="text-xl font-bold">{t('reportReservation')}</h2>
|
||||||
<p className="text-red-100 text-sm mt-1">رقم الحجز: #{reservation.id}</p>
|
<p className="text-red-100 text-sm mt-1">{t('reservationNumber', { id: reservation.id })}</p>
|
||||||
</div>
|
</div>
|
||||||
<button onClick={onClose} className="rounded-full p-1 hover:bg-white/20">
|
<button onClick={onClose} className="rounded-full p-1 hover:bg-white/20">
|
||||||
<XCircle className="h-6 w-6" />
|
<XCircle className="h-6 w-6" />
|
||||||
@ -935,13 +938,13 @@ function ReportDialog({ isOpen, reservation, onClose, onSubmit, submitting }) {
|
|||||||
|
|
||||||
<div className="p-6">
|
<div className="p-6">
|
||||||
<p className="text-gray-700 mb-4 leading-7">
|
<p className="text-gray-700 mb-4 leading-7">
|
||||||
اخبر فريق الدعم بما حدث التفاصيل الواضحة تساعدنا على مراجعة هذا الحجز بشكل اسرع
|
{t('reportDescription')}
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<textarea
|
<textarea
|
||||||
value={message}
|
value={message}
|
||||||
onChange={(e) => setMessage(e.target.value)}
|
onChange={(e) => setMessage(e.target.value)}
|
||||||
placeholder="اكتب تفاصيل البلاغ هنا..."
|
placeholder={t('reportPlaceholder')}
|
||||||
rows={5}
|
rows={5}
|
||||||
className="w-full resize-none rounded-xl border border-gray-300 p-3 text-sm focus:outline-none focus:ring-2 focus:ring-red-500"
|
className="w-full resize-none rounded-xl border border-gray-300 p-3 text-sm focus:outline-none focus:ring-2 focus:ring-red-500"
|
||||||
/>
|
/>
|
||||||
@ -955,14 +958,14 @@ function ReportDialog({ isOpen, reservation, onClose, onSubmit, submitting }) {
|
|||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
{submitting ? <Loader2 className="h-4 w-4 animate-spin" /> : <Flag className="h-4 w-4" />}
|
{submitting ? <Loader2 className="h-4 w-4 animate-spin" /> : <Flag className="h-4 w-4" />}
|
||||||
{submitting ? 'جاري الإرسال...' : 'إرسال البلاغ'}
|
{submitting ? t('sending') : t('sendReport')}
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
onClick={onClose}
|
onClick={onClose}
|
||||||
disabled={submitting}
|
disabled={submitting}
|
||||||
className="rounded-xl bg-gray-200 px-4 py-2.5 text-sm font-medium text-gray-700 hover:bg-gray-300 transition-colors disabled:cursor-not-allowed"
|
className="rounded-xl bg-gray-200 px-4 py-2.5 text-sm font-medium text-gray-700 hover:bg-gray-300 transition-colors disabled:cursor-not-allowed"
|
||||||
>
|
>
|
||||||
إلغاء
|
{t('cancel')}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -972,6 +975,7 @@ function ReportDialog({ isOpen, reservation, onClose, onSubmit, submitting }) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function ReservationCard({ r, onViewDetails, onPay, onReport, payingId, reportingId }) {
|
function ReservationCard({ r, onViewDetails, onPay, onReport, payingId, reportingId }) {
|
||||||
|
const { t } = useTranslation();
|
||||||
const p = r._prop;
|
const p = r._prop;
|
||||||
const imgs = propImages(p, r);
|
const imgs = propImages(p, r);
|
||||||
const img = imgs.length > 0 ? `${API_BASE}${imgs[0]}` : null;
|
const img = imgs.length > 0 ? `${API_BASE}${imgs[0]}` : null;
|
||||||
@ -1004,54 +1008,54 @@ function ReservationCard({ r, onViewDetails, onPay, onReport, payingId, reportin
|
|||||||
</div>
|
</div>
|
||||||
<div className="text-left">
|
<div className="text-left">
|
||||||
<div className="text-lg font-bold text-amber-600">{r.totalPrice?.toLocaleString() ?? '—'}</div>
|
<div className="text-lg font-bold text-amber-600">{r.totalPrice?.toLocaleString() ?? '—'}</div>
|
||||||
<div className="text-xs text-gray-500">السعر الإجمالي</div>
|
<div className="text-xs text-gray-500">{t('totalPrice')}</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{(beds||baths) && <div className="flex gap-3 mb-3 text-sm text-gray-600">{beds>0&&<span>{beds} غرف</span>}{baths>0&&<span>{baths} حمامات</span>}</div>}
|
{(beds||baths) && <div className="flex gap-3 mb-3 text-sm text-gray-600">{beds>0&&<span>{beds} {t('rooms')}</span>}{baths>0&&<span>{baths} {t('bathrooms')}</span>}</div>}
|
||||||
<div className="grid grid-cols-2 gap-3 mb-4 text-center">
|
<div className="grid grid-cols-2 gap-3 mb-4 text-center">
|
||||||
<div className="bg-gray-50 p-2 rounded-lg">
|
<div className="bg-gray-50 p-2 rounded-lg">
|
||||||
<Calendar className="w-4 h-4 text-amber-500 mx-auto mb-1"/><div className="text-xs text-gray-500">من</div>
|
<Calendar className="w-4 h-4 text-amber-500 mx-auto mb-1"/><div className="text-xs text-gray-500">{t('from')}</div>
|
||||||
<div className="text-sm font-medium">{new Date(r.startDate).toLocaleDateString('ar')}</div>
|
<div className="text-sm font-medium">{new Date(r.startDate).toLocaleDateString('ar')}</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="bg-gray-50 p-2 rounded-lg">
|
<div className="bg-gray-50 p-2 rounded-lg">
|
||||||
<Calendar className="w-4 h-4 text-amber-500 mx-auto mb-1"/><div className="text-xs text-gray-500">إلى</div>
|
<Calendar className="w-4 h-4 text-amber-500 mx-auto mb-1"/><div className="text-xs text-gray-500">{t('to')}</div>
|
||||||
<div className="text-sm font-medium">{new Date(r.endDate).toLocaleDateString('ar')}</div>
|
<div className="text-sm font-medium">{new Date(r.endDate).toLocaleDateString('ar')}</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{isOwnerConfirmed && hasTimeWindow && <div className="bg-blue-50 p-3 rounded-xl mb-3">
|
{isOwnerConfirmed && hasTimeWindow && <div className="bg-blue-50 p-3 rounded-xl mb-3">
|
||||||
<div className="flex items-center justify-between mb-1">
|
<div className="flex items-center justify-between mb-1">
|
||||||
<span className="text-sm text-blue-800 font-medium flex items-center gap-1"><Timer className="w-4 h-4"/> متبقي للدفع:</span>
|
<span className="text-sm text-blue-800 font-medium flex items-center gap-1"><Timer className="w-4 h-4"/> {t('remainingForPayment')}</span>
|
||||||
<CountdownTimer deadline={deadline} />
|
<CountdownTimer deadline={deadline} />
|
||||||
</div>
|
</div>
|
||||||
<div className="text-xs text-blue-600">مدة الدفع: {formatWindowDuration(p.allowedPaymentPeriod)}</div>
|
<div className="text-xs text-blue-600">{t('paymentDuration', { duration: formatWindowDuration(p.allowedPaymentPeriod) })}</div>
|
||||||
</div>}
|
</div>}
|
||||||
<div className="flex gap-3 pt-3 border-t border-gray-100">
|
<div className="flex gap-3 pt-3 border-t border-gray-100">
|
||||||
<button onClick={() => onViewDetails(r)}
|
<button onClick={() => onViewDetails(r)}
|
||||||
className="flex-1 bg-gray-100 text-gray-700 py-2 rounded-xl text-sm font-medium hover:bg-gray-200 transition-colors flex items-center justify-center gap-2">
|
className="flex-1 bg-gray-100 text-gray-700 py-2 rounded-xl text-sm font-medium hover:bg-gray-200 transition-colors flex items-center justify-center gap-2">
|
||||||
<Eye className="w-4 h-4"/> التفاصيل
|
<Eye className="w-4 h-4"/> {t('details')}
|
||||||
</button>
|
</button>
|
||||||
{isOwnerConfirmed && !isExpired && <button onClick={() => onPay(r)} disabled={isPaying}
|
{isOwnerConfirmed && !isExpired && <button onClick={() => onPay(r)} disabled={isPaying}
|
||||||
className={`flex-1 py-2 rounded-xl text-sm font-medium transition-colors flex items-center justify-center gap-2 ${isPaying ? 'bg-gray-300 text-gray-500 cursor-not-allowed' : 'bg-amber-500 text-white hover:bg-amber-600'}`}>
|
className={`flex-1 py-2 rounded-xl text-sm font-medium transition-colors flex items-center justify-center gap-2 ${isPaying ? 'bg-gray-300 text-gray-500 cursor-not-allowed' : 'bg-amber-500 text-white hover:bg-amber-600'}`}>
|
||||||
{isPaying ? <Loader2 className="w-4 h-4 animate-spin"/> : <CreditCard className="w-4 h-4"/>} {isPaying ? 'جاري الدفع...' : 'ادفع الآن'}
|
{isPaying ? <Loader2 className="w-4 h-4 animate-spin"/> : <CreditCard className="w-4 h-4"/>} {isPaying ? t('processingPayment') : t('payNow')}
|
||||||
</button>}
|
</button>}
|
||||||
</div>
|
</div>
|
||||||
<button onClick={() => onReport(r)} disabled={isReporting}
|
<button onClick={() => onReport(r)} disabled={isReporting}
|
||||||
className={`w-full mt-3 py-2 rounded-xl text-sm font-medium transition-colors flex items-center justify-center gap-2 ${isReporting ? 'bg-gray-300 text-gray-500 cursor-not-allowed' : 'bg-red-50 text-red-700 hover:bg-red-100'}`}>
|
className={`w-full mt-3 py-2 rounded-xl text-sm font-medium transition-colors flex items-center justify-center gap-2 ${isReporting ? 'bg-gray-300 text-gray-500 cursor-not-allowed' : 'bg-red-50 text-red-700 hover:bg-red-100'}`}>
|
||||||
{isReporting ? <Loader2 className="w-4 h-4 animate-spin"/> : <Flag className="w-4 h-4"/>} {isReporting ? 'جاري الإبلاغ...' : 'إبلاغ'}
|
{isReporting ? <Loader2 className="w-4 h-4 animate-spin"/> : <Flag className="w-4 h-4"/>} {isReporting ? t('reportingAction') : t('report')}
|
||||||
</button>
|
</button>
|
||||||
{canRate && !showRating && <button onClick={() => setShowRating(true)}
|
{canRate && !showRating && <button onClick={() => setShowRating(true)}
|
||||||
className="w-full mt-3 bg-amber-50 text-amber-700 py-2 rounded-xl text-sm font-medium hover:bg-amber-100 transition-colors flex items-center justify-center gap-2">
|
className="w-full mt-3 bg-amber-50 text-amber-700 py-2 rounded-xl text-sm font-medium hover:bg-amber-100 transition-colors flex items-center justify-center gap-2">
|
||||||
<Star className="w-4 h-4"/> قيّم هذا العقار
|
<Star className="w-4 h-4"/> {t('rateThisProperty')}
|
||||||
</button>}
|
</button>}
|
||||||
{canRate && showRating && <div className="mt-3 bg-amber-50 p-3 rounded-xl">
|
{canRate && showRating && <div className="mt-3 bg-amber-50 p-3 rounded-xl">
|
||||||
<div className="space-y-2 mb-3">
|
<div className="space-y-2 mb-3">
|
||||||
{[
|
{[
|
||||||
{ key: 'clean', label: 'النظافة' },
|
{ key: 'clean', label: 'ratingCategory.clean' },
|
||||||
{ key: 'services', label: 'الخدمات' },
|
{ key: 'services', label: 'ratingCategory.services' },
|
||||||
{ key: 'ownerBehavior', label: 'تعامل المالك' },
|
{ key: 'ownerBehavior', label: 'ratingCategory.ownerBehavior' },
|
||||||
{ key: 'experience', label: 'التجربة العامة' },
|
{ key: 'experience', label: 'ratingCategory.experience' },
|
||||||
].map(cat => <div key={cat.key} className="flex items-center justify-between">
|
].map(cat => <div key={cat.key} className="flex items-center justify-between">
|
||||||
<span className="text-sm text-gray-700">{cat.label}</span>
|
<span className="text-sm text-gray-700">{t(cat.label)}</span>
|
||||||
<div className="flex gap-0.5">
|
<div className="flex gap-0.5">
|
||||||
{[1,2,3,4,5].map(n => (
|
{[1,2,3,4,5].map(n => (
|
||||||
<button key={n} onClick={() => setRatings(p => ({...p, [cat.key]: n}))}
|
<button key={n} onClick={() => setRatings(p => ({...p, [cat.key]: n}))}
|
||||||
@ -1063,26 +1067,26 @@ function ReservationCard({ r, onViewDetails, onPay, onReport, payingId, reportin
|
|||||||
</div>)}
|
</div>)}
|
||||||
</div>
|
</div>
|
||||||
<textarea value={ratingComment} onChange={e => setRatingComment(e.target.value)}
|
<textarea value={ratingComment} onChange={e => setRatingComment(e.target.value)}
|
||||||
placeholder="أكتب تعليقك (اختياري)"
|
placeholder={t('writeCommentOptional')}
|
||||||
className="w-full p-2 text-sm border border-amber-200 rounded-lg resize-none focus:outline-none focus:ring-2 focus:ring-amber-500 mb-2" rows={2} />
|
className="w-full p-2 text-sm border border-amber-200 rounded-lg resize-none focus:outline-none focus:ring-2 focus:ring-amber-500 mb-2" rows={2} />
|
||||||
<div className="flex gap-2">
|
<div className="flex gap-2">
|
||||||
<button onClick={async () => {
|
<button onClick={async () => {
|
||||||
if (!ratings.clean || !ratings.services || !ratings.ownerBehavior || !ratings.experience) return toast.error('قيّم جميع الفئات');
|
if (!ratings.clean || !ratings.services || !ratings.ownerBehavior || !ratings.experience) return toast.error(t('rateAllCategories'));
|
||||||
setSubmittingRating(true);
|
setSubmittingRating(true);
|
||||||
try {
|
try {
|
||||||
await addPropertyRating({ reservationId: r.id, cleanRating: ratings.clean, servicesRating: ratings.services, ownerBehaviorRating: ratings.ownerBehavior, experienceRating: ratings.experience, comment: ratingComment || null });
|
await addPropertyRating({ reservationId: r.id, cleanRating: ratings.clean, servicesRating: ratings.services, ownerBehaviorRating: ratings.ownerBehavior, experienceRating: ratings.experience, comment: ratingComment || null });
|
||||||
toast.success('تم إرسال التقييم');
|
toast.success(t('ratingSubmittedToast'));
|
||||||
setShowRating(false);
|
setShowRating(false);
|
||||||
setRatings({ clean: 0, services: 0, ownerBehavior: 0, experience: 0 });
|
setRatings({ clean: 0, services: 0, ownerBehavior: 0, experience: 0 });
|
||||||
setRatingComment('');
|
setRatingComment('');
|
||||||
} catch (e) { toast.error(e?.message || 'فشل إرسال التقييم'); }
|
} catch (e) { toast.error(e?.message || t('ratingFailed')); }
|
||||||
finally { setSubmittingRating(false); }
|
finally { setSubmittingRating(false); }
|
||||||
}} disabled={submittingRating}
|
}} disabled={submittingRating}
|
||||||
className="flex-1 bg-amber-500 text-white py-1.5 rounded-lg text-sm font-medium hover:bg-amber-600 transition-colors disabled:bg-gray-300">
|
className="flex-1 bg-amber-500 text-white py-1.5 rounded-lg text-sm font-medium hover:bg-amber-600 transition-colors disabled:bg-gray-300">
|
||||||
{submittingRating ? 'جاري الإرسال...' : 'إرسال التقييم'}
|
{submittingRating ? t('sending') : t('submitRating')}
|
||||||
</button>
|
</button>
|
||||||
<button onClick={() => { setShowRating(false); setRatings({ clean: 0, services: 0, ownerBehavior: 0, experience: 0 }); setRatingComment(''); }}
|
<button onClick={() => { setShowRating(false); setRatings({ clean: 0, services: 0, ownerBehavior: 0, experience: 0 }); setRatingComment(''); }}
|
||||||
className="px-4 py-1.5 bg-gray-200 text-gray-700 rounded-lg text-sm hover:bg-gray-300 transition-colors">إلغاء</button>
|
className="px-4 py-1.5 bg-gray-200 text-gray-700 rounded-lg text-sm hover:bg-gray-300 transition-colors">{t('cancel')}</button>
|
||||||
</div>
|
</div>
|
||||||
</div>}
|
</div>}
|
||||||
</div>
|
</div>
|
||||||
@ -1091,6 +1095,7 @@ function ReservationCard({ r, onViewDetails, onPay, onReport, payingId, reportin
|
|||||||
}
|
}
|
||||||
|
|
||||||
function DetailsModal({ r, isOpen, onClose, onPay, onReport, payingId, reportingId }) {
|
function DetailsModal({ r, isOpen, onClose, onPay, onReport, payingId, reportingId }) {
|
||||||
|
const { t } = useTranslation();
|
||||||
if (!isOpen || !r) return null;
|
if (!isOpen || !r) return null;
|
||||||
const p = r._prop;
|
const p = r._prop;
|
||||||
const isOwnerConfirmed = STATUS_MAP[r.status] === 'ownerConfirmed';
|
const isOwnerConfirmed = STATUS_MAP[r.status] === 'ownerConfirmed';
|
||||||
@ -1109,47 +1114,47 @@ function DetailsModal({ r, isOpen, onClose, onPay, onReport, payingId, reporting
|
|||||||
className="bg-white rounded-2xl w-full max-w-2xl max-h-[90vh] overflow-y-auto shadow-2xl" onClick={e=>e.stopPropagation()}>
|
className="bg-white rounded-2xl w-full max-w-2xl max-h-[90vh] overflow-y-auto shadow-2xl" onClick={e=>e.stopPropagation()}>
|
||||||
<div className="sticky top-0 bg-gradient-to-r from-amber-500 to-amber-600 p-6 text-white">
|
<div className="sticky top-0 bg-gradient-to-r from-amber-500 to-amber-600 p-6 text-white">
|
||||||
<div className="flex justify-between items-center">
|
<div className="flex justify-between items-center">
|
||||||
<h2 className="text-xl font-bold">تفاصيل الحجز</h2>
|
<h2 className="text-xl font-bold">{t('bookingDetails')}</h2>
|
||||||
<button onClick={onClose} className="p-1 hover:bg-white/20 rounded-full"><XCircle className="w-6 h-6"/></button>
|
<button onClick={onClose} className="p-1 hover:bg-white/20 rounded-full"><XCircle className="w-6 h-6"/></button>
|
||||||
</div>
|
</div>
|
||||||
<p className="text-amber-100 text-sm mt-1">رقم الحجز: #{r.id}</p>
|
<p className="text-amber-100 text-sm mt-1">{t('reservationNumber', { id: r.id })}</p>
|
||||||
</div>
|
</div>
|
||||||
<div className="p-6 space-y-6">
|
<div className="p-6 space-y-6">
|
||||||
{p && <div className="bg-gray-50 p-4 rounded-xl">
|
{p && <div className="bg-gray-50 p-4 rounded-xl">
|
||||||
<h3 className="font-bold text-gray-900 mb-3 flex items-center gap-2"><Home className="w-5 h-5 text-amber-500"/> معلومات العقار</h3>
|
<h3 className="font-bold text-gray-900 mb-3 flex items-center gap-2"><Home className="w-5 h-5 text-amber-500"/> {t('propertyInfo')}</h3>
|
||||||
<p><span className="text-gray-500">العنوان:</span> {propAddr(p, r)||'—'}</p>
|
<p><span className="text-gray-500">{t('addressLabel')}</span> {propAddr(p, r)||'—'}</p>
|
||||||
{(propBeds(p, r)||propBaths(p, r)) && <div className="flex gap-3 mt-2">
|
{(propBeds(p, r)||propBaths(p, r)) && <div className="flex gap-3 mt-2">
|
||||||
{propBeds(p, r)>0&&<span className="text-sm bg-white px-2 py-1 rounded-lg">{propBeds(p, r)} غرف</span>}
|
{propBeds(p, r)>0&&<span className="text-sm bg-white px-2 py-1 rounded-lg">{propBeds(p, r)} {t('rooms')}</span>}
|
||||||
{propBaths(p, r)>0&&<span className="text-sm bg-white px-2 py-1 rounded-lg">{propBaths(p, r)} حمامات</span>}
|
{propBaths(p, r)>0&&<span className="text-sm bg-white px-2 py-1 rounded-lg">{propBaths(p, r)} {t('bathrooms')}</span>}
|
||||||
</div>}
|
</div>}
|
||||||
</div>}
|
</div>}
|
||||||
<div className="bg-gray-50 p-4 rounded-xl">
|
<div className="bg-gray-50 p-4 rounded-xl">
|
||||||
<h3 className="font-bold text-gray-900 mb-3 flex items-center gap-2"><Calendar className="w-5 h-5 text-amber-500"/> تفاصيل الحجز</h3>
|
<h3 className="font-bold text-gray-900 mb-3 flex items-center gap-2"><Calendar className="w-5 h-5 text-amber-500"/> {t('bookingDetails')}</h3>
|
||||||
<div className="grid grid-cols-2 gap-4">
|
<div className="grid grid-cols-2 gap-4">
|
||||||
<div><p className="text-gray-500">تاريخ البداية</p><p className="font-medium">{new Date(r.startDate).toLocaleDateString('ar')}</p></div>
|
<div><p className="text-gray-500">{t('startDateLabel')}</p><p className="font-medium">{new Date(r.startDate).toLocaleDateString('ar')}</p></div>
|
||||||
<div><p className="text-gray-500">تاريخ النهاية</p><p className="font-medium">{new Date(r.endDate).toLocaleDateString('ar')}</p></div>
|
<div><p className="text-gray-500">{t('endDateLabel')}</p><p className="font-medium">{new Date(r.endDate).toLocaleDateString('ar')}</p></div>
|
||||||
<div><p className="text-gray-500">الحالة</p><StatusBadge code={r.status}/></div>
|
<div><p className="text-gray-500">{t('statusLabel')}</p><StatusBadge code={r.status}/></div>
|
||||||
<div><p className="text-gray-500">تاريخ الإنشاء</p><p className="font-medium">{new Date(r.createdAt).toLocaleDateString('ar')}</p></div>
|
<div><p className="text-gray-500">{t('createdAtLabel')}</p><p className="font-medium">{new Date(r.createdAt).toLocaleDateString('ar')}</p></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="bg-amber-50 p-4 rounded-xl">
|
<div className="bg-amber-50 p-4 rounded-xl">
|
||||||
<h3 className="font-bold text-amber-700 mb-3 flex items-center gap-2"><DollarSign className="w-5 h-5"/> المعلومات المالية</h3>
|
<h3 className="font-bold text-amber-700 mb-3 flex items-center gap-2"><DollarSign className="w-5 h-5"/> {t('financialInfo')}</h3>
|
||||||
<div className="flex justify-between font-bold"><span className="text-gray-900">الإجمالي</span><span className="text-amber-600 text-lg">{r.totalPrice?.toLocaleString()??'—'}</span></div>
|
<div className="flex justify-between font-bold"><span className="text-gray-900">{t('total')}</span><span className="text-amber-600 text-lg">{r.totalPrice?.toLocaleString()??'—'}</span></div>
|
||||||
</div>
|
</div>
|
||||||
{isOwnerConfirmed && hasTimeWindow && <div className="bg-blue-50 p-4 rounded-xl">
|
{isOwnerConfirmed && hasTimeWindow && <div className="bg-blue-50 p-4 rounded-xl">
|
||||||
<div className="flex items-center justify-between mb-2">
|
<div className="flex items-center justify-between mb-2">
|
||||||
<span className="text-blue-800 font-medium flex items-center gap-2"><Timer className="w-5 h-5"/> متبقي للدفع:</span>
|
<span className="text-blue-800 font-medium flex items-center gap-2"><Timer className="w-5 h-5"/> {t('remainingForPayment')}</span>
|
||||||
<CountdownTimer deadline={deadline} />
|
<CountdownTimer deadline={deadline} />
|
||||||
</div>
|
</div>
|
||||||
<div className="text-xs text-blue-600 mb-3">مدة الدفع: {formatWindowDuration(p.allowedPaymentPeriod)}</div>
|
<div className="text-xs text-blue-600 mb-3">{t('paymentDuration', { duration: formatWindowDuration(p.allowedPaymentPeriod) })}</div>
|
||||||
{!isExpired && <button onClick={() => { onPay(r); onClose(); }} disabled={isPaying}
|
{!isExpired && <button onClick={() => { onPay(r); onClose(); }} disabled={isPaying}
|
||||||
className={`w-full py-2 rounded-xl font-medium transition-colors flex items-center justify-center gap-2 ${isPaying ? 'bg-gray-300 text-gray-500 cursor-not-allowed' : 'bg-amber-500 text-white hover:bg-amber-600'}`}>
|
className={`w-full py-2 rounded-xl font-medium transition-colors flex items-center justify-center gap-2 ${isPaying ? 'bg-gray-300 text-gray-500 cursor-not-allowed' : 'bg-amber-500 text-white hover:bg-amber-600'}`}>
|
||||||
{isPaying ? <Loader2 className="w-5 h-5 animate-spin"/> : <CreditCard className="w-5 h-5"/>} {isPaying ? 'جاري الدفع...' : 'ادفع الآن'}
|
{isPaying ? <Loader2 className="w-5 h-5 animate-spin"/> : <CreditCard className="w-5 h-5"/>} {isPaying ? t('processingPayment') : t('payNow')}
|
||||||
</button>}
|
</button>}
|
||||||
</div>}
|
</div>}
|
||||||
<button onClick={() => { onReport(r); onClose(); }} disabled={isReporting}
|
<button onClick={() => { onReport(r); onClose(); }} disabled={isReporting}
|
||||||
className={`w-full py-2 rounded-xl font-medium transition-colors flex items-center justify-center gap-2 ${isReporting ? 'bg-gray-300 text-gray-500 cursor-not-allowed' : 'bg-red-50 text-red-700 hover:bg-red-100'}`}>
|
className={`w-full py-2 rounded-xl font-medium transition-colors flex items-center justify-center gap-2 ${isReporting ? 'bg-gray-300 text-gray-500 cursor-not-allowed' : 'bg-red-50 text-red-700 hover:bg-red-100'}`}>
|
||||||
{isReporting ? <Loader2 className="w-5 h-5 animate-spin"/> : <Flag className="w-5 h-5"/>} {isReporting ? 'جاري الإبلاغ...' : 'إبلاغ'}
|
{isReporting ? <Loader2 className="w-5 h-5 animate-spin"/> : <Flag className="w-5 h-5"/>} {isReporting ? t('reportingAction') : t('report')}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</motion.div>
|
</motion.div>
|
||||||
@ -1158,6 +1163,7 @@ function DetailsModal({ r, isOpen, onClose, onPay, onReport, payingId, reporting
|
|||||||
}
|
}
|
||||||
|
|
||||||
export default function UserReservationsPage() {
|
export default function UserReservationsPage() {
|
||||||
|
const { t } = useTranslation();
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const [reservations, setReservations] = useState([]);
|
const [reservations, setReservations] = useState([]);
|
||||||
const [filtered, setFiltered] = useState([]);
|
const [filtered, setFiltered] = useState([]);
|
||||||
@ -1196,7 +1202,7 @@ export default function UserReservationsPage() {
|
|||||||
setFiltered(enriched);
|
setFiltered(enriched);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error(err);
|
console.error(err);
|
||||||
toast.error('فشل تحميل الحجوزات');
|
toast.error(t('failedToLoadBookings'));
|
||||||
setReservations([]);
|
setReservations([]);
|
||||||
setFiltered([]);
|
setFiltered([]);
|
||||||
}
|
}
|
||||||
@ -1233,11 +1239,11 @@ export default function UserReservationsPage() {
|
|||||||
transactionType: 1,
|
transactionType: 1,
|
||||||
comment: null,
|
comment: null,
|
||||||
});
|
});
|
||||||
toast.success('تم دفع السلفة بنجاح!');
|
toast.success(t('depositPaidSuccess'));
|
||||||
closePaymentDialog();
|
closePaymentDialog();
|
||||||
loadReservations();
|
loadReservations();
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
toast.error(err?.message || 'فشل عملية الدفع');
|
toast.error(err?.message || t('paymentFailed'));
|
||||||
} finally {
|
} finally {
|
||||||
setPayingId(null);
|
setPayingId(null);
|
||||||
}
|
}
|
||||||
@ -1257,10 +1263,10 @@ export default function UserReservationsPage() {
|
|||||||
setReportingId(reportDialog.reservation.id);
|
setReportingId(reportDialog.reservation.id);
|
||||||
try {
|
try {
|
||||||
await reportReservation(reportDialog.reservation.id, message.trim() || null);
|
await reportReservation(reportDialog.reservation.id, message.trim() || null);
|
||||||
toast.success('تم إرسال البلاغ بنجاح');
|
toast.success(t('reportSubmittedSuccess'));
|
||||||
closeReportDialog();
|
closeReportDialog();
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
toast.error(err?.message || 'فشل إرسال البلاغ');
|
toast.error(err?.message || t('reportFailed'));
|
||||||
} finally {
|
} finally {
|
||||||
setReportingId(null);
|
setReportingId(null);
|
||||||
}
|
}
|
||||||
@ -1298,9 +1304,9 @@ export default function UserReservationsPage() {
|
|||||||
/>
|
/>
|
||||||
<div className="container mx-auto px-4">
|
<div className="container mx-auto px-4">
|
||||||
<motion.div initial={{opacity:0,y:-20}} animate={{opacity:1,y:0}} className="mb-8">
|
<motion.div initial={{opacity:0,y:-20}} animate={{opacity:1,y:0}} className="mb-8">
|
||||||
<button onClick={() => router.back()} className="flex items-center gap-2 text-gray-600 hover:text-amber-600 mb-4"><ArrowLeft className="w-5 h-5"/> الرجوع</button>
|
<button onClick={() => router.back()} className="flex items-center gap-2 text-gray-600 hover:text-amber-600 mb-4"><ArrowLeft className="w-5 h-5"/> {t('back')}</button>
|
||||||
<h1 className="text-3xl font-bold text-gray-900 mb-2">حجوزاتي</h1>
|
<h1 className="text-3xl font-bold text-gray-900 mb-2">{t('myBookings')}</h1>
|
||||||
<p className="text-gray-600">لديك {reservations.length} حجز</p>
|
<p className="text-gray-600">{t('youHaveBookings', { count: reservations.length })}</p>
|
||||||
</motion.div>
|
</motion.div>
|
||||||
<div className="grid grid-cols-2 md:grid-cols-4 gap-4 mb-8">
|
<div className="grid grid-cols-2 md:grid-cols-4 gap-4 mb-8">
|
||||||
{Object.entries(counts).map(([s, c]) => (
|
{Object.entries(counts).map(([s, c]) => (
|
||||||
@ -1308,20 +1314,20 @@ export default function UserReservationsPage() {
|
|||||||
className={`bg-white rounded-xl shadow-sm p-4 text-center border cursor-pointer hover:shadow-md transition-all ${filterStatus===s?'border-amber-500 bg-amber-50':'border-gray-200'}`}
|
className={`bg-white rounded-xl shadow-sm p-4 text-center border cursor-pointer hover:shadow-md transition-all ${filterStatus===s?'border-amber-500 bg-amber-50':'border-gray-200'}`}
|
||||||
onClick={() => setFilterStatus(s)}>
|
onClick={() => setFilterStatus(s)}>
|
||||||
<div className="text-2xl font-bold text-amber-600">{c}</div>
|
<div className="text-2xl font-bold text-amber-600">{c}</div>
|
||||||
<div className="text-sm text-gray-600">{s==='all'?'الكل':(STATUS_UI[s]?.label||s)}</div>
|
<div className="text-sm text-gray-600">{s==='all'?t('all'):t(`bookingStatus.${s}`)}</div>
|
||||||
</motion.div>
|
</motion.div>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
<div className="mb-6 relative">
|
<div className="mb-6 relative">
|
||||||
<Search className="absolute left-3 top-1/2 -translate-y-1/2 w-5 h-5 text-gray-400"/>
|
<Search className="absolute left-3 top-1/2 -translate-y-1/2 w-5 h-5 text-gray-400"/>
|
||||||
<input type="text" placeholder="ابحث بعنوان العقار أو رقم الحجز..." value={searchTerm} onChange={e=>setSearchTerm(e.target.value)}
|
<input type="text" placeholder={t('searchPlaceholder')} value={searchTerm} onChange={e=>setSearchTerm(e.target.value)}
|
||||||
className="w-full pl-10 pr-4 py-3 border border-gray-300 rounded-xl focus:outline-none focus:ring-2 focus:ring-amber-500"/>
|
className="w-full pl-10 pr-4 py-3 border border-gray-300 rounded-xl focus:outline-none focus:ring-2 focus:ring-amber-500"/>
|
||||||
</div>
|
</div>
|
||||||
{filtered.length === 0 ? (
|
{filtered.length === 0 ? (
|
||||||
<div className="bg-white rounded-2xl p-12 text-center border-2 border-dashed border-gray-300">
|
<div className="bg-white rounded-2xl p-12 text-center border-2 border-dashed border-gray-300">
|
||||||
<Calendar className="w-12 h-12 text-amber-600 mx-auto mb-4"/>
|
<Calendar className="w-12 h-12 text-amber-600 mx-auto mb-4"/>
|
||||||
<h3 className="text-xl font-bold text-gray-900 mb-2">لا توجد حجوزات</h3>
|
<h3 className="text-xl font-bold text-gray-900 mb-2">{t('noBookings')}</h3>
|
||||||
<p className="text-gray-600">لم تقم بأي حجز حتى الآن</p>
|
<p className="text-gray-600">{t('noReservationsDesc')}</p>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
|
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
|
||||||
|
|||||||
@ -1,257 +1,3 @@
|
|||||||
// 'use client';
|
|
||||||
|
|
||||||
// import { useState } from 'react';
|
|
||||||
// import { useRouter } from 'next/navigation';
|
|
||||||
// import Link from 'next/link';
|
|
||||||
// import { motion } from 'framer-motion';
|
|
||||||
// import {
|
|
||||||
// User,
|
|
||||||
// Shield,
|
|
||||||
// Trash2,
|
|
||||||
// LogOut,
|
|
||||||
// ChevronLeft,
|
|
||||||
// Bell,
|
|
||||||
// Lock,
|
|
||||||
// Eye,
|
|
||||||
// FileText,
|
|
||||||
// HelpCircle,
|
|
||||||
// MessageCircle,
|
|
||||||
// Loader2,
|
|
||||||
// AlertTriangle,
|
|
||||||
// X
|
|
||||||
// } from 'lucide-react';
|
|
||||||
// import toast, { Toaster } from 'react-hot-toast';
|
|
||||||
// import AuthService from '../services/AuthService';
|
|
||||||
// import { changePassword, deleteMyAccount } from '../utils/api';
|
|
||||||
|
|
||||||
// export default function SettingsPage() {
|
|
||||||
// const router = useRouter();
|
|
||||||
// const [showDeleteDialog, setShowDeleteDialog] = useState(false);
|
|
||||||
// const [deletePassword, setDeletePassword] = useState('');
|
|
||||||
// const [isDeleting, setIsDeleting] = useState(false);
|
|
||||||
// const [showDeleteConfirm, setShowDeleteConfirm] = useState(false);
|
|
||||||
|
|
||||||
// const handleSignOut = () => {
|
|
||||||
// AuthService.deleteToken();
|
|
||||||
// toast.success('تم تسجيل الخروج بنجاح');
|
|
||||||
// router.push('/');
|
|
||||||
// };
|
|
||||||
|
|
||||||
// const handleDeleteAccount = async () => {
|
|
||||||
// if (!deletePassword) {
|
|
||||||
// toast.error('الرجاء إدخال كلمة المرور');
|
|
||||||
// return;
|
|
||||||
// }
|
|
||||||
// setIsDeleting(true);
|
|
||||||
// try {
|
|
||||||
// await deleteMyAccount(deletePassword);
|
|
||||||
// AuthService.deleteToken();
|
|
||||||
// toast.success('تم حذف الحساب بنجاح');
|
|
||||||
// router.push('/');
|
|
||||||
// } catch (err) {
|
|
||||||
// toast.error(err.message || 'فشل حذف الحساب');
|
|
||||||
// } finally {
|
|
||||||
// setIsDeleting(false);
|
|
||||||
// setShowDeleteDialog(false);
|
|
||||||
// setDeletePassword('');
|
|
||||||
// }
|
|
||||||
// };
|
|
||||||
|
|
||||||
// const sections = [
|
|
||||||
// {
|
|
||||||
// title: 'الحساب',
|
|
||||||
// items: [
|
|
||||||
// { icon: User, label: 'الملف الشخصي', href: '/profile', desc: 'عرض وتعديل معلوماتك الشخصية' },
|
|
||||||
// { icon: Lock, label: 'تغيير كلمة المرور', href: '/change-password', desc: 'تحديث كلمة المرور الخاصة بك' },
|
|
||||||
// { icon: Shield, label: 'التحقق من الحساب', href: '/account-verification', desc: 'تأكيد البريد الإلكتروني ورقم الهاتف' },
|
|
||||||
// ]
|
|
||||||
// },
|
|
||||||
// {
|
|
||||||
// title: 'الإشعارات',
|
|
||||||
// items: [
|
|
||||||
// { icon: Bell, label: 'الإشعارات', href: '/notifications', desc: 'إدارة تفضيلات الإشعارات' },
|
|
||||||
// ]
|
|
||||||
// },
|
|
||||||
// {
|
|
||||||
// title: 'الدعم',
|
|
||||||
// items: [
|
|
||||||
// { icon: HelpCircle, label: 'الأسئلة الشائعة', href: '/faq', desc: 'إجابات للأسئلة المتكررة' },
|
|
||||||
// { icon: MessageCircle, label: 'تواصل معنا', href: '/support', desc: 'الحصول على المساعدة والدعم' },
|
|
||||||
// { icon: FileText, label: 'الشروط والأحكام', href: '/terms', desc: 'سياسة الاستخدام والخصوصية' },
|
|
||||||
// { icon: Eye, label: 'سياسة الخصوصية', href: '/privacy', desc: 'كيف نحمي بياناتك' },
|
|
||||||
// ]
|
|
||||||
// },
|
|
||||||
// ];
|
|
||||||
|
|
||||||
// const containerVariants = {
|
|
||||||
// hidden: { opacity: 0 },
|
|
||||||
// visible: {
|
|
||||||
// opacity: 1,
|
|
||||||
// transition: { staggerChildren: 0.08 }
|
|
||||||
// }
|
|
||||||
// };
|
|
||||||
|
|
||||||
// const itemVariants = {
|
|
||||||
// hidden: { opacity: 0, y: 20 },
|
|
||||||
// visible: { opacity: 1, y: 0 }
|
|
||||||
// };
|
|
||||||
|
|
||||||
// return (
|
|
||||||
// <div className="min-h-screen bg-gray-50 py-8" dir="rtl">
|
|
||||||
// <Toaster position="top-center" reverseOrder={false} />
|
|
||||||
|
|
||||||
// <div className="container mx-auto px-4 max-w-2xl">
|
|
||||||
// <motion.div
|
|
||||||
// initial={{ opacity: 0, y: -20 }}
|
|
||||||
// animate={{ opacity: 1, y: 0 }}
|
|
||||||
// className="flex items-center gap-3 mb-8"
|
|
||||||
// >
|
|
||||||
// <Link
|
|
||||||
// href="/profile"
|
|
||||||
// className="p-2 rounded-xl hover:bg-gray-200 transition-colors text-gray-600"
|
|
||||||
// >
|
|
||||||
// <ChevronLeft className="w-5 h-5" />
|
|
||||||
// </Link>
|
|
||||||
// <h1 className="text-2xl font-bold text-gray-900">الإعدادات</h1>
|
|
||||||
// </motion.div>
|
|
||||||
|
|
||||||
// <motion.div
|
|
||||||
// variants={containerVariants}
|
|
||||||
// initial="hidden"
|
|
||||||
// animate="visible"
|
|
||||||
// className="space-y-6"
|
|
||||||
// >
|
|
||||||
// {sections.map((section) => (
|
|
||||||
// <motion.div
|
|
||||||
// key={section.title}
|
|
||||||
// variants={itemVariants}
|
|
||||||
// className="bg-white rounded-2xl shadow-sm overflow-hidden"
|
|
||||||
// >
|
|
||||||
// <div className="px-6 py-4 border-b border-gray-100">
|
|
||||||
// <h2 className="text-lg font-semibold text-gray-800">{section.title}</h2>
|
|
||||||
// </div>
|
|
||||||
// <div className="divide-y divide-gray-50">
|
|
||||||
// {section.items.map((item) => {
|
|
||||||
// const Icon = item.icon;
|
|
||||||
// return (
|
|
||||||
// <Link
|
|
||||||
// key={item.label}
|
|
||||||
// href={item.href}
|
|
||||||
// className="flex items-center gap-4 px-6 py-4 hover:bg-gray-50 transition-colors group"
|
|
||||||
// >
|
|
||||||
// <div className="w-10 h-10 rounded-xl bg-amber-50 flex items-center justify-center flex-shrink-0 group-hover:bg-amber-100 transition-colors">
|
|
||||||
// <Icon className="w-5 h-5 text-amber-600" />
|
|
||||||
// </div>
|
|
||||||
// <div className="flex-1 min-w-0">
|
|
||||||
// <p className="text-sm font-medium text-gray-900">{item.label}</p>
|
|
||||||
// <p className="text-xs text-gray-500 truncate">{item.desc}</p>
|
|
||||||
// </div>
|
|
||||||
// <ChevronLeft className="w-4 h-4 text-gray-400" />
|
|
||||||
// </Link>
|
|
||||||
// );
|
|
||||||
// })}
|
|
||||||
// </div>
|
|
||||||
// </motion.div>
|
|
||||||
// ))}
|
|
||||||
|
|
||||||
// <motion.div variants={itemVariants} className="space-y-4">
|
|
||||||
// <div className="bg-white rounded-2xl shadow-sm overflow-hidden">
|
|
||||||
// <div className="px-6 py-4 border-b border-gray-100">
|
|
||||||
// <h2 className="text-lg font-semibold text-gray-800">الأمان</h2>
|
|
||||||
// </div>
|
|
||||||
// <div className="p-6">
|
|
||||||
// <button
|
|
||||||
// onClick={() => setShowDeleteDialog(true)}
|
|
||||||
// className="w-full flex items-center gap-3 px-4 py-3 rounded-xl border border-red-200 text-red-600 hover:bg-red-50 transition-colors"
|
|
||||||
// >
|
|
||||||
// <Trash2 className="w-5 h-5" />
|
|
||||||
// <span className="text-sm font-medium">حذف الحساب</span>
|
|
||||||
// </button>
|
|
||||||
// <p className="text-xs text-gray-500 mt-2 pr-12">
|
|
||||||
// سيتم حذف جميع بياناتك بشكل دائم ولا يمكن التراجع عن هذا الإجراء
|
|
||||||
// </p>
|
|
||||||
// </div>
|
|
||||||
// </div>
|
|
||||||
|
|
||||||
// <div className="bg-white rounded-2xl shadow-sm overflow-hidden">
|
|
||||||
// <div className="p-6">
|
|
||||||
// <button
|
|
||||||
// onClick={handleSignOut}
|
|
||||||
// className="w-full flex items-center gap-3 px-4 py-3 rounded-xl border border-gray-200 text-gray-700 hover:bg-gray-50 transition-colors"
|
|
||||||
// >
|
|
||||||
// <LogOut className="w-5 h-5" />
|
|
||||||
// <span className="text-sm font-medium">تسجيل الخروج</span>
|
|
||||||
// </button>
|
|
||||||
// </div>
|
|
||||||
// </div>
|
|
||||||
// </motion.div>
|
|
||||||
// </motion.div>
|
|
||||||
// </div>
|
|
||||||
|
|
||||||
// {showDeleteDialog && (
|
|
||||||
// <div className="fixed inset-0 z-50 flex items-center justify-center p-4 bg-black/50">
|
|
||||||
// <motion.div
|
|
||||||
// initial={{ opacity: 0, scale: 0.95 }}
|
|
||||||
// animate={{ opacity: 1, scale: 1 }}
|
|
||||||
// className="bg-white rounded-2xl shadow-xl max-w-md w-full p-6"
|
|
||||||
// >
|
|
||||||
// <div className="flex items-center gap-3 mb-4">
|
|
||||||
// <div className="w-10 h-10 rounded-full bg-red-100 flex items-center justify-center">
|
|
||||||
// <AlertTriangle className="w-5 h-5 text-red-600" />
|
|
||||||
// </div>
|
|
||||||
// <div>
|
|
||||||
// <h3 className="text-lg font-semibold text-gray-900">حذف الحساب</h3>
|
|
||||||
// <p className="text-sm text-gray-500">هذا الإجراء لا يمكن التراجع عنه</p>
|
|
||||||
// </div>
|
|
||||||
// <button
|
|
||||||
// onClick={() => { setShowDeleteDialog(false); setDeletePassword(''); }}
|
|
||||||
// className="mr-auto p-1 hover:bg-gray-100 rounded-lg transition-colors"
|
|
||||||
// >
|
|
||||||
// <X className="w-5 h-5 text-gray-400" />
|
|
||||||
// </button>
|
|
||||||
// </div>
|
|
||||||
|
|
||||||
// <p className="text-sm text-gray-600 mb-4">
|
|
||||||
// أدخل كلمة المرور لتأكيد حذف حسابك نهائياً. سيتم حذف جميع بياناتك وملفاتك بشكل دائم.
|
|
||||||
// </p>
|
|
||||||
|
|
||||||
// <input
|
|
||||||
// type="password"
|
|
||||||
// value={deletePassword}
|
|
||||||
// onChange={(e) => setDeletePassword(e.target.value)}
|
|
||||||
// placeholder="كلمة المرور"
|
|
||||||
// className="w-full px-4 py-3 border border-gray-300 rounded-xl mb-4 focus:ring-2 focus:ring-red-500 focus:border-transparent outline-none"
|
|
||||||
// />
|
|
||||||
|
|
||||||
// <div className="flex gap-3">
|
|
||||||
// <button
|
|
||||||
// onClick={() => { setShowDeleteDialog(false); setDeletePassword(''); }}
|
|
||||||
// className="flex-1 px-4 py-3 rounded-xl border border-gray-200 text-gray-700 hover:bg-gray-50 transition-colors text-sm font-medium"
|
|
||||||
// >
|
|
||||||
// إلغاء
|
|
||||||
// </button>
|
|
||||||
// <button
|
|
||||||
// onClick={handleDeleteAccount}
|
|
||||||
// disabled={isDeleting}
|
|
||||||
// className="flex-1 px-4 py-3 rounded-xl bg-red-600 text-white hover:bg-red-700 transition-colors text-sm font-medium disabled:opacity-50 flex items-center justify-center gap-2"
|
|
||||||
// >
|
|
||||||
// {isDeleting ? <Loader2 className="w-4 h-4 animate-spin" /> : <Trash2 className="w-4 h-4" />}
|
|
||||||
// {isDeleting ? 'جاري الحذف...' : 'تأكيد الحذف'}
|
|
||||||
// </button>
|
|
||||||
// </div>
|
|
||||||
// </motion.div>
|
|
||||||
// </div>
|
|
||||||
// )}
|
|
||||||
// </div>
|
|
||||||
// );
|
|
||||||
// }
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
@ -266,19 +12,21 @@ import {
|
|||||||
ChevronLeft,
|
ChevronLeft,
|
||||||
Bell,
|
Bell,
|
||||||
Lock,
|
Lock,
|
||||||
Eye,
|
|
||||||
FileText,
|
|
||||||
HelpCircle,
|
HelpCircle,
|
||||||
MessageCircle,
|
MessageCircle,
|
||||||
|
FileText,
|
||||||
|
Eye,
|
||||||
Loader2,
|
Loader2,
|
||||||
AlertTriangle,
|
AlertTriangle,
|
||||||
X
|
X
|
||||||
} from 'lucide-react';
|
} from 'lucide-react';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
import toast, { Toaster } from 'react-hot-toast';
|
import toast, { Toaster } from 'react-hot-toast';
|
||||||
import AuthService from '../services/AuthService';
|
import AuthService from '../services/AuthService';
|
||||||
import { changePassword, deleteMyAccount, submitReport } from '../utils/api';
|
import { changePassword, deleteMyAccount, submitReport } from '../utils/api';
|
||||||
|
|
||||||
export default function SettingsPage() {
|
export default function SettingsPage() {
|
||||||
|
const { t, i18n } = useTranslation();
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const [showDeleteDialog, setShowDeleteDialog] = useState(false);
|
const [showDeleteDialog, setShowDeleteDialog] = useState(false);
|
||||||
const [deletePassword, setDeletePassword] = useState('');
|
const [deletePassword, setDeletePassword] = useState('');
|
||||||
@ -292,24 +40,24 @@ export default function SettingsPage() {
|
|||||||
|
|
||||||
const handleSignOut = () => {
|
const handleSignOut = () => {
|
||||||
AuthService.deleteToken();
|
AuthService.deleteToken();
|
||||||
toast.success('تم تسجيل الخروج بنجاح');
|
toast.success(t('logout-success'));
|
||||||
router.push('/');
|
router.push('/');
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleDeleteAccount = async () => {
|
const handleDeleteAccount = async () => {
|
||||||
if (!deletePassword) {
|
if (!deletePassword) {
|
||||||
toast.error('الرجاء إدخال كلمة المرور');
|
toast.error(t('enter-password-prompt'));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
setIsDeleting(true);
|
setIsDeleting(true);
|
||||||
try {
|
try {
|
||||||
await deleteMyAccount(deletePassword);
|
await deleteMyAccount(deletePassword);
|
||||||
AuthService.deleteToken();
|
AuthService.deleteToken();
|
||||||
toast.success('تم حذف الحساب بنجاح');
|
toast.success(t('delete-account-success'));
|
||||||
router.push('/');
|
router.push('/');
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('Delete account error:', err);
|
console.error('Delete account error:', err);
|
||||||
toast.error(err.message || 'فشل حذف الحساب');
|
toast.error(err.message || t('delete-account-failed'));
|
||||||
} finally {
|
} finally {
|
||||||
setIsDeleting(false);
|
setIsDeleting(false);
|
||||||
setShowDeleteDialog(false);
|
setShowDeleteDialog(false);
|
||||||
@ -319,19 +67,19 @@ export default function SettingsPage() {
|
|||||||
|
|
||||||
const handleSendContact = async () => {
|
const handleSendContact = async () => {
|
||||||
if (!contactSubject.trim() || !contactBody.trim()) {
|
if (!contactSubject.trim() || !contactBody.trim()) {
|
||||||
toast.error('الرجاء تعبئة العنوان والرسالة');
|
toast.error(t('fill-subject-and-message'));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
setIsSendingContact(true);
|
setIsSendingContact(true);
|
||||||
try {
|
try {
|
||||||
await submitReport(contactSubject.trim(), contactBody.trim());
|
await submitReport(contactSubject.trim(), contactBody.trim());
|
||||||
toast.success('تم إرسال رسالتك بنجاح');
|
toast.success(t('send-success'));
|
||||||
setShowContactDialog(false);
|
setShowContactDialog(false);
|
||||||
setContactSubject('');
|
setContactSubject('');
|
||||||
setContactBody('');
|
setContactBody('');
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
toast.error(err.message || 'حدث خطأ أثناء الإرسال');
|
toast.error(err.message || t('send-error'));
|
||||||
} finally {
|
} finally {
|
||||||
setIsSendingContact(false);
|
setIsSendingContact(false);
|
||||||
}
|
}
|
||||||
@ -339,26 +87,26 @@ export default function SettingsPage() {
|
|||||||
|
|
||||||
const sections = [
|
const sections = [
|
||||||
{
|
{
|
||||||
title: 'الحساب',
|
title: t('account'),
|
||||||
items: [
|
items: [
|
||||||
{ icon: User, label: 'الملف الشخصي', href: '/profile', desc: 'عرض وتعديل معلوماتك الشخصية' },
|
{ icon: User, label: t('profile'), href: '/profile', desc: t('profileDesc') },
|
||||||
{ icon: Lock, label: 'تغيير كلمة المرور', href: '/change-password', desc: 'تحديث كلمة المرور الخاصة بك' },
|
{ icon: Lock, label: t('change-password'), href: '/change-password', desc: t('change-password-desc') },
|
||||||
{ icon: Shield, label: 'التحقق من الحساب', href: '/account-verification', desc: 'تأكيد البريد الإلكتروني ورقم الهاتف' },
|
{ icon: Shield, label: t('account-verification'), href: '/account-verification', desc: t('account-verification-desc') },
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'الإشعارات',
|
title: t('notifications'),
|
||||||
items: [
|
items: [
|
||||||
{ icon: Bell, label: 'الإشعارات', href: '/notifications', desc: 'إدارة تفضيلات الإشعارات' },
|
{ icon: Bell, label: t('notifications'), href: '/notifications', desc: t('notification-preferences-desc') },
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'الدعم',
|
title: t('support'),
|
||||||
items: [
|
items: [
|
||||||
{ icon: HelpCircle, label: 'الأسئلة الشائعة', href: '/faq', desc: 'إجابات للأسئلة المتكررة' },
|
{ icon: HelpCircle, label: t('faq'), href: '/faq', desc: t('faq-desc') },
|
||||||
{ icon: MessageCircle, label: 'تواصل معنا', desc: 'الحصول على المساعدة والدعم', action: () => setShowContactDialog(true) },
|
{ icon: MessageCircle, label: t('contactUs'), desc: t('contact-us-desc'), action: () => setShowContactDialog(true) },
|
||||||
{ icon: FileText, label: 'الشروط والأحكام', href: '/terms', desc: 'سياسة الاستخدام والخصوصية' },
|
{ icon: FileText, label: t('terms-and-conditions'), href: '/terms', desc: t('terms-desc') },
|
||||||
{ icon: Eye, label: 'سياسة الخصوصية', href: '/privacy', desc: 'كيف نحمي بياناتك' },
|
{ icon: Eye, label: t('privacy'), href: '/privacy', desc: t('privacy-desc') },
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
@ -377,7 +125,7 @@ export default function SettingsPage() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="min-h-screen bg-gray-50 py-8 pb-28" dir="rtl">
|
<div className="min-h-screen bg-gray-50 py-8 pb-28" dir={i18n.language === 'ar' ? 'rtl' : 'ltr'}>
|
||||||
<Toaster position="top-center" reverseOrder={false} />
|
<Toaster position="top-center" reverseOrder={false} />
|
||||||
|
|
||||||
<div className="container mx-auto px-4 max-w-2xl">
|
<div className="container mx-auto px-4 max-w-2xl">
|
||||||
@ -392,7 +140,7 @@ export default function SettingsPage() {
|
|||||||
>
|
>
|
||||||
<ChevronLeft className="w-5 h-5" />
|
<ChevronLeft className="w-5 h-5" />
|
||||||
</Link>
|
</Link>
|
||||||
<h1 className="text-2xl font-bold text-gray-900">الإعدادات</h1>
|
<h1 className="text-2xl font-bold text-gray-900">{t('settings')}</h1>
|
||||||
</motion.div>
|
</motion.div>
|
||||||
|
|
||||||
<motion.div
|
<motion.div
|
||||||
@ -456,7 +204,7 @@ export default function SettingsPage() {
|
|||||||
<motion.div variants={itemVariants} className="space-y-4">
|
<motion.div variants={itemVariants} className="space-y-4">
|
||||||
<div className="bg-white rounded-2xl shadow-sm overflow-hidden">
|
<div className="bg-white rounded-2xl shadow-sm overflow-hidden">
|
||||||
<div className="px-6 py-4 border-b border-gray-100">
|
<div className="px-6 py-4 border-b border-gray-100">
|
||||||
<h2 className="text-lg font-semibold text-gray-800">الأمان</h2>
|
<h2 className="text-lg font-semibold text-gray-800">{t('security')}</h2>
|
||||||
</div>
|
</div>
|
||||||
<div className="p-6">
|
<div className="p-6">
|
||||||
<button
|
<button
|
||||||
@ -464,10 +212,10 @@ export default function SettingsPage() {
|
|||||||
className="w-full flex items-center gap-3 px-4 py-3 rounded-xl border border-red-200 text-red-600 hover:bg-red-50 transition-colors"
|
className="w-full flex items-center gap-3 px-4 py-3 rounded-xl border border-red-200 text-red-600 hover:bg-red-50 transition-colors"
|
||||||
>
|
>
|
||||||
<Trash2 className="w-5 h-5" />
|
<Trash2 className="w-5 h-5" />
|
||||||
<span className="text-sm font-medium">حذف الحساب</span>
|
<span className="text-sm font-medium">{t('deleteAccount')}</span>
|
||||||
</button>
|
</button>
|
||||||
<p className="text-xs text-gray-500 mt-2 pr-12">
|
<p className="text-xs text-gray-500 mt-2 pr-12">
|
||||||
سيتم حذف جميع بياناتك بشكل دائم ولا يمكن التراجع عن هذا الإجراء
|
{t('delete-account-warning')}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -479,7 +227,7 @@ export default function SettingsPage() {
|
|||||||
className="w-full flex items-center gap-3 px-4 py-3 rounded-xl border border-gray-200 text-gray-700 hover:bg-gray-50 transition-colors"
|
className="w-full flex items-center gap-3 px-4 py-3 rounded-xl border border-gray-200 text-gray-700 hover:bg-gray-50 transition-colors"
|
||||||
>
|
>
|
||||||
<LogOut className="w-5 h-5" />
|
<LogOut className="w-5 h-5" />
|
||||||
<span className="text-sm font-medium">تسجيل الخروج</span>
|
<span className="text-sm font-medium">{t('signOut')}</span>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -499,8 +247,8 @@ export default function SettingsPage() {
|
|||||||
<AlertTriangle className="w-5 h-5 text-red-600" />
|
<AlertTriangle className="w-5 h-5 text-red-600" />
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<h3 className="text-lg font-semibold text-gray-900">حذف الحساب</h3>
|
<h3 className="text-lg font-semibold text-gray-900">{t('deleteDialogTitle')}</h3>
|
||||||
<p className="text-sm text-gray-500">هذا الإجراء لا يمكن التراجع عنه</p>
|
<p className="text-sm text-gray-500">{t('deleteDialogWarning')}</p>
|
||||||
</div>
|
</div>
|
||||||
<button
|
<button
|
||||||
onClick={() => { setShowDeleteDialog(false); setDeletePassword(''); }}
|
onClick={() => { setShowDeleteDialog(false); setDeletePassword(''); }}
|
||||||
@ -511,14 +259,14 @@ export default function SettingsPage() {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<p className="text-sm text-gray-600 mb-4">
|
<p className="text-sm text-gray-600 mb-4">
|
||||||
أدخل كلمة المرور لتأكيد حذف حسابك نهائياً. سيتم حذف جميع بياناتك وملفاتك بشكل دائم.
|
{t('delete-account-confirm-desc')}
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<input
|
<input
|
||||||
type="password"
|
type="password"
|
||||||
value={deletePassword}
|
value={deletePassword}
|
||||||
onChange={(e) => setDeletePassword(e.target.value)}
|
onChange={(e) => setDeletePassword(e.target.value)}
|
||||||
placeholder="كلمة المرور"
|
placeholder={t('enter-password-prompt')}
|
||||||
className="w-full px-4 py-3 border border-gray-300 rounded-xl mb-4 focus:ring-2 focus:ring-red-500 focus:border-transparent outline-none"
|
className="w-full px-4 py-3 border border-gray-300 rounded-xl mb-4 focus:ring-2 focus:ring-red-500 focus:border-transparent outline-none"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
@ -527,7 +275,7 @@ export default function SettingsPage() {
|
|||||||
onClick={() => { setShowDeleteDialog(false); setDeletePassword(''); }}
|
onClick={() => { setShowDeleteDialog(false); setDeletePassword(''); }}
|
||||||
className="flex-1 px-4 py-3 rounded-xl border border-gray-200 text-gray-700 hover:bg-gray-50 transition-colors text-sm font-medium"
|
className="flex-1 px-4 py-3 rounded-xl border border-gray-200 text-gray-700 hover:bg-gray-50 transition-colors text-sm font-medium"
|
||||||
>
|
>
|
||||||
إلغاء
|
{t('cancel')}
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
onClick={handleDeleteAccount}
|
onClick={handleDeleteAccount}
|
||||||
@ -535,7 +283,7 @@ export default function SettingsPage() {
|
|||||||
className="flex-1 px-4 py-3 rounded-xl bg-red-600 text-white hover:bg-red-700 transition-colors text-sm font-medium disabled:opacity-50 flex items-center justify-center gap-2"
|
className="flex-1 px-4 py-3 rounded-xl bg-red-600 text-white hover:bg-red-700 transition-colors text-sm font-medium disabled:opacity-50 flex items-center justify-center gap-2"
|
||||||
>
|
>
|
||||||
{isDeleting ? <Loader2 className="w-4 h-4 animate-spin" /> : <Trash2 className="w-4 h-4" />}
|
{isDeleting ? <Loader2 className="w-4 h-4 animate-spin" /> : <Trash2 className="w-4 h-4" />}
|
||||||
{isDeleting ? 'جاري الحذف...' : 'تأكيد الحذف'}
|
{isDeleting ? t('deleting') : t('confirm-delete')}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</motion.div>
|
</motion.div>
|
||||||
@ -554,8 +302,8 @@ export default function SettingsPage() {
|
|||||||
<MessageCircle className="w-5 h-5 text-amber-600" />
|
<MessageCircle className="w-5 h-5 text-amber-600" />
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<h3 className="text-lg font-semibold text-gray-900">تواصل معنا</h3>
|
<h3 className="text-lg font-semibold text-gray-900">{t('contactUs')}</h3>
|
||||||
<p className="text-sm text-gray-500">سنقوم بالرد عليك في أقرب وقت</p>
|
<p className="text-sm text-gray-500">{t('contact-reply-prompt')}</p>
|
||||||
</div>
|
</div>
|
||||||
<button
|
<button
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
@ -573,7 +321,7 @@ export default function SettingsPage() {
|
|||||||
type="text"
|
type="text"
|
||||||
value={contactSubject}
|
value={contactSubject}
|
||||||
onChange={(e) => setContactSubject(e.target.value)}
|
onChange={(e) => setContactSubject(e.target.value)}
|
||||||
placeholder="الموضوع"
|
placeholder={t('subject')}
|
||||||
maxLength={300}
|
maxLength={300}
|
||||||
className="w-full px-4 py-3 border border-gray-300 rounded-xl mb-4 focus:ring-2 focus:ring-amber-500 focus:border-transparent outline-none"
|
className="w-full px-4 py-3 border border-gray-300 rounded-xl mb-4 focus:ring-2 focus:ring-amber-500 focus:border-transparent outline-none"
|
||||||
/>
|
/>
|
||||||
@ -581,7 +329,7 @@ export default function SettingsPage() {
|
|||||||
<textarea
|
<textarea
|
||||||
value={contactBody}
|
value={contactBody}
|
||||||
onChange={(e) => setContactBody(e.target.value)}
|
onChange={(e) => setContactBody(e.target.value)}
|
||||||
placeholder="اكتب رسالتك هنا..."
|
placeholder={t('write-message-placeholder')}
|
||||||
rows={5}
|
rows={5}
|
||||||
className="w-full px-4 py-3 border border-gray-300 rounded-xl mb-4 focus:ring-2 focus:ring-amber-500 focus:border-transparent outline-none resize-none"
|
className="w-full px-4 py-3 border border-gray-300 rounded-xl mb-4 focus:ring-2 focus:ring-amber-500 focus:border-transparent outline-none resize-none"
|
||||||
/>
|
/>
|
||||||
@ -595,7 +343,7 @@ export default function SettingsPage() {
|
|||||||
}}
|
}}
|
||||||
className="flex-1 px-4 py-3 rounded-xl border border-gray-200 text-gray-700 hover:bg-gray-50 transition-colors text-sm font-medium"
|
className="flex-1 px-4 py-3 rounded-xl border border-gray-200 text-gray-700 hover:bg-gray-50 transition-colors text-sm font-medium"
|
||||||
>
|
>
|
||||||
إلغاء
|
{t('cancel')}
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
onClick={handleSendContact}
|
onClick={handleSendContact}
|
||||||
@ -603,7 +351,7 @@ export default function SettingsPage() {
|
|||||||
className="flex-1 px-4 py-3 rounded-xl bg-amber-600 text-white hover:bg-amber-700 transition-colors text-sm font-medium disabled:opacity-50 flex items-center justify-center gap-2"
|
className="flex-1 px-4 py-3 rounded-xl bg-amber-600 text-white hover:bg-amber-700 transition-colors text-sm font-medium disabled:opacity-50 flex items-center justify-center gap-2"
|
||||||
>
|
>
|
||||||
{isSendingContact ? <Loader2 className="w-4 h-4 animate-spin" /> : <MessageCircle className="w-4 h-4" />}
|
{isSendingContact ? <Loader2 className="w-4 h-4 animate-spin" /> : <MessageCircle className="w-4 h-4" />}
|
||||||
{isSendingContact ? 'جاري الإرسال...' : 'إرسال'}
|
{isSendingContact ? t('sending') : t('send')}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</motion.div>
|
</motion.div>
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
import { motion } from "framer-motion";
|
import { motion } from "framer-motion";
|
||||||
import {
|
import {
|
||||||
MessageCircle,
|
MessageCircle,
|
||||||
@ -15,6 +16,7 @@ import { submitReport } from "../utils/api";
|
|||||||
import AuthService from "../services/AuthService";
|
import AuthService from "../services/AuthService";
|
||||||
|
|
||||||
export default function SupportPage() {
|
export default function SupportPage() {
|
||||||
|
const { t, i18n } = useTranslation();
|
||||||
const [subject, setSubject] = useState("");
|
const [subject, setSubject] = useState("");
|
||||||
const [body, setBody] = useState("");
|
const [body, setBody] = useState("");
|
||||||
const [isSubmitting, setIsSubmitting] = useState(false);
|
const [isSubmitting, setIsSubmitting] = useState(false);
|
||||||
@ -22,23 +24,23 @@ export default function SupportPage() {
|
|||||||
const handleSubmit = async (e) => {
|
const handleSubmit = async (e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
if (!subject.trim() || !body.trim()) {
|
if (!subject.trim() || !body.trim()) {
|
||||||
toast.error("يرجى تعبئة جميع الحقول");
|
toast.error(t('support.fillAllFields'));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!AuthService.isAuthenticated()) {
|
if (!AuthService.isAuthenticated()) {
|
||||||
toast.error("يرجى تسجيل الدخول أولاً لإرسال طلب دعم");
|
toast.error(t('auth.loginRequired'));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
setIsSubmitting(true);
|
setIsSubmitting(true);
|
||||||
try {
|
try {
|
||||||
await submitReport(subject, body);
|
await submitReport(subject, body);
|
||||||
toast.success("تم إرسال طلب الدعم بنجاح");
|
toast.success(t('support.submittedSuccess'));
|
||||||
setSubject("");
|
setSubject("");
|
||||||
setBody("");
|
setBody("");
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
toast.error("حدث خطأ أثناء إرسال الطلب. حاول مرة أخرى");
|
toast.error(t('support.submitError'));
|
||||||
} finally {
|
} finally {
|
||||||
setIsSubmitting(false);
|
setIsSubmitting(false);
|
||||||
}
|
}
|
||||||
@ -47,7 +49,7 @@ export default function SupportPage() {
|
|||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className="min-h-screen bg-gradient-to-b from-amber-50/50 to-white py-12"
|
className="min-h-screen bg-gradient-to-b from-amber-50/50 to-white py-12"
|
||||||
dir="rtl"
|
dir={i18n.language === 'ar' ? 'rtl' : 'ltr'}
|
||||||
>
|
>
|
||||||
<Toaster position="top-center" reverseOrder={false} />
|
<Toaster position="top-center" reverseOrder={false} />
|
||||||
<div className="container mx-auto px-4 max-w-5xl">
|
<div className="container mx-auto px-4 max-w-5xl">
|
||||||
@ -60,11 +62,10 @@ export default function SupportPage() {
|
|||||||
<MessageCircle className="w-10 h-10 text-amber-600" />
|
<MessageCircle className="w-10 h-10 text-amber-600" />
|
||||||
</div>
|
</div>
|
||||||
<h1 className="text-4xl font-bold text-gray-900 mb-4">
|
<h1 className="text-4xl font-bold text-gray-900 mb-4">
|
||||||
خدمة العملاء
|
{t('support.customerService')}
|
||||||
</h1>
|
</h1>
|
||||||
<p className="text-lg text-gray-600 max-w-2xl mx-auto">
|
<p className="text-lg text-gray-600 max-w-2xl mx-auto">
|
||||||
نحن هنا لمساعدتك. تواصل معنا عبر النموذج أدناه أو من خلال معلومات
|
{t('support.hereToHelp')}
|
||||||
الاتصال المباشرة
|
|
||||||
</p>
|
</p>
|
||||||
</motion.div>
|
</motion.div>
|
||||||
|
|
||||||
@ -77,30 +78,30 @@ export default function SupportPage() {
|
|||||||
>
|
>
|
||||||
<div className="bg-white rounded-2xl shadow-sm border border-gray-200 p-8">
|
<div className="bg-white rounded-2xl shadow-sm border border-gray-200 p-8">
|
||||||
<h2 className="text-2xl font-bold text-gray-900 mb-6">
|
<h2 className="text-2xl font-bold text-gray-900 mb-6">
|
||||||
أرسل لنا رسالة
|
{t('support.sendMessage')}
|
||||||
</h2>
|
</h2>
|
||||||
<form onSubmit={handleSubmit} className="space-y-6">
|
<form onSubmit={handleSubmit} className="space-y-6">
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-sm font-medium text-gray-700 mb-2">
|
<label className="block text-sm font-medium text-gray-700 mb-2">
|
||||||
الموضوع
|
{t('support.subjectLabel')}
|
||||||
</label>
|
</label>
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
value={subject}
|
value={subject}
|
||||||
onChange={(e) => setSubject(e.target.value)}
|
onChange={(e) => setSubject(e.target.value)}
|
||||||
placeholder="اكتب موضوع الرسالة..."
|
placeholder={t('support.subjectPlaceholder')}
|
||||||
className="w-full px-4 py-3 bg-white border border-gray-200 rounded-xl focus:outline-none focus:ring-2 focus:ring-amber-500 focus:border-transparent text-gray-900 placeholder-gray-400"
|
className="w-full px-4 py-3 bg-white border border-gray-200 rounded-xl focus:outline-none focus:ring-2 focus:ring-amber-500 focus:border-transparent text-gray-900 placeholder-gray-400"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-sm font-medium text-gray-700 mb-2">
|
<label className="block text-sm font-medium text-gray-700 mb-2">
|
||||||
الرسالة
|
{t('support.messageLabel')}
|
||||||
</label>
|
</label>
|
||||||
<textarea
|
<textarea
|
||||||
value={body}
|
value={body}
|
||||||
onChange={(e) => setBody(e.target.value)}
|
onChange={(e) => setBody(e.target.value)}
|
||||||
rows={6}
|
rows={6}
|
||||||
placeholder="اكتب تفاصيل طلبك..."
|
placeholder={t('support.messagePlaceholder')}
|
||||||
className="w-full px-4 py-3 bg-white border border-gray-200 rounded-xl focus:outline-none focus:ring-2 focus:ring-amber-500 focus:border-transparent text-gray-900 placeholder-gray-400 resize-none"
|
className="w-full px-4 py-3 bg-white border border-gray-200 rounded-xl focus:outline-none focus:ring-2 focus:ring-amber-500 focus:border-transparent text-gray-900 placeholder-gray-400 resize-none"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@ -114,7 +115,7 @@ export default function SupportPage() {
|
|||||||
) : (
|
) : (
|
||||||
<Send className="w-5 h-5" />
|
<Send className="w-5 h-5" />
|
||||||
)}
|
)}
|
||||||
{isSubmitting ? "جاري الإرسال..." : "إرسال"}
|
{isSubmitting ? t('support.sending') : t('support.send')}
|
||||||
</button>
|
</button>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
@ -128,7 +129,7 @@ export default function SupportPage() {
|
|||||||
>
|
>
|
||||||
<div className="bg-white rounded-2xl shadow-sm border border-gray-200 p-6">
|
<div className="bg-white rounded-2xl shadow-sm border border-gray-200 p-6">
|
||||||
<h3 className="text-lg font-bold text-gray-900 mb-4">
|
<h3 className="text-lg font-bold text-gray-900 mb-4">
|
||||||
معلومات الاتصال
|
{t('support.contactInfo')}
|
||||||
</h3>
|
</h3>
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
<div className="flex items-center gap-3">
|
<div className="flex items-center gap-3">
|
||||||
@ -136,7 +137,7 @@ export default function SupportPage() {
|
|||||||
<Mail className="w-5 h-5 text-blue-600" />
|
<Mail className="w-5 h-5 text-blue-600" />
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<p className="text-sm text-gray-500">البريد الإلكتروني</p>
|
<p className="text-sm text-gray-500">{t('support.email')}</p>
|
||||||
<p className="font-medium text-gray-900">
|
<p className="font-medium text-gray-900">
|
||||||
support@sweethome.com
|
support@sweethome.com
|
||||||
</p>
|
</p>
|
||||||
@ -147,7 +148,7 @@ export default function SupportPage() {
|
|||||||
<Phone className="w-5 h-5 text-green-600" />
|
<Phone className="w-5 h-5 text-green-600" />
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<p className="text-sm text-gray-500">رقم الهاتف</p>
|
<p className="text-sm text-gray-500">{t('support.phone')}</p>
|
||||||
<p className="font-medium text-gray-900" dir="ltr">
|
<p className="font-medium text-gray-900" dir="ltr">
|
||||||
+963 11 234 5678
|
+963 11 234 5678
|
||||||
</p>
|
</p>
|
||||||
@ -158,8 +159,8 @@ export default function SupportPage() {
|
|||||||
<MapPin className="w-5 h-5 text-amber-600" />
|
<MapPin className="w-5 h-5 text-amber-600" />
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<p className="text-sm text-gray-500">العنوان</p>
|
<p className="text-sm text-gray-500">{t('support.address')}</p>
|
||||||
<p className="font-medium text-gray-900">دمشق، سورية</p>
|
<p className="font-medium text-gray-900">{t('support.addressValue')}</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -167,16 +168,16 @@ export default function SupportPage() {
|
|||||||
|
|
||||||
<div className="bg-amber-50 rounded-2xl border border-amber-200 p-6">
|
<div className="bg-amber-50 rounded-2xl border border-amber-200 p-6">
|
||||||
<h3 className="text-lg font-bold text-amber-800 mb-2">
|
<h3 className="text-lg font-bold text-amber-800 mb-2">
|
||||||
ساعات العمل
|
{t('support.workingHours')}
|
||||||
</h3>
|
</h3>
|
||||||
<div className="space-y-2 text-amber-700">
|
<div className="space-y-2 text-amber-700">
|
||||||
<div className="flex justify-between">
|
<div className="flex justify-between">
|
||||||
<span>السبت - الخميس</span>
|
<span>{t('support.weekdays')}</span>
|
||||||
<span>9:00 - 18:00</span>
|
<span>9:00 - 18:00</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex justify-between">
|
<div className="flex justify-between">
|
||||||
<span>الجمعة</span>
|
<span>{t('support.friday')}</span>
|
||||||
<span>مغلق</span>
|
<span>{t('support.closed')}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user