Added translation to all site with edit style
All checks were successful
Build frontend / build (push) Successful in 1m40s

This commit is contained in:
Rahaf
2026-07-01 15:43:58 +03:00
parent 3052209df8
commit dab5b62fb7
56 changed files with 5136 additions and 3190 deletions

View File

@ -32,8 +32,9 @@ import Image from 'next/image';
import { getRentProperties, getSaleProperties } from './utils/api';
import { BuildingTypeKeys, PropertyStatusKeys, extractCity } from './enums';
import AuthService from './services/AuthService';
import { useTranslation } from 'react-i18next';
function mapApiProperty(item, index) {
function mapApiProperty(t, item, index) {
const info = item.propertyInformation || {};
const dailyPrice = item.dailyRent ?? 0;
@ -48,11 +49,11 @@ function mapApiProperty(item, index) {
const status = PropertyStatusKeys[info.status] ?? PropertyStatusKeys[item.status] ?? 'available';
const features = [];
if (item.isSmokeAllow) features.push('يسمح بالتدخين');
if (item.isVisitorAllow) features.push('يسمح بالزوار');
if (item.specializedFor) features.push('متخصص');
if (info.numberOfBedRooms) features.push(`${info.numberOfBedRooms} غرف نوم`);
if (info.numberOfBathRooms) features.push(`${info.numberOfBathRooms} حمامات`);
if (item.isSmokeAllow) features.push(t('smoke-allowed'));
if (item.isVisitorAllow) features.push(t('visitors-allowed'));
if (item.specializedFor) features.push(t('specialized'));
if (info.numberOfBedRooms) features.push(`${info.numberOfBedRooms} ${t('rooms')}`);
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 rawImages = Array.isArray(info.images) ? info.images : [];
@ -68,7 +69,7 @@ function mapApiProperty(item, index) {
return {
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 || '',
type: propType,
price: price,
@ -76,7 +77,7 @@ function mapApiProperty(item, index) {
priceUnit,
listingType: isRentListing ? 'rent' : 'sale',
location: {
city: extractCity(info.address) || 'دمشق',
city: extractCity(info.address) || 'damascus',
district: info.address || '',
address: info.address || '',
lat: parseFloat(info.cordsX) || 0,
@ -103,6 +104,7 @@ function mapApiProperty(item, index) {
}
export default function HomePage() {
const { t } = useTranslation();
const mapSectionRef = useRef(null);
const [searchFilters, setSearchFilters] = useState(null);
const [showMap, setShowMap] = useState(false);
@ -143,8 +145,8 @@ export default function HomePage() {
const rentList = Array.isArray(rentData) ? rentData : [];
const saleList = Array.isArray(saleData) ? saleData : [];
const mappedRent = rentList.map((p, i) => mapApiProperty(p, i));
const mappedSale = saleList.map((p, i) => mapApiProperty(p, rentList.length + i));
const mappedRent = rentList.map((p, i) => mapApiProperty(t, p, i));
const mappedSale = saleList.map((p, i) => mapApiProperty(t, p, rentList.length + i));
setRentProperties(mappedRent);
setSaleProperties(mappedSale);
@ -297,7 +299,7 @@ export default function HomePage() {
visible: { opacity: 1, y: 0 }
}}
>
إيجاد منزلك الجديد<br />
{t("heroTitleLine1")}<br />
<motion.span
className="text-amber-300"
animate={{
@ -309,7 +311,7 @@ export default function HomePage() {
ease: "easeInOut"
}}
>
أصبح سهلاً
{t("heroTitleLine2")}
</motion.span>
</motion.h1>
<motion.p
@ -319,7 +321,7 @@ export default function HomePage() {
visible: { opacity: 1, y: 0 }
}}
>
نوفر قوائم عقارات عالية الجودة لمساعدتك في إيجاد المنزل المثالي
{t("heroSubtitle")}
</motion.p>
</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"
>
<h2 className="text-2xl font-bold text-amber-50 mb-2">
مرحباً {user?.name}!
{t("ownerGreeting")} {user?.name}!
</h2>
<p className="text-amber-50/80 mb-4">
يمكنك إدارة عقاراتك من خلال لوحة التحكم الخاصة بك
{t("ownerDescription")}
</p>
<Link
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"
>
<Building className="w-5 h-5" />
إدارة عقاراتي
{t("manageMyProperties")}
</Link>
</motion.div>
)}
@ -373,7 +375,7 @@ export default function HomePage() {
)}
</section>
{!isOwner && (
{!isOwner && (<>
<AnimatePresence mode="wait">
{showMap && (
<motion.section
@ -407,7 +409,7 @@ export default function HomePage() {
>
<div className="flex items-center justify-center gap-4 mb-2">
<h2 className="text-3xl font-bold text-gray-900">
{filteredProperties.length > 0 ? 'نتائج البحث' : 'لا توجد نتائج'}
{filteredProperties.length > 0 ? t('searchResults') : t('no-properties')}
</h2>
<motion.button
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">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M10 19l-7-7m0 0l7-7m-7 7h18" />
</svg>
بحث جديد
{t('newSearch')}
</motion.button>
</div>
{filteredProperties.length > 0 ? (
<p className="text-gray-600">
تم العثور على {filteredProperties.length} عقار يطابق معايير البحث
{t('foundPropertiesCount', { count: filteredProperties.length })}
</p>
) : (
<p className="text-gray-600">
لا توجد عقارات تطابق معايير البحث. جرب تغيير الفلاتر.
{t('noPropertiesMatchFilters')}
</p>
)}
</motion.div>
@ -456,156 +458,143 @@ 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" />
</svg>
</div>
<h3 className="text-xl font-bold text-gray-700 mb-2">لا توجد نتائج</h3>
<p className="text-gray-500">حاول تغيير معايير البحث</p>
<h3 className="text-xl font-bold text-gray-700 mb-2">{t('no-properties')}</h3>
<p className="text-gray-500">{t('try-changing-search-criteria')}</p>
</div>
)}
</motion.div>
</div>
</motion.section>
)}
</AnimatePresence>
</>)}
{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>
</motion.section>
)}
</AnimatePresence>
)}
<motion.div
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
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">
{t('whyChooseUsTitle')}
</h2>
<p className="text-gray-600 max-w-2xl mx-auto text-lg">
{t('whyChooseUsSubtitle')}
</p>
</motion.div>
<section className="py-20 bg-gradient-to-b from-white to-gray-50">
<div className="container mx-auto px-4">
<motion.div
className="text-center mb-12"
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
transition={{ duration: 0.6 }}
>
<h2 className="text-3xl md:text-4xl font-bold text-gray-900 mb-4 tracking-tight">
لماذا تختار سويت هوم؟
</h2>
<p className="text-gray-600 max-w-2xl mx-auto text-lg">
نجعل عملية إيجاد منزلك المثالي سهلة وسريعة
</p>
</motion.div>
<div className="grid grid-cols-1 md:grid-cols-3 gap-6 mb-12">
<motion.div
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 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
transition={{ duration: 0.5, delay: 0.1 }}
whileHover={{ y: -4 }}
>
<div className="flex items-center gap-4 mb-4">
<div className="w-12 h-12 bg-amber-100 rounded-xl flex items-center justify-center group-hover:bg-amber-200 transition-colors duration-300">
<ShieldCheck className="w-6 h-6 text-amber-600" />
</div>
<h3 className="text-lg font-bold text-gray-900">
{t('feature1Title')}
</h3>
</div>
<p className="text-gray-600 text-sm leading-relaxed">
{t('feature1Description')}
</p>
</motion.div>
<div className="grid grid-cols-1 md:grid-cols-3 gap-6">
<motion.div
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 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
transition={{ duration: 0.5, delay: 0.1 }}
whileHover={{ y: -4 }}
>
<div className="flex items-center gap-4 mb-4">
<div className="w-12 h-12 bg-amber-100 rounded-xl flex items-center justify-center group-hover:bg-amber-200 transition-colors duration-300">
<ShieldCheck className="w-6 h-6 text-amber-600" />
</div>
<h3 className="text-lg font-bold text-gray-900">
قوائم موثوقة
</h3>
<motion.div
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 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
transition={{ duration: 0.5, delay: 0.2 }}
whileHover={{ y: -4 }}
>
<div className="flex items-center gap-4 mb-4">
<div className="w-12 h-12 bg-blue-100 rounded-xl flex items-center justify-center group-hover:bg-blue-200 transition-colors duration-300">
<Lock className="w-6 h-6 text-blue-600" />
</div>
<h3 className="text-lg font-bold text-gray-900">
{t('feature2Title')}
</h3>
</div>
<p className="text-gray-600 text-sm leading-relaxed">
{t('feature2Description')}
</p>
</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"
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
transition={{ duration: 0.5, delay: 0.3 }}
whileHover={{ y: -4 }}
>
<div className="flex items-center gap-4 mb-4">
<div className="w-12 h-12 bg-green-100 rounded-xl flex items-center justify-center group-hover:bg-green-200 transition-colors duration-300">
<Zap className="w-6 h-6 text-green-600" />
</div>
<h3 className="text-lg font-bold text-gray-900">
{t('feature3Title')}
</h3>
</div>
<p className="text-gray-600 text-sm leading-relaxed">
{t('feature3Description')}
</p>
</motion.div>
</div>
<p className="text-gray-600 text-sm leading-relaxed">
كل عقار يتم التحقق منه بدقة لضمان الدقة والجودة.
</p>
</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"
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
transition={{ duration: 0.5, delay: 0.2 }}
whileHover={{ y: -4 }}
>
<div className="flex items-center gap-4 mb-4">
<div className="w-12 h-12 bg-blue-100 rounded-xl flex items-center justify-center group-hover:bg-blue-200 transition-colors duration-300">
<Lock className="w-6 h-6 text-blue-600" />
</div>
<h3 className="text-lg font-bold text-gray-900">
عمليات آمنة
</h3>
</div>
<p className="text-gray-600 text-sm leading-relaxed">
سلامتك هي أولويتنا. نوفر معاملات آمنة ونحمي معلوماتك الشخصية.
</p>
</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"
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
transition={{ duration: 0.5, delay: 0.3 }}
whileHover={{ y: -4 }}
>
<div className="flex items-center gap-4 mb-4">
<div className="w-12 h-12 bg-green-100 rounded-xl flex items-center justify-center group-hover:bg-green-200 transition-colors duration-300">
<Zap className="w-6 h-6 text-green-600" />
</div>
<h3 className="text-lg font-bold text-gray-900">
نتائج سريعة
</h3>
</div>
<p className="text-gray-600 text-sm leading-relaxed">
اعثر على منزلك المثالي في دقائق باستخدام خوارزميات البحث والمطابقة المتقدمة لدينا.
</p>
</motion.div>
</div>
</div>
</section>
{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 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>
);
}