Compare commits
2 Commits
f761ab6f48
...
ae600ad41b
| Author | SHA1 | Date | |
|---|---|---|---|
| ae600ad41b | |||
| 16b1c7c6f6 |
@ -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>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
69
app/page.js
69
app/page.js
@ -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>
|
||||||
|
|||||||
Reference in New Issue
Block a user