diff --git a/app/property/[id]/PropertyDetail.js b/app/property/[id]/PropertyDetail.js new file mode 100644 index 0000000..2366bec --- /dev/null +++ b/app/property/[id]/PropertyDetail.js @@ -0,0 +1,908 @@ +'use client'; + +import { useState, useEffect } from 'react'; +import { motion } from 'framer-motion'; +import toast, { Toaster } from 'react-hot-toast'; +import Image from 'next/image'; +import Link from 'next/link'; +import { useParams } from 'next/navigation'; +import { + MapPin, + Bed, + Bath, + Square, + DollarSign, + Heart, + Share2, + Phone, + Mail, + MessageCircle, + Calendar, + Shield, + Star, + ChevronLeft, + ChevronRight, + Check, + X, + Wifi, + Car, + Coffee, + Wind, + Thermometer, + Lock, + Camera, + Home, + Building2, + Users, + Ruler, + CalendarDays, + Clock, + Award, + FileText, + Printer, + Download, + ArrowLeft, + LogIn +} from 'lucide-react'; +import { getRentProperty, getSaleProperty, bookReservation, checkAvailability, getAvailableDateRanges } from '../../utils/api'; +import AuthService from '../../services/AuthService'; +import { BuildingTypeKeys, PropertyStatusKeys, extractCity } from '../../enums'; + +// Map API response to the UI format +function mapApiDetail(item) { + if (!item) return null; + + const info = item.propertyInformation || {}; + + const dailyPrice = item.dailyRent ?? item.monthlyRent ?? item.price ?? 0; + const monthlyPrice = item.monthlyRent ?? 0; + + const propType = BuildingTypeKeys[info.buildingType] ?? BuildingTypeKeys[item.type] ?? 'apartment'; + const status = PropertyStatusKeys[info.status] ?? PropertyStatusKeys[item.status] ?? 'available'; + + const features = []; + if (item.isSmokeAllow) features.push({ name: 'يسمح بالتدخين', available: true, description: '' }); + if (item.isVisitorAllow) features.push({ name: 'يسمح بالزوار', available: true, description: '' }); + if (item.specializedFor) features.push({ name: 'متخصص', available: true, description: '' }); + if (info.numberOfBedRooms) features.push({ name: 'غرف النوم', available: true, description: `${info.numberOfBedRooms} غرف` }); + if (info.numberOfBathRooms) features.push({ name: 'الحمامات', available: true, description: `${info.numberOfBathRooms} حمامات` }); + if (info.space) features.push({ name: 'المساحة', available: true, description: `${info.space} م²` }); + + const typeLabels = { 0: 'شقة', 1: 'فيلا', 2: 'بيت' }; + + // Extract images from API and build full URLs + const apiBase = typeof window !== 'undefined' ? (process.env.NEXT_PUBLIC_API_URL || 'http://45.93.137.91/api') : ''; + const rawImages = Array.isArray(info.images) ? info.images : []; + const images = rawImages.length > 0 + ? rawImages.map(img => img.startsWith('http') ? img : `${apiBase}${img.startsWith('/') ? '' : '/'}${img}`) + : ['/property-placeholder.jpg', '/villa1.jpg', '/villa2.jpg']; + + return { + id: item.id, + title: info.address || `عقار #${item.id}`, + description: info.description || 'عقار سكني مميز في موقع استراتيجي.', + type: propType, + price: dailyPrice, + priceUnit: 'daily', + location: { + city: extractCity(info.address) || 'دمشق', + district: info.address || '', + address: info.address || '', + lat: parseFloat(info.cordsX) || 0, + lng: parseFloat(info.cordsY) || 0, + }, + bedrooms: info.numberOfBedRooms || 0, + bathrooms: info.numberOfBathRooms || 0, + area: info.space || 0, + features: features.length > 0 ? features : [ + { name: 'متاح للإيجار', available: true, description: '' }, + ], + images, + status, + rating: item.rating || 4.5, + reviews: 0, + reviewList: [], + owner: { + name: 'المالك', + phone: '—', + email: '—', + rating: 4.8, + properties: 1, + memberSince: '2024', + responseRate: '95%', + responseTime: 'خلال ساعات', + }, + nearby: [], + specifications: { + constructionYear: null, + floor: '-', + parking: 0, + gardenArea: 0, + poolArea: 0, + furnished: false, + airConditioning: '-', + heating: '-', + electricity: '220V', + water: 'شبكة عامة', + }, + rules: [], + _raw: item, + }; +} + +// extractCity is now imported from @/app/enums + +// API-only — no fallback data + +export default function PropertyDetailsPage() { + const params = useParams(); + const [currentImage, setCurrentImage] = useState(0); + const [showContact, setShowContact] = useState(false); + const [bookingDates, setBookingDates] = useState({ start: '', end: '' }); + const [selectedDuration, setSelectedDuration] = useState(1); + const [property, setProperty] = useState(null); + const [loading, setLoading] = useState(true); + const [bookingError, setBookingError] = useState(null); + const [bookingSuccess, setBookingSuccess] = useState(false); + const [availableRanges, setAvailableRanges] = useState([]); + const [calendarMonth, setCalendarMonth] = useState(new Date()); + const [selectingEnd, setSelectingEnd] = useState(false); + const [showLoginDialog, setShowLoginDialog] = useState(false); + + useEffect(() => { + const id = params.id; + setLoading(true); + setBookingError(null); + setBookingSuccess(false); + + async function fetchProperty() { + try { + // Try RentProperties first, then SaleProperties + let data = null; + try { + data = await getRentProperty(id); + } catch { + try { + data = await getSaleProperty(id); + } catch { + // neither worked + } + } + + if (data) { + const mapped = mapApiDetail(data); + if (mapped) { + setProperty(mapped); + setLoading(false); + return; + } + } + setProperty(null); + } catch (err) { + console.error('[Property] Failed to fetch property:', err); + setProperty(null); + } finally { + setLoading(false); + } + } + + fetchProperty(); + }, [params.id]); + + // Fetch available date ranges + useEffect(() => { + if (!property) return; + const propId = property._raw?.id || params.id; + console.log('[Property] Fetching available dates for:', propId); + getAvailableDateRanges(propId) + .then((data) => { + const ranges = Array.isArray(data) ? data : []; + console.log('[Property] Available date ranges:', ranges); + setAvailableRanges(ranges); + }) + .catch((err) => { + console.warn('[Property] Failed to fetch available dates:', err); + }); + }, [property, params.id]); + + // Set Open Graph meta tags dynamically for Facebook/Twitter sharing + useEffect(() => { + if (!property) return; + + const typeLabel = property.type === 'villa' ? 'فيلا' : property.type === 'apartment' ? 'شقة' : 'بيت'; + const priceLabel = `${formatCurrency(property.price)} / ${property.priceUnit === 'daily' ? 'يوم' : 'شهر'}`; + const desc = `${typeLabel} في ${property.location?.address || ''} · ${property.bedrooms} غرف نوم · ${property.bathrooms} حمامات · ${property.area} م²`; + const imageUrl = property.images?.[0] + ? (property.images[0].startsWith('http') ? property.images[0] : `http://45.93.137.91${property.images[0]}`) + : ''; + + const setMeta = (prop, content) => { + let tag = document.querySelector(`meta[property="${prop}"]`); + if (!tag) { + tag = document.createElement('meta'); + tag.setAttribute('property', prop); + document.head.appendChild(tag); + } + tag.setAttribute('content', content); + }; + + const setMetaName = (name, content) => { + let tag = document.querySelector(`meta[name="${name}"]`); + if (!tag) { + tag = document.createElement('meta'); + tag.setAttribute('name', name); + document.head.appendChild(tag); + } + tag.setAttribute('content', content); + }; + + setMeta('og:title', `${property.title} - ${priceLabel}`); + setMeta('og:description', desc); + if (imageUrl) setMeta('og:image', imageUrl); + setMeta('og:url', window.location.href); + setMeta('og:type', 'website'); + setMeta('og:site_name', 'SweetHome'); + + // Twitter cards + setMetaName('twitter:card', 'summary_large_image'); + setMetaName('twitter:title', `${property.title} - ${priceLabel}`); + setMetaName('twitter:description', desc); + if (imageUrl) setMetaName('twitter:image', imageUrl); + }, [property]); + + const formatCurrency = (amount) => { + return amount?.toLocaleString() + ' ل.س'; + }; + + const calculateTotalPrice = () => { + if (!property) return 0; + const days = bookingDates.start && bookingDates.end + ? Math.ceil((new Date(bookingDates.end) - new Date(bookingDates.start)) / (1000 * 60 * 60 * 24)) + : selectedDuration; + return property.price * (days > 0 ? days : 1); + }; + + // Calendar helpers + const isDateAvailable = (dateStr) => { + const d = new Date(dateStr + 'T00:00:00'); + return availableRanges.some((range) => { + const start = new Date(range.startDate); + const end = new Date(range.endDate); + return d >= start && d <= end; + }); + }; + + const isInRange = (dateStr) => { + if (!bookingDates.start) return false; + const d = new Date(dateStr + 'T00:00:00'); + const start = new Date(bookingDates.start + 'T00:00:00'); + const end = bookingDates.end ? new Date(bookingDates.end + 'T00:00:00') : start; + return d >= start && d <= end; + }; + + const isRangeFullyAvailable = (startStr, endStr) => { + const start = new Date(startStr + 'T00:00:00'); + const end = new Date(endStr + 'T00:00:00'); + for (let d = new Date(start); d <= end; d.setDate(d.getDate() + 1)) { + if (!isDateAvailable(d.toISOString().split('T')[0])) return false; + } + return true; + }; + + const handleCalendarClick = (dateStr) => { + if (!isDateAvailable(dateStr)) return; + + if (!bookingDates.start || selectingEnd) { + if (!bookingDates.start) { + setBookingDates({ start: dateStr, end: '' }); + setSelectingEnd(true); + } else { + const start = bookingDates.start; + const end = dateStr; + const [s, e] = end > start ? [start, end] : [end, start]; + if (isRangeFullyAvailable(s, e)) { + setBookingDates({ start: s, end: e }); + setSelectingEnd(false); + } else { + toast.error('بعض التواريخ في هذه الفترة غير متاحة'); + } + } + } else { + setBookingDates({ start: dateStr, end: '' }); + setSelectingEnd(true); + } + }; + + const getDaysInMonth = (year, month) => new Date(year, month + 1, 0).getDate(); + const getFirstDayOfMonth = (year, month) => new Date(year, month, 1).getDay(); + + const formatDateStr = (year, month, day) => { + return `${year}-${String(month + 1).padStart(2, '0')}-${String(day).padStart(2, '0')}`; + }; + + const monthNames = ['يناير', 'فبراير', 'مارس', 'أبريل', 'مايو', 'يونيو', 'يوليو', 'أغسطس', 'سبتمبر', 'أكتوبر', 'نوفمبر', 'ديسمبر']; + const dayNames = ['أح', 'إث', 'ثل', 'أر', 'خم', 'جم', 'سب']; + + const handleBooking = async () => { + if (!AuthService.isAuthenticated()) { + setShowLoginDialog(true); + return; + } + + setBookingError(null); + setBookingSuccess(false); + + if (!bookingDates.start || !bookingDates.end) { + setBookingError('يرجى اختيار تاريخ البداية والنهاية'); + return; + } + + const propId = property?._raw?.id || parseInt(params.id); + const startDate = new Date(bookingDates.start).toISOString(); + const endDate = new Date(bookingDates.end).toISOString(); + + console.log('[Booking] Reserving:', { propertyId: propId, startDate, endDate }); + + try { + const res = await bookReservation(propId, startDate, endDate); + console.log('[Booking] Success:', res); + setBookingSuccess(true); + toast.success('تم إرسال طلب الحجز بنجاح!'); + } catch (err) { + console.error('[Booking] Failed:', err); + setBookingError(err.message || 'فشل في إرسال طلب الحجز'); + } + }; + + if (loading) { + return ( +
+
+
+

جاري تحميل تفاصيل العقار...

+
+
+ ); + } + + if (!property) { + return ( +
+
+ +

العقار غير موجود

+

لم نتمكن من العثور على العقار المطلوب

+ + العودة إلى العقارات + +
+
+ ); + } + + return ( +
+ +
+
+
+ + + العودة إلى العقارات + +
+ + +
+
+
+
+ +
+ +
+
+ {property.title} + + {property.images.length > 1 && ( + <> + + + + )} + +
+ {property.images.map((_, idx) => ( +
+ +
+ + {currentImage + 1} / {property.images.length} +
+
+ +
+ {property.images.slice(1, 5).map((img, idx) => ( +
setCurrentImage(idx + 1)} + className="relative h-[240px] rounded-2xl overflow-hidden cursor-pointer hover:opacity-90 transition-opacity bg-gray-100" + > + {`${property.title} +
+ ))} +
+
+
+ +
+
+ +
+
+

{property.title}

+
+ + {property.location.address} +
+
+
+
{formatCurrency(property.price)}
+
/{property.priceUnit === 'daily' ? 'يوم' : 'شهر'}
+
+
+ +
+
+ + {property.rating} + {property.reviews > 0 && ({property.reviews} تقييم)} +
+
+ + {property.status === 'available' ? 'متاح للإيجار' : 'محجوز حالياً'} + +
+ + + +

المواصفات الرئيسية

+
+
+ +
{property.bedrooms}
+
غرف نوم
+
+
+ +
{property.bathrooms}
+
حمامات
+
+
+ +
{property.area}
+
م²
+
+
+ +
+ {property.type === 'villa' ? 'فيلا' : + property.type === 'apartment' ? 'شقة' : 'بيت'} +
+
نوع العقار
+
+
+
+ + +

وصف العقار

+

{property.description || 'لا يوجد وصف متاح.'}

+
+ + +

المميزات والخدمات

+
+ {property.features.map((feature, idx) => ( +
+
+ {feature.available ? ( + + ) : ( + + )} +
+
+ + {feature.name} + + {feature.description && ( +

+ {feature.description} +

+ )} +
+
+ ))} +
+
+ + {property.reviewList && property.reviewList.length > 0 && ( + +

تقييمات المستأجرين

+
+ {property.reviewList.map((review, idx) => ( +
+
+
+ {review.user} +
+ {[...Array(5)].map((_, i) => ( + + ))} +
+
+ {review.date} +
+

{review.comment}

+
+ ))} +
+
+ )} + + {property.rules && property.rules.length > 0 && ( + +

قوانين المنزل

+
    + {property.rules.map((rule, idx) => ( +
  • +
    + {rule} +
  • + ))} +
+
+ )} +
+ +
+
+ +

احجز هذا العقار

+ + {/* Selected dates display */} +
+
+ من + {bookingDates.start || '—'} +
+
+ إلى + {bookingDates.end || '—'} +
+
+ + {bookingDates.start && bookingDates.end && (() => { + const days = Math.ceil((new Date(bookingDates.end) - new Date(bookingDates.start)) / (1000 * 60 * 60 * 24)); + return days > 0 ? ( +
+ {days} يوم{days > 1 ? 'اً' : 'اً'} {selectingEnd ? '— اضغط على تاريخ النهاية' : '✓'} +
+ ) : null; + })()} + + {/* Calendar */} +
+
+ + {monthNames[calendarMonth.getMonth()]} {calendarMonth.getFullYear()} + +
+ +
+ {dayNames.map((d) => ( +
{d}
+ ))} +
+ +
+ {(() => { + const year = calendarMonth.getFullYear(); + const month = calendarMonth.getMonth(); + const daysInMonth = getDaysInMonth(year, month); + const firstDay = getFirstDayOfMonth(year, month); + const today = new Date().toISOString().split('T')[0]; + const cells = []; + + // Empty cells before first day + for (let i = 0; i < firstDay; i++) { + cells.push(
); + } + + for (let day = 1; day <= daysInMonth; day++) { + const dateStr = formatDateStr(year, month, day); + const available = isDateAvailable(dateStr); + const isStart = bookingDates.start === dateStr; + const isEnd = bookingDates.end === dateStr; + const inRange = isInRange(dateStr); + const isPast = dateStr < today; + + cells.push( + + ); + } + return cells; + })()} +
+ +
+
+
+ متاح +
+
+
+ محدد +
+
+
+ غير متاح +
+
+
+ +
+ {(() => { + const days = bookingDates.start && bookingDates.end + ? Math.ceil((new Date(bookingDates.end) - new Date(bookingDates.start)) / (1000 * 60 * 60 * 24)) + : 0; + const effectiveDays = days > 0 ? days : 1; + return ( + <> +
+ السعر لـ {effectiveDays} يوم{effectiveDays > 1 ? 'اً' : 'اً'} + {formatCurrency(property.price * effectiveDays)} +
+
+ سلفة ضمان + {formatCurrency(property._raw?.deposit || 0)} +
+
+ الإجمالي + {formatCurrency(property.price * effectiveDays + (property._raw?.deposit || 0))} +
+ + ); + })()} +
+ + {bookingError && ( +
+ {bookingError} +
+ )} + + {bookingSuccess && ( +
+ تم إرسال طلب الحجز بنجاح. سيتم التواصل معك قريباً. +
+ )} + + + +
+ + الدفع آمن ومضمون. سلفة الضمان قابلة للاسترداد. +
+ + + +

معلومات المالك

+
+
+ + {property.owner.name.charAt(0)} + +
+
+
{property.owner.name}
+
+ + {property.owner.rating} + · {property.owner.properties} عقارات +
+ {property.owner.responseRate && ( +
+ + استجابة: {property.owner.responseRate} +
+ )} +
+
+ + {showContact ? ( +
+
+ + {property.owner.phone} +
+
+ + {property.owner.email} +
+
+ ) : ( + + )} + + +
+
+
+
+
+ + {/* Login/Register Dialog */} + {showLoginDialog && ( +
setShowLoginDialog(false)}> + e.stopPropagation()} + className="bg-white rounded-2xl p-8 max-w-md w-full mx-4 shadow-2xl text-center" + > +
+ +
+

سجّل الدخول للمتابعة

+

يجب عليك إنشاء حساب أو تسجيل الدخول لحجز هذا العقار

+
+ + إنشاء حساب جديد + + + تسجيل الدخول + + +
+
+
+ )} +
+ ); +} diff --git a/app/property/[id]/page.js b/app/property/[id]/page.js index 2366bec..993d40b 100644 --- a/app/property/[id]/page.js +++ b/app/property/[id]/page.js @@ -1,908 +1,90 @@ -'use client'; +import PropertyDetail from './PropertyDetail'; -import { useState, useEffect } from 'react'; -import { motion } from 'framer-motion'; -import toast, { Toaster } from 'react-hot-toast'; -import Image from 'next/image'; -import Link from 'next/link'; -import { useParams } from 'next/navigation'; -import { - MapPin, - Bed, - Bath, - Square, - DollarSign, - Heart, - Share2, - Phone, - Mail, - MessageCircle, - Calendar, - Shield, - Star, - ChevronLeft, - ChevronRight, - Check, - X, - Wifi, - Car, - Coffee, - Wind, - Thermometer, - Lock, - Camera, - Home, - Building2, - Users, - Ruler, - CalendarDays, - Clock, - Award, - FileText, - Printer, - Download, - ArrowLeft, - LogIn -} from 'lucide-react'; -import { getRentProperty, getSaleProperty, bookReservation, checkAvailability, getAvailableDateRanges } from '../../utils/api'; -import AuthService from '../../services/AuthService'; -import { BuildingTypeKeys, PropertyStatusKeys, extractCity } from '../../enums'; +// Server-side API fetch for metadata (runs at request time on server) +async function fetchPropertyForMeta(id) { + try { + const res = await fetch(`http://45.93.137.91/api/RentProperties/GetRentProperties`, { + next: { revalidate: 60 }, + }); + if (!res.ok) return null; + const text = await res.text(); + const json = JSON.parse(text); + const items = Array.isArray(json?.data) ? json.data : Array.isArray(json) ? json : []; + return items.find(p => p.id == id) || items[0] || null; + } catch { + return null; + } +} -// Map API response to the UI format -function mapApiDetail(item) { - if (!item) return null; +function mapProperty(item) { + const info = item.propertyInformation || item.PropertyInformation || {}; + let details = {}; + try { details = JSON.parse(info.detailsJSON || info.DetailsJSON || '{}'); } catch {} - const info = item.propertyInformation || {}; - - const dailyPrice = item.dailyRent ?? item.monthlyRent ?? item.price ?? 0; - const monthlyPrice = item.monthlyRent ?? 0; - - const propType = BuildingTypeKeys[info.buildingType] ?? BuildingTypeKeys[item.type] ?? 'apartment'; - const status = PropertyStatusKeys[info.status] ?? PropertyStatusKeys[item.status] ?? 'available'; - - const features = []; - if (item.isSmokeAllow) features.push({ name: 'يسمح بالتدخين', available: true, description: '' }); - if (item.isVisitorAllow) features.push({ name: 'يسمح بالزوار', available: true, description: '' }); - if (item.specializedFor) features.push({ name: 'متخصص', available: true, description: '' }); - if (info.numberOfBedRooms) features.push({ name: 'غرف النوم', available: true, description: `${info.numberOfBedRooms} غرف` }); - if (info.numberOfBathRooms) features.push({ name: 'الحمامات', available: true, description: `${info.numberOfBathRooms} حمامات` }); - if (info.space) features.push({ name: 'المساحة', available: true, description: `${info.space} م²` }); - - const typeLabels = { 0: 'شقة', 1: 'فيلا', 2: 'بيت' }; - - // Extract images from API and build full URLs - const apiBase = typeof window !== 'undefined' ? (process.env.NEXT_PUBLIC_API_URL || 'http://45.93.137.91/api') : ''; - const rawImages = Array.isArray(info.images) ? info.images : []; - const images = rawImages.length > 0 - ? rawImages.map(img => img.startsWith('http') ? img : `${apiBase}${img.startsWith('/') ? '' : '/'}${img}`) - : ['/property-placeholder.jpg', '/villa1.jpg', '/villa2.jpg']; + const price = item.monthlyRent || item.MonthlyRent || item.dailyRent || item.DailyRent || 0; + const priceUnit = item.monthlyRent || item.MonthlyRent ? 'monthly' : 'daily'; + const buildingType = info.buildingType ?? info.BuildingType ?? 0; + const type = { 0: 'apartment', 1: 'villa', 2: 'house' }[buildingType] || 'apartment'; + const typeLabel = { 0: 'شقة', 1: 'فيلا', 2: 'بيت' }[buildingType] || 'عقار'; + const address = info.address || info.Address || ''; + const bedrooms = info.numberOfBedRooms || info.NumberOfBedRooms || 0; + const bathrooms = info.numberOfBathRooms || info.NumberOfBathRooms || 0; + const area = info.space || info.Space || 0; + const desc = info.description || info.Description || ''; + const images = info.images || info.Images || []; + const firstImage = Array.isArray(images) && images[0] ? images[0] : ''; return { - id: item.id, - title: info.address || `عقار #${item.id}`, - description: info.description || 'عقار سكني مميز في موقع استراتيجي.', - type: propType, - price: dailyPrice, - priceUnit: 'daily', - location: { - city: extractCity(info.address) || 'دمشق', - district: info.address || '', - address: info.address || '', - lat: parseFloat(info.cordsX) || 0, - lng: parseFloat(info.cordsY) || 0, - }, - bedrooms: info.numberOfBedRooms || 0, - bathrooms: info.numberOfBathRooms || 0, - area: info.space || 0, - features: features.length > 0 ? features : [ - { name: 'متاح للإيجار', available: true, description: '' }, - ], - images, - status, - rating: item.rating || 4.5, - reviews: 0, - reviewList: [], - owner: { - name: 'المالك', - phone: '—', - email: '—', - rating: 4.8, - properties: 1, - memberSince: '2024', - responseRate: '95%', - responseTime: 'خلال ساعات', - }, - nearby: [], - specifications: { - constructionYear: null, - floor: '-', - parking: 0, - gardenArea: 0, - poolArea: 0, - furnished: false, - airConditioning: '-', - heating: '-', - electricity: '220V', - water: 'شبكة عامة', - }, - rules: [], - _raw: item, + title: `${typeLabel} في ${address}`, + description: desc || `${typeLabel} في ${address} · ${bedrooms} غرف نوم · ${bathrooms} حمامات · ${area} م²`, + price, + priceUnit, + typeLabel, + address, + bedrooms, + bathrooms, + area, + image: firstImage, }; } -// extractCity is now imported from @/app/enums +export async function generateMetadata({ params }) { + const { id } = await params; + const raw = await fetchPropertyForMeta(id); -// API-only — no fallback data - -export default function PropertyDetailsPage() { - const params = useParams(); - const [currentImage, setCurrentImage] = useState(0); - const [showContact, setShowContact] = useState(false); - const [bookingDates, setBookingDates] = useState({ start: '', end: '' }); - const [selectedDuration, setSelectedDuration] = useState(1); - const [property, setProperty] = useState(null); - const [loading, setLoading] = useState(true); - const [bookingError, setBookingError] = useState(null); - const [bookingSuccess, setBookingSuccess] = useState(false); - const [availableRanges, setAvailableRanges] = useState([]); - const [calendarMonth, setCalendarMonth] = useState(new Date()); - const [selectingEnd, setSelectingEnd] = useState(false); - const [showLoginDialog, setShowLoginDialog] = useState(false); - - useEffect(() => { - const id = params.id; - setLoading(true); - setBookingError(null); - setBookingSuccess(false); - - async function fetchProperty() { - try { - // Try RentProperties first, then SaleProperties - let data = null; - try { - data = await getRentProperty(id); - } catch { - try { - data = await getSaleProperty(id); - } catch { - // neither worked - } - } - - if (data) { - const mapped = mapApiDetail(data); - if (mapped) { - setProperty(mapped); - setLoading(false); - return; - } - } - setProperty(null); - } catch (err) { - console.error('[Property] Failed to fetch property:', err); - setProperty(null); - } finally { - setLoading(false); - } - } - - fetchProperty(); - }, [params.id]); - - // Fetch available date ranges - useEffect(() => { - if (!property) return; - const propId = property._raw?.id || params.id; - console.log('[Property] Fetching available dates for:', propId); - getAvailableDateRanges(propId) - .then((data) => { - const ranges = Array.isArray(data) ? data : []; - console.log('[Property] Available date ranges:', ranges); - setAvailableRanges(ranges); - }) - .catch((err) => { - console.warn('[Property] Failed to fetch available dates:', err); - }); - }, [property, params.id]); - - // Set Open Graph meta tags dynamically for Facebook/Twitter sharing - useEffect(() => { - if (!property) return; - - const typeLabel = property.type === 'villa' ? 'فيلا' : property.type === 'apartment' ? 'شقة' : 'بيت'; - const priceLabel = `${formatCurrency(property.price)} / ${property.priceUnit === 'daily' ? 'يوم' : 'شهر'}`; - const desc = `${typeLabel} في ${property.location?.address || ''} · ${property.bedrooms} غرف نوم · ${property.bathrooms} حمامات · ${property.area} م²`; - const imageUrl = property.images?.[0] - ? (property.images[0].startsWith('http') ? property.images[0] : `http://45.93.137.91${property.images[0]}`) - : ''; - - const setMeta = (prop, content) => { - let tag = document.querySelector(`meta[property="${prop}"]`); - if (!tag) { - tag = document.createElement('meta'); - tag.setAttribute('property', prop); - document.head.appendChild(tag); - } - tag.setAttribute('content', content); + if (!raw) { + return { + title: 'SweetHome - عقار', + description: 'اكتشف أفضل العقارات للإيجار', }; - - const setMetaName = (name, content) => { - let tag = document.querySelector(`meta[name="${name}"]`); - if (!tag) { - tag = document.createElement('meta'); - tag.setAttribute('name', name); - document.head.appendChild(tag); - } - tag.setAttribute('content', content); - }; - - setMeta('og:title', `${property.title} - ${priceLabel}`); - setMeta('og:description', desc); - if (imageUrl) setMeta('og:image', imageUrl); - setMeta('og:url', window.location.href); - setMeta('og:type', 'website'); - setMeta('og:site_name', 'SweetHome'); - - // Twitter cards - setMetaName('twitter:card', 'summary_large_image'); - setMetaName('twitter:title', `${property.title} - ${priceLabel}`); - setMetaName('twitter:description', desc); - if (imageUrl) setMetaName('twitter:image', imageUrl); - }, [property]); - - const formatCurrency = (amount) => { - return amount?.toLocaleString() + ' ل.س'; - }; - - const calculateTotalPrice = () => { - if (!property) return 0; - const days = bookingDates.start && bookingDates.end - ? Math.ceil((new Date(bookingDates.end) - new Date(bookingDates.start)) / (1000 * 60 * 60 * 24)) - : selectedDuration; - return property.price * (days > 0 ? days : 1); - }; - - // Calendar helpers - const isDateAvailable = (dateStr) => { - const d = new Date(dateStr + 'T00:00:00'); - return availableRanges.some((range) => { - const start = new Date(range.startDate); - const end = new Date(range.endDate); - return d >= start && d <= end; - }); - }; - - const isInRange = (dateStr) => { - if (!bookingDates.start) return false; - const d = new Date(dateStr + 'T00:00:00'); - const start = new Date(bookingDates.start + 'T00:00:00'); - const end = bookingDates.end ? new Date(bookingDates.end + 'T00:00:00') : start; - return d >= start && d <= end; - }; - - const isRangeFullyAvailable = (startStr, endStr) => { - const start = new Date(startStr + 'T00:00:00'); - const end = new Date(endStr + 'T00:00:00'); - for (let d = new Date(start); d <= end; d.setDate(d.getDate() + 1)) { - if (!isDateAvailable(d.toISOString().split('T')[0])) return false; - } - return true; - }; - - const handleCalendarClick = (dateStr) => { - if (!isDateAvailable(dateStr)) return; - - if (!bookingDates.start || selectingEnd) { - if (!bookingDates.start) { - setBookingDates({ start: dateStr, end: '' }); - setSelectingEnd(true); - } else { - const start = bookingDates.start; - const end = dateStr; - const [s, e] = end > start ? [start, end] : [end, start]; - if (isRangeFullyAvailable(s, e)) { - setBookingDates({ start: s, end: e }); - setSelectingEnd(false); - } else { - toast.error('بعض التواريخ في هذه الفترة غير متاحة'); - } - } - } else { - setBookingDates({ start: dateStr, end: '' }); - setSelectingEnd(true); - } - }; - - const getDaysInMonth = (year, month) => new Date(year, month + 1, 0).getDate(); - const getFirstDayOfMonth = (year, month) => new Date(year, month, 1).getDay(); - - const formatDateStr = (year, month, day) => { - return `${year}-${String(month + 1).padStart(2, '0')}-${String(day).padStart(2, '0')}`; - }; - - const monthNames = ['يناير', 'فبراير', 'مارس', 'أبريل', 'مايو', 'يونيو', 'يوليو', 'أغسطس', 'سبتمبر', 'أكتوبر', 'نوفمبر', 'ديسمبر']; - const dayNames = ['أح', 'إث', 'ثل', 'أر', 'خم', 'جم', 'سب']; - - const handleBooking = async () => { - if (!AuthService.isAuthenticated()) { - setShowLoginDialog(true); - return; - } - - setBookingError(null); - setBookingSuccess(false); - - if (!bookingDates.start || !bookingDates.end) { - setBookingError('يرجى اختيار تاريخ البداية والنهاية'); - return; - } - - const propId = property?._raw?.id || parseInt(params.id); - const startDate = new Date(bookingDates.start).toISOString(); - const endDate = new Date(bookingDates.end).toISOString(); - - console.log('[Booking] Reserving:', { propertyId: propId, startDate, endDate }); - - try { - const res = await bookReservation(propId, startDate, endDate); - console.log('[Booking] Success:', res); - setBookingSuccess(true); - toast.success('تم إرسال طلب الحجز بنجاح!'); - } catch (err) { - console.error('[Booking] Failed:', err); - setBookingError(err.message || 'فشل في إرسال طلب الحجز'); - } - }; - - if (loading) { - return ( -
-
-
-

جاري تحميل تفاصيل العقار...

-
-
- ); } - if (!property) { - return ( -
-
- -

العقار غير موجود

-

لم نتمكن من العثور على العقار المطلوب

- - العودة إلى العقارات - -
-
- ); - } + const p = mapProperty(raw); + const priceStr = `${p.price.toLocaleString()} ل.س / ${p.priceUnit === 'daily' ? 'يوم' : 'شهر'}`; + const imageUrl = p.image + ? (p.image.startsWith('http') ? p.image : `http://45.93.137.91${p.image}`) + : ''; - return ( -
- -
-
-
- - - العودة إلى العقارات - -
- - -
-
-
-
- -
- -
-
- {property.title} - - {property.images.length > 1 && ( - <> - - - - )} - -
- {property.images.map((_, idx) => ( -
- -
- - {currentImage + 1} / {property.images.length} -
-
- -
- {property.images.slice(1, 5).map((img, idx) => ( -
setCurrentImage(idx + 1)} - className="relative h-[240px] rounded-2xl overflow-hidden cursor-pointer hover:opacity-90 transition-opacity bg-gray-100" - > - {`${property.title} -
- ))} -
-
-
- -
-
- -
-
-

{property.title}

-
- - {property.location.address} -
-
-
-
{formatCurrency(property.price)}
-
/{property.priceUnit === 'daily' ? 'يوم' : 'شهر'}
-
-
- -
-
- - {property.rating} - {property.reviews > 0 && ({property.reviews} تقييم)} -
-
- - {property.status === 'available' ? 'متاح للإيجار' : 'محجوز حالياً'} - -
- - - -

المواصفات الرئيسية

-
-
- -
{property.bedrooms}
-
غرف نوم
-
-
- -
{property.bathrooms}
-
حمامات
-
-
- -
{property.area}
-
م²
-
-
- -
- {property.type === 'villa' ? 'فيلا' : - property.type === 'apartment' ? 'شقة' : 'بيت'} -
-
نوع العقار
-
-
-
- - -

وصف العقار

-

{property.description || 'لا يوجد وصف متاح.'}

-
- - -

المميزات والخدمات

-
- {property.features.map((feature, idx) => ( -
-
- {feature.available ? ( - - ) : ( - - )} -
-
- - {feature.name} - - {feature.description && ( -

- {feature.description} -

- )} -
-
- ))} -
-
- - {property.reviewList && property.reviewList.length > 0 && ( - -

تقييمات المستأجرين

-
- {property.reviewList.map((review, idx) => ( -
-
-
- {review.user} -
- {[...Array(5)].map((_, i) => ( - - ))} -
-
- {review.date} -
-

{review.comment}

-
- ))} -
-
- )} - - {property.rules && property.rules.length > 0 && ( - -

قوانين المنزل

-
    - {property.rules.map((rule, idx) => ( -
  • -
    - {rule} -
  • - ))} -
-
- )} -
- -
-
- -

احجز هذا العقار

- - {/* Selected dates display */} -
-
- من - {bookingDates.start || '—'} -
-
- إلى - {bookingDates.end || '—'} -
-
- - {bookingDates.start && bookingDates.end && (() => { - const days = Math.ceil((new Date(bookingDates.end) - new Date(bookingDates.start)) / (1000 * 60 * 60 * 24)); - return days > 0 ? ( -
- {days} يوم{days > 1 ? 'اً' : 'اً'} {selectingEnd ? '— اضغط على تاريخ النهاية' : '✓'} -
- ) : null; - })()} - - {/* Calendar */} -
-
- - {monthNames[calendarMonth.getMonth()]} {calendarMonth.getFullYear()} - -
- -
- {dayNames.map((d) => ( -
{d}
- ))} -
- -
- {(() => { - const year = calendarMonth.getFullYear(); - const month = calendarMonth.getMonth(); - const daysInMonth = getDaysInMonth(year, month); - const firstDay = getFirstDayOfMonth(year, month); - const today = new Date().toISOString().split('T')[0]; - const cells = []; - - // Empty cells before first day - for (let i = 0; i < firstDay; i++) { - cells.push(
); - } - - for (let day = 1; day <= daysInMonth; day++) { - const dateStr = formatDateStr(year, month, day); - const available = isDateAvailable(dateStr); - const isStart = bookingDates.start === dateStr; - const isEnd = bookingDates.end === dateStr; - const inRange = isInRange(dateStr); - const isPast = dateStr < today; - - cells.push( - - ); - } - return cells; - })()} -
- -
-
-
- متاح -
-
-
- محدد -
-
-
- غير متاح -
-
-
- -
- {(() => { - const days = bookingDates.start && bookingDates.end - ? Math.ceil((new Date(bookingDates.end) - new Date(bookingDates.start)) / (1000 * 60 * 60 * 24)) - : 0; - const effectiveDays = days > 0 ? days : 1; - return ( - <> -
- السعر لـ {effectiveDays} يوم{effectiveDays > 1 ? 'اً' : 'اً'} - {formatCurrency(property.price * effectiveDays)} -
-
- سلفة ضمان - {formatCurrency(property._raw?.deposit || 0)} -
-
- الإجمالي - {formatCurrency(property.price * effectiveDays + (property._raw?.deposit || 0))} -
- - ); - })()} -
- - {bookingError && ( -
- {bookingError} -
- )} - - {bookingSuccess && ( -
- تم إرسال طلب الحجز بنجاح. سيتم التواصل معك قريباً. -
- )} - - - -
- - الدفع آمن ومضمون. سلفة الضمان قابلة للاسترداد. -
- - - -

معلومات المالك

-
-
- - {property.owner.name.charAt(0)} - -
-
-
{property.owner.name}
-
- - {property.owner.rating} - · {property.owner.properties} عقارات -
- {property.owner.responseRate && ( -
- - استجابة: {property.owner.responseRate} -
- )} -
-
- - {showContact ? ( -
-
- - {property.owner.phone} -
-
- - {property.owner.email} -
-
- ) : ( - - )} - - -
-
-
-
-
- - {/* Login/Register Dialog */} - {showLoginDialog && ( -
setShowLoginDialog(false)}> - e.stopPropagation()} - className="bg-white rounded-2xl p-8 max-w-md w-full mx-4 shadow-2xl text-center" - > -
- -
-

سجّل الدخول للمتابعة

-

يجب عليك إنشاء حساب أو تسجيل الدخول لحجز هذا العقار

-
- - إنشاء حساب جديد - - - تسجيل الدخول - - -
-
-
- )} -
- ); + return { + title: `${p.title} - ${priceStr}`, + description: p.description, + openGraph: { + title: `${p.title} - ${priceStr}`, + description: p.description, + images: imageUrl ? [imageUrl] : [], + url: `https://sweethome.example/property/${id}`, + type: 'website', + siteName: 'SweetHome', + }, + twitter: { + card: 'summary_large_image', + title: `${p.title} - ${priceStr}`, + description: p.description, + images: imageUrl ? [imageUrl] : [], + }, + }; +} + +export default function PropertyPage({ params }) { + return ; }