-
-
محدد
+
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",