diff --git a/app/components/PropertyMapWithMarkers.js b/app/components/PropertyMapWithMarkers.js
new file mode 100644
index 0000000..3665d53
--- /dev/null
+++ b/app/components/PropertyMapWithMarkers.js
@@ -0,0 +1,100 @@
+'use client';
+
+import { useState, useEffect, useRef } from 'react';
+import L from 'leaflet';
+import 'leaflet/dist/leaflet.css';
+
+delete L.Icon.Default.prototype._getIconUrl;
+L.Icon.Default.mergeOptions({
+ iconRetinaUrl: 'https://cdnjs.cloudflare.com/ajax/libs/leaflet/1.7.1/images/marker-icon-2x.png',
+ iconUrl: 'https://cdnjs.cloudflare.com/ajax/libs/leaflet/1.7.1/images/marker-icon.png',
+ shadowUrl: 'https://cdnjs.cloudflare.com/ajax/libs/leaflet/1.7.1/images/marker-shadow.png',
+});
+
+export default function PropertyMapWithMarkers({ properties = [], onPropertyClick }) {
+ const mapRef = useRef(null);
+ const mapInstanceRef = useRef(null);
+ const markersRef = useRef([]);
+ const [mapLoaded, setMapLoaded] = useState(false);
+
+ useEffect(() => {
+ if (!mapRef.current || mapInstanceRef.current) return;
+
+ const map = L.map(mapRef.current).setView([33.5138, 38.9968], 7);
+
+ L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
+ attribution: '© OpenStreetMap',
+ maxZoom: 19,
+ }).addTo(map);
+
+ mapInstanceRef.current = map;
+ setMapLoaded(true);
+
+ return () => {
+ if (mapInstanceRef.current) {
+ mapInstanceRef.current.remove();
+ mapInstanceRef.current = null;
+ }
+ };
+ }, []);
+
+ useEffect(() => {
+ if (!mapInstanceRef.current || !mapLoaded) return;
+
+ markersRef.current.forEach(marker => marker.remove());
+ markersRef.current = [];
+
+ properties.forEach(property => {
+ if (property.lat && property.lng) {
+ const marker = L.marker([property.lat, property.lng]).addTo(mapInstanceRef.current);
+
+ const popupContent = `
+
+
${property.title || 'عقار'}
+
${property.address || property.location?.address || ''}
+
+ ${formatPrice(property)}
+
+ ${property.images && property.images.length > 0 ? `

` : ''}
+
+ ${property.type ? `
النوع: ${property.type}
` : ''}
+ ${property.bedrooms > 0 ? `
غرف نوم: ${property.bedrooms}
` : ''}
+ ${property.bathrooms > 0 ? `
حمامات: ${property.bathrooms}
` : ''}
+
+
+ `;
+
+ marker.bindPopup(popupContent);
+
+ marker.on('click', () => {
+ if (onPropertyClick) {
+ onPropertyClick(property);
+ }
+ });
+
+ markersRef.current.push(marker);
+ }
+ });
+
+ if (markersRef.current.length > 0) {
+ const group = L.featureGroup(markersRef.current);
+ mapInstanceRef.current.fitBounds(group.getBounds(), { padding: [50, 100] });
+ }
+ }, [properties, mapLoaded]);
+
+ const formatPrice = (property) => {
+ if (property.priceUnit === 'monthly') {
+ return `${property.price?.toLocaleString() || 0} ل.س/شهر`;
+ } else if (property.priceUnit === 'daily') {
+ return `${property.price?.toLocaleString() || 0} ل.س/يوم`;
+ } else {
+ return `${property.price?.toLocaleString() || 0} ل.س`;
+ }
+ };
+
+ return (
+
+ );
+}
\ No newline at end of file
diff --git a/app/components/home/HeroSearch.js b/app/components/home/HeroSearch.js
index 7f21093..ff6d0c4 100644
--- a/app/components/home/HeroSearch.js
+++ b/app/components/home/HeroSearch.js
@@ -67,7 +67,9 @@ export default function HeroSearch({ onSearch, isAuthenticated }) {
setActiveTab(tab);
if ((tab === 'rent' || tab === 'sell') && !isAuthenticated) {
setShowLoginDialog(true);
+ return;
}
+ handleSearch();
};
const handleSearch = () => {
diff --git a/app/page.js b/app/page.js
index 67b3065..4d965ce 100644
--- a/app/page.js
+++ b/app/page.js
@@ -26,15 +26,13 @@ import {
MessageCircle
} from 'lucide-react';
import HeroSearch from './components/home/HeroSearch';
-import PropertyMap from './components/home/PropertyMap';
+import PropertyMapWithMarkers from './components/PropertyMapWithMarkers';
import Link from 'next/link';
import Image from 'next/image';
import { getRentProperties, getSaleProperties } from './utils/api';
import { BuildingTypeKeys, PropertyStatusKeys, extractCity } from './enums';
import AuthService from './services/AuthService';
-// Map API property data to the format the UI expects
-// API returns { propertyInformationId, deposit, monthlyRent, dailyRent, rating, propertyInformation: {...}, ... }
function mapApiProperty(item, index) {
const info = item.propertyInformation || {};
@@ -56,7 +54,6 @@ function mapApiProperty(item, index) {
if (info.numberOfBedRooms) features.push(`${info.numberOfBedRooms} غرف نوم`);
if (info.numberOfBathRooms) features.push(`${info.numberOfBathRooms} حمامات`);
- // Extract images from API and build full URLs
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 : [];
const images = rawImages.length > 0
@@ -105,10 +102,6 @@ function mapApiProperty(item, index) {
};
}
-// extractCity is now imported from @/app/enums
-
-// API-only — no fallback data
-
export default function HomePage() {
const mapSectionRef = useRef(null);
const [searchFilters, setSearchFilters] = useState(null);
@@ -121,9 +114,10 @@ export default function HomePage() {
const pathname = usePathname();
const [allProperties, setAllProperties] = useState([]);
+ const [rentProperties, setRentProperties] = useState([]);
+ const [saleProperties, setSaleProperties] = useState([]);
const [loading, setLoading] = useState(true);
- // Re-read user from JWT on every route change
useEffect(() => {
const authUser = AuthService.getUser();
if (authUser) {
@@ -137,7 +131,6 @@ export default function HomePage() {
}
}, [pathname]);
- // Fetch properties from API on mount
useEffect(() => {
async function fetchProperties() {
@@ -150,15 +143,12 @@ export default function HomePage() {
const rentList = Array.isArray(rentData) ? rentData : [];
const saleList = Array.isArray(saleData) ? saleData : [];
- const mapped = [
- ...rentList.map((p, i) => mapApiProperty(p, i)),
- ...saleList.map((p, i) => mapApiProperty(p, rentList.length + i)),
- ];
+ const mappedRent = rentList.map((p, i) => mapApiProperty(p, i));
+ const mappedSale = saleList.map((p, i) => mapApiProperty(p, rentList.length + i));
- if (mapped.length > 0) {
- setAllProperties(mapped);
- }
- // If API returns empty, keep fallback
+ setRentProperties(mappedRent);
+ setSaleProperties(mappedSale);
+ setAllProperties([...mappedRent, ...mappedSale]);
} catch (err) {
console.error('[Home] Failed to fetch properties:', err);
} finally {
@@ -170,14 +160,10 @@ export default function HomePage() {
}, []);
useEffect(() => {
- const handleClickOutside = (event) => {
- if (menuRef.current && !menuRef.current.contains(event.target)) {
- setShowUserMenu(false);
- }
- };
- document.addEventListener('mousedown', handleClickOutside);
- return () => document.removeEventListener('mousedown', handleClickOutside);
- }, []);
+ if (searchFilters) {
+ applyFilters(searchFilters);
+ }
+ }, [rentProperties, saleProperties, searchFilters]);
const logout = () => {
AuthService.deleteToken();
@@ -188,17 +174,16 @@ export default function HomePage() {
const applyFilters = (filters) => {
setSearchFilters(filters);
- 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;
- }
+ let propertiesToFilter = [];
+ if (filters.mode === 'rent') {
+ propertiesToFilter = rentProperties;
+ } else if (filters.mode === 'buy' || filters.mode === 'sell') {
+ propertiesToFilter = saleProperties;
+ } else {
+ propertiesToFilter = allProperties;
+ }
+ const filtered = propertiesToFilter.filter(property => {
if (filters.city && filters.city !== 'all' && property.location.city !== filters.city) {
return false;
}
@@ -467,9 +452,16 @@ export default function HomePage() {
transition={{ delay: 0.3, type: "spring" }}
>
{filteredProperties.length > 0 ? (
- ({
+ ...p,
+ lat: p.location.lat,
+ lng: p.location.lng,
+ address: p.location.address
+ }))}
+ onPropertyClick={(property) => {
+ console.log('Property clicked:', property);
+ }}
/>
) : (