Edit button nav

Edit add properties for owner
This commit is contained in:
Rahaf
2026-06-28 14:55:23 +03:00
parent 12cb804b58
commit 9dcbb940c6
4 changed files with 364 additions and 160 deletions

View File

@ -159,7 +159,7 @@ export default function ClientLayout({ children }) {
}
return (
<>
<div className="min-h-screen flex flex-col">
{!isAuthPage && !isAuthenticated && (
<nav className="fixed top-0 left-0 right-0 bg-white/95 backdrop-blur-sm border-b border-gray-200 z-50 transition-all duration-300 shadow-sm">
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
@ -714,14 +714,15 @@ export default function ClientLayout({ children }) {
<NotificationsProvider>
<FavoritesProvider>
<main
className={`${!isAuthPage && !isProfilePage && !isAuthenticated ? "pt-20" : ""} min-h-screen bg-gradient-to-b from-gray-50 to-white ${currentLanguage === "ar" ? "text-right" : "text-left"}`}
className={`${!isAuthPage && !isProfilePage && !isAuthenticated ? "pt-20" : ""} flex-1 flex flex-col bg-gray-50 ${currentLanguage === "ar" ? "text-right" : "text-left"}`}
>
{children}
<div className="flex-1">
{children}
</div>
{isAuthenticated && !isAuthPage && (
<BottomNav isOwner={isOwner} isOwnerOrAgent={isOwnerOrAgent} />
)}
</main>
{isAuthenticated && !isAuthPage && (
<BottomNav isOwner={isOwner} isOwnerOrAgent={isOwnerOrAgent} />
)}
</FavoritesProvider>
</NotificationsProvider>
@ -801,6 +802,6 @@ export default function ClientLayout({ children }) {
</footer>
)}
<NotificationHandler />
</>
</div>
);
}

View File

@ -33,8 +33,8 @@ export default function BottomNav({ isOwner, isOwnerOrAgent }) {
};
return (
<nav className="fixed bottom-4 left-1/2 transform -translate-x-1/2 z-50">
<div className="bg-white/95 backdrop-blur-sm border border-gray-200 rounded-3xl shadow-lg px-2 py-2 flex items-center gap-3">
<nav className="bg-gray-50 pb-4 pt-2">
<div className="flex items-center justify-center gap-3">
{items.map((it) => {
const Icon = it.icon;
const active = isActive(it.href);

View File

@ -0,0 +1,85 @@
'use client';
import { useEffect, useRef } from 'react';
export default function AddPropertyMap({ mapCenter, mapZoom, selectedLocation, onMapClick, onMarkerDragEnd }) {
const containerRef = useRef(null);
const mapRef = useRef(null);
const markerRef = useRef(null);
const LRef = useRef(null);
useEffect(() => {
async function init() {
const L = await import('leaflet');
LRef.current = L;
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',
});
if (!containerRef.current || mapRef.current) return;
const map = L.map(containerRef.current, {
center: mapCenter,
zoom: mapZoom,
doubleClickZoom: false,
});
L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
attribution: '&copy; OpenStreetMap contributors',
}).addTo(map);
map.on('click', (e) => {
const pos = [e.latlng.lat, e.latlng.lng];
placeMarker(pos);
map.setView(pos, mapZoom);
onMapClick(pos);
});
mapRef.current = map;
setTimeout(() => map.invalidateSize(), 200);
}
init();
return () => {
if (mapRef.current) {
mapRef.current.remove();
mapRef.current = null;
}
};
}, []);
function placeMarker(pos) {
const L = LRef.current;
if (!L || !mapRef.current) return;
if (markerRef.current) {
markerRef.current.setLatLng(pos);
} else {
markerRef.current = L.marker(pos, { draggable: true }).addTo(mapRef.current);
markerRef.current.on('dragend', (ev) => {
const p = ev.target.getLatLng();
onMarkerDragEnd(p.lat, p.lng);
});
}
}
useEffect(() => {
const map = mapRef.current;
if (!map) return;
map.setView(mapCenter, mapZoom);
}, [mapCenter, mapZoom]);
useEffect(() => {
if (!selectedLocation || !mapRef.current) return;
const pos = [selectedLocation.lat, selectedLocation.lng];
mapRef.current.setView(pos, mapZoom);
placeMarker(pos);
}, [selectedLocation]);
return <div ref={containerRef} className="w-full h-full z-0" />;
}

View File

@ -5,7 +5,6 @@ import { motion, AnimatePresence } from 'framer-motion';
import { useRouter, useSearchParams } from 'next/navigation';
import Link from 'next/link';
import Image from 'next/image';
import dynamic from 'next/dynamic';
import 'leaflet/dist/leaflet.css';
import {
ArrowLeft,
@ -68,21 +67,8 @@ import {
CurrencyLabels
} from '../../../enums';
const MapContainer = dynamic(() => import('react-leaflet').then(mod => mod.MapContainer), { ssr: false });
const TileLayer = dynamic(() => import('react-leaflet').then(mod => mod.TileLayer), { ssr: false });
const Marker = dynamic(() => import('react-leaflet').then(mod => mod.Marker), { ssr: false });
const Popup = dynamic(() => import('react-leaflet').then(mod => mod.Popup), { ssr: false });
import { useMapEvents } from 'react-leaflet';
function MapClickHandler({ onMapClick }) {
const map = useMapEvents({
click: (e) => {
const { lat, lng } = e.latlng;
onMapClick([lat, lng]);
},
});
return null;
}
import dynamic from 'next/dynamic';
const AddPropertyMap = dynamic(() => import('@/app/components/property/AddPropertyMap'), { ssr: false });
export default function AddPropertyPage() {
const router = useRouter();
@ -123,7 +109,7 @@ export default function AddPropertyPage() {
[PropertyTerm.NO_PARTIES]: false
},
offerType: 'daily',
offerType: '',
dailyPrice: '',
monthlyPrice: '',
@ -174,13 +160,13 @@ export default function AddPropertyPage() {
const [mapCenter, setMapCenter] = useState([33.5138, 36.2765]);
const [mapZoom, setMapZoom] = useState(13);
const [searchQuery, setSearchQuery] = useState('');
const [mapLoaded, setMapLoaded] = useState(false);
const [currencies, setCurrencies] = useState([]);
const [selectedCurrencyId, setSelectedCurrencyId] = useState(Currency.SYP);
const [errors, setErrors] = useState({});
const [isLoading, setIsLoading] = useState(false);
const [agreeToTerms, setAgreeToTerms] = useState(false);
const fileInputRef = useRef(null);
@ -188,7 +174,7 @@ export default function AddPropertyPage() {
{ id: 'apartment', label: 'شقة', icon: Building },
{ id: 'villa', label: 'فيلا', icon: Home },
{ id: 'sweet', label: 'سويت', icon: Sofa },
{ id: 'room', label: 'غرفة', icon: DoorOpen },
{ id: 'room', label: 'غرفة ضمن شقة (سكن مشترك)', icon: DoorOpen },
{ id: 'studio', label: 'استوديو', icon: Sofa },
{ id: 'office', label: 'مكتب', icon: Building },
{ id: 'farms', label: 'مزرعة', icon: Trees },
@ -219,17 +205,6 @@ export default function AddPropertyPage() {
].filter(Boolean);
useEffect(() => {
if (typeof window !== 'undefined') {
const L = require('leaflet');
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',
});
}
setMapLoaded(true);
// Fetch available currencies
getCurrencies().then((data) => {
if (Array.isArray(data) && data.length > 0) {
@ -353,6 +328,25 @@ const handleMapClick = async (coords) => {
setMapZoom(18);
toast.success('تم تحديد الموقع', { id: 'location' });
}
};
const handleMarkerDragEnd = async (lat, lng) => {
try {
const response = await fetch(
`https://nominatim.openstreetmap.org/reverse?format=json&lat=${lat}&lon=${lng}&accept-language=ar`
);
const data = await response.json();
setSelectedLocation({
lat,
lng,
address: data.display_name || 'موقع محدد'
});
} catch (error) {
setSelectedLocation({
lat,
lng,
address: 'موقع محدد'
});
}
};
const confirmLocation = () => {
if (selectedLocation) {
@ -382,8 +376,8 @@ const handleMapClick = async (coords) => {
const handleImageUpload = async (files) => {
const newImages = Array.from(files);
if (formData.images.length + newImages.length > 10) {
toast.error('يمكنك رفع 10 صور كحد أقصى');
if (formData.images.length + newImages.length > 5) {
toast.error('يمكنك رفع 5 صور كحد أقصى');
return;
}
@ -545,13 +539,13 @@ const handleMapClick = async (coords) => {
if (purpose === 'sale') {
if (!formData.salePrice) newErrors.salePrice = 'سعر البيع مطلوب';
} else {
if (formData.offerType === 'daily' && !formData.dailyPrice) {
if (!formData.offerType) {
newErrors.offerType = 'الرجاء اختيار نوع العرض';
} else if (formData.offerType === 'daily' && !formData.dailyPrice) {
newErrors.dailyPrice = 'السعر اليومي مطلوب';
}
if (formData.offerType === 'monthly' && !formData.monthlyPrice) {
} else if (formData.offerType === 'monthly' && !formData.monthlyPrice) {
newErrors.monthlyPrice = 'السعر الشهري مطلوب';
}
if (formData.offerType === 'both') {
} else if (formData.offerType === 'both') {
if (!formData.dailyPrice) newErrors.dailyPrice = 'السعر اليومي مطلوب';
if (!formData.monthlyPrice) newErrors.monthlyPrice = 'السعر الشهري مطلوب';
}
@ -562,8 +556,11 @@ const handleMapClick = async (coords) => {
if (!formData.lat || !formData.lng) {
newErrors.location = 'الرجاء تحديد موقع العقار على الخريطة';
}
if (formData.images.length === 0) {
newErrors.images = 'يجب رفع صورة واحدة على الأقل';
if (formData.images.length < 2) {
newErrors.images = 'يجب رفع صورتين على الأقل';
}
if (formData.images.length > 5) {
newErrors.images = 'يمكن رفع 5 صور كحد أقصى';
}
break;
}
@ -707,6 +704,8 @@ const handleMapClick = async (coords) => {
transition: { duration: 0.5 }
};
const currencySymbol = selectedCurrencyId === Currency.USD ? '$' : 'SP';
return (
<div className="min-h-screen bg-gray-50 py-8">
<Toaster position="top-center" reverseOrder={false} />
@ -854,8 +853,9 @@ const handleMapClick = async (coords) => {
<div className="relative">
<Square className="absolute right-3 top-1/2 transform -translate-y-1/2 w-4 h-4 text-gray-400" />
<input
type="number"
value={formData.space}
type="number"
min="0"
value={formData.space}
onChange={(e) => setFormData({...formData, space: e.target.value})}
className="w-full pr-10 pl-3 py-3 border border-gray-300 rounded-xl focus:outline-none focus:ring-2 focus:ring-amber-500"
placeholder="مثال: 120"
@ -963,6 +963,7 @@ const handleMapClick = async (coords) => {
<label className="block text-sm font-medium text-gray-700 mb-2">عدد الصالونات</label>
<input
type="number"
min="0"
value={formData.salons}
onChange={(e) => setFormData({...formData, salons: e.target.value})}
className="w-full px-4 py-3 border border-gray-300 rounded-xl focus:outline-none focus:ring-2 focus:ring-amber-500"
@ -970,9 +971,10 @@ const handleMapClick = async (coords) => {
/>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-2">عدد البلكونات</label>
<label className="block text-sm font-medium text-gray-700 mb-2">عدد الشرفات</label>
<input
type="number"
min="0"
value={formData.balconies}
onChange={(e) => setFormData({...formData, balconies: e.target.value})}
className="w-full px-4 py-3 border border-gray-300 rounded-xl focus:outline-none focus:ring-2 focus:ring-amber-500"
@ -1113,11 +1115,18 @@ const handleMapClick = async (coords) => {
سعر البيع (ل.س) <span className="text-red-500">*</span>
</label>
<div className="relative">
<DollarSign className="absolute right-3 top-1/2 transform -translate-y-1/2 w-5 h-5 text-gray-400" />
<span className="absolute right-3 top-1/2 transform -translate-y-1/2 w-5 h-5 text-gray-400 font-bold text-sm flex items-center justify-center">{currencySymbol}</span>
<input
type="number"
min="0"
value={formData.salePrice || ''}
onChange={(e) => setFormData({...formData, salePrice: e.target.value})}
onChange={(e) => {
const val = e.target.value;
if (val === '' || parseFloat(val) >= 0) {
setFormData({...formData, salePrice: val});
}
}}
onKeyDown={(e) => { if (['-', 'e', 'E', '+'].includes(e.key)) e.preventDefault() }}
className={`w-full pr-12 pl-4 py-3 border rounded-xl focus:outline-none focus:ring-2 focus:ring-amber-500 ${
errors.salePrice ? 'border-red-500' : 'border-gray-300'
}`}
@ -1190,52 +1199,25 @@ const handleMapClick = async (coords) => {
onChange={(e) => setSelectedCurrencyId(parseInt(e.target.value))}
className="w-full px-4 py-3 border border-gray-300 rounded-xl focus:outline-none focus:ring-2 focus:ring-amber-500"
>
{Object.entries(CurrencyLabels).map(([id, label]) => (
<option key={id} value={id}>
{label}
</option>
))}
<option value={Currency.USD}>دولار امريكي $</option>
<option value={Currency.SYP}>عملة سورية SP</option>
</select>
</div>
{/* Deposit field */}
<div>
<label className="block text-sm font-medium text-gray-700 mb-2">
مبلغ الضمان (العربون)
</label>
<div className="relative">
<DollarSign className="absolute right-3 top-1/2 transform -translate-y-1/2 w-5 h-5 text-gray-400" />
<input
type="number"
value={formData.deposit || ''}
onChange={(e) => setFormData({...formData, deposit: e.target.value})}
className="w-full pr-12 pl-4 py-3 border border-gray-300 rounded-xl focus:outline-none focus:ring-2 focus:ring-amber-500"
placeholder="مثال: 500000"
/>
{/* Price and deposit fields - conditional on offerType */}
{errors.offerType && (
<p className="text-red-500 text-sm mt-1">{errors.offerType}</p>
)}
{!formData.offerType && (
<div className="border-2 border-dashed border-gray-200 rounded-2xl p-8 text-center">
<DollarSign className="w-8 h-8 text-gray-300 mx-auto mb-3" />
<p className="text-gray-400 font-medium">اختر نوع العرض اولا</p>
</div>
</div>
{/* Payment period picker */}
<div>
<label className="block text-sm font-medium text-gray-700 mb-2">
مدة السداد
</label>
<select
value={formData.allowedPaymentPeriod}
onChange={(e) => setFormData({...formData, allowedPaymentPeriod: e.target.value})}
className="w-full px-4 py-3 border border-gray-300 rounded-xl focus:outline-none focus:ring-2 focus:ring-amber-500"
>
<option value="">اختر المدة</option>
<option value="1.00:00:00">يومي</option>
<option value="7.00:00:00">أسبوعي</option>
<option value="30.00:00:00">شهري</option>
<option value="90.00:00:00">ربع سنوي</option>
<option value="365.00:00:00">سنوي</option>
</select>
</div>
)}
<AnimatePresence mode="wait">
{(formData.offerType === 'daily' || formData.offerType === 'both') && (
{formData.offerType === 'daily' && (
<motion.div
key="daily"
initial={{ opacity: 0, height: 0 }}
@ -1245,14 +1227,21 @@ const handleMapClick = async (coords) => {
>
<div>
<label className="block text-sm font-medium text-gray-700 mb-2">
السعر اليومي (ل.س) <span className="text-red-500">*</span>
سعر الايجار اليومي ({currencySymbol}) <span className="text-red-500">*</span>
</label>
<div className="relative">
<DollarSign className="absolute right-3 top-1/2 transform -translate-y-1/2 w-5 h-5 text-gray-400" />
<span className="absolute right-3 top-1/2 transform -translate-y-1/2 w-5 h-5 text-gray-400 font-bold text-sm flex items-center justify-center">{currencySymbol}</span>
<input
type="number"
min="0"
value={formData.dailyPrice}
onChange={(e) => setFormData({...formData, dailyPrice: e.target.value})}
onChange={(e) => {
const val = e.target.value;
if (val === '' || parseFloat(val) >= 0) {
setFormData({...formData, dailyPrice: val});
}
}}
onKeyDown={(e) => { if (['-', 'e', 'E', '+'].includes(e.key)) e.preventDefault() }}
className={`w-full pr-12 pl-4 py-3 border rounded-xl focus:outline-none focus:ring-2 focus:ring-amber-500 ${
errors.dailyPrice ? 'border-red-500' : 'border-gray-300'
}`}
@ -1263,10 +1252,32 @@ const handleMapClick = async (coords) => {
<p className="text-red-500 text-sm mt-1">{errors.dailyPrice}</p>
)}
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-2">
مبلغ التأمين ({currencySymbol})
</label>
<div className="relative">
<span className="absolute right-3 top-1/2 transform -translate-y-1/2 w-5 h-5 text-gray-400 font-bold text-sm flex items-center justify-center">{currencySymbol}</span>
<input
type="number"
min="0"
value={formData.deposit || ''}
onChange={(e) => {
const val = e.target.value;
if (val === '' || parseFloat(val) >= 0) {
setFormData({...formData, deposit: val});
}
}}
onKeyDown={(e) => { if (['-', 'e', 'E', '+'].includes(e.key)) e.preventDefault() }}
className="w-full pr-12 pl-4 py-3 border border-gray-300 rounded-xl focus:outline-none focus:ring-2 focus:ring-amber-500"
placeholder="مثال: 500000"
/>
</div>
</div>
</motion.div>
)}
{(formData.offerType === 'monthly' || formData.offerType === 'both') && (
{formData.offerType === 'monthly' && (
<motion.div
key="monthly"
initial={{ opacity: 0, height: 0 }}
@ -1276,14 +1287,21 @@ const handleMapClick = async (coords) => {
>
<div>
<label className="block text-sm font-medium text-gray-700 mb-2">
السعر الشهري (ل.س) <span className="text-red-500">*</span>
سعر الايجار الشهري ({currencySymbol}) <span className="text-red-500">*</span>
</label>
<div className="relative">
<DollarSign className="absolute right-3 top-1/2 transform -translate-y-1/2 w-5 h-5 text-gray-400" />
<span className="absolute right-3 top-1/2 transform -translate-y-1/2 w-5 h-5 text-gray-400 font-bold text-sm flex items-center justify-center">{currencySymbol}</span>
<input
type="number"
min="0"
value={formData.monthlyPrice}
onChange={(e) => setFormData({...formData, monthlyPrice: e.target.value})}
onChange={(e) => {
const val = e.target.value;
if (val === '' || parseFloat(val) >= 0) {
setFormData({...formData, monthlyPrice: val});
}
}}
onKeyDown={(e) => { if (['-', 'e', 'E', '+'].includes(e.key)) e.preventDefault() }}
className={`w-full pr-12 pl-4 py-3 border rounded-xl focus:outline-none focus:ring-2 focus:ring-amber-500 ${
errors.monthlyPrice ? 'border-red-500' : 'border-gray-300'
}`}
@ -1294,6 +1312,115 @@ const handleMapClick = async (coords) => {
<p className="text-red-500 text-sm mt-1">{errors.monthlyPrice}</p>
)}
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-2">
مبلغ التأمين ({currencySymbol})
</label>
<div className="relative">
<span className="absolute right-3 top-1/2 transform -translate-y-1/2 w-5 h-5 text-gray-400 font-bold text-sm flex items-center justify-center">{currencySymbol}</span>
<input
type="number"
min="0"
value={formData.deposit || ''}
onChange={(e) => {
const val = e.target.value;
if (val === '' || parseFloat(val) >= 0) {
setFormData({...formData, deposit: val});
}
}}
onKeyDown={(e) => { if (['-', 'e', 'E', '+'].includes(e.key)) e.preventDefault() }}
className="w-full pr-12 pl-4 py-3 border border-gray-300 rounded-xl focus:outline-none focus:ring-2 focus:ring-amber-500"
placeholder="مثال: 500000"
/>
</div>
</div>
</motion.div>
)}
{formData.offerType === 'both' && (
<motion.div
key="both"
initial={{ opacity: 0, height: 0 }}
animate={{ opacity: 1, height: 'auto' }}
exit={{ opacity: 0, height: 0 }}
className="space-y-4"
>
<div>
<label className="block text-sm font-medium text-gray-700 mb-2">
سعر الايجار اليومي ({currencySymbol}) <span className="text-red-500">*</span>
</label>
<div className="relative">
<span className="absolute right-3 top-1/2 transform -translate-y-1/2 w-5 h-5 text-gray-400 font-bold text-sm flex items-center justify-center">{currencySymbol}</span>
<input
type="number"
min="0"
value={formData.dailyPrice}
onChange={(e) => {
const val = e.target.value;
if (val === '' || parseFloat(val) >= 0) {
setFormData({...formData, dailyPrice: val});
}
}}
onKeyDown={(e) => { if (['-', 'e', 'E', '+'].includes(e.key)) e.preventDefault() }}
className={`w-full pr-12 pl-4 py-3 border rounded-xl focus:outline-none focus:ring-2 focus:ring-amber-500 ${
errors.dailyPrice ? 'border-red-500' : 'border-gray-300'
}`}
placeholder="مثال: 50000"
/>
</div>
{errors.dailyPrice && (
<p className="text-red-500 text-sm mt-1">{errors.dailyPrice}</p>
)}
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-2">
سعر الايجار الشهري ({currencySymbol}) <span className="text-red-500">*</span>
</label>
<div className="relative">
<span className="absolute right-3 top-1/2 transform -translate-y-1/2 w-5 h-5 text-gray-400 font-bold text-sm flex items-center justify-center">{currencySymbol}</span>
<input
type="number"
min="0"
value={formData.monthlyPrice}
onChange={(e) => {
const val = e.target.value;
if (val === '' || parseFloat(val) >= 0) {
setFormData({...formData, monthlyPrice: val});
}
}}
onKeyDown={(e) => { if (['-', 'e', 'E', '+'].includes(e.key)) e.preventDefault() }}
className={`w-full pr-12 pl-4 py-3 border rounded-xl focus:outline-none focus:ring-2 focus:ring-amber-500 ${
errors.monthlyPrice ? 'border-red-500' : 'border-gray-300'
}`}
placeholder="مثال: 1000000"
/>
</div>
{errors.monthlyPrice && (
<p className="text-red-500 text-sm mt-1">{errors.monthlyPrice}</p>
)}
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-2">
مبلغ التأمين ({currencySymbol})
</label>
<div className="relative">
<span className="absolute right-3 top-1/2 transform -translate-y-1/2 w-5 h-5 text-gray-400 font-bold text-sm flex items-center justify-center">{currencySymbol}</span>
<input
type="number"
min="0"
value={formData.deposit || ''}
onChange={(e) => {
const val = e.target.value;
if (val === '' || parseFloat(val) >= 0) {
setFormData({...formData, deposit: val});
}
}}
onKeyDown={(e) => { if (['-', 'e', 'E', '+'].includes(e.key)) e.preventDefault() }}
className="w-full pr-12 pl-4 py-3 border border-gray-300 rounded-xl focus:outline-none focus:ring-2 focus:ring-amber-500"
placeholder="مثال: 500000"
/>
</div>
</div>
</motion.div>
)}
</AnimatePresence>
@ -1333,63 +1460,22 @@ const handleMapClick = async (coords) => {
</button>
</div>
<div className="bg-amber-50 border border-amber-200 rounded-xl p-4 mb-4 text-sm text-amber-800 leading-relaxed">
<Search className="w-4 h-4 inline ml-1" />
ابحث أولاً ثم اضغط على النتيجة لتحريك الخريطة و تثبيت العلامة .
<br />
<MapPin className="w-4 h-4 inline ml-1" />
اضغط على الخريطة لتحديد موقع العقار
</div>
<div className="relative w-full h-96 rounded-xl overflow-hidden border-2 border-gray-200 mb-4">
{mapLoaded && (
<MapContainer
center={mapCenter}
zoom={mapZoom}
style={{ height: '100%', width: '100%' }}
className="z-0"
doubleClickZoom={false}
>
<TileLayer
url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
attribution='&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a>'
/>
<MapClickHandler onMapClick={handleMapClick} />
{selectedLocation && (
<Marker
position={[selectedLocation.lat, selectedLocation.lng]}
draggable={true}
eventHandlers={{
dragend: async (e) => {
const marker = e.target;
const position = marker.getLatLng();
try {
const response = await fetch(
`https://nominatim.openstreetmap.org/reverse?format=json&lat=${position.lat}&lon=${position.lng}&accept-language=ar`
);
const data = await response.json();
setSelectedLocation({
lat: position.lat,
lng: position.lng,
address: data.display_name || 'موقع محدد'
});
} catch (error) {
setSelectedLocation({
lat: position.lat,
lng: position.lng,
address: 'موقع محدد'
});
}
}
}}
>
<Popup>
<div className="text-right p-2 max-w-xs">
<p className="font-bold text-sm mb-1">موقع العقار</p>
<p className="text-xs text-gray-600">{selectedLocation.address}</p>
</div>
</Popup>
</Marker>
)}
</MapContainer>
)}
<AddPropertyMap
mapCenter={mapCenter}
mapZoom={mapZoom}
selectedLocation={selectedLocation}
onMapClick={handleMapClick}
onMarkerDragEnd={handleMarkerDragEnd}
/>
</div>
{selectedLocation && !formData.lat && (
@ -1438,10 +1524,16 @@ const handleMapClick = async (coords) => {
<Upload className="w-12 h-12 text-gray-400 mx-auto mb-3" />
<p className="text-gray-600 font-medium">اضغط لرفع الصور</p>
<p className="text-xs text-gray-500 mt-2">
JPEG, PNG, JPG حتى 5MB 800x600 بكسل حد أقصى 10 صور
JPEG, PNG, JPG حتى 5MB 800x600 بكسل 2-5 صور
</p>
</div>
<p className="text-sm text-gray-500 mt-2 mb-4 text-center">
يرجى رفع عدة صور واضحة
<br />
(اضغط على الرفع أكثر من مرة)
</p>
{errors.images && (
<p className="text-red-500 text-sm text-center mt-2">{errors.images}</p>
)}
@ -1472,6 +1564,32 @@ const handleMapClick = async (coords) => {
</div>
)}
</div>
{step === 4 && (
<div className="border-t border-gray-200 pt-6 mt-8">
<div className="bg-gray-50 rounded-2xl p-5 border border-gray-200">
<p className="text-sm text-gray-700 leading-relaxed mb-4">
أنا صاحب العقار أقر أنني مالك العقار أو مخول قانونياً بعرضه للإيجار أو البيع،
وأن جميع المعلومات المدخلة صحيحة، وأتحمل كامل المسؤولية عن أي معلومات غير صحيحة.
</p>
<label className="flex items-start gap-3 cursor-pointer">
<input
type="checkbox"
checked={agreeToTerms}
onChange={(e) => setAgreeToTerms(e.target.checked)}
className="w-5 h-5 mt-0.5 text-amber-500 rounded shrink-0"
/>
<span className="text-sm text-gray-600">
أوافق على{' '}
<Link href="/terms" target="_blank" className="text-amber-600 underline hover:text-amber-700">
شروط الاستخدام
</Link>
</span>
</label>
</div>
</div>
)}
</motion.div>
)}
@ -1499,7 +1617,7 @@ const handleMapClick = async (coords) => {
) : (
<button
onClick={handleSubmit}
disabled={isLoading}
disabled={isLoading || !agreeToTerms}
className="flex-1 py-3 px-4 bg-gradient-to-r from-amber-500 to-amber-600 text-white rounded-xl font-medium hover:from-amber-600 hover:to-amber-700 transition-all disabled:opacity-50 flex items-center justify-center gap-2"
>
{isLoading ? (