Added translation to all site with edit style
All checks were successful
Build frontend / build (push) Successful in 1m40s
All checks were successful
Build frontend / build (push) Successful in 1m40s
This commit is contained in:
315
app/page.js
315
app/page.js
@ -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>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user