forked from amer2004/SweetHome
Added translation to all site with edit style
This commit is contained in:
@ -4,8 +4,10 @@ import Link from "next/link";
|
||||
import { usePathname } from "next/navigation";
|
||||
import { Home, Building, Calendar, CreditCard, Briefcase, BookOpen } from "lucide-react";
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
export default function BottomNav({ isOwner, isOwnerOrAgent }) {
|
||||
const { t } = useTranslation();
|
||||
const pathname = usePathname();
|
||||
const [isMounted, setIsMounted] = useState(false);
|
||||
const bookingsHref = isOwner ? "/owner/reservations" : "/reservations";
|
||||
@ -15,12 +17,12 @@ export default function BottomNav({ isOwner, isOwnerOrAgent }) {
|
||||
}, []);
|
||||
|
||||
const items = [
|
||||
{ href: "/", label: "الرئيسية", icon: Home },
|
||||
{ href: "/properties", label: "عقاراتنا", icon: Building },
|
||||
...(isOwnerOrAgent ? [{ href: "/owner/properties", label: "عقاراتي", icon: Briefcase }] : []),
|
||||
{ href: bookingsHref, label: "الحجوزات", icon: Calendar },
|
||||
{ href: "/payments", label: "المدفوعات", icon: CreditCard },
|
||||
...(isOwnerOrAgent ? [{ href: "/owner/account-book", label: "دفتر الحسابات", icon: BookOpen }] : []),
|
||||
{ href: "/", label: t("home"), icon: Home },
|
||||
{ href: "/properties", label: t("ourProperties"), icon: Building },
|
||||
...(isOwnerOrAgent ? [{ href: "/owner/properties", label: t("myProperties"), icon: Briefcase }] : []),
|
||||
{ href: bookingsHref, label: t("reservations"), icon: Calendar },
|
||||
{ href: "/payments", label: t("payments"), icon: CreditCard },
|
||||
...(isOwnerOrAgent ? [{ href: "/owner/account-book", label: t("accountBook"), icon: BookOpen }] : []),
|
||||
];
|
||||
|
||||
const isActive = (href) => {
|
||||
|
||||
@ -4,11 +4,13 @@ import { useState } from 'react';
|
||||
import { motion } from 'framer-motion';
|
||||
import Link from 'next/link';
|
||||
import { Heart, Bell, CreditCard, Settings } from 'lucide-react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useFavorites } from '@/app/contexts/FavoritesContext';
|
||||
import { useNotifications } from '@/app/contexts/NotificationsContext';
|
||||
import AuthService from '@/app/services/AuthService';
|
||||
|
||||
export default function FloatingSidebar({ isRTL }) {
|
||||
const { t } = useTranslation();
|
||||
const { favorites } = useFavorites();
|
||||
const { unreadCount } = useNotifications();
|
||||
const [tooltip, setTooltip] = useState(null);
|
||||
@ -88,7 +90,7 @@ export default function FloatingSidebar({ isRTL }) {
|
||||
)}
|
||||
</div>
|
||||
</Link>
|
||||
{renderTooltip('favorites', 'المفضلة')}
|
||||
{renderTooltip('favorites', t('favorites'))}
|
||||
</motion.div>
|
||||
<motion.div
|
||||
className="relative group"
|
||||
@ -116,7 +118,7 @@ export default function FloatingSidebar({ isRTL }) {
|
||||
)}
|
||||
</div>
|
||||
</Link>
|
||||
{renderTooltip('notifications', 'الإشعارات')}
|
||||
{renderTooltip('notifications', t('notifications'))}
|
||||
</motion.div>
|
||||
<motion.div
|
||||
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" />
|
||||
</Link>
|
||||
{renderTooltip('payments', 'المدفوعات')}
|
||||
{renderTooltip('payments', t('payments'))}
|
||||
</motion.div>
|
||||
<motion.div
|
||||
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" />
|
||||
</Link>
|
||||
{renderTooltip('settings', 'الإعدادات')}
|
||||
{renderTooltip('settings', t('settings'))}
|
||||
</motion.div>
|
||||
</div>
|
||||
</motion.div>
|
||||
|
||||
@ -21,46 +21,46 @@ export default function HeroSearch({ onSearch, isAuthenticated }) {
|
||||
const [showLoginDialog, setShowLoginDialog] = useState(false);
|
||||
|
||||
const cities = [
|
||||
{ id: 'all', label: 'جميع المدن' },
|
||||
{ id: 'دمشق', label: 'دمشق' },
|
||||
{ id: 'حلب', label: 'حلب' },
|
||||
{ id: 'حمص', label: 'حمص' },
|
||||
{ id: 'اللاذقية', label: 'اللاذقية' },
|
||||
{ id: 'درعا', label: 'درعا' }
|
||||
{ id: 'all', label: t('allCities') },
|
||||
{ id: 'دمشق', label: t('damascus') },
|
||||
{ id: 'حلب', label: t('aleppo') },
|
||||
{ id: 'حمص', label: t('homs') },
|
||||
{ id: 'اللاذقية', label: t('latakia') },
|
||||
{ id: 'درعا', label: t('daraa') }
|
||||
];
|
||||
|
||||
const propertyTypes = [
|
||||
{ id: 'all', label: 'الكل' },
|
||||
{ id: 'apartment', label: 'شقق سكنية' },
|
||||
{ id: 'studio', label: 'استوديو' },
|
||||
{ id: 'commercial', label: 'عقار تجاري' },
|
||||
{ id: 'villa', label: 'فيلا / مزرعة' }
|
||||
{ id: 'all', label: t('all') },
|
||||
{ id: 'apartment', label: t('residentialApartments') },
|
||||
{ id: 'studio', label: t('studio') },
|
||||
{ id: 'commercial', label: t('commercialProperty') },
|
||||
{ id: 'villa', label: t('villaFarm') }
|
||||
];
|
||||
|
||||
const priceRanges = [
|
||||
{ id: 'all', label: 'جميع الأسعار' },
|
||||
{ id: '0-500', label: 'أقل من 50$' },
|
||||
{ id: '500-1000', label: '50$ - 100$' },
|
||||
{ id: '1000-2000', label: '100$ - 200$' },
|
||||
{ id: '2000-3000', label: '200$ - 300$' },
|
||||
{ id: '3000+', label: 'أكثر من 300$' }
|
||||
{ id: 'all', label: t('allPrices') },
|
||||
{ id: '0-500', label: t('priceRange1') },
|
||||
{ id: '500-1000', label: t('priceRange2') },
|
||||
{ id: '1000-2000', label: t('priceRange3') },
|
||||
{ id: '2000-3000', label: t('priceRange4') },
|
||||
{ id: '3000+', label: t('priceRange5') }
|
||||
];
|
||||
|
||||
const identityTypes = [
|
||||
{ id: 'syrian', label: 'هوية سورية' },
|
||||
{ id: 'passport', label: 'جواز سفر' }
|
||||
{ id: 'syrian', label: t('syrianId') },
|
||||
{ id: 'passport', label: t('passport') }
|
||||
];
|
||||
|
||||
const ownerSources = [
|
||||
{ id: 'all', label: 'الكل' },
|
||||
{ id: 'owner', label: 'من المالك' },
|
||||
{ id: 'agency', label: 'من مكتب عقاري' }
|
||||
{ id: 'all', label: t('all') },
|
||||
{ id: 'owner', label: t('fromOwner') },
|
||||
{ id: 'agency', label: t('fromAgency') }
|
||||
];
|
||||
|
||||
const rentPeriods = [
|
||||
{ id: 'all', label: 'الكل' },
|
||||
{ id: 'daily', label: 'إيجار يومي' },
|
||||
{ id: 'monthly', label: 'إيجار شهري' }
|
||||
{ id: 'all', label: t('all') },
|
||||
{ id: 'daily', label: t('dailyRent') },
|
||||
{ id: 'monthly', label: t('monthlyRent') }
|
||||
];
|
||||
|
||||
const handleTabClick = (tab) => {
|
||||
@ -190,7 +190,7 @@ export default function HeroSearch({ onSearch, isAuthenticated }) {
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-white mb-2">
|
||||
<div className="flex items-center gap-1">
|
||||
نوع الوثيقة
|
||||
{t("identityTypeLabel")}
|
||||
</div>
|
||||
</label>
|
||||
<select
|
||||
@ -213,7 +213,7 @@ export default function HeroSearch({ onSearch, isAuthenticated }) {
|
||||
</div>
|
||||
<div className="grid grid-cols-1 md:grid-cols-4 gap-4 mt-4">
|
||||
<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
|
||||
value={filters.ownerSource}
|
||||
onChange={(e) => setFilters({ ...filters, ownerSource: e.target.value })}
|
||||
@ -236,7 +236,7 @@ export default function HeroSearch({ onSearch, isAuthenticated }) {
|
||||
|
||||
{activeTab === 'rent' && (
|
||||
<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
|
||||
value={filters.rentPeriod}
|
||||
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 })}
|
||||
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>
|
||||
</div>
|
||||
</div>
|
||||
@ -289,13 +289,13 @@ export default function HeroSearch({ onSearch, isAuthenticated }) {
|
||||
<div className="flex items-center gap-3 mb-5">
|
||||
<ShieldCheck className="w-7 h-7 text-amber-500" />
|
||||
<div>
|
||||
<h3 className="text-lg font-semibold text-gray-900">يرجى تسجيل الدخول</h3>
|
||||
<p className="text-sm text-gray-600">للوصول إلى خيارات التأجير والبيع، يجب أن تكون مسجلاً.</p>
|
||||
<h3 className="text-lg font-semibold text-gray-900">{t("loginRequired")}</h3>
|
||||
<p className="text-sm text-gray-600">{t("loginRequiredDesc")}</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="space-y-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 className="flex flex-col gap-3 sm:flex-row sm:justify-end">
|
||||
<button
|
||||
@ -303,13 +303,13 @@ export default function HeroSearch({ onSearch, isAuthenticated }) {
|
||||
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"
|
||||
>
|
||||
إغلاق
|
||||
{t("close")}
|
||||
</button>
|
||||
<Link
|
||||
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"
|
||||
>
|
||||
تسجيل الدخول
|
||||
{t("login")}
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
@ -318,4 +318,4 @@ export default function HeroSearch({ onSearch, isAuthenticated }) {
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ -2,6 +2,7 @@
|
||||
|
||||
import { useState } from 'react';
|
||||
import { X, Loader2 } from 'lucide-react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import toast from 'react-hot-toast';
|
||||
import StarRating from './StarRating';
|
||||
import { addCustomerRating } from '../../utils/ratings';
|
||||
@ -10,11 +11,12 @@ const RatingField = ({ label, value, onChange }) => (
|
||||
<div className="space-y-2">
|
||||
<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} />
|
||||
{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>
|
||||
);
|
||||
|
||||
export default function CustomerRatingForm({ reservationId, onSuccess, onCancel }) {
|
||||
const { t } = useTranslation();
|
||||
const [furnitureIntegrityRating, setFurnitureIntegrityRating] = useState(0);
|
||||
const [termsComplianceRating, setTermsComplianceRating] = useState(0);
|
||||
const [renterBehaviorRating, setRenterBehaviorRating] = useState(0);
|
||||
@ -22,9 +24,9 @@ export default function CustomerRatingForm({ reservationId, onSuccess, onCancel
|
||||
const [loading, setLoading] = useState(false);
|
||||
|
||||
const validate = () => {
|
||||
if (furnitureIntegrityRating === 0) return 'الحفاظ على الأثاث';
|
||||
if (termsComplianceRating === 0) return 'الالتزام بالشروط';
|
||||
if (renterBehaviorRating === 0) return 'سلوك المستأجر';
|
||||
if (furnitureIntegrityRating === 0) return t('ratings.furnitureIntegrity');
|
||||
if (termsComplianceRating === 0) return t('ratings.termsCompliance');
|
||||
if (renterBehaviorRating === 0) return t('ratings.renterBehavior');
|
||||
return null;
|
||||
};
|
||||
|
||||
@ -32,7 +34,7 @@ export default function CustomerRatingForm({ reservationId, onSuccess, onCancel
|
||||
e.preventDefault();
|
||||
const missing = validate();
|
||||
if (missing) {
|
||||
toast.error(`يرجى تقييم: ${missing}`);
|
||||
toast.error(t('ratings.pleaseRate') + ': ' + missing);
|
||||
return;
|
||||
}
|
||||
|
||||
@ -45,10 +47,10 @@ export default function CustomerRatingForm({ reservationId, onSuccess, onCancel
|
||||
renterBehaviorRating,
|
||||
comment: comment.trim() || null,
|
||||
});
|
||||
toast.success('تم إرسال تقييم المستأجر بنجاح!');
|
||||
toast.success(t('ratings.customerRatingSubmitted'));
|
||||
onSuccess?.();
|
||||
} catch (err) {
|
||||
toast.error('حدث خطأ، حاول مرة أخرى');
|
||||
toast.error(t('ratings.submitError'));
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
@ -57,7 +59,7 @@ export default function CustomerRatingForm({ reservationId, onSuccess, onCancel
|
||||
return (
|
||||
<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">
|
||||
<h3 className="text-xl font-bold text-gray-900">تقييم المستأجر</h3>
|
||||
<h3 className="text-xl font-bold text-gray-900">{t('ratings.customerRating')}</h3>
|
||||
{onCancel && (
|
||||
<button onClick={onCancel} className="text-gray-400 hover:text-gray-600">
|
||||
<X size={20} />
|
||||
@ -65,17 +67,17 @@ export default function CustomerRatingForm({ reservationId, onSuccess, onCancel
|
||||
)}
|
||||
</div>
|
||||
<form onSubmit={handleSubmit} className="space-y-5">
|
||||
<RatingField label="الحفاظ على الأثاث" value={furnitureIntegrityRating} onChange={setFurnitureIntegrityRating} />
|
||||
<RatingField label="الالتزام بالشروط" value={termsComplianceRating} onChange={setTermsComplianceRating} />
|
||||
<RatingField label="سلوك المستأجر" value={renterBehaviorRating} onChange={setRenterBehaviorRating} />
|
||||
<RatingField label={t('ratings.furnitureIntegrity')} value={furnitureIntegrityRating} onChange={setFurnitureIntegrityRating} />
|
||||
<RatingField label={t('ratings.termsCompliance')} value={termsComplianceRating} onChange={setTermsComplianceRating} />
|
||||
<RatingField label={t('ratings.renterBehavior')} value={renterBehaviorRating} onChange={setRenterBehaviorRating} />
|
||||
<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
|
||||
rows={3}
|
||||
value={comment}
|
||||
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"
|
||||
placeholder="شارك تجربتك مع المستأجر..."
|
||||
placeholder={t('ratings.commentPlaceholder')}
|
||||
/>
|
||||
</div>
|
||||
<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"
|
||||
>
|
||||
{loading && <Loader2 className="w-5 h-5 animate-spin" />}
|
||||
{loading ? 'جاري الإرسال...' : 'إرسال التقييم'}
|
||||
{loading ? t('ratings.sending') : t('ratings.submitRating')}
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user