Compare commits

..

2 Commits

Author SHA1 Message Date
ae600ad41b Merge branch 'main' of http://45.93.137.91:3000/Rahaf/SweetHome
Some checks failed
Build frontend / build (push) Failing after 43s
2026-04-13 00:38:59 +03:00
16b1c7c6f6 Edit home 2026-04-13 00:25:29 +03:00
2 changed files with 203 additions and 21 deletions

View File

@ -1,19 +1,24 @@
'use client'; 'use client';
import { useState } from 'react'; import { useState } from 'react';
import Link from 'next/link';
import { motion } from 'framer-motion'; import { motion } from 'framer-motion';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { Search, MapPin, Home, DollarSign } from 'lucide-react'; import { Search, MapPin, Home, DollarSign, ShieldCheck } from 'lucide-react';
export default function HeroSearch({ onSearch }) { export default function HeroSearch({ onSearch, isAuthenticated }) {
const { t } = useTranslation(); const { t } = useTranslation();
const [activeTab, setActiveTab] = useState('rent'); const [activeTab, setActiveTab] = useState('buy');
const [filters, setFilters] = useState({ const [filters, setFilters] = useState({
city: '', city: 'all',
propertyType: '', propertyType: 'all',
priceRange: '', priceRange: 'all',
identityType: 'syrian' identityType: 'syrian',
ownerSource: 'all',
rentPeriod: 'all',
availableToday: false
}); });
const [showLoginDialog, setShowLoginDialog] = useState(false);
const cities = [ const cities = [
{ id: 'all', label: 'جميع المدن' }, { id: 'all', label: 'جميع المدن' },
@ -26,10 +31,10 @@ export default function HeroSearch({ onSearch }) {
const propertyTypes = [ const propertyTypes = [
{ id: 'all', label: 'الكل' }, { id: 'all', label: 'الكل' },
{ id: 'apartment', label: 'شقة' }, { id: 'apartment', label: 'شقق سكنية' },
{ id: 'villa', label: 'فيلا' }, { id: 'studio', label: 'استوديو' },
{ id: 'house', label: 'بيت' }, { id: 'commercial', label: 'عقار تجاري' },
{ id: 'studio', label: 'استوديو' } { id: 'villa', label: 'فيلا / مزرعة' }
]; ];
const priceRanges = [ const priceRanges = [
@ -46,16 +51,44 @@ export default function HeroSearch({ onSearch }) {
{ id: 'passport', label: 'جواز سفر' } { id: 'passport', label: 'جواز سفر' }
]; ];
const ownerSources = [
{ id: 'all', label: 'الكل' },
{ id: 'owner', label: 'من المالك' },
{ id: 'agency', label: 'من مكتب عقاري' }
];
const rentPeriods = [
{ id: 'all', label: 'الكل' },
{ id: 'daily', label: 'إيجار يومي' },
{ id: 'monthly', label: 'إيجار شهري' }
];
const handleTabClick = (tab) => {
setActiveTab(tab);
if ((tab === 'rent' || tab === 'sell') && !isAuthenticated) {
setShowLoginDialog(true);
}
};
const handleSearch = () => { const handleSearch = () => {
if ((activeTab === 'rent' || activeTab === 'sell') && !isAuthenticated) {
setShowLoginDialog(true);
return;
}
onSearch({ onSearch({
...filters, ...filters,
propertyType: filters.propertyType || 'all', mode: activeTab,
city: filters.city || 'all', city: filters.city || 'all',
priceRange: filters.priceRange || 'all' propertyType: filters.propertyType || 'all',
priceRange: filters.priceRange || 'all',
ownerSource: filters.ownerSource || 'all',
rentPeriod: filters.rentPeriod || 'all'
}); });
}; };
return ( return (
<>
<motion.div <motion.div
className="bg-white/10 backdrop-blur-lg rounded-2xl p-6 sm:p-8 border border-white/20 shadow-2xl" className="bg-white/10 backdrop-blur-lg rounded-2xl p-6 sm:p-8 border border-white/20 shadow-2xl"
initial={{ opacity: 0, y: 30 }} initial={{ opacity: 0, y: 30 }}
@ -66,7 +99,7 @@ export default function HeroSearch({ onSearch }) {
{['rent', 'buy', 'sell'].map((tab) => ( {['rent', 'buy', 'sell'].map((tab) => (
<motion.button <motion.button
key={tab} key={tab}
onClick={() => setActiveTab(tab)} onClick={() => handleTabClick(tab)}
className={`px-4 py-2 rounded-lg font-medium text-sm transition-all ${ className={`px-4 py-2 rounded-lg font-medium text-sm transition-all ${
activeTab === tab activeTab === tab
? 'bg-amber-500 text-white' ? 'bg-amber-500 text-white'
@ -176,6 +209,63 @@ export default function HeroSearch({ onSearch }) {
</select> </select>
</div> </div>
</div> </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>
<select
value={filters.ownerSource}
onChange={(e) => setFilters({ ...filters, ownerSource: e.target.value })}
className="w-full px-4 py-3 bg-white/90 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-amber-500 text-sm appearance-none cursor-pointer"
style={{
backgroundImage: `url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 24 24' stroke='%23666'%3E%3Cpath stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M19 9l-7 7-7-7'%3E%3C/path%3E%3C/svg%3E")`,
backgroundRepeat: 'no-repeat',
backgroundPosition: 'left 1rem center',
backgroundSize: '1rem',
paddingLeft: '2.5rem'
}}
>
{ownerSources.map((source) => (
<option key={source.id} value={source.id}>
{source.label}
</option>
))}
</select>
</div>
<div>
<label className="block text-sm font-medium text-white mb-2">نوع الإيجار</label>
<select
value={filters.rentPeriod}
onChange={(e) => setFilters({ ...filters, rentPeriod: e.target.value })}
className="w-full px-4 py-3 bg-white/90 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-amber-500 text-sm appearance-none cursor-pointer"
style={{
backgroundImage: `url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 24 24' stroke='%23666'%3E%3Cpath stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M19 9l-7 7-7-7'%3E%3C/path%3E%3C/svg%3E")`,
backgroundRepeat: 'no-repeat',
backgroundPosition: 'left 1rem center',
backgroundSize: '1rem',
paddingLeft: '2.5rem'
}}
>
{rentPeriods.map((period) => (
<option key={period.id} value={period.id}>
{period.label}
</option>
))}
</select>
</div>
<div className="md:col-span-2 flex flex-col justify-between p-4 rounded-2xl border border-dashed border-white/30 bg-white/5">
<label className="mt-4 flex items-center gap-3 text-white text-sm">
<input
type="checkbox"
checked={filters.availableToday}
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>
</label>
</div>
</div>
<div className="mt-6"> <div className="mt-6">
<motion.button <motion.button
onClick={handleSearch} onClick={handleSearch}
@ -188,5 +278,40 @@ export default function HeroSearch({ onSearch }) {
</motion.button> </motion.button>
</div> </div>
</motion.div> </motion.div>
{showLoginDialog && !isAuthenticated && (
<div className="fixed inset-0 z-50 flex items-center justify-center bg-black/60 px-4 py-8">
<div className="w-full max-w-md rounded-3xl bg-white p-6 shadow-2xl border border-gray-200">
<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>
</div>
</div>
<div className="space-y-4">
<div className="rounded-2xl bg-gray-50 p-4">
<p className="text-sm text-gray-700">اضغط على تسجيل الدخول لاستكمال البحث أو إدارة عقاراتك.</p>
</div>
<div className="flex flex-col gap-3 sm:flex-row sm:justify-end">
<button
type="button"
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"
>
إغلاق
</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"
>
تسجيل الدخول
</Link>
</div>
</div>
</div>
</div>
)}
</>
); );
} }

View File

@ -38,10 +38,15 @@ import AuthService from './services/AuthService';
function mapApiProperty(item, index) { function mapApiProperty(item, index) {
const info = item.propertyInformation || {}; const info = item.propertyInformation || {};
const dailyPrice = item.dailyRent ?? item.monthlyRent ?? item.price ?? 0; const dailyPrice = item.dailyRent ?? 0;
const monthlyPrice = item.monthlyRent ?? 0; const monthlyPrice = item.monthlyRent ?? 0;
const salePrice = item.price ?? 0;
const isRentListing = Boolean(item.dailyRent != null || item.monthlyRent != null);
const propType = BuildingTypeKeys[info.buildingType] ?? BuildingTypeKeys[item.type] ?? 'apartment'; const price = isRentListing ? (dailyPrice || monthlyPrice || 0) : salePrice;
const priceUnit = isRentListing ? (monthlyPrice ? 'monthly' : 'daily') : 'sale';
const propType = BuildingTypeKeys[info.buildingType] ?? BuildingTypeKeys[item.type] ?? (item.type || 'apartment');
const status = PropertyStatusKeys[info.status] ?? PropertyStatusKeys[item.status] ?? 'available'; const status = PropertyStatusKeys[info.status] ?? PropertyStatusKeys[item.status] ?? 'available';
const features = []; const features = [];
@ -58,14 +63,21 @@ function mapApiProperty(item, index) {
? rawImages.map(img => img.startsWith('http') ? img : `${apiBase}${img.startsWith('/') ? '' : '/Pictures/'}${img}`) ? rawImages.map(img => img.startsWith('http') ? img : `${apiBase}${img.startsWith('/') ? '' : '/Pictures/'}${img}`)
: ['/property-placeholder.jpg']; : ['/property-placeholder.jpg'];
const ownerSource = info.ownerType == null && item.ownerType == null
? 'all'
: [info.ownerType, item.ownerType].find((value) => value != null) === 1
? 'agency'
: 'owner';
return { return {
id: item.id ?? index + 1, id: item.id ?? index + 1,
title: info.address || `عقار #${item.id || index + 1}`, title: info.address || `عقار #${item.id || index + 1}`,
description: info.description || '', description: info.description || '',
type: propType, type: propType,
price: dailyPrice, price: price,
priceUSD: dailyPrice, priceUSD: price,
priceUnit: 'daily', priceUnit,
listingType: isRentListing ? 'rent' : 'sale',
location: { location: {
city: extractCity(info.address) || 'دمشق', city: extractCity(info.address) || 'دمشق',
district: info.address || '', district: info.address || '',
@ -85,7 +97,9 @@ function mapApiProperty(item, index) {
priceDisplay: { priceDisplay: {
daily: dailyPrice, daily: dailyPrice,
monthly: monthlyPrice, monthly: monthlyPrice,
sale: salePrice,
}, },
ownerSource,
bookings: [], bookings: [],
_raw: item, _raw: item,
}; };
@ -175,6 +189,16 @@ export default function HomePage() {
setSearchFilters(filters); setSearchFilters(filters);
const filtered = allProperties.filter(property => { const filtered = allProperties.filter(property => {
if (filters.mode === 'rent' && property.listingType !== 'rent') {
return false;
}
if (filters.mode === 'sell' && property.listingType !== 'sale') {
return false;
}
if (filters.mode === 'buy' && property.listingType !== 'sale') {
return false;
}
if (filters.city && filters.city !== 'all' && property.location.city !== filters.city) { if (filters.city && filters.city !== 'all' && property.location.city !== filters.city) {
return false; return false;
} }
@ -194,6 +218,20 @@ export default function HomePage() {
} }
} }
if (filters.ownerSource && filters.ownerSource !== 'all') {
if (filters.ownerSource === 'owner' && property.ownerSource !== 'owner') return false;
if (filters.ownerSource === 'agency' && property.ownerSource !== 'agency') return false;
}
if (filters.rentPeriod && filters.rentPeriod !== 'all' && property.listingType === 'rent') {
if (filters.rentPeriod === 'daily' && !property.priceDisplay.daily) return false;
if (filters.rentPeriod === 'monthly' && !property.priceDisplay.monthly) return false;
}
if (filters.availableToday) {
if (property.status !== 'available') return false;
}
if (filters.identityType && property.allowedIdentities) { if (filters.identityType && property.allowedIdentities) {
if (!property.allowedIdentities.includes(filters.identityType)) { if (!property.allowedIdentities.includes(filters.identityType)) {
return false; return false;
@ -312,7 +350,7 @@ export default function HomePage() {
</motion.p> </motion.p>
</motion.div> </motion.div>
{!isOwner && <HeroSearch onSearch={applyFilters} />} {!isOwner && <HeroSearch onSearch={applyFilters} isAuthenticated={!!user} />}
{isOwner && ( {isOwner && (
<motion.div <motion.div
@ -477,6 +515,25 @@ export default function HomePage() {
searchFilters.priceRange === '2000-3000' ? '200$ - 300$' : 'أكثر من 300$'} searchFilters.priceRange === '2000-3000' ? '200$ - 300$' : 'أكثر من 300$'}
</span> </span>
</div> </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> </motion.div>
)} )}
</div> </div>