From 8c75c7c6590c6fa295c7829ca6eb677bd6e48145 Mon Sep 17 00:00:00 2001 From: Rahaf Date: Sat, 7 Mar 2026 07:34:31 +0300 Subject: [PATCH] Added map for home --- app/components/home/HeroSearch.js | 192 +++++ app/components/home/PropertyMap.js | 287 ++++++++ app/components/property/BookingCalendar.js | 92 +-- app/components/property/PropertyMap.js | 166 +++++ app/globals.css | 48 ++ app/page.js | 809 +++++++++++++-------- app/properties/page.js | 15 +- package-lock.json | 35 +- package.json | 4 +- 9 files changed, 1256 insertions(+), 392 deletions(-) create mode 100644 app/components/home/HeroSearch.js create mode 100644 app/components/home/PropertyMap.js create mode 100644 app/components/property/PropertyMap.js diff --git a/app/components/home/HeroSearch.js b/app/components/home/HeroSearch.js new file mode 100644 index 0000000..0d9f997 --- /dev/null +++ b/app/components/home/HeroSearch.js @@ -0,0 +1,192 @@ +'use client'; + +import { useState } from 'react'; +import { motion } from 'framer-motion'; +import { useTranslation } from 'react-i18next'; +import { Search, MapPin, Home, DollarSign } from 'lucide-react'; + +export default function HeroSearch({ onSearch }) { + const { t } = useTranslation(); + const [activeTab, setActiveTab] = useState('rent'); + const [filters, setFilters] = useState({ + city: '', + propertyType: '', + priceRange: '', + identityType: 'syrian' + }); + + const cities = [ + { id: 'all', label: 'جميع المدن' }, + { id: 'دمشق', label: 'دمشق' }, + { id: 'حلب', label: 'حلب' }, + { id: 'حمص', label: 'حمص' }, + { id: 'اللاذقية', label: 'اللاذقية' }, + { id: 'درعا', label: 'درعا' } + ]; + + const propertyTypes = [ + { id: 'all', label: 'الكل' }, + { id: 'apartment', label: 'شقة' }, + { id: 'villa', label: 'فيلا' }, + { id: 'house', label: 'بيت' }, + { id: 'studio', label: 'استوديو' } + ]; + + const priceRanges = [ + { id: 'all', label: 'جميع الأسعار' }, + { id: '0-500', label: 'أقل من 50$' }, + { id: '500-1000', label: '50$ - 100$' }, + { id: '1000-2000', label: '100$ - 200$' }, + { id: '2000-3000', label: '200$ - 300$' }, + { id: '3000+', label: 'أكثر من 300$' } + ]; + + const identityTypes = [ + { id: 'syrian', label: 'هوية سورية' }, + { id: 'passport', label: 'جواز سفر' } + ]; + + const handleSearch = () => { + onSearch({ + ...filters, + propertyType: filters.propertyType || 'all', + city: filters.city || 'all', + priceRange: filters.priceRange || 'all' + }); + }; + + return ( + +
+ {['rent', 'buy', 'sell'].map((tab) => ( + setActiveTab(tab)} + className={`px-4 py-2 rounded-lg font-medium text-sm transition-all ${ + activeTab === tab + ? 'bg-amber-500 text-white' + : 'bg-white/20 text-white hover:bg-white/30' + }`} + whileHover={{ scale: 1.05 }} + whileTap={{ scale: 0.95 }} + > + {t(`${tab}Tab`)} + + ))} +
+
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+
+ + + {t("searchButton")} + +
+
+ ); +} \ No newline at end of file diff --git a/app/components/home/PropertyMap.js b/app/components/home/PropertyMap.js new file mode 100644 index 0000000..7e78dad --- /dev/null +++ b/app/components/home/PropertyMap.js @@ -0,0 +1,287 @@ +'use client'; + +import { useState, useEffect, useRef } from 'react'; +import { motion, AnimatePresence } from 'framer-motion'; +import { MapPin, DollarSign, X, Navigation, Bed, Bath, Square, Star, Calendar, Heart, ChevronLeft, ChevronRight } from 'lucide-react'; +import L from 'leaflet'; +import 'leaflet/dist/leaflet.css'; +import Image from 'next/image'; +import Link from 'next/link'; +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', +}); + +const PropertyPopup = ({ property, onClose, onBook }) => { + const [currentImage, setCurrentImage] = useState(0); + const [isFavorite, setIsFavorite] = useState(false); + const [selectedDays, setSelectedDays] = useState(1); + + const formatCurrency = (amount) => { + return amount?.toLocaleString() + ' ل.س'; + }; + + const quickDays = [1, 3, 7, 14]; + + return ( + +
+ {property.title} +
+ + +
+
{formatCurrency(property.price)}
+
/ليلة
+
+
+ + {property.rating} +
+ {property.images.length > 1 && ( + <> + + + + )} + {property.images.length > 1 && ( +
+ {property.images.map((_, idx) => ( +
+ ))} +
+ )} +
+
+
+

{property.title}

+
+ + {property.location.address || `${property.location.city}، ${property.location.district}`} +
+
+
+
+ + {property.bedrooms} غرف +
+
+
+ + {property.bathrooms} حمامات +
+
+
+ + {property.area}م² +
+
+
+ +
+ {quickDays.map(days => ( + + ))} +
+
+
+ الإجمالي +
+ {formatCurrency(property.price * selectedDays)} + لـ {selectedDays} {selectedDays === 1 ? 'يوم' : 'أيام'} +
+
+
+
+ {property.features.slice(0, 6).map((feature, idx) => ( + + {feature} + + ))} +
+
+ +
+ + التفاصيل + + +
+
+ +
+
+
+ + ); +}; + +export default function PropertyMap({ properties, userIdentity = 'syrian' }) { + const [selectedProperty, setSelectedProperty] = useState(null); + const [mapLoaded, setMapLoaded] = useState(false); + const mapRef = useRef(null); + const mapInstanceRef = useRef(null); + const markersRef = useRef([]); + + const filteredProperties = properties.filter(property => { + if (!property.allowedIdentities) return true; + return property.allowedIdentities.includes(userIdentity); + }); + + useEffect(() => { + if (!mapRef.current || mapInstanceRef.current) return; + + const map = L.map(mapRef.current).setView([34.8021, 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 || !filteredProperties.length) return; + + markersRef.current.forEach(marker => marker.remove()); + markersRef.current = []; + + filteredProperties.forEach(property => { + if (property.location?.lat && property.location?.lng) { + const customIcon = L.divIcon({ + className: 'custom-marker', + html: ` +
+
+
+
+
+ $${property.priceUSD} +
+
+ `, + iconSize: [36, 45], + iconAnchor: [18, 45], + popupAnchor: [0, -45], + }); + + const marker = L.marker([property.location.lat, property.location.lng], { icon: customIcon }) + .addTo(mapInstanceRef.current) + .on('click', () => { + setSelectedProperty(property); + mapInstanceRef.current.panBy([0, -100], { animate: true, duration: 0.5 }); + }); + + markersRef.current.push(marker); + } + }); + + if (markersRef.current.length > 0) { + const group = L.featureGroup(markersRef.current); + mapInstanceRef.current.fitBounds(group.getBounds(), { padding: [50, 100] }); + } + }, [filteredProperties, mapLoaded]); + + return ( +
+
+ + + {selectedProperty && ( + setSelectedProperty(null)} + /> + )} + + +
+ {filteredProperties.length} + عقار +
+
+ ); +} \ No newline at end of file diff --git a/app/components/property/BookingCalendar.js b/app/components/property/BookingCalendar.js index 8ea9d0c..4406f60 100644 --- a/app/components/property/BookingCalendar.js +++ b/app/components/property/BookingCalendar.js @@ -4,10 +4,9 @@ import { useState } from 'react'; import { motion } from 'framer-motion'; import { Calendar as CalendarIcon, ChevronLeft, ChevronRight } from 'lucide-react'; -export default function BookingCalendar({ property, onDateSelect }) { +export default function BookingCalendar({ property }) { const [currentMonth, setCurrentMonth] = useState(new Date()); const [selectedRange, setSelectedRange] = useState({ start: null, end: null }); - const [bookedDates, setBookedDates] = useState(property.bookings || []); const daysInMonth = new Date( currentMonth.getFullYear(), @@ -27,8 +26,9 @@ export default function BookingCalendar({ property, onDateSelect }) { ]; const isDateBooked = (date) => { + if (!property.bookings) return false; const dateStr = date.toISOString().split('T')[0]; - return bookedDates.some(booking => { + return property.bookings.some(booking => { const start = new Date(booking.startDate); const end = new Date(booking.endDate); const current = new Date(date); @@ -36,35 +36,8 @@ export default function BookingCalendar({ property, onDateSelect }) { }); }; - const isInSelectedRange = (date) => { - if (!selectedRange.start || !selectedRange.end) return false; - const dateStr = date.toISOString().split('T')[0]; - return dateStr >= selectedRange.start && dateStr <= selectedRange.end; - }; - const handleDateClick = (date) => { if (isDateBooked(date)) return; - - const dateStr = date.toISOString().split('T')[0]; - - if (!selectedRange.start || (selectedRange.start && selectedRange.end)) { - setSelectedRange({ start: dateStr, end: null }); - } else { - if (dateStr > selectedRange.start) { - setSelectedRange({ ...selectedRange, end: dateStr }); - onDateSelect?.({ start: selectedRange.start, end: dateStr }); - } else { - setSelectedRange({ start: dateStr, end: null }); - } - } - }; - - const changeMonth = (direction) => { - setCurrentMonth(new Date( - currentMonth.getFullYear(), - currentMonth.getMonth() + direction, - 1 - )); }; const renderDays = () => { @@ -83,24 +56,19 @@ export default function BookingCalendar({ property, onDateSelect }) { ); const isBooked = isDateBooked(date); - const isSelected = isInSelectedRange(date); - const isToday = date.toDateString() === new Date().toDateString(); days.push( - handleDateClick(date)} disabled={isBooked} className={` - p-2 rounded-lg text-center transition-all + p-2 rounded-lg text-center text-sm transition-all ${isBooked ? 'bg-gray-200 text-gray-400 cursor-not-allowed line-through' : 'hover:bg-amber-100 cursor-pointer'} - ${isSelected ? 'bg-amber-500 text-white hover:bg-amber-600' : ''} - ${isToday && !isSelected && !isBooked ? 'border-2 border-amber-500' : ''} `} > {dayNumber} - + ); } } @@ -108,53 +76,47 @@ export default function BookingCalendar({ property, onDateSelect }) { }; return ( -
-
+
+
-

- +

+ {monthNames[currentMonth.getMonth()]} {currentMonth.getFullYear()} -

+

- -
-
جمعة
-
سبت
+
أحد
إثنين
ثلاثاء
أربعاء
خميس
+
جمعة
+
سبت
{renderDays()}
- -
-
-
- محدد +
+
+
+ محجوز
-
-
- محجوز -
-
-
- اليوم +
+
+ متاح
diff --git a/app/components/property/PropertyMap.js b/app/components/property/PropertyMap.js new file mode 100644 index 0000000..a4f397f --- /dev/null +++ b/app/components/property/PropertyMap.js @@ -0,0 +1,166 @@ +'use client'; + +import { useState, useEffect, useRef } from 'react'; +import { motion, AnimatePresence } from 'framer-motion'; +import { MapPin, DollarSign, X, Navigation } from 'lucide-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 PropertyMap({ properties, onPropertySelect }) { + const [selectedProperty, setSelectedProperty] = useState(null); + const [mapLoaded, setMapLoaded] = useState(false); + const mapRef = useRef(null); + const mapInstanceRef = useRef(null); + const markersRef = useRef([]); + + useEffect(() => { + if (!mapRef.current || mapInstanceRef.current) return; + + const map = L.map(mapRef.current).setView([34.8021, 38.9968], 7); + + L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', { + attribution: '© OpenStreetMap contributors', + maxZoom: 19, + }).addTo(map); + + mapInstanceRef.current = map; + setMapLoaded(true); + + return () => { + if (mapInstanceRef.current) { + mapInstanceRef.current.remove(); + mapInstanceRef.current = null; + } + }; + }, []); + + useEffect(() => { + if (!mapInstanceRef.current || !properties.length) return; + markersRef.current.forEach(marker => marker.remove()); + markersRef.current = []; + const customIcon = L.divIcon({ + className: 'custom-marker', + html: `
+ +
`, + iconSize: [40, 40], + iconAnchor: [20, 40], + popupAnchor: [0, -40], + }); + + properties.forEach(property => { + if (property.location?.lat && property.location?.lng) { + const marker = L.marker([property.location.lat, property.location.lng], { icon: customIcon }) + .addTo(mapInstanceRef.current) + .on('click', () => { + setSelectedProperty(property); + onPropertySelect?.(property); + }); + + marker.bindTooltip(property.title, { + permanent: false, + direction: 'top', + offset: [0, -40], + className: 'property-tooltip' + }); + + markersRef.current.push(marker); + } + }); + + if (markersRef.current.length > 0) { + const group = L.featureGroup(markersRef.current); + mapInstanceRef.current.fitBounds(group.getBounds(), { padding: [50, 50] }); + } + }, [properties, mapLoaded, onPropertySelect]); + + return ( +
+
+ + {selectedProperty && ( + + + +

{selectedProperty.title}

+ +
+ + {selectedProperty.location?.address || `${selectedProperty.location.city}، ${selectedProperty.location.district}`} +
+ +
+
+
يومي
+
+ {selectedProperty.priceDisplay?.daily?.toLocaleString() || selectedProperty.price?.toLocaleString()} ل.س +
+
+
+
شهري
+
+ {selectedProperty.priceDisplay?.monthly?.toLocaleString() || (selectedProperty.price * 30)?.toLocaleString()} ل.س +
+
+
+ +
+ + +
+
+ )} +
+ +
+ ); +} \ No newline at end of file diff --git a/app/globals.css b/app/globals.css index d592175..4e9397e 100644 --- a/app/globals.css +++ b/app/globals.css @@ -24,3 +24,51 @@ body { color: var(--foreground); font-family: Arial, Helvetica, sans-serif; } + +.leaflet-container { + font-family: inherit; + width: 100%; + height: 100%; +} + +.leaflet-popup-content-wrapper { + border-radius: 1rem; + box-shadow: 0 10px 25px -5px rgba(0, 0, 0, 0.1), 0 8px 10px -6px rgba(0, 0, 0, 0.1); +} + +.leaflet-popup-content { + margin: 0; + min-width: 200px; +} + +.leaflet-popup-tip { + box-shadow: 0 5px 10px rgba(0, 0, 0, 0.1); +} + +.custom-marker { + background: transparent; + border: none; +} + +.custom-marker div { + transition: transform 0.2s ease; +} + +.custom-marker:hover div { + transform: scale(1.1); +} + +.property-tooltip { + background: white !important; + color: #1f2937 !important; + font-weight: 500 !important; + font-size: 0.875rem !important; + padding: 0.5rem 1rem !important; + border-radius: 0.75rem !important; + border: 1px solid #e5e7eb !important; + box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06) !important; +} + +.property-tooltip::before { + border-top-color: white !important; +} \ No newline at end of file diff --git a/app/page.js b/app/page.js index b63f67c..50c6939 100644 --- a/app/page.js +++ b/app/page.js @@ -1,67 +1,271 @@ 'use client'; -import { motion } from 'framer-motion'; -import { useRef } from 'react'; +import { useState, useRef, useEffect } from 'react'; +import { motion, AnimatePresence } from 'framer-motion'; import { useTranslation } from 'react-i18next'; +import { + ShieldCheck, + Lock, + Zap, + Star, + Rocket, + Search, + MapPin, + Home, + DollarSign, + ChevronDown, + Shield, + Award, + Sparkles +} from 'lucide-react'; import './i18n/config'; +import HeroSearch from './components/home/HeroSearch'; +import PropertyMap from './components/home/PropertyMap'; export default function HomePage() { const { t } = useTranslation(); - const constraintsRef = useRef(null); + const mapSectionRef = useRef(null); + const [searchFilters, setSearchFilters] = useState(null); + const [showMap, setShowMap] = useState(false); + const [filteredProperties, setFilteredProperties] = useState([]); + const [isScrolling, setIsScrolling] = useState(false); - const fadeInUp = { - hidden: { opacity: 0, y: 20 }, - visible: { - opacity: 1, - y: 0, - transition: { - duration: 0.6, - ease: "easeOut" - } - } - }; - - const staggerContainer = { - hidden: { opacity: 0 }, - visible: { - opacity: 1, - transition: { - staggerChildren: 0.2, - delayChildren: 0.3 - } - } - }; - - const buttonHover = { - rest: { scale: 1 }, - hover: { - scale: 1.05, - transition: { - type: "spring", - stiffness: 400, - damping: 10 - } + const [allProperties] = useState([ + { + id: 1, + title: 'فيلا فاخرة في المزة', + description: 'فيلا فاخرة مع حديقة خاصة ومسبح في أفضل أحياء دمشق.', + type: 'villa', + price: 500000, + priceUSD: 50, + priceUnit: 'daily', + location: { + city: 'دمشق', + district: 'المزة', + address: 'شارع المزة - فيلات غربية', + lat: 33.5138, + lng: 36.2765 + }, + bedrooms: 5, + bathrooms: 4, + area: 450, + features: ['مسبح', 'حديقة خاصة', 'موقف سيارات', 'أمن 24/7', 'تدفئة مركزية', 'تكييف مركزي'], + images: ['/villa1.jpg', '/villa2.jpg', '/villa3.jpg'], + status: 'available', + rating: 4.8, + isNew: true, + allowedIdentities: ['syrian', 'passport'], + priceDisplay: { + daily: 500000, + monthly: 15000000 + }, + bookings: [ + { startDate: '2024-03-10', endDate: '2024-03-15' }, + { startDate: '2024-03-20', endDate: '2024-03-25' } + ] }, - tap: { scale: 0.95 } + { + id: 2, + title: 'شقة حديثة في الشهباء', + description: 'شقة عصرية في حي الشهباء الراقي بحلب.', + type: 'apartment', + price: 250000, + priceUSD: 25, + priceUnit: 'daily', + location: { + city: 'حلب', + district: 'الشهباء', + address: 'شارع النيل - بناء الرحاب', + lat: 36.2021, + lng: 37.1347 + }, + bedrooms: 3, + bathrooms: 2, + area: 180, + features: ['مطبخ مجهز', 'بلكونة', 'موقف سيارات', 'مصعد'], + images: ['/apartment1.jpg', '/apartment2.jpg'], + status: 'available', + rating: 4.5, + isNew: false, + allowedIdentities: ['syrian'], + priceDisplay: { + daily: 250000, + monthly: 7500000 + }, + bookings: [ + { startDate: '2024-03-05', endDate: '2024-03-08' } + ] + }, + { + id: 3, + title: 'بيت عائلي في بابا عمرو', + description: 'بيت واسع مناسب للعائلات في حمص.', + type: 'house', + price: 350000, + priceUSD: 35, + priceUnit: 'daily', + location: { + city: 'حمص', + district: 'بابا عمرو', + address: 'حي الزهور', + lat: 34.7265, + lng: 36.7186 + }, + bedrooms: 4, + bathrooms: 3, + area: 300, + features: ['حديقة كبيرة', 'موقف سيارات', 'مدفأة', 'كراج'], + images: ['/house1.jpg'], + status: 'booked', + rating: 4.3, + isNew: false, + allowedIdentities: ['syrian', 'passport'], + priceDisplay: { + daily: 350000, + monthly: 10500000 + }, + bookings: [] + }, + { + id: 4, + title: 'شقة بجانب البحر', + description: 'شقة رائعة مع إطلالة بحرية في اللاذقية.', + type: 'apartment', + price: 300000, + priceUSD: 30, + priceUnit: 'daily', + location: { + city: 'اللاذقية', + district: 'الشاطئ الأزرق', + address: 'الكورنيش الغربي', + lat: 35.5306, + lng: 35.7801 + }, + bedrooms: 3, + bathrooms: 2, + area: 200, + features: ['إطلالة بحرية', 'شرفة', 'تكييف', 'أمن'], + images: ['/seaside1.jpg', '/seaside2.jpg', '/seaside3.jpg'], + status: 'available', + rating: 4.9, + isNew: true, + allowedIdentities: ['passport'], + priceDisplay: { + daily: 300000, + monthly: 9000000 + }, + bookings: [] + }, + { + id: 5, + title: 'فيلا في درعا', + description: 'فيلا فاخرة في حي الأطباء بدرعا.', + type: 'villa', + price: 400000, + priceUSD: 40, + priceUnit: 'daily', + location: { + city: 'درعا', + district: 'حي الأطباء', + address: 'شارع الشفاء', + lat: 32.6237, + lng: 36.1016 + }, + bedrooms: 4, + bathrooms: 3, + area: 350, + features: ['حديقة مثمرة', 'أنظمة أمن', 'مسبح', 'كراج'], + images: ['/villa4.jpg', '/villa5.jpg'], + status: 'available', + rating: 4.6, + isNew: false, + allowedIdentities: ['syrian', 'passport'], + priceDisplay: { + daily: 400000, + monthly: 12000000 + }, + bookings: [] + } + ]); + + const applyFilters = (filters) => { + setSearchFilters(filters); + + const filtered = allProperties.filter(property => { + if (filters.city && filters.city !== 'all' && property.location.city !== filters.city) { + return false; + } + + if (filters.propertyType && filters.propertyType !== 'all' && property.type !== filters.propertyType) { + return false; + } + + if (filters.priceRange && filters.priceRange !== 'all') { + const priceUSD = property.priceUSD; + switch(filters.priceRange) { + case '0-500': if (priceUSD > 50) return false; break; + case '500-1000': if (priceUSD < 51 || priceUSD > 100) return false; break; + case '1000-2000': if (priceUSD < 101 || priceUSD > 200) return false; break; + case '2000-3000': if (priceUSD < 201 || priceUSD > 300) return false; break; + case '3000+': if (priceUSD < 301) return false; break; + } + } + + if (filters.identityType && property.allowedIdentities) { + if (!property.allowedIdentities.includes(filters.identityType)) { + return false; + } + } + + return true; + }); + + setFilteredProperties(filtered); + + if (!showMap) { + setShowMap(true); + + setTimeout(() => { + if (mapSectionRef.current) { + setIsScrolling(true); + mapSectionRef.current.scrollIntoView({ + behavior: 'smooth', + block: 'center' + }); + + setTimeout(() => setIsScrolling(false), 1000); + } + }, 300); + } else { + if (mapSectionRef.current) { + setIsScrolling(true); + mapSectionRef.current.scrollIntoView({ + behavior: 'smooth', + block: 'center' + }); + setTimeout(() => setIsScrolling(false), 1000); + } + } }; - const floatingAnimation = { - y: [0, -10, 0], - transition: { - duration: 2, - repeat: Infinity, - ease: "easeInOut" - } + const resetSearch = () => { + setShowMap(false); + setSearchFilters(null); + setFilteredProperties([]); + window.scrollTo({ + top: 0, + behavior: 'smooth' + }); }; return ( -
+
-
{t("heroTitleLine1")}
{t("heroTitleLine2")}
{t("heroSubtitle")}
- - -
- - {t("rentTab")} - - - {t("buyTab")} - - - {t("sellTab")} - -
- -
-
- -
-
- - - - -
- -
-
- -
- -
-
- - - -
- -
- - - -
-
-
- -
- -
-
- - - -
- -
- - - -
-
-
- -
- - - - - {t("searchButton")} - -
-
-
+
- - - - - - + {!showMap && ( + window.scrollTo({ + top: window.innerHeight, + behavior: 'smooth' + })} + > + + + + + )}
- -
-
- + {showMap && ( + -

- {t("whyChooseUsTitle")} -

-

- {t("whyChooseUsSubtitle")} -

-
- - - + {isScrolling && ( - -
- - - -
- -

- {t("feature1Title")} -

-

- {t("feature1Description")} -

-
+ )} - - - -
+ - - - -
- -

- {t("feature2Title")} -

-

- {t("feature2Description")} -

-
+
+

+ {filteredProperties.length > 0 ? 'نتائج البحث' : 'لا توجد نتائج'} +

+ + + + + بحث جديد + +
- + {filteredProperties.length > 0 ? ( +

+ تم العثور على {filteredProperties.length} عقار يطابق معايير البحث +

+ ) : ( +

+ لا توجد عقارات تطابق معايير البحث. جرب تغيير الفلاتر. +

+ )} +
- -
- - - -
- -

- {t("feature3Title")} -

-

- {t("feature3Description")} -

-
-
+ {filteredProperties.length > 0 ? ( + + ) : ( +
+
+ + + +
+

لا توجد نتائج

+

حاول تغيير معايير البحث

+
+ )} +
+ {filteredProperties.length > 0 && searchFilters && ( + +
+ المدينة: + + {searchFilters.city === 'all' ? 'جميع المدن' : searchFilters.city} + +
+
+ نوع العقار: + + {searchFilters.propertyType === 'all' ? 'الكل' : + searchFilters.propertyType === 'apartment' ? 'شقة' : + searchFilters.propertyType === 'villa' ? 'فيلا' : 'بيت'} + +
+
+ نطاق السعر: + + {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$'} + +
+
+ )} +
+ + )} + +
+
+ +
+ لماذا نحن؟ +
+

+ {t("whyChooseUsTitle")} +

+

+ {t("whyChooseUsSubtitle")} +

+
+ +
+ +
+
+ +
+

+ {t("feature1Title")} +

-
+ +

+ {t("feature1Description")} +

+ + +
+
+ +
+

+ {t("feature2Title")} +

+
+ +

+ {t("feature2Description")} +

+
+ +
+
+ +
+

+ {t("feature3Title")} +

+
+ +

+ {t("feature3Description")} +

+
+
+
+
); } \ No newline at end of file diff --git a/app/properties/page.js b/app/properties/page.js index 881994e..6acea43 100644 --- a/app/properties/page.js +++ b/app/properties/page.js @@ -33,6 +33,7 @@ import { import Image from 'next/image'; import Link from 'next/link'; + const PropertyCard = ({ property, viewMode = 'grid' }) => { const [isFavorite, setIsFavorite] = useState(false); const [currentImage, setCurrentImage] = useState(0); @@ -315,11 +316,11 @@ const FilterBar = ({ filters, onFilterChange }) => { const cities = [ { id: 'all', label: 'جميع المدن' }, - { id: 'damascus', label: 'دمشق' }, - { id: 'aleppo', label: 'حلب' }, - { id: 'homs', label: 'حمص' }, - { id: 'latakia', label: 'اللاذقية' }, - { id: 'daraa', label: 'درعا' } + { id: 'دمشق', label: 'دمشق' }, + { id: 'حلب', label: 'حلب' }, + { id: 'حمص', label: 'حمص' }, + { id: 'اللاذقية', label: 'اللاذقية' }, + { id: 'درعا', label: 'درعا' } ]; return ( @@ -495,7 +496,7 @@ const FilterBar = ({ filters, onFilterChange }) => { }; export default function PropertiesPage() { - const [viewMode, setViewMode] = useState('grid'); + const [viewMode, setViewMode] = useState('grid'); const [sortBy, setSortBy] = useState('newest'); const [filters, setFilters] = useState({ search: '', @@ -670,6 +671,7 @@ export default function PropertiesPage() { className={`p-2 rounded-xl transition-colors ${ viewMode === 'grid' ? 'bg-gray-800 text-white' : 'bg-gray-100 text-gray-600 hover:bg-gray-200' }`} + title="عرض شبكي" > @@ -678,6 +680,7 @@ export default function PropertiesPage() { className={`p-2 rounded-xl transition-colors ${ viewMode === 'list' ? 'bg-gray-800 text-white' : 'bg-gray-100 text-gray-600 hover:bg-gray-200' }`} + title="عرض قائمة" > diff --git a/package-lock.json b/package-lock.json index 197b25f..46c6cde 100644 --- a/package-lock.json +++ b/package-lock.json @@ -13,11 +13,13 @@ "framer-motion": "^12.29.2", "i18next": "^25.8.0", "i18next-browser-languagedetector": "^8.2.0", + "leaflet": "^1.9.4", "lucide-react": "^0.563.0", "next": "16.1.6", "react": "19.2.3", "react-dom": "19.2.3", - "react-i18next": "^16.5.4" + "react-i18next": "^16.5.4", + "react-leaflet": "^5.0.0" }, "devDependencies": { "@tailwindcss/postcss": "^4", @@ -851,6 +853,17 @@ "url": "https://opencollective.com/popperjs" } }, + "node_modules/@react-leaflet/core": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@react-leaflet/core/-/core-3.0.0.tgz", + "integrity": "sha512-3EWmekh4Nz+pGcr+xjf0KNyYfC3U2JjnkWsh0zcqaexYqmmB5ZhH37kz41JXGmKzpaMZCnPofBBm64i+YrEvGQ==", + "license": "Hippocratic-2.1", + "peerDependencies": { + "leaflet": "^1.9.0", + "react": "^19.0.0", + "react-dom": "^19.0.0" + } + }, "node_modules/@rollup/plugin-node-resolve": { "version": "15.3.1", "resolved": "https://registry.npmjs.org/@rollup/plugin-node-resolve/-/plugin-node-resolve-15.3.1.tgz", @@ -1886,6 +1899,12 @@ "node": ">= 8" } }, + "node_modules/leaflet": { + "version": "1.9.4", + "resolved": "https://registry.npmjs.org/leaflet/-/leaflet-1.9.4.tgz", + "integrity": "sha512-nxS1ynzJOmOlHp+iL3FyWqK89GtNL8U8rvlMOsQdTTssxZwCXh8N2NB3GDQOL+YR3XnWyZAxwQixURb+FA74PA==", + "license": "BSD-2-Clause" + }, "node_modules/lightningcss": { "version": "1.30.2", "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.30.2.tgz", @@ -2471,6 +2490,20 @@ } } }, + "node_modules/react-leaflet": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/react-leaflet/-/react-leaflet-5.0.0.tgz", + "integrity": "sha512-CWbTpr5vcHw5bt9i4zSlPEVQdTVcML390TjeDG0cK59z1ylexpqC6M1PJFjV8jD7CF+ACBFsLIDs6DRMoLEofw==", + "license": "Hippocratic-2.1", + "dependencies": { + "@react-leaflet/core": "^3.0.0" + }, + "peerDependencies": { + "leaflet": "^1.9.0", + "react": "^19.0.0", + "react-dom": "^19.0.0" + } + }, "node_modules/readdirp": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz", diff --git a/package.json b/package.json index 3cc1602..564fd5f 100644 --- a/package.json +++ b/package.json @@ -13,11 +13,13 @@ "framer-motion": "^12.29.2", "i18next": "^25.8.0", "i18next-browser-languagedetector": "^8.2.0", + "leaflet": "^1.9.4", "lucide-react": "^0.563.0", "next": "16.1.6", "react": "19.2.3", "react-dom": "19.2.3", - "react-i18next": "^16.5.4" + "react-i18next": "^16.5.4", + "react-leaflet": "^5.0.0" }, "devDependencies": { "@tailwindcss/postcss": "^4",