1373 lines
68 KiB
JavaScript
1373 lines
68 KiB
JavaScript
// '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,
|
||
// Copy
|
||
// } from 'lucide-react';
|
||
// import { getRentProperty, getSaleProperty, bookReservation, checkAvailability, getAvailableDateRanges } from '../../utils/api';
|
||
// import AuthService from '../../services/AuthService';
|
||
// import { useFavorites } from '@/app/contexts/FavoritesContext';
|
||
// import { BuildingTypeKeys, PropertyStatusKeys, extractCity } from '../../enums';
|
||
// import RatingForm from '@/app/components/ratings/RatingForm.js';
|
||
// import RatingList from '@/app/components/ratings/RatingList.js';
|
||
// import StarRating from '@/app/components/ratings/StarRating.js';
|
||
|
||
// // Copy to clipboard that works on HTTP too
|
||
// async function copyToClipboard(text) {
|
||
// try {
|
||
// await navigator.clipboard.writeText(text);
|
||
// return true;
|
||
// } catch {
|
||
// // Fallback for HTTP / older browsers
|
||
// const textarea = document.createElement('textarea');
|
||
// textarea.value = text;
|
||
// textarea.style.position = 'fixed';
|
||
// textarea.style.opacity = '0';
|
||
// document.body.appendChild(textarea);
|
||
// textarea.select();
|
||
// try {
|
||
// document.execCommand('copy');
|
||
// return true;
|
||
// } catch {
|
||
// return false;
|
||
// } finally {
|
||
// document.body.removeChild(textarea);
|
||
// }
|
||
// }
|
||
// }
|
||
|
||
// // 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 || 'https://45.93.137.91.nip.io/api') : '';
|
||
// const rawImages = Array.isArray(info.images) ? info.images : [];
|
||
// const images = rawImages.length > 0
|
||
// ? rawImages.map(img => img.startsWith('http') ? img : `${apiBase}${img.startsWith('/') ? '' : '/Pictures/'}${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 { isFavorite, addFavorite, removeFavorite } = useFavorites();
|
||
// const [currentImage, setCurrentImage] = useState(0);
|
||
// const [showContact, setShowContact] = useState(false);
|
||
// const [showShareMenu, setShowShareMenu] = 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 [favLoading, setFavLoading] = useState(false);
|
||
// const [selectingEnd, setSelectingEnd] = useState(false);
|
||
// const [showLoginDialog, setShowLoginDialog] = useState(false);
|
||
// const [showRatingForm, setShowRatingForm] = useState(false);
|
||
// const [currentRating, setCurrentRating] = useState(0);
|
||
// const [currentComment, setCurrentComment] = useState('');
|
||
// const [userRating, setUserRating] = useState(null);
|
||
// const [canRate, setCanRate] = 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 user rating and check if they can rate
|
||
// useEffect(() => {
|
||
// async function fetchUserRatingAndCheck() {
|
||
// if (!property || !AuthService.isAuthenticated()) return;
|
||
|
||
// try {
|
||
// // Check if user has already rated
|
||
// const rating = await getUserPropertyRating(property._raw?.id || parseInt(params.id), AuthService.getUserId());
|
||
// if (rating) {
|
||
// setUserRating(rating);
|
||
// setCurrentRating(rating.rating);
|
||
// setCurrentComment(rating.comment || '');
|
||
// }
|
||
|
||
// // Check if user can rate (e.g., after renting)
|
||
// const canRateProperty = await canRateProperty(property._raw?.id || parseInt(params.id), AuthService.getUserId());
|
||
// setCanRate(canRateProperty);
|
||
// } catch (error) {
|
||
// console.error('[Property] Failed to fetch user rating:', error);
|
||
// }
|
||
// }
|
||
|
||
// fetchUserRatingAndCheck();
|
||
// }, [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 (
|
||
// <div className="min-h-screen bg-gray-50 flex items-center justify-center">
|
||
// <div className="text-center">
|
||
// <div className="w-16 h-16 border-4 border-gray-200 border-t-gray-800 rounded-full animate-spin mx-auto mb-4"></div>
|
||
// <p className="text-gray-600">جاري تحميل تفاصيل العقار...</p>
|
||
// </div>
|
||
// </div>
|
||
// );
|
||
// }
|
||
|
||
// if (!property) {
|
||
// return (
|
||
// <div className="min-h-screen bg-gray-50 flex items-center justify-center">
|
||
// <div className="text-center">
|
||
// <Home className="w-16 h-16 text-gray-400 mx-auto mb-4" />
|
||
// <h2 className="text-2xl font-bold text-gray-900 mb-2">العقار غير موجود</h2>
|
||
// <p className="text-gray-600 mb-4">لم نتمكن من العثور على العقار المطلوب</p>
|
||
// <Link href="/properties" className="bg-gray-800 text-white px-6 py-3 rounded-xl font-medium hover:bg-gray-900 transition-colors">
|
||
// العودة إلى العقارات
|
||
// </Link>
|
||
// </div>
|
||
// </div>
|
||
// );
|
||
// }
|
||
|
||
// return (
|
||
// <div className="min-h-screen bg-gray-50">
|
||
// <Toaster position="top-center" reverseOrder={false} />
|
||
// <div className="bg-white border-b sticky top-16 z-40 shadow-sm">
|
||
// <div className="container mx-auto px-4">
|
||
// <div className="flex items-center justify-between h-16">
|
||
// <Link href="/properties" className="flex items-center gap-2 text-gray-600 hover:text-gray-900 transition-colors">
|
||
// <ArrowLeft className="w-5 h-5" />
|
||
// <span>العودة إلى العقارات</span>
|
||
// </Link>
|
||
// <div className="flex gap-2">
|
||
// <button
|
||
// onClick={async () => {
|
||
// if (!AuthService.isAuthenticated()) { setShowLoginDialog(true); return; }
|
||
// const propId = property?._raw?.id || parseInt(params.id);
|
||
// setFavLoading(true);
|
||
// if (isFavorite(propId)) {
|
||
// await removeFavorite(propId);
|
||
// toast.success('تمت الإزالة من المفضلة');
|
||
// } else {
|
||
// await addFavorite(propId);
|
||
// toast.success('تمت الإضافة إلى المفضلة');
|
||
// }
|
||
// setFavLoading(false);
|
||
// }}
|
||
// disabled={favLoading}
|
||
// className="p-2 hover:bg-gray-100 rounded-full transition-colors"
|
||
// >
|
||
// <Heart className={`w-5 h-5 ${isFavorite(property?._raw?.id || parseInt(params.id)) ? 'fill-red-500 text-red-500' : 'text-gray-600'}`} />
|
||
// </button>
|
||
// {/* Share Dropdown */}
|
||
// <div className="relative">
|
||
// <button
|
||
// onClick={() => setShowShareMenu(!showShareMenu)}
|
||
// className="p-2 hover:bg-gray-100 rounded-full transition-colors flex items-center gap-1"
|
||
// title="مشاركة"
|
||
// >
|
||
// <Share2 className="w-5 h-5 text-gray-500" />
|
||
// </button>
|
||
|
||
// {showShareMenu && (
|
||
// <>
|
||
// <div className="fixed inset-0 z-40" onClick={() => setShowShareMenu(false)} />
|
||
// <div className="absolute left-0 top-full mt-2 w-56 bg-white rounded-xl shadow-xl border border-gray-200 overflow-hidden z-50">
|
||
// <div className="p-2 space-y-1">
|
||
// {/* Facebook */}
|
||
// <button
|
||
// onClick={() => {
|
||
// const typeLabel = property.type === 'villa' ? 'فيلا' : property.type === 'apartment' ? 'شقة' : 'بيت';
|
||
// const priceLabel = `${formatCurrency(property.price)} / ${property.priceUnit === 'daily' ? 'يوم' : 'شهر'}`;
|
||
// const lines = [
|
||
// `🏠 ${typeLabel} مفروشة في ${property.location?.address || ''}`,
|
||
// `💰 ${priceLabel}`,
|
||
// `🛏️ ${property.bedrooms} غرف نوم | 🚿 ${property.bathrooms} حمامات | 📐 ${property.area} م²`,
|
||
// property.description ? `📝 ${property.description.substring(0, 80)}...` : '',
|
||
// '',
|
||
// '🔥 احجز الآن على SweetHome',
|
||
// ].filter(Boolean);
|
||
// const quote = encodeURIComponent(lines.join('\n'));
|
||
// const url = encodeURIComponent(window.location.href);
|
||
// window.open(`https://www.facebook.com/sharer/sharer.php?u=${url}"e=${quote}`, '_blank', 'width=600,height=400');
|
||
// setShowShareMenu(false);
|
||
// }}
|
||
// className="w-full flex items-center gap-3 px-3 py-2 rounded-lg hover:bg-blue-50 transition-colors"
|
||
// >
|
||
// <div className="w-8 h-8 bg-blue-600 rounded-lg flex items-center justify-center">
|
||
// <svg className="w-4 h-4 text-white" fill="currentColor" viewBox="0 0 24 24"><path d="M24 12.073c0-6.627-5.373-12-12-12s-12 5.373-12 12c0 5.99 4.388 10.954 10.125 11.854v-8.385H7.078v-3.47h3.047V9.43c0-3.007 1.792-4.669 4.533-4.669 1.312 0 2.686.235 2.686.235v2.953H15.83c-1.491 0-1.956.925-1.956 1.874v2.25h3.328l-.532 3.47h-2.796v8.385C19.612 23.027 24 18.062 24 12.073z"/></svg>
|
||
// </div>
|
||
// <span className="text-sm font-medium text-gray-700">فيسبوك</span>
|
||
// </button>
|
||
|
||
// {/* WhatsApp */}
|
||
// <button
|
||
// onClick={() => {
|
||
// const typeLabel = property.type === 'villa' ? 'فيلا' : property.type === 'apartment' ? 'شقة' : 'بيت';
|
||
// const priceLabel = `${formatCurrency(property.price)} / ${property.priceUnit === 'daily' ? 'يوم' : 'شهر'}`;
|
||
// const text = encodeURIComponent(
|
||
// `🏠 ${typeLabel} في ${property.location?.address || ''}\n💰 ${priceLabel}\n🛏️ ${property.bedrooms} غرف | 📐 ${property.area} م²\n\n🔗 ${window.location.href}\n\n🔥 احجز على SweetHome`
|
||
// );
|
||
// window.open(`https://wa.me/?text=${text}`, '_blank');
|
||
// setShowShareMenu(false);
|
||
// }}
|
||
// className="w-full flex items-center gap-3 px-3 py-2 rounded-lg hover:bg-green-50 transition-colors"
|
||
// >
|
||
// <div className="w-8 h-8 bg-green-500 rounded-lg flex items-center justify-center">
|
||
// <svg className="w-4 h-4 text-white" fill="currentColor" viewBox="0 0 24 24"><path d="M17.472 14.382c-.297-.149-1.758-.867-2.03-.967-.273-.099-.471-.148-.67.15-.197.297-.767.966-.94 1.164-.173.199-.347.223-.644.075-.297-.15-1.255-.463-2.39-1.475-.883-.788-1.48-1.761-1.653-2.059-.173-.297-.018-.458.13-.606.134-.133.298-.347.446-.52.149-.174.198-.298.298-.497.099-.198.05-.371-.025-.52-.075-.149-.669-1.612-.916-2.207-.242-.579-.487-.5-.669-.51-.173-.008-.371-.01-.57-.01-.198 0-.52.074-.792.372-.272.297-1.04 1.016-1.04 2.479 0 1.462 1.065 2.875 1.213 3.074.149.198 2.096 3.2 5.077 4.487.709.306 1.262.489 1.694.625.712.227 1.36.195 1.871.118.571-.085 1.758-.719 2.006-1.413.248-.694.248-1.289.173-1.413-.074-.124-.272-.198-.57-.347m-5.421 7.403h-.004a9.87 9.87 0 01-5.031-1.378l-.361-.214-3.741.982.998-3.648-.235-.374a9.86 9.86 0 01-1.51-5.26c.001-5.45 4.436-9.884 9.888-9.884 2.64 0 5.122 1.03 6.988 2.898a9.825 9.825 0 012.893 6.994c-.003 5.45-4.437 9.884-9.885 9.884m8.413-18.297A11.815 11.815 0 0012.05 0C5.495 0 .16 5.335.157 11.892c0 2.096.547 4.142 1.588 5.945L.057 24l6.305-1.654a11.882 11.882 0 005.683 1.448h.005c6.554 0 11.89-5.335 11.893-11.893a11.821 11.821 0 00-3.48-8.413z"/></svg>
|
||
// </div>
|
||
// <span className="text-sm font-medium text-gray-700">واتساب</span>
|
||
// </button>
|
||
|
||
// {/* Telegram */}
|
||
// <button
|
||
// onClick={() => {
|
||
// const typeLabel = property.type === 'villa' ? 'فيلا' : property.type === 'apartment' ? 'شقة' : 'بيت';
|
||
// const priceLabel = `${formatCurrency(property.price)} / ${property.priceUnit === 'daily' ? 'يوم' : 'شهر'}`;
|
||
// const text = encodeURIComponent(
|
||
// `🏠 ${typeLabel} في ${property.location?.address || ''}\n💰 ${priceLabel}\n🛏️ ${property.bedrooms} غرف | 📐 ${property.area} م²\n\n🔥 احجز على SweetHome`
|
||
// );
|
||
// const url = encodeURIComponent(window.location.href);
|
||
// window.open(`https://t.me/share/url?url=${url}&text=${text}`, '_blank');
|
||
// setShowShareMenu(false);
|
||
// }}
|
||
// className="w-full flex items-center gap-3 px-3 py-2 rounded-lg hover:bg-sky-50 transition-colors"
|
||
// >
|
||
// <div className="w-8 h-8 bg-sky-500 rounded-lg flex items-center justify-center">
|
||
// <svg className="w-4 h-4 text-white" fill="currentColor" viewBox="0 0 24 24"><path d="M11.944 0A12 12 0 0 0 0 12a12 12 0 0 0 12 12 12 12 0 0 0 12-12A12 12 0 0 0 12 0a12 12 0 0 0-.056 0zm4.962 7.224c.1-.002.321.023.465.14a.506.506 0 0 1 .171.325c.016.093.036.306.02.472-.18 1.898-.962 6.502-1.36 8.627-.168.9-.499 1.201-.82 1.23-.696.065-1.225-.46-1.9-.902-1.056-.693-1.653-1.124-2.678-1.8-1.185-.78-.417-1.21.258-1.91.177-.184 3.247-2.977 3.307-3.23.007-.032.014-.15-.056-.212s-.174-.041-.249-.024c-.106.024-1.793 1.14-5.061 3.345-.479.33-.913.49-1.302.48-.428-.008-1.252-.241-1.865-.44-.752-.245-1.349-.374-1.297-.789.027-.216.325-.437.893-.663 3.498-1.524 5.83-2.529 6.998-3.014 3.332-1.386 4.025-1.627 4.476-1.635z"/></svg>
|
||
// </div>
|
||
// <span className="text-sm font-medium text-gray-700">تيليجرام</span>
|
||
// </button>
|
||
|
||
// {/* Instagram (copy link) */}
|
||
// <button
|
||
// onClick={async () => {
|
||
// const ok = await copyToClipboard(window.location.href);
|
||
// if (ok) {
|
||
// toast.success('تم نسخ الرابط! الصقه في انستغرام');
|
||
// } else {
|
||
// toast.error('فشل نسخ الرابط');
|
||
// }
|
||
// window.open('https://www.instagram.com', '_blank');
|
||
// setShowShareMenu(false);
|
||
// }}
|
||
// className="w-full flex items-center gap-3 px-3 py-2 rounded-lg hover:bg-pink-50 transition-colors"
|
||
// >
|
||
// <div className="w-8 h-8 bg-gradient-to-br from-purple-600 via-pink-500 to-orange-400 rounded-lg flex items-center justify-center">
|
||
// <svg className="w-4 h-4 text-white" fill="currentColor" viewBox="0 0 24 24"><path d="M12 2.163c3.204 0 3.584.012 4.85.07 3.252.148 4.771 1.691 4.919 4.919.058 1.265.069 1.645.069 4.849 0 3.205-.012 3.584-.069 4.849-.149 3.225-1.664 4.771-4.919 4.919-1.266.058-1.644.07-4.85.07-3.204 0-3.584-.012-4.849-.07-3.26-.149-4.771-1.699-4.919-4.92-.058-1.265-.07-1.644-.07-4.849 0-3.204.013-3.583.07-4.849.149-3.227 1.664-4.771 4.919-4.919 1.266-.057 1.645-.069 4.849-.069zM12 0C8.741 0 8.333.014 7.053.072 2.695.272.273 2.69.073 7.052.014 8.333 0 8.741 0 12c0 3.259.014 3.668.072 4.948.2 4.358 2.618 6.78 6.98 6.98C8.333 23.986 8.741 24 12 24c3.259 0 3.668-.014 4.948-.072 4.354-.2 6.782-2.618 6.979-6.98.059-1.28.073-1.689.073-4.948 0-3.259-.014-3.667-.072-4.947-.196-4.354-2.617-6.78-6.979-6.98C15.668.014 15.259 0 12 0zm0 5.838a6.162 6.162 0 100 12.324 6.162 6.162 0 000-12.324zM12 16a4 4 0 110-8 4 4 0 010 8zm6.406-11.845a1.44 1.44 0 100 2.881 1.44 1.44 0 000-2.881z"/></svg>
|
||
// </div>
|
||
// <span className="text-sm font-medium text-gray-700">انستغرام</span>
|
||
// </button>
|
||
|
||
// {/* Copy Link */}
|
||
// <button
|
||
// onClick={async () => {
|
||
// const ok = await copyToClipboard(window.location.href);
|
||
// if (ok) {
|
||
// toast.success('✅ تم نسخ الرابط');
|
||
// } else {
|
||
// toast.error('فشل نسخ الرابط');
|
||
// }
|
||
// setShowShareMenu(false);
|
||
// }}
|
||
// className="w-full flex items-center gap-3 px-3 py-2 rounded-lg hover:bg-gray-100 transition-colors"
|
||
// >
|
||
// <div className="w-8 h-8 bg-gray-500 rounded-lg flex items-center justify-center">
|
||
// <Copy className="w-4 h-4 text-white" />
|
||
// </div>
|
||
// <span className="text-sm font-medium text-gray-700">نسخ الرابط</span>
|
||
// </button>
|
||
// </div>
|
||
// </div>
|
||
// </>
|
||
// )}
|
||
// </div>
|
||
// </div>
|
||
// </div>
|
||
// </div>
|
||
// </div>
|
||
|
||
// <div className="container mx-auto px-4 py-8">
|
||
// <motion.div
|
||
// initial={{ opacity: 0, y: 20 }}
|
||
// animate={{ opacity: 1, y: 0 }}
|
||
// className="mb-8"
|
||
// >
|
||
// <div className="grid grid-cols-1 lg:grid-cols-2 gap-4">
|
||
// <div className="relative h-[500px] rounded-2xl overflow-hidden group bg-gray-100">
|
||
// <Image
|
||
// src={property.images[currentImage] || '/property-placeholder.jpg'}
|
||
// alt={property.title}
|
||
// fill
|
||
// className="object-cover"
|
||
// />
|
||
|
||
// {property.images.length > 1 && (
|
||
// <>
|
||
// <button
|
||
// onClick={() => setCurrentImage(prev => Math.max(0, prev - 1))}
|
||
// className="absolute left-4 top-1/2 transform -translate-y-1/2 w-10 h-10 bg-white/90 rounded-full flex items-center justify-center opacity-0 group-hover:opacity-100 transition-opacity shadow-lg hover:bg-white"
|
||
// >
|
||
// <ChevronLeft className="w-5 h-5" />
|
||
// </button>
|
||
// <button
|
||
// onClick={() => setCurrentImage(prev => Math.min(property.images.length - 1, prev + 1))}
|
||
// className="absolute right-4 top-1/2 transform -translate-y-1/2 w-10 h-10 bg-white/90 rounded-full flex items-center justify-center opacity-0 group-hover:opacity-100 transition-opacity shadow-lg hover:bg-white"
|
||
// >
|
||
// <ChevronRight className="w-5 h-5" />
|
||
// </button>
|
||
// </>
|
||
// )}
|
||
|
||
// <div className="absolute bottom-4 left-1/2 transform -translate-x-1/2 flex gap-2">
|
||
// {property.images.map((_, idx) => (
|
||
// <button
|
||
// key={idx}
|
||
// onClick={() => setCurrentImage(idx)}
|
||
// className={`w-2 h-2 rounded-full transition-all ${idx === currentImage ? 'bg-gray-800 w-4' : 'bg-white/70 hover:bg-white'
|
||
// }`}
|
||
// />
|
||
// ))}
|
||
// </div>
|
||
|
||
// <div className="absolute bottom-4 right-4 bg-black/50 text-white px-3 py-1 rounded-full text-sm backdrop-blur-sm">
|
||
// <Camera className="w-4 h-4 inline ml-1" />
|
||
// {currentImage + 1} / {property.images.length}
|
||
// </div>
|
||
// </div>
|
||
|
||
// <div className="grid grid-cols-2 gap-4">
|
||
// {property.images.slice(1, 5).map((img, idx) => (
|
||
// <div
|
||
// key={idx}
|
||
// onClick={() => setCurrentImage(idx + 1)}
|
||
// className="relative h-[240px] rounded-2xl overflow-hidden cursor-pointer hover:opacity-90 transition-opacity bg-gray-100"
|
||
// >
|
||
// <Image src={img} alt={`${property.title} ${idx + 2}`} fill className="object-cover" />
|
||
// </div>
|
||
// ))}
|
||
// </div>
|
||
// </div>
|
||
// </motion.div>
|
||
|
||
// <div className="grid grid-cols-1 lg:grid-cols-3 gap-8">
|
||
// <div className="lg:col-span-2 space-y-6">
|
||
// <motion.div
|
||
// initial={{ opacity: 0, y: 20 }}
|
||
// animate={{ opacity: 1, y: 0 }}
|
||
// transition={{ delay: 0.1 }}
|
||
// className="bg-white rounded-2xl p-6 shadow-sm border border-gray-100"
|
||
// >
|
||
// <div className="flex justify-between items-start mb-4">
|
||
// <div>
|
||
// <h1 className="text-3xl font-bold text-gray-900 mb-2">{property.title}</h1>
|
||
// <div className="flex items-center gap-2 text-gray-500">
|
||
// <MapPin className="w-5 h-5" />
|
||
// <span>{property.location.address}</span>
|
||
// </div>
|
||
// </div>
|
||
// <div className="text-left">
|
||
// <div className="text-3xl font-bold text-gray-900">{formatCurrency(property.price)}</div>
|
||
// <div className="text-sm text-gray-500">/{property.priceUnit === 'daily' ? 'يوم' : 'شهر'}</div>
|
||
// </div>
|
||
// </div>
|
||
|
||
// <div className="flex items-center gap-4">
|
||
// <div className="flex items-center gap-1">
|
||
// <Star className="w-5 h-5 fill-gray-800 text-gray-800" />
|
||
// <span className="font-bold text-gray-900">{property.rating}</span>
|
||
// {property.reviews > 0 && <span className="text-gray-500">({property.reviews} تقييم)</span>}
|
||
// </div>
|
||
// <div className="w-px h-4 bg-gray-200" />
|
||
// <span className={`font-medium ${property.status === 'available' ? 'text-gray-800' : 'text-gray-500'}`}>
|
||
// {property.status === 'available' ? 'متاح للإيجار' : 'محجوز حالياً'}
|
||
// </span>
|
||
// </div>
|
||
// </motion.div>
|
||
|
||
// <motion.div
|
||
// initial={{ opacity: 0, y: 20 }}
|
||
// animate={{ opacity: 1, y: 0 }}
|
||
// transition={{ delay: 0.2 }}
|
||
// className="bg-white rounded-2xl p-6 shadow-sm border border-gray-100"
|
||
// >
|
||
// <h2 className="text-xl font-bold mb-4 text-gray-900">المواصفات الرئيسية</h2>
|
||
// <div className="grid grid-cols-2 md:grid-cols-4 gap-4">
|
||
// <div className="text-center p-4 bg-gray-50 rounded-xl">
|
||
// <Bed className="w-6 h-6 text-gray-700 mx-auto mb-2" />
|
||
// <div className="font-bold text-gray-900">{property.bedrooms}</div>
|
||
// <div className="text-sm text-gray-500">غرف نوم</div>
|
||
// </div>
|
||
// <div className="text-center p-4 bg-gray-50 rounded-xl">
|
||
// <Bath className="w-6 h-6 text-gray-700 mx-auto mb-2" />
|
||
// <div className="font-bold text-gray-900">{property.bathrooms}</div>
|
||
// <div className="text-sm text-gray-500">حمامات</div>
|
||
// </div>
|
||
// <div className="text-center p-4 bg-gray-50 rounded-xl">
|
||
// <Square className="w-6 h-6 text-gray-700 mx-auto mb-2" />
|
||
// <div className="font-bold text-gray-900">{property.area}</div>
|
||
// <div className="text-sm text-gray-500">م²</div>
|
||
// </div>
|
||
// <div className="text-center p-4 bg-gray-50 rounded-xl">
|
||
// <Home className="w-6 h-6 text-gray-700 mx-auto mb-2" />
|
||
// <div className="font-bold text-gray-900">
|
||
// {property.type === 'villa' ? 'فيلا' :
|
||
// property.type === 'apartment' ? 'شقة' : 'بيت'}
|
||
// </div>
|
||
// <div className="text-sm text-gray-500">نوع العقار</div>
|
||
// </div>
|
||
// </div>
|
||
// </motion.div>
|
||
|
||
// <motion.div
|
||
// initial={{ opacity: 0, y: 20 }}
|
||
// animate={{ opacity: 1, y: 0 }}
|
||
// transition={{ delay: 0.3 }}
|
||
// className="bg-white rounded-2xl p-6 shadow-sm border border-gray-100"
|
||
// >
|
||
// <h2 className="text-xl font-bold mb-4 text-gray-900">وصف العقار</h2>
|
||
// <p className="text-gray-600 whitespace-pre-line leading-relaxed">{property.description || 'لا يوجد وصف متاح.'}</p>
|
||
// </motion.div>
|
||
|
||
// <motion.div
|
||
// initial={{ opacity: 0, y: 20 }}
|
||
// animate={{ opacity: 1, y: 0 }}
|
||
// transition={{ delay: 0.4 }}
|
||
// className="bg-white rounded-2xl p-6 shadow-sm border border-gray-100"
|
||
// >
|
||
// <h2 className="text-xl font-bold mb-4 text-gray-900">المميزات والخدمات</h2>
|
||
// <div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||
// {property.features.map((feature, idx) => (
|
||
// <div key={idx} className="flex items-start gap-3 p-3 bg-gray-50 rounded-xl">
|
||
// <div className={`w-6 h-6 rounded-full flex items-center justify-center flex-shrink-0 ${feature.available ? 'bg-gray-800 text-white' : 'bg-gray-200 text-gray-500'
|
||
// }`}>
|
||
// {feature.available ? (
|
||
// <Check className="w-4 h-4" />
|
||
// ) : (
|
||
// <X className="w-4 h-4" />
|
||
// )}
|
||
// </div>
|
||
// <div>
|
||
// <span className={`font-medium ${feature.available ? 'text-gray-900' : 'text-gray-400'}`}>
|
||
// {feature.name}
|
||
// </span>
|
||
// {feature.description && (
|
||
// <p className={`text-sm mt-1 ${feature.available ? 'text-gray-500' : 'text-gray-400'}`}>
|
||
// {feature.description}
|
||
// </p>
|
||
// )}
|
||
// </div>
|
||
// </div>
|
||
// ))}
|
||
// </div>
|
||
// </motion.div>
|
||
|
||
// {property.reviewList && property.reviewList.length > 0 && (
|
||
// <motion.div
|
||
// initial={{ opacity: 0, y: 20 }}
|
||
// animate={{ opacity: 1, y: 0 }}
|
||
// transition={{ delay: 0.6 }}
|
||
// className="bg-white rounded-2xl p-6 shadow-sm border border-gray-100"
|
||
// >
|
||
// <h2 className="text-xl font-bold mb-4 text-gray-900">تقييمات المستأجرين</h2>
|
||
// <div className="space-y-4">
|
||
// {property.reviewList.map((review, idx) => (
|
||
// <div key={idx} className="border-b border-gray-100 last:border-0 pb-4 last:pb-0">
|
||
// <div className="flex justify-between items-start mb-2">
|
||
// <div className="flex items-start gap-2">
|
||
// <div className="w-10 h-10 bg-gray-100 rounded-full flex items-center justify-center flex-shrink-0">
|
||
// <User className="w-6 h-6 text-gray-600" />
|
||
// </div>
|
||
// <div>
|
||
// <span className="font-bold text-gray-900">{review.user}</span>
|
||
// <div className="flex items-center gap-1 mt-1">
|
||
// {[...Array(5)].map((_, i) => (
|
||
// <Star key={i} className={`w-4 h-4 ${i < review.rating ? 'fill-gray-800 text-gray-800' : 'text-gray-300'}`} />
|
||
// ))}
|
||
// </div>
|
||
// </div>
|
||
// </div>
|
||
// <span className="text-sm text-gray-500">{review.date}</span>
|
||
// </div>
|
||
// <p className="text-gray-600">{review.comment}</p>
|
||
// </div>
|
||
// ))}
|
||
// </div>
|
||
// </motion.div>
|
||
// )}
|
||
|
||
// {/* New Rating Components */}
|
||
// {AuthService.isAuthenticated() && canRate && !userRating && (
|
||
// <motion.div
|
||
// initial={{ opacity: 0, y: 20 }}
|
||
// animate={{ opacity: 1, y: 0 }}
|
||
// transition={{ delay: 0.65 }}
|
||
// className="bg-amber-50 border-2 border-amber-200 rounded-xl p-4 text-center cursor-pointer hover:border-amber-300 transition-all"
|
||
// onClick={() => setShowRatingForm(true)}
|
||
// >
|
||
// <Star className="w-8 h-8 text-amber-500 mx-auto mb-2" />
|
||
// <h3 className="font-bold text-amber-700 mb-2">قيّم هذا العقار</h3>
|
||
// <p className="text-sm text-amber-600">شارك تجربتك مع المستأجرين الآخرين</p>
|
||
// </motion.div>
|
||
// )}
|
||
|
||
// <motion.div
|
||
// initial={{ opacity: 0, y: 20 }}
|
||
// animate={{ opacity: 1, y: 0 }}
|
||
// transition={{ delay: 0.7 }}
|
||
// className="bg-white rounded-2xl p-6 shadow-sm border border-gray-200"
|
||
// >
|
||
// <RatingList
|
||
// propertyId={property._raw?.id || parseInt(params.id)}
|
||
// userId={AuthService.getUserId()}
|
||
// />
|
||
// </motion.div>
|
||
// </div>
|
||
|
||
// <div className="space-y-6">
|
||
// <div className="sticky top-28">
|
||
// <motion.div
|
||
// initial={{ opacity: 0, x: 20 }}
|
||
// animate={{ opacity: 1, x: 0 }}
|
||
// className="bg-white rounded-2xl p-6 shadow-sm border border-gray-100 mb-6"
|
||
// >
|
||
// <h2 className="text-xl font-bold mb-4 text-gray-900">احجز هذا العقار</h2>
|
||
|
||
// {/* Selected dates display */}
|
||
// <div className="mb-4 flex gap-2 text-sm">
|
||
// <div className="flex-1 bg-gray-50 p-3 rounded-xl">
|
||
// <span className="text-gray-500 block mb-1">من</span>
|
||
// <span className="font-medium text-gray-900">{bookingDates.start || '—'}</span>
|
||
// </div>
|
||
// <div className="flex-1 bg-gray-50 p-3 rounded-xl">
|
||
// <span className="text-gray-500 block mb-1">إلى</span>
|
||
// <span className="font-medium text-gray-900">{bookingDates.end || '—'}</span>
|
||
// </div>
|
||
// </div>
|
||
|
||
// {bookingDates.start && bookingDates.end && (() => {
|
||
// const days = Math.ceil((new Date(bookingDates.end) - new Date(bookingDates.start)) / (1000 * 60 * 60 * 24));
|
||
// return days > 0 ? (
|
||
// <div className="mb-4 text-center text-sm text-amber-600 font-medium bg-amber-50 p-2 rounded-xl">
|
||
// {days} يوم{days > 1 ? 'اً' : 'اً'} {selectingEnd ? '— اضغط على تاريخ النهاية' : '✓'}
|
||
// </div>
|
||
// ) : null;
|
||
// })()}
|
||
|
||
// {/* Calendar */}
|
||
// <div className="mb-4 relative">
|
||
// <div className="flex items-center justify-between mb-3">
|
||
// <button onClick={() => setCalendarMonth(new Date(calendarMonth.getFullYear(), calendarMonth.getMonth() - 1))} className="p-1 hover:bg-gray-100 rounded-lg">
|
||
// <ChevronRight className="w-5 h-5" />
|
||
// </button>
|
||
// <span className="font-bold text-gray-900">{monthNames[calendarMonth.getMonth()]} {calendarMonth.getFullYear()}</span>
|
||
// <button onClick={() => setCalendarMonth(new Date(calendarMonth.getFullYear(), calendarMonth.getMonth() + 1))} className="p-1 hover:bg-gray-100 rounded-lg">
|
||
// <ChevronLeft className="w-5 h-5" />
|
||
// </button>
|
||
// </div>
|
||
|
||
// <div className="grid grid-cols-7 gap-1 mb-1">
|
||
// {dayNames.map((d) => (
|
||
// <div key={d} className="text-center text-xs text-gray-500 font-medium py-1">{d}</div>
|
||
// ))}
|
||
// </div>
|
||
|
||
// <div className="grid grid-cols-7 gap-1">
|
||
// {(() => {
|
||
// 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(<div key={`empty-${i}`} />);
|
||
// }
|
||
|
||
// 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(
|
||
// <button
|
||
// key={dateStr}
|
||
// onClick={() => !isPast && handleCalendarClick(dateStr)}
|
||
// disabled={isPast || !available}
|
||
// className={`w-full aspect-square rounded-lg text-sm font-medium transition-all ${
|
||
// isStart || isEnd
|
||
// ? 'bg-amber-500 text-white'
|
||
// : inRange
|
||
// ? 'bg-amber-100 text-amber-800'
|
||
// : available && !isPast
|
||
// ? 'bg-green-50 text-green-700 hover:bg-green-100 cursor-pointer'
|
||
// : 'text-gray-300 cursor-not-allowed'
|
||
// }`}
|
||
// >
|
||
// {day}
|
||
// </button>
|
||
// );
|
||
// }
|
||
// return cells;
|
||
// })()}
|
||
// </div>
|
||
|
||
// <div className="flex items-center gap-4 mt-3 text-xs text-gray-500">
|
||
// <div className="flex items-center gap-1">
|
||
// <div className="w-3 h-3 rounded bg-green-50 border border-green-200" />
|
||
// <span>متاح</span>
|
||
// </div>
|
||
// <div className="flex items-center gap-1">
|
||
// <div className="w-3 h-3 rounded bg-amber-500" />
|
||
// <span>محدد</span>
|
||
// </div>
|
||
// <div className="flex items-center gap-1">
|
||
// <div className="w-3 h-3 rounded bg-gray-100" />
|
||
// <span>غير متاح</span>
|
||
// </div>
|
||
// </div>
|
||
// </div>
|
||
|
||
// <div className="bg-gray-50 p-4 rounded-xl mb-6">
|
||
// {(() => {
|
||
// 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 (
|
||
// <>
|
||
// <div className="flex justify-between mb-2">
|
||
// <span className="text-gray-600">السعر لـ {effectiveDays} يوم{effectiveDays > 1 ? 'اً' : 'اً'}</span>
|
||
// <span className="font-bold text-gray-900">{formatCurrency(property.price * effectiveDays)}</span>
|
||
// </div>
|
||
// <div className="flex justify-between mb-2">
|
||
// <span className="text-gray-600">سلفة ضمان</span>
|
||
// <span className="font-bold text-gray-900">{formatCurrency(property._raw?.deposit || 0)}</span>
|
||
// </div>
|
||
// <div className="flex justify-between pt-2 border-t border-gray-200 font-bold">
|
||
// <span className="text-gray-900">الإجمالي</span>
|
||
// <span className="text-gray-900">{formatCurrency(property.price * effectiveDays + (property._raw?.deposit || 0))}</span>
|
||
// </div>
|
||
// </>
|
||
// );
|
||
// })()}
|
||
// </div>
|
||
|
||
// {bookingError && (
|
||
// <div className="mb-4 p-3 bg-red-50 border border-red-200 rounded-xl text-red-700 text-sm">
|
||
// {bookingError}
|
||
// </div>
|
||
// )}
|
||
|
||
// {bookingSuccess && (
|
||
// <div className="mb-4 p-3 bg-green-50 border border-green-200 rounded-xl text-green-700 text-sm">
|
||
// تم إرسال طلب الحجز بنجاح. سيتم التواصل معك قريباً.
|
||
// </div>
|
||
// )}
|
||
|
||
// <button
|
||
// onClick={handleBooking}
|
||
// disabled={bookingSuccess}
|
||
// className="w-full bg-gray-800 text-white py-4 rounded-xl font-bold text-lg hover:bg-gray-900 transition-colors mb-4 disabled:opacity-50 disabled:cursor-not-allowed"
|
||
// >
|
||
// {bookingSuccess ? 'تم الإرسال ✓' : 'تأكيد الحجز'}
|
||
// </button>
|
||
|
||
// <div className="flex items-center gap-2 text-sm text-gray-500">
|
||
// <Shield className="w-4 h-4 text-gray-600" />
|
||
// <span>الدفع آمن ومضمون. سلفة الضمان قابلة للاسترداد.</span>
|
||
// </div>
|
||
// </motion.div>
|
||
|
||
// <motion.div
|
||
// initial={{ opacity: 0, x: 20 }}
|
||
// animate={{ opacity: 1, x: 0 }}
|
||
// transition={{ delay: 0.2 }}
|
||
// className="bg-white rounded-2xl p-6 shadow-sm border border-gray-100"
|
||
// >
|
||
// <h3 className="font-bold mb-4 text-gray-900">معلومات المالك</h3>
|
||
// <div className="flex items-center gap-3 mb-4">
|
||
// <div className="w-12 h-12 bg-gray-100 rounded-full flex items-center justify-center">
|
||
// <span className="text-xl font-bold text-gray-700">
|
||
// {property.owner.name.charAt(0)}
|
||
// </span>
|
||
// </div>
|
||
// <div>
|
||
// <div className="font-bold text-gray-900">{property.owner.name}</div>
|
||
// <div className="flex items-center gap-1 text-sm text-gray-500">
|
||
// <Star className="w-3 h-3 fill-gray-600 text-gray-600" />
|
||
// <span>{property.owner.rating}</span>
|
||
// <span>· {property.owner.properties} عقارات</span>
|
||
// </div>
|
||
// {property.owner.responseRate && (
|
||
// <div className="flex items-center gap-1 text-xs text-gray-500 mt-1">
|
||
// <Clock className="w-3 h-3" />
|
||
// <span>استجابة: {property.owner.responseRate}</span>
|
||
// </div>
|
||
// )}
|
||
// </div>
|
||
// </div>
|
||
|
||
// {showContact ? (
|
||
// <div className="space-y-3">
|
||
// <div className="flex items-center gap-2 p-3 bg-gray-50 rounded-xl">
|
||
// <Phone className="w-4 h-4 text-gray-600" />
|
||
// <span className="font-medium text-gray-900">{property.owner.phone}</span>
|
||
// </div>
|
||
// <div className="flex items-center gap-2 p-3 bg-gray-50 rounded-xl">
|
||
// <Mail className="w-4 h-4 text-gray-600" />
|
||
// <span className="font-medium text-gray-900">{property.owner.email}</span>
|
||
// </div>
|
||
// </div>
|
||
// ) : (
|
||
// <button
|
||
// onClick={() => setShowContact(true)}
|
||
// className="w-full bg-gray-800 text-white py-3 rounded-xl font-medium hover:bg-gray-900 transition-colors flex items-center justify-center gap-2"
|
||
// >
|
||
// <Phone className="w-5 h-5" />
|
||
// عرض معلومات الاتصال
|
||
// </button>
|
||
// )}
|
||
|
||
// <button className="w-full mt-3 bg-gray-100 text-gray-700 py-3 rounded-xl font-medium hover:bg-gray-200 transition-colors flex items-center justify-center gap-2">
|
||
// <MessageCircle className="w-5 h-5" />
|
||
// مراسلة المالك
|
||
// </button>
|
||
// </motion.div>
|
||
// </div>
|
||
// </div>
|
||
// </div>
|
||
// </div>
|
||
|
||
// {/* Login/Register Dialog */}
|
||
// {showLoginDialog && (
|
||
// <div className="fixed inset-0 z-50 flex items-center justify-center bg-black/50 backdrop-blur-sm" onClick={() => setShowLoginDialog(false)}>
|
||
// <motion.div
|
||
// initial={{ opacity: 0, scale: 0.95 }}
|
||
// animate={{ opacity: 1, scale: 1 }}
|
||
// onClick={(e) => e.stopPropagation()}
|
||
// className="bg-white rounded-2xl p-8 max-w-md w-full mx-4 shadow-2xl text-center"
|
||
// >
|
||
// <div className="w-16 h-16 bg-amber-100 rounded-full flex items-center justify-center mx-auto mb-4">
|
||
// <LogIn className="w-8 h-8 text-amber-600" />
|
||
// </div>
|
||
// <h3 className="text-xl font-bold text-gray-900 mb-2">سجّل الدخول للمتابعة</h3>
|
||
// <p className="text-gray-500 mb-6">يجب عليك إنشاء حساب أو تسجيل الدخول لحجز هذا العقار</p>
|
||
// <div className="space-y-3">
|
||
// <Link
|
||
// href="/auth/choose-role"
|
||
// className="block w-full bg-amber-500 text-white py-3 rounded-xl font-bold hover:bg-amber-600 transition-colors"
|
||
// >
|
||
// إنشاء حساب جديد
|
||
// </Link>
|
||
// <Link
|
||
// href="/login"
|
||
// className="block w-full bg-gray-100 text-gray-700 py-3 rounded-xl font-bold hover:bg-gray-200 transition-colors"
|
||
// >
|
||
// تسجيل الدخول
|
||
// </Link>
|
||
// <button
|
||
// onClick={() => setShowLoginDialog(false)}
|
||
// className="block w-full text-gray-400 py-2 text-sm hover:text-gray-600 transition-colors"
|
||
// >
|
||
// إلغاء
|
||
// </button>
|
||
// </div>
|
||
// </motion.div>
|
||
// </div>
|
||
// )}
|
||
// </div>
|
||
// );
|
||
// }
|
||
|
||
'use client';
|
||
|
||
import { useState, useEffect } from 'react';
|
||
import { motion, AnimatePresence } 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, Heart, Share2, Phone, Mail, MessageCircle,
|
||
Calendar, Shield, Star, ChevronLeft, ChevronRight, Check, X,
|
||
Camera, Home, Building2, ArrowLeft, LogIn, Copy, Clock, User, Loader2
|
||
} from 'lucide-react';
|
||
import AuthService from '../../services/AuthService';
|
||
import { useFavorites } from '@/app/contexts/FavoritesContext';
|
||
import { BuildingTypeKeys, PropertyStatusKeys, extractCity } from '../../enums';
|
||
import PropertyRatingList from '@/app/components/ratings/PropertyRatingList';
|
||
import PropertyRatingForm from '@/app/components/ratings/PropertyRatingForm';
|
||
import { getPropertyAverageRating } from '@/app/utils/ratings';
|
||
import { getRentProperty, getSaleProperty, bookReservation } from '../../utils/api';
|
||
|
||
function formatCurrency(amount) {
|
||
return amount?.toLocaleString() + ' ل.س';
|
||
}
|
||
|
||
function mapApiDetail(item) {
|
||
if (!item) return null;
|
||
const info = item.propertyInformation || {};
|
||
const buildingType = info.buildingType ?? 0;
|
||
const propType = BuildingTypeKeys[buildingType] ?? 'apartment';
|
||
const status = PropertyStatusKeys[info.status] ?? 'available';
|
||
const dailyPrice = item.dailyRent ?? item.monthlyRent ?? item.price ?? 0;
|
||
const priceUnit = item.monthlyRent ? 'monthly' : 'daily';
|
||
|
||
const apiBase = process.env.NEXT_PUBLIC_API_URL || 'https://45.93.137.91.nip.io/api';
|
||
const rawImages = Array.isArray(info.images) ? info.images : [];
|
||
const images = rawImages.length > 0
|
||
? rawImages.map(img => img.startsWith('http') ? img : `${apiBase}${img.startsWith('/') ? '' : '/Pictures/'}${img}`)
|
||
: ['/property-placeholder.jpg'];
|
||
|
||
return {
|
||
id: item.id,
|
||
title: info.address || `عقار #${item.id}`,
|
||
description: info.description || '',
|
||
type: propType,
|
||
price: dailyPrice,
|
||
priceUnit,
|
||
location: { city: extractCity(info.address) || 'دمشق', address: info.address || '' },
|
||
bedrooms: info.numberOfBedRooms || 0,
|
||
bathrooms: info.numberOfBathRooms || 0,
|
||
area: info.space || 0,
|
||
images,
|
||
status,
|
||
deposit: item.deposit || 0,
|
||
_raw: item,
|
||
};
|
||
}
|
||
|
||
export default function PropertyDetailsPage() {
|
||
const params = useParams();
|
||
const propertyId = parseInt(params.id);
|
||
const { isFavorite, addFavorite, removeFavorite } = useFavorites();
|
||
|
||
const [property, setProperty] = useState(null);
|
||
const [loading, setLoading] = useState(true);
|
||
const [currentImage, setCurrentImage] = useState(0);
|
||
const [showContact, setShowContact] = useState(false);
|
||
const [showShareMenu, setShowShareMenu] = useState(false);
|
||
const [favLoading, setFavLoading] = useState(false);
|
||
const [showLoginDialog, setShowLoginDialog] = useState(false);
|
||
const [bookingDates, setBookingDates] = useState({ start: '', end: '' });
|
||
const [bookingError, setBookingError] = useState(null);
|
||
const [bookingSuccess, setBookingSuccess] = useState(false);
|
||
const [averageRating, setAverageRating] = useState(null);
|
||
const [eligibleReservationId, setEligibleReservationId] = useState(null);
|
||
const [showRatingForm, setShowRatingForm] = useState(false);
|
||
|
||
useEffect(() => {
|
||
async function fetchProperty() {
|
||
try {
|
||
let data = null;
|
||
try { data = await getRentProperty(propertyId); } catch {}
|
||
if (!data) try { data = await getSaleProperty(propertyId); } catch {}
|
||
if (data) setProperty(mapApiDetail(data));
|
||
else throw new Error('Property not found');
|
||
} catch (err) {
|
||
console.error(err);
|
||
toast.error('فشل تحميل العقار');
|
||
} finally {
|
||
setLoading(false);
|
||
}
|
||
}
|
||
fetchProperty();
|
||
}, [propertyId]);
|
||
|
||
useEffect(() => {
|
||
async function fetchAvg() {
|
||
if (!propertyId) return;
|
||
try {
|
||
const avg = await getPropertyAverageRating(propertyId);
|
||
setAverageRating(avg);
|
||
} catch (err) { console.error(err); }
|
||
}
|
||
fetchAvg();
|
||
}, [propertyId]);
|
||
|
||
useEffect(() => {
|
||
async function checkEligibility() {
|
||
if (!propertyId || !AuthService.isAuthenticated()) return;
|
||
try {
|
||
const res = await fetch(`http://45.93.137.91/api/Reservations/GetUserReservations?userId=${AuthService.getUserId()}`);
|
||
const data = await res.json();
|
||
const reservations = data?.data || data || [];
|
||
const completed = reservations.find(r =>
|
||
r.propertyId == propertyId &&
|
||
(r.status === 'Completed' || r.status === 'Confirmed') &&
|
||
!r.hasRatedProperty
|
||
);
|
||
setEligibleReservationId(completed?.id || null);
|
||
} catch (err) {
|
||
console.error('[Eligibility]', err);
|
||
}
|
||
}
|
||
checkEligibility();
|
||
}, [propertyId]);
|
||
|
||
const handleBooking = async () => {
|
||
if (!AuthService.isAuthenticated()) { setShowLoginDialog(true); return; }
|
||
setBookingError(null);
|
||
if (!bookingDates.start || !bookingDates.end) {
|
||
setBookingError('يرجى اختيار تاريخ البداية والنهاية');
|
||
return;
|
||
}
|
||
try {
|
||
await bookReservation(propertyId, bookingDates.start, bookingDates.end);
|
||
setBookingSuccess(true);
|
||
toast.success('تم إرسال طلب الحجز بنجاح');
|
||
} catch (err) {
|
||
setBookingError(err.message || 'فشل الحجز');
|
||
}
|
||
};
|
||
|
||
if (loading) {
|
||
return (
|
||
<div className="min-h-screen flex items-center justify-center">
|
||
<Loader2 className="w-12 h-12 animate-spin text-amber-500" />
|
||
</div>
|
||
);
|
||
}
|
||
if (!property) {
|
||
return <div className="text-center py-20 text-gray-600">العقار غير موجود</div>;
|
||
}
|
||
|
||
return (
|
||
<div className="min-h-screen bg-gray-50">
|
||
<Toaster position="top-center" />
|
||
<div className="bg-white border-b sticky top-16 z-40 shadow-sm">
|
||
<div className="container mx-auto px-4">
|
||
<div className="flex items-center justify-between h-16">
|
||
<Link href="/properties" className="flex items-center gap-2 text-gray-600 hover:text-gray-900">
|
||
<ArrowLeft className="w-5 h-5" /><span>العودة</span>
|
||
</Link>
|
||
<div className="flex gap-2">
|
||
<button
|
||
onClick={async () => {
|
||
if (!AuthService.isAuthenticated()) { setShowLoginDialog(true); return; }
|
||
setFavLoading(true);
|
||
if (isFavorite(property.id)) await removeFavorite(property.id);
|
||
else await addFavorite(property.id);
|
||
setFavLoading(false);
|
||
}}
|
||
disabled={favLoading}
|
||
>
|
||
<Heart className={`w-5 h-5 ${isFavorite(property.id) ? 'fill-red-500 text-red-500' : 'text-gray-600'}`} />
|
||
</button>
|
||
{/* زر المشاركة (اختصار) */}
|
||
<button onClick={() => setShowShareMenu(!showShareMenu)} className="p-2 hover:bg-gray-100 rounded-full">
|
||
<Share2 className="w-5 h-5 text-gray-600" />
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div className="container mx-auto px-4 py-8">
|
||
<div className="relative h-96 rounded-2xl overflow-hidden mb-8">
|
||
<Image src={property.images[currentImage]} alt={property.title} fill className="object-cover" />
|
||
{property.images.length > 1 && (
|
||
<div className="absolute bottom-4 left-1/2 transform -translate-x-1/2 flex gap-2">
|
||
{property.images.map((_, idx) => (
|
||
<button key={idx} onClick={() => setCurrentImage(idx)} className={`w-2 h-2 rounded-full ${idx === currentImage ? 'bg-amber-500 w-4' : 'bg-white/70'}`} />
|
||
))}
|
||
</div>
|
||
)}
|
||
</div>
|
||
|
||
<div className="grid lg:grid-cols-3 gap-8">
|
||
<div className="lg:col-span-2 space-y-6">
|
||
<div className="bg-white rounded-2xl p-6">
|
||
<h1 className="text-2xl font-bold">{property.title}</h1>
|
||
<div className="flex items-center gap-2 text-gray-500 mt-1"><MapPin className="w-4 h-4" />{property.location.address}</div>
|
||
<div className="flex items-center gap-4 mt-2">
|
||
<div className="flex items-center gap-1">
|
||
<Star className="w-5 h-5 fill-amber-500 text-amber-500" />
|
||
<span className="font-bold">{averageRating > 0 ? averageRating.toFixed(1) : property.rating}</span>
|
||
</div>
|
||
<span className={`px-2 py-1 rounded-full text-xs ${property.status === 'available' ? 'bg-green-100 text-green-700' : 'bg-red-100 text-red-700'}`}>
|
||
{property.status === 'available' ? 'متاح' : 'محجوز'}
|
||
</span>
|
||
</div>
|
||
</div>
|
||
|
||
<div className="bg-white rounded-2xl p-6">
|
||
<h2 className="text-xl font-bold mb-4">المواصفات</h2>
|
||
<div className="grid grid-cols-2 md:grid-cols-4 gap-4">
|
||
<div><Bed className="w-6 h-6 mb-1" /><div>{property.bedrooms} غرف نوم</div></div>
|
||
<div><Bath className="w-6 h-6 mb-1" /><div>{property.bathrooms} حمامات</div></div>
|
||
<div><Square className="w-6 h-6 mb-1" /><div>{property.area} م²</div></div>
|
||
</div>
|
||
</div>
|
||
|
||
<div className="bg-white rounded-2xl p-6">
|
||
<h2 className="text-xl font-bold mb-4">الوصف</h2>
|
||
<p className="text-gray-600">{property.description}</p>
|
||
</div>
|
||
|
||
<PropertyRatingList propertyId={propertyId} />
|
||
|
||
{eligibleReservationId && !showRatingForm && (
|
||
<div className="bg-amber-50 border-2 border-amber-200 rounded-xl p-4 text-center cursor-pointer" onClick={() => setShowRatingForm(true)}>
|
||
<Star className="w-8 h-8 text-amber-500 mx-auto mb-2" />
|
||
<h3 className="font-bold text-amber-700">قيّم هذا العقار</h3>
|
||
<p className="text-sm text-amber-600">شارك تجربتك مع المستأجرين الآخرين</p>
|
||
</div>
|
||
)}
|
||
{showRatingForm && eligibleReservationId && (
|
||
<PropertyRatingForm
|
||
reservationId={eligibleReservationId}
|
||
onSuccess={() => {
|
||
setShowRatingForm(false);
|
||
setEligibleReservationId(null);
|
||
getPropertyAverageRating(propertyId).then(setAverageRating);
|
||
}}
|
||
onCancel={() => setShowRatingForm(false)}
|
||
/>
|
||
)}
|
||
</div>
|
||
|
||
<div className="space-y-6">
|
||
<div className="bg-white rounded-2xl p-6 sticky top-28">
|
||
<h2 className="text-xl font-bold mb-4">احجز هذا العقار</h2>
|
||
<div className="flex gap-2 mb-4">
|
||
<input type="date" className="border rounded-xl p-2 flex-1" value={bookingDates.start} onChange={(e) => setBookingDates({ ...bookingDates, start: e.target.value })} />
|
||
<input type="date" className="border rounded-xl p-2 flex-1" value={bookingDates.end} onChange={(e) => setBookingDates({ ...bookingDates, end: e.target.value })} />
|
||
</div>
|
||
<div className="bg-gray-50 p-4 rounded-xl mb-4">
|
||
<div className="flex justify-between"><span>السعر اليومي</span><span>{formatCurrency(property.price)}</span></div>
|
||
<div className="flex justify-between"><span>سلفة ضمان</span><span>{formatCurrency(property.deposit)}</span></div>
|
||
<div className="flex justify-between font-bold pt-2 border-t mt-2"><span>الإجمالي</span><span>{formatCurrency(property.price + property.deposit)}</span></div>
|
||
</div>
|
||
<button onClick={handleBooking} className="w-full bg-amber-500 text-white py-3 rounded-xl font-bold">تأكيد الحجز</button>
|
||
{bookingError && <p className="text-red-500 text-sm mt-2">{bookingError}</p>}
|
||
</div>
|
||
<div className="bg-white rounded-2xl p-6">
|
||
<h3 className="font-bold mb-4">معلومات المالك</h3>
|
||
{showContact ? (
|
||
<div><Phone className="w-4 h-4 inline ml-2" />{property._raw?.phoneNumber || 'غير متوفر'}</div>
|
||
) : (
|
||
<button onClick={() => setShowContact(true)} className="w-full bg-gray-800 text-white py-2 rounded-xl">عرض معلومات الاتصال</button>
|
||
)}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
{showLoginDialog && (
|
||
<div className="fixed inset-0 bg-black/50 flex items-center justify-center z-50" onClick={() => setShowLoginDialog(false)}>
|
||
<div className="bg-white rounded-2xl p-6 max-w-sm text-center" onClick={(e) => e.stopPropagation()}>
|
||
<h3 className="text-xl font-bold">تسجيل الدخول مطلوب</h3>
|
||
<p className="text-gray-500 my-4">للحجز أو إضافة المفضلة، سجل دخولك.</p>
|
||
<Link href="/login" className="block w-full bg-amber-500 text-white py-2 rounded-xl mb-2">تسجيل الدخول</Link>
|
||
<Link href="/auth/choose-role" className="block w-full bg-gray-100 py-2 rounded-xl">إنشاء حساب</Link>
|
||
<button onClick={() => setShowLoginDialog(false)} className="mt-3 text-gray-400">إلغاء</button>
|
||
</div>
|
||
</div>
|
||
)}
|
||
</div>
|
||
);
|
||
} |