2026-08-06 22:26:35 +03:00
|
|
|
"use client";
|
|
|
|
|
|
|
|
|
|
import { useState, useRef, useEffect } from "react";
|
|
|
|
|
import { motion, AnimatePresence } from "framer-motion";
|
|
|
|
|
import { useRouter, useSearchParams } from "next/navigation";
|
|
|
|
|
import Link from "next/link";
|
|
|
|
|
import Image from "next/image";
|
|
|
|
|
import "leaflet/dist/leaflet.css";
|
|
|
|
|
import { useTranslation } from "react-i18next";
|
2026-03-17 20:36:59 +03:00
|
|
|
import {
|
|
|
|
|
ArrowLeft,
|
|
|
|
|
MapPin,
|
|
|
|
|
Camera,
|
|
|
|
|
X,
|
|
|
|
|
Home,
|
|
|
|
|
Building,
|
|
|
|
|
Bed,
|
|
|
|
|
Bath,
|
|
|
|
|
Square,
|
|
|
|
|
DollarSign,
|
|
|
|
|
Calendar,
|
|
|
|
|
Clock,
|
|
|
|
|
CheckCircle,
|
|
|
|
|
AlertCircle,
|
|
|
|
|
Info,
|
|
|
|
|
ChevronRight,
|
|
|
|
|
ChevronLeft,
|
|
|
|
|
Loader2,
|
|
|
|
|
Upload,
|
|
|
|
|
FileText,
|
|
|
|
|
Shield,
|
|
|
|
|
HelpCircle,
|
|
|
|
|
Search,
|
|
|
|
|
Navigation,
|
|
|
|
|
Wifi,
|
|
|
|
|
Zap,
|
|
|
|
|
Flame,
|
|
|
|
|
Droplets,
|
|
|
|
|
Cigarette,
|
|
|
|
|
Dog,
|
|
|
|
|
Music,
|
|
|
|
|
Star,
|
|
|
|
|
Sofa,
|
|
|
|
|
DoorOpen,
|
|
|
|
|
Warehouse,
|
|
|
|
|
Layers,
|
|
|
|
|
Plus,
|
|
|
|
|
Minus,
|
|
|
|
|
Save,
|
|
|
|
|
Wind,
|
2026-05-25 21:27:39 +03:00
|
|
|
Move,
|
2026-08-06 22:26:35 +03:00
|
|
|
Trees,
|
|
|
|
|
} from "lucide-react";
|
|
|
|
|
import toast, { Toaster } from "react-hot-toast";
|
|
|
|
|
import { addRentProperty, addSaleProperty, buildRentPropertyPayload, getCurrencies, uploadPicture } from "../../../utils/api";
|
2026-03-29 15:48:48 +00:00
|
|
|
import {
|
|
|
|
|
BuildingType,
|
|
|
|
|
RentPropertyCondition,
|
|
|
|
|
RentPropertyType,
|
|
|
|
|
RentType,
|
|
|
|
|
PropertyService,
|
|
|
|
|
PropertyServiceLabels,
|
|
|
|
|
PropertyServicesList,
|
|
|
|
|
PropertyTerm,
|
|
|
|
|
PropertyTermLabels,
|
|
|
|
|
PropertyTermsList,
|
2026-07-04 02:53:34 +03:00
|
|
|
DocumentType,
|
|
|
|
|
DocumentTypeList,
|
|
|
|
|
Governorate,
|
|
|
|
|
GovernorateLabels,
|
|
|
|
|
GovernorateList,
|
2026-03-29 15:48:48 +00:00
|
|
|
Currency,
|
2026-08-06 22:26:35 +03:00
|
|
|
CurrencyLabels,
|
|
|
|
|
} from "../../../enums";
|
2026-03-17 20:36:59 +03:00
|
|
|
|
2026-08-06 22:26:35 +03:00
|
|
|
import dynamic from "next/dynamic";
|
|
|
|
|
import { CityEnum, GovernorateToBackendCity } from "@/app/enums/City";
|
|
|
|
|
const AddPropertyMap = dynamic(() => import("@/app/components/property/AddPropertyMap"), { ssr: false });
|
2026-03-17 20:36:59 +03:00
|
|
|
|
|
|
|
|
export default function AddPropertyPage() {
|
|
|
|
|
const router = useRouter();
|
2026-05-25 21:27:39 +03:00
|
|
|
const searchParams = useSearchParams();
|
2026-08-06 22:26:35 +03:00
|
|
|
const purpose = searchParams.get("purpose") || "rent";
|
2026-07-01 15:43:58 +03:00
|
|
|
const { t } = useTranslation();
|
2026-08-06 22:26:35 +03:00
|
|
|
|
2026-03-17 20:36:59 +03:00
|
|
|
const [step, setStep] = useState(1);
|
2026-08-06 22:26:35 +03:00
|
|
|
const totalSteps = purpose === "sale" ? 4 : 4;
|
|
|
|
|
|
2026-03-17 20:36:59 +03:00
|
|
|
const [formData, setFormData] = useState({
|
2026-08-06 22:26:35 +03:00
|
|
|
propertyType: "apartment",
|
|
|
|
|
|
2026-03-17 20:36:59 +03:00
|
|
|
furnished: false,
|
2026-07-04 02:53:34 +03:00
|
|
|
documentType: DocumentType.PASSPORT,
|
2026-08-06 22:26:35 +03:00
|
|
|
|
2026-03-17 20:36:59 +03:00
|
|
|
bedrooms: 1,
|
|
|
|
|
bathrooms: 1,
|
|
|
|
|
livingRooms: 1,
|
2026-08-06 22:26:35 +03:00
|
|
|
floorNumber: "",
|
|
|
|
|
salons: "",
|
|
|
|
|
balconies: "",
|
|
|
|
|
space: "",
|
|
|
|
|
|
2026-03-17 20:36:59 +03:00
|
|
|
services: {
|
2026-03-30 01:01:42 +00:00
|
|
|
[PropertyService.ELECTRICITY]: false,
|
|
|
|
|
[PropertyService.INTERNET]: false,
|
|
|
|
|
[PropertyService.HEATING]: false,
|
|
|
|
|
[PropertyService.WATER]: false,
|
|
|
|
|
[PropertyService.CENTRAL_AIR_CONDITIONING]: false,
|
|
|
|
|
[PropertyService.PARKING]: false,
|
2026-08-06 22:26:35 +03:00
|
|
|
[PropertyService.ELEVATOR]: false,
|
2026-03-17 20:36:59 +03:00
|
|
|
},
|
2026-08-06 22:26:35 +03:00
|
|
|
|
2026-03-30 01:01:42 +00:00
|
|
|
serviceDetails: {},
|
2026-08-06 22:26:35 +03:00
|
|
|
|
2026-03-17 20:36:59 +03:00
|
|
|
terms: {
|
2026-03-30 01:01:42 +00:00
|
|
|
[PropertyTerm.NO_SMOKING]: false,
|
|
|
|
|
[PropertyTerm.NO_ANIMALS]: false,
|
2026-08-06 22:26:35 +03:00
|
|
|
[PropertyTerm.NO_PARTIES]: false,
|
2026-03-17 20:36:59 +03:00
|
|
|
},
|
2026-08-06 22:26:35 +03:00
|
|
|
|
|
|
|
|
offerType: "",
|
|
|
|
|
|
|
|
|
|
dailyPrice: "",
|
|
|
|
|
monthlyPrice: "",
|
|
|
|
|
deposit: "",
|
2026-08-12 17:42:51 +03:00
|
|
|
commission: "",
|
2026-08-06 22:26:35 +03:00
|
|
|
allowedPaymentPeriod: "",
|
|
|
|
|
|
2026-07-04 02:53:34 +03:00
|
|
|
city: Governorate.DAMASCUS,
|
2026-08-06 22:26:35 +03:00
|
|
|
district: "",
|
|
|
|
|
address: "",
|
2026-03-17 20:36:59 +03:00
|
|
|
lat: null,
|
|
|
|
|
lng: null,
|
2026-08-06 22:26:35 +03:00
|
|
|
|
|
|
|
|
description: "",
|
|
|
|
|
|
2026-05-25 23:18:03 +03:00
|
|
|
images: [],
|
2026-08-06 22:26:35 +03:00
|
|
|
|
|
|
|
|
nearbySchool: "",
|
|
|
|
|
nearbyHospital: "",
|
|
|
|
|
nearbyRestaurant: "",
|
|
|
|
|
nearbyUniversity: "",
|
|
|
|
|
nearbyPark: "",
|
|
|
|
|
nearbyMall: "",
|
2026-08-11 02:29:54 +03:00
|
|
|
neaebymosque: "",
|
2026-08-06 22:26:35 +03:00
|
|
|
|
|
|
|
|
roomAreaType: "Private room",
|
|
|
|
|
roomPeopleAllowed: "",
|
|
|
|
|
roomFurniture: "",
|
|
|
|
|
roomEntrance: "Shared entrance",
|
|
|
|
|
roomBathroom: "Shared",
|
|
|
|
|
roomKitchen: "Not available",
|
2026-05-25 23:18:03 +03:00
|
|
|
roomRestrictedAreas: false,
|
2026-08-06 22:26:35 +03:00
|
|
|
roomResidents: "",
|
|
|
|
|
roomGender: "Family",
|
|
|
|
|
roomLanguage: "",
|
2026-05-25 23:18:03 +03:00
|
|
|
roomChildren: false,
|
|
|
|
|
roomPets: false,
|
2026-08-06 22:26:35 +03:00
|
|
|
roomDedicatedTo: "Everyone",
|
2026-05-25 23:18:03 +03:00
|
|
|
roomVisitors: true,
|
|
|
|
|
roomQuietTimes: false,
|
2026-08-06 22:26:35 +03:00
|
|
|
roomQuietTimesDetails: "",
|
2026-03-17 20:36:59 +03:00
|
|
|
});
|
2026-08-06 22:26:35 +03:00
|
|
|
|
2026-03-17 20:36:59 +03:00
|
|
|
const [imagePreviews, setImagePreviews] = useState([]);
|
2026-03-30 01:06:23 +00:00
|
|
|
const [uploadedImagePaths, setUploadedImagePaths] = useState([]);
|
2026-05-26 01:20:05 +03:00
|
|
|
const [customTerms, setCustomTerms] = useState([]);
|
2026-08-06 22:26:35 +03:00
|
|
|
const [customTermInput, setCustomTermInput] = useState("");
|
|
|
|
|
|
2026-03-17 20:36:59 +03:00
|
|
|
const [selectedLocation, setSelectedLocation] = useState(null);
|
2026-08-06 22:26:35 +03:00
|
|
|
const [mapCenter, setMapCenter] = useState([33.5138, 36.2765]);
|
2026-03-28 18:12:41 +00:00
|
|
|
const [mapZoom, setMapZoom] = useState(13);
|
2026-08-06 22:26:35 +03:00
|
|
|
const [searchQuery, setSearchQuery] = useState("");
|
2026-03-30 01:25:48 +00:00
|
|
|
const [currencies, setCurrencies] = useState([]);
|
2026-03-29 15:48:48 +00:00
|
|
|
const [selectedCurrencyId, setSelectedCurrencyId] = useState(Currency.SYP);
|
2026-08-06 22:26:35 +03:00
|
|
|
|
2026-03-17 20:36:59 +03:00
|
|
|
const [errors, setErrors] = useState({});
|
2026-08-06 22:26:35 +03:00
|
|
|
|
2026-03-17 20:36:59 +03:00
|
|
|
const [isLoading, setIsLoading] = useState(false);
|
2026-07-15 20:03:38 +03:00
|
|
|
const [isUploadingImages, setIsUploadingImages] = useState(false);
|
2026-06-28 14:55:23 +03:00
|
|
|
const [agreeToTerms, setAgreeToTerms] = useState(false);
|
2026-08-06 22:26:35 +03:00
|
|
|
|
2026-03-17 20:36:59 +03:00
|
|
|
const fileInputRef = useRef(null);
|
2026-08-06 22:26:35 +03:00
|
|
|
|
2026-03-17 20:36:59 +03:00
|
|
|
const propertyTypes = [
|
2026-08-06 22:26:35 +03:00
|
|
|
{ id: "apartment", label: t("buildingType.apartment"), icon: Building },
|
|
|
|
|
{ id: "villa", label: t("buildingType.villa"), icon: Home },
|
|
|
|
|
{ id: "sweet", label: t("buildingType.sweet"), icon: Sofa },
|
|
|
|
|
{ id: "room", label: t("buildingType.room"), icon: DoorOpen },
|
|
|
|
|
{ id: "studio", label: t("buildingType.studio"), icon: Sofa },
|
|
|
|
|
{ id: "office", label: t("buildingType.office"), icon: Building },
|
|
|
|
|
{ id: "farms", label: t("buildingType.farms"), icon: Trees },
|
|
|
|
|
{ id: "shop", label: t("buildingType.shop"), icon: Warehouse },
|
|
|
|
|
{ id: "warehouse", label: t("buildingType.warehouse"), icon: Warehouse },
|
2026-03-17 20:36:59 +03:00
|
|
|
];
|
2026-08-06 22:26:35 +03:00
|
|
|
|
2026-03-17 20:36:59 +03:00
|
|
|
const serviceList = [
|
2026-03-29 15:48:48 +00:00
|
|
|
{ id: PropertyService.ELECTRICITY, label: PropertyServiceLabels[PropertyService.ELECTRICITY], icon: Zap },
|
|
|
|
|
{ id: PropertyService.INTERNET, label: PropertyServiceLabels[PropertyService.INTERNET], icon: Wifi },
|
|
|
|
|
{ id: PropertyService.HEATING, label: PropertyServiceLabels[PropertyService.HEATING], icon: Flame },
|
|
|
|
|
{ id: PropertyService.WATER, label: PropertyServiceLabels[PropertyService.WATER], icon: Droplets },
|
|
|
|
|
{ id: PropertyService.CENTRAL_AIR_CONDITIONING, label: PropertyServiceLabels[PropertyService.CENTRAL_AIR_CONDITIONING], icon: Wind },
|
|
|
|
|
{ id: PropertyService.PARKING, label: PropertyServiceLabels[PropertyService.PARKING], icon: Warehouse },
|
|
|
|
|
{ id: PropertyService.ELEVATOR, label: PropertyServiceLabels[PropertyService.ELEVATOR], icon: Layers },
|
2026-03-17 20:36:59 +03:00
|
|
|
];
|
2026-08-06 22:26:35 +03:00
|
|
|
|
2026-03-17 20:36:59 +03:00
|
|
|
const termsList = [
|
2026-03-29 15:48:48 +00:00
|
|
|
{ id: PropertyTerm.NO_SMOKING, label: PropertyTermLabels[PropertyTerm.NO_SMOKING], icon: Cigarette },
|
|
|
|
|
{ id: PropertyTerm.NO_ANIMALS, label: PropertyTermLabels[PropertyTerm.NO_ANIMALS], icon: Dog },
|
|
|
|
|
{ id: PropertyTerm.NO_PARTIES, label: PropertyTermLabels[PropertyTerm.NO_PARTIES], icon: Music },
|
2026-03-17 20:36:59 +03:00
|
|
|
];
|
2026-08-06 22:26:35 +03:00
|
|
|
|
2026-03-17 20:36:59 +03:00
|
|
|
const offerTypes = [
|
2026-08-06 22:26:35 +03:00
|
|
|
{ id: "daily", label: t("dailyRent"), icon: Clock },
|
|
|
|
|
{ id: "monthly", label: t("monthlyRent"), icon: Calendar },
|
|
|
|
|
{ id: "both", label: t("addProperty.dailyAndMonthly"), icon: Calendar },
|
2026-03-30 00:57:52 +00:00
|
|
|
].filter(Boolean);
|
2026-03-17 20:36:59 +03:00
|
|
|
|
2026-07-04 02:53:34 +03:00
|
|
|
const getDocumentTypeLabel = (type) => {
|
|
|
|
|
switch (type) {
|
|
|
|
|
case DocumentType.PASSPORT:
|
2026-08-06 22:26:35 +03:00
|
|
|
return t("documentType.passport");
|
2026-07-04 02:53:34 +03:00
|
|
|
case DocumentType.IDCARD:
|
2026-08-06 22:26:35 +03:00
|
|
|
return t("documentType.idCard");
|
2026-07-04 02:53:34 +03:00
|
|
|
case DocumentType.BOTH:
|
2026-08-06 22:26:35 +03:00
|
|
|
return t("documentType.both");
|
2026-07-04 02:53:34 +03:00
|
|
|
default:
|
|
|
|
|
return type;
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
2026-03-17 20:36:59 +03:00
|
|
|
useEffect(() => {
|
2026-08-06 22:26:35 +03:00
|
|
|
getCurrencies()
|
|
|
|
|
.then((data) => {
|
|
|
|
|
if (Array.isArray(data) && data.length > 0) {
|
|
|
|
|
setCurrencies(data);
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
.catch((err) => {
|
|
|
|
|
console.warn("[AddProperty] Failed to load currencies:", err);
|
|
|
|
|
});
|
2026-03-17 20:36:59 +03:00
|
|
|
}, []);
|
|
|
|
|
|
|
|
|
|
const handleSearch = async () => {
|
|
|
|
|
if (!searchQuery) return;
|
|
|
|
|
|
2026-08-06 22:26:35 +03:00
|
|
|
toast.loading(t("toast.searching"), { id: "search" });
|
|
|
|
|
|
2026-03-17 20:36:59 +03:00
|
|
|
try {
|
2026-08-06 22:26:35 +03:00
|
|
|
const response = await fetch(`https://nominatim.openstreetmap.org/search?format=json&q=${encodeURIComponent(searchQuery)}&limit=1&accept-language=ar`);
|
2026-03-17 20:36:59 +03:00
|
|
|
const data = await response.json();
|
2026-08-06 22:26:35 +03:00
|
|
|
|
2026-03-17 20:36:59 +03:00
|
|
|
if (data && data.length > 0) {
|
|
|
|
|
const result = data[0];
|
|
|
|
|
const lat = parseFloat(result.lat);
|
|
|
|
|
const lng = parseFloat(result.lon);
|
2026-08-06 22:26:35 +03:00
|
|
|
|
2026-03-17 20:36:59 +03:00
|
|
|
setMapCenter([lat, lng]);
|
|
|
|
|
setMapZoom(18);
|
2026-08-06 22:26:35 +03:00
|
|
|
|
|
|
|
|
const addressResponse = await fetch(`https://nominatim.openstreetmap.org/reverse?format=json&lat=${lat}&lon=${lng}&accept-language=ar`);
|
2026-03-17 20:36:59 +03:00
|
|
|
const addressData = await addressResponse.json();
|
2026-08-06 22:26:35 +03:00
|
|
|
|
2026-03-17 20:36:59 +03:00
|
|
|
setSelectedLocation({
|
|
|
|
|
lat: lat,
|
|
|
|
|
lng: lng,
|
2026-08-06 22:26:35 +03:00
|
|
|
address: addressData.display_name || result.display_name,
|
2026-03-17 20:36:59 +03:00
|
|
|
});
|
2026-08-06 22:26:35 +03:00
|
|
|
|
|
|
|
|
toast.success(t("toast.locationFound"), { id: "search" });
|
2026-03-17 20:36:59 +03:00
|
|
|
} else {
|
2026-08-06 22:26:35 +03:00
|
|
|
toast.error(t("toast.locationNotFound"), { id: "search" });
|
2026-03-17 20:36:59 +03:00
|
|
|
}
|
|
|
|
|
} catch (error) {
|
2026-08-06 22:26:35 +03:00
|
|
|
console.error(t("search.error"), error);
|
|
|
|
|
toast.error(t("search.error"), { id: "search" });
|
2026-03-17 20:36:59 +03:00
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const handleGeolocation = () => {
|
2026-07-16 10:56:59 -07:00
|
|
|
if (!navigator.geolocation) {
|
2026-08-06 22:26:35 +03:00
|
|
|
toast.error(t("toast.geolocationNotSupported"));
|
2026-03-17 20:36:59 +03:00
|
|
|
return;
|
|
|
|
|
}
|
2026-08-06 22:26:35 +03:00
|
|
|
|
|
|
|
|
toast.loading(t("toast.geolocationLoading"), { id: "geolocation" });
|
2026-03-17 20:36:59 +03:00
|
|
|
|
|
|
|
|
navigator.geolocation.getCurrentPosition(
|
|
|
|
|
async (position) => {
|
|
|
|
|
const { latitude, longitude } = position.coords;
|
|
|
|
|
setMapCenter([latitude, longitude]);
|
|
|
|
|
setMapZoom(18);
|
2026-08-06 22:26:35 +03:00
|
|
|
|
2026-07-16 10:56:59 -07:00
|
|
|
try {
|
2026-08-06 22:26:35 +03:00
|
|
|
const response = await fetch(`https://nominatim.openstreetmap.org/reverse?format=json&lat=${latitude}&lon=${longitude}&accept-language=ar`);
|
|
|
|
|
const data = await response.json();
|
|
|
|
|
|
|
|
|
|
setSelectedLocation({
|
|
|
|
|
lat: latitude,
|
|
|
|
|
lng: longitude,
|
|
|
|
|
address: data.display_name || t("addProperty.currentLocation"),
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
toast.success(t("toast.geolocationSuccess"), { id: "geolocation" });
|
|
|
|
|
} catch (error) {
|
|
|
|
|
setSelectedLocation({
|
|
|
|
|
lat: latitude,
|
|
|
|
|
lng: longitude,
|
|
|
|
|
address: t("addProperty.currentLocation"),
|
|
|
|
|
});
|
|
|
|
|
toast.success(t("toast.geolocationSuccess"), { id: "geolocation" });
|
|
|
|
|
}
|
2026-03-17 20:36:59 +03:00
|
|
|
},
|
|
|
|
|
(error) => {
|
2026-08-06 22:26:35 +03:00
|
|
|
toast.error(t("toast.geolocationFailed"), { id: "geolocation" });
|
|
|
|
|
},
|
2026-03-17 20:36:59 +03:00
|
|
|
);
|
|
|
|
|
};
|
|
|
|
|
|
2026-07-16 10:56:59 -07:00
|
|
|
const handleMapClick = async (coords) => {
|
|
|
|
|
try {
|
|
|
|
|
const [lat, lng] = coords;
|
2026-08-06 22:26:35 +03:00
|
|
|
|
|
|
|
|
toast.loading(t("addProperty.locationSelecting"), { id: "location" });
|
|
|
|
|
|
|
|
|
|
const response = await fetch(`https://nominatim.openstreetmap.org/reverse?format=json&lat=${lat}&lon=${lng}&accept-language=ar`);
|
|
|
|
|
const data = await response.json();
|
|
|
|
|
|
|
|
|
|
setSelectedLocation({
|
|
|
|
|
lat: lat,
|
|
|
|
|
lng: lng,
|
|
|
|
|
address: data.display_name || t("addProperty.defaultAddress"),
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
setMapZoom(18);
|
|
|
|
|
toast.success(t("toast.locationSelectedSuccess"), { id: "location" });
|
2026-07-16 10:56:59 -07:00
|
|
|
} catch (error) {
|
2026-08-06 22:26:35 +03:00
|
|
|
console.error(t("addProperty.locationError"), error);
|
2026-07-16 10:56:59 -07:00
|
|
|
const [lat, lng] = coords;
|
|
|
|
|
setSelectedLocation({
|
|
|
|
|
lat: lat,
|
|
|
|
|
lng: lng,
|
2026-08-06 22:26:35 +03:00
|
|
|
address: t("addProperty.defaultAddress"),
|
2026-07-16 10:56:59 -07:00
|
|
|
});
|
|
|
|
|
setMapZoom(18);
|
2026-08-06 22:26:35 +03:00
|
|
|
toast.success(t("toast.locationConfirmed"), { id: "location" });
|
2026-07-16 10:56:59 -07:00
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const handleMarkerDragEnd = async (lat, lng) => {
|
|
|
|
|
try {
|
2026-08-06 22:26:35 +03:00
|
|
|
const response = await fetch(`https://nominatim.openstreetmap.org/reverse?format=json&lat=${lat}&lon=${lng}&accept-language=ar`);
|
2026-07-16 10:56:59 -07:00
|
|
|
const data = await response.json();
|
|
|
|
|
setSelectedLocation({
|
|
|
|
|
lat,
|
|
|
|
|
lng,
|
2026-08-06 22:26:35 +03:00
|
|
|
address: data.display_name || t("addProperty.defaultAddress"),
|
2026-07-16 10:56:59 -07:00
|
|
|
});
|
|
|
|
|
} catch (error) {
|
|
|
|
|
setSelectedLocation({
|
|
|
|
|
lat,
|
|
|
|
|
lng,
|
2026-08-06 22:26:35 +03:00
|
|
|
address: t("addProperty.defaultAddress"),
|
2026-07-16 10:56:59 -07:00
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
2026-03-17 20:36:59 +03:00
|
|
|
const confirmLocation = () => {
|
|
|
|
|
if (selectedLocation) {
|
|
|
|
|
setFormData({
|
|
|
|
|
...formData,
|
|
|
|
|
lat: selectedLocation.lat,
|
|
|
|
|
lng: selectedLocation.lng,
|
2026-08-06 22:26:35 +03:00
|
|
|
address: selectedLocation.address,
|
2026-03-17 20:36:59 +03:00
|
|
|
});
|
2026-08-06 22:26:35 +03:00
|
|
|
|
|
|
|
|
toast.success(t("toast.locationConfirmed"));
|
2026-03-17 20:36:59 +03:00
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const resetLocation = () => {
|
|
|
|
|
setSelectedLocation(null);
|
|
|
|
|
setFormData({
|
|
|
|
|
...formData,
|
|
|
|
|
lat: null,
|
|
|
|
|
lng: null,
|
2026-08-06 22:26:35 +03:00
|
|
|
address: "",
|
2026-03-17 20:36:59 +03:00
|
|
|
});
|
|
|
|
|
setMapZoom(15);
|
2026-08-06 22:26:35 +03:00
|
|
|
toast.info(t("toast.clearLocation"));
|
2026-03-17 20:36:59 +03:00
|
|
|
};
|
|
|
|
|
|
2026-03-30 01:15:57 +00:00
|
|
|
const handleImageUpload = async (files) => {
|
2026-03-17 20:36:59 +03:00
|
|
|
const newImages = Array.from(files);
|
2026-08-06 22:26:35 +03:00
|
|
|
|
2026-06-28 14:55:23 +03:00
|
|
|
if (formData.images.length + newImages.length > 5) {
|
2026-08-06 22:26:35 +03:00
|
|
|
toast.error(t("toast.maxImages"));
|
2026-03-17 20:36:59 +03:00
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-30 01:15:57 +00:00
|
|
|
for (const file of newImages) {
|
2026-08-06 22:26:35 +03:00
|
|
|
if (!file.type.startsWith("image/")) {
|
|
|
|
|
toast.error(t("toast.imageRequired"));
|
2026-03-30 01:15:57 +00:00
|
|
|
continue;
|
2026-03-17 20:36:59 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (file.size > 5 * 1024 * 1024) {
|
2026-08-06 22:26:35 +03:00
|
|
|
toast.error(t("toast.imageSize"));
|
2026-03-30 01:15:57 +00:00
|
|
|
continue;
|
2026-03-17 20:36:59 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const reader = new FileReader();
|
|
|
|
|
reader.onloadend = () => {
|
2026-08-06 22:26:35 +03:00
|
|
|
setImagePreviews((prev) => [...prev, reader.result]);
|
2026-03-17 20:36:59 +03:00
|
|
|
};
|
|
|
|
|
reader.readAsDataURL(file);
|
|
|
|
|
|
2026-08-06 22:26:35 +03:00
|
|
|
setFormData((prev) => ({
|
2026-03-30 01:15:57 +00:00
|
|
|
...prev,
|
2026-08-06 22:26:35 +03:00
|
|
|
images: [...prev.images, file],
|
2026-03-30 01:15:57 +00:00
|
|
|
}));
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
const path = await uploadPicture(file);
|
2026-08-06 22:26:35 +03:00
|
|
|
setUploadedImagePaths((prev) => [...prev, path]);
|
2026-03-30 01:15:57 +00:00
|
|
|
} catch (err) {
|
2026-08-06 22:26:35 +03:00
|
|
|
console.error("[AddProperty] Image upload failed:", err);
|
|
|
|
|
toast.error(`${t("toast.imageUploadFailed")}: ${file.name}`);
|
2026-03-30 01:15:57 +00:00
|
|
|
}
|
|
|
|
|
}
|
2026-03-17 20:36:59 +03:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const removeImage = (index) => {
|
|
|
|
|
const newImages = [...formData.images];
|
|
|
|
|
newImages.splice(index, 1);
|
2026-08-06 22:26:35 +03:00
|
|
|
|
2026-03-17 20:36:59 +03:00
|
|
|
const newPreviews = [...imagePreviews];
|
|
|
|
|
newPreviews.splice(index, 1);
|
2026-08-06 22:26:35 +03:00
|
|
|
|
2026-03-30 01:15:57 +00:00
|
|
|
const newPaths = [...uploadedImagePaths];
|
|
|
|
|
newPaths.splice(index, 1);
|
2026-08-06 22:26:35 +03:00
|
|
|
|
|
|
|
|
setFormData((prev) => ({ ...prev, images: newImages }));
|
2026-03-17 20:36:59 +03:00
|
|
|
setImagePreviews(newPreviews);
|
2026-03-30 01:15:57 +00:00
|
|
|
setUploadedImagePaths(newPaths);
|
2026-03-17 20:36:59 +03:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const toggleService = (serviceId) => {
|
2026-08-06 22:26:35 +03:00
|
|
|
setFormData((prev) => {
|
2026-03-30 00:57:52 +00:00
|
|
|
const services = { ...prev.services };
|
|
|
|
|
services[serviceId] = !services[serviceId];
|
|
|
|
|
return { ...prev, services };
|
2026-03-17 20:36:59 +03:00
|
|
|
});
|
|
|
|
|
};
|
|
|
|
|
|
2026-03-30 00:57:52 +00:00
|
|
|
const updateServiceDetail = (serviceId, value) => {
|
2026-08-06 22:26:35 +03:00
|
|
|
setFormData((prev) => ({
|
2026-03-30 00:57:52 +00:00
|
|
|
...prev,
|
2026-08-06 22:26:35 +03:00
|
|
|
serviceDetails: { ...prev.serviceDetails, [serviceId]: value },
|
2026-03-30 00:57:52 +00:00
|
|
|
}));
|
|
|
|
|
};
|
|
|
|
|
|
2026-03-17 20:36:59 +03:00
|
|
|
const toggleTerm = (termId) => {
|
2026-08-06 22:26:35 +03:00
|
|
|
setFormData((prev) => {
|
2026-03-30 00:57:52 +00:00
|
|
|
const terms = { ...prev.terms };
|
|
|
|
|
terms[termId] = !terms[termId];
|
|
|
|
|
return { ...prev, terms };
|
2026-03-17 20:36:59 +03:00
|
|
|
});
|
|
|
|
|
};
|
|
|
|
|
|
2026-05-26 01:20:05 +03:00
|
|
|
const addCustomTerm = () => {
|
|
|
|
|
const val = customTermInput.trim();
|
|
|
|
|
if (!val) return;
|
|
|
|
|
if (customTerms.includes(val)) return;
|
2026-08-06 22:26:35 +03:00
|
|
|
setCustomTerms((prev) => [...prev, val]);
|
|
|
|
|
setCustomTermInput("");
|
2026-05-26 01:20:05 +03:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const removeCustomTerm = (term) => {
|
2026-08-06 22:26:35 +03:00
|
|
|
setCustomTerms((prev) => prev.filter((t) => t !== term));
|
2026-05-26 01:20:05 +03:00
|
|
|
};
|
|
|
|
|
|
2026-03-17 20:36:59 +03:00
|
|
|
const incrementBedrooms = () => {
|
|
|
|
|
setFormData({
|
|
|
|
|
...formData,
|
2026-08-06 22:26:35 +03:00
|
|
|
bedrooms: formData.bedrooms + 1,
|
2026-03-17 20:36:59 +03:00
|
|
|
});
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const decrementBedrooms = () => {
|
|
|
|
|
if (formData.bedrooms > 1) {
|
|
|
|
|
setFormData({
|
|
|
|
|
...formData,
|
2026-08-06 22:26:35 +03:00
|
|
|
bedrooms: formData.bedrooms - 1,
|
2026-03-17 20:36:59 +03:00
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const incrementBathrooms = () => {
|
|
|
|
|
setFormData({
|
|
|
|
|
...formData,
|
2026-08-06 22:26:35 +03:00
|
|
|
bathrooms: formData.bathrooms + 1,
|
2026-03-17 20:36:59 +03:00
|
|
|
});
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const decrementBathrooms = () => {
|
|
|
|
|
if (formData.bathrooms > 1) {
|
|
|
|
|
setFormData({
|
|
|
|
|
...formData,
|
2026-08-06 22:26:35 +03:00
|
|
|
bathrooms: formData.bathrooms - 1,
|
2026-03-17 20:36:59 +03:00
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const incrementLivingRooms = () => {
|
|
|
|
|
setFormData({
|
|
|
|
|
...formData,
|
2026-08-06 22:26:35 +03:00
|
|
|
livingRooms: formData.livingRooms + 1,
|
2026-03-17 20:36:59 +03:00
|
|
|
});
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const decrementLivingRooms = () => {
|
|
|
|
|
if (formData.livingRooms > 1) {
|
|
|
|
|
setFormData({
|
|
|
|
|
...formData,
|
2026-08-06 22:26:35 +03:00
|
|
|
livingRooms: formData.livingRooms - 1,
|
2026-03-17 20:36:59 +03:00
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const validateStep = () => {
|
|
|
|
|
const newErrors = {};
|
|
|
|
|
|
2026-08-06 22:26:35 +03:00
|
|
|
switch (step) {
|
2026-03-17 20:36:59 +03:00
|
|
|
case 1:
|
|
|
|
|
if (!formData.propertyType) {
|
2026-08-06 22:26:35 +03:00
|
|
|
newErrors.propertyType = t("addProperty.propertyTypeRequired");
|
2026-03-17 20:36:59 +03:00
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case 2:
|
|
|
|
|
if (!formData.bedrooms) {
|
2026-08-06 22:26:35 +03:00
|
|
|
newErrors.bedrooms = t("addProperty.bedroomsRequired");
|
2026-03-17 20:36:59 +03:00
|
|
|
}
|
|
|
|
|
if (!formData.bathrooms) {
|
2026-08-06 22:26:35 +03:00
|
|
|
newErrors.bathrooms = t("addProperty.bathroomsRequired");
|
2026-03-17 20:36:59 +03:00
|
|
|
}
|
|
|
|
|
if (!formData.livingRooms) {
|
2026-08-06 22:26:35 +03:00
|
|
|
newErrors.livingRooms = t("addProperty.livingRoomsRequired");
|
2026-03-17 20:36:59 +03:00
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case 3:
|
2026-08-06 22:26:35 +03:00
|
|
|
if (purpose === "sale") {
|
|
|
|
|
if (!formData.salePrice) newErrors.salePrice = t("addProperty.salePriceRequired");
|
2026-05-25 21:27:39 +03:00
|
|
|
} else {
|
2026-06-28 14:55:23 +03:00
|
|
|
if (!formData.offerType) {
|
2026-08-06 22:26:35 +03:00
|
|
|
newErrors.offerType = t("addProperty.offerTypeRequired");
|
|
|
|
|
} else if (formData.offerType === "daily" && !formData.dailyPrice) {
|
|
|
|
|
newErrors.dailyPrice = t("addProperty.dailyPriceRequired");
|
|
|
|
|
} else if (formData.offerType === "monthly" && !formData.monthlyPrice) {
|
|
|
|
|
newErrors.monthlyPrice = t("addProperty.monthlyPriceRequired");
|
|
|
|
|
} else if (formData.offerType === "both") {
|
|
|
|
|
if (!formData.dailyPrice) newErrors.dailyPrice = t("addProperty.dailyPriceRequired");
|
|
|
|
|
if (!formData.monthlyPrice) newErrors.monthlyPrice = t("addProperty.monthlyPriceRequired");
|
2026-08-12 17:42:51 +03:00
|
|
|
|
2026-05-25 21:27:39 +03:00
|
|
|
}
|
2026-03-17 20:36:59 +03:00
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case 4:
|
|
|
|
|
if (!formData.lat || !formData.lng) {
|
2026-08-06 22:26:35 +03:00
|
|
|
newErrors.location = t("addProperty.locationRequired");
|
2026-03-17 20:36:59 +03:00
|
|
|
}
|
2026-06-28 14:55:23 +03:00
|
|
|
if (formData.images.length < 2) {
|
2026-08-06 22:26:35 +03:00
|
|
|
newErrors.images = t("addProperty.minImagesRequired");
|
2026-06-28 14:55:23 +03:00
|
|
|
}
|
|
|
|
|
if (formData.images.length > 5) {
|
2026-08-06 22:26:35 +03:00
|
|
|
newErrors.images = t("addProperty.maxImagesError");
|
2026-03-17 20:36:59 +03:00
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
setErrors(newErrors);
|
|
|
|
|
return Object.keys(newErrors).length === 0;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const handleNext = () => {
|
|
|
|
|
if (validateStep()) {
|
|
|
|
|
setStep(step + 1);
|
2026-08-06 22:26:35 +03:00
|
|
|
window.scrollTo({ top: 0, behavior: "smooth" });
|
2026-03-17 20:36:59 +03:00
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const handleBack = () => {
|
|
|
|
|
setStep(step - 1);
|
2026-08-06 22:26:35 +03:00
|
|
|
window.scrollTo({ top: 0, behavior: "smooth" });
|
2026-03-17 20:36:59 +03:00
|
|
|
};
|
|
|
|
|
|
2026-07-15 20:03:38 +03:00
|
|
|
const ensureImagesUploaded = async () => {
|
|
|
|
|
if (formData.images.length === 0) {
|
|
|
|
|
return [];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
setIsUploadingImages(true);
|
|
|
|
|
const finalPaths = [];
|
|
|
|
|
|
|
|
|
|
for (let index = 0; index < formData.images.length; index += 1) {
|
|
|
|
|
const file = formData.images[index];
|
|
|
|
|
const existingPath = uploadedImagePaths[index];
|
|
|
|
|
|
|
|
|
|
if (existingPath) {
|
|
|
|
|
finalPaths.push(existingPath);
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
const path = await uploadPicture(file);
|
|
|
|
|
finalPaths.push(path);
|
|
|
|
|
} catch (err) {
|
2026-08-06 22:26:35 +03:00
|
|
|
console.error("[AddProperty] Image upload failed:", err);
|
|
|
|
|
toast.error(`${t("toast.imageUploadFailed")}: ${file.name}`);
|
2026-07-15 20:03:38 +03:00
|
|
|
setIsUploadingImages(false);
|
|
|
|
|
return [];
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
setUploadedImagePaths(finalPaths);
|
|
|
|
|
setIsUploadingImages(false);
|
|
|
|
|
return finalPaths;
|
|
|
|
|
};
|
|
|
|
|
|
2026-03-17 20:36:59 +03:00
|
|
|
const handleSubmit = async () => {
|
|
|
|
|
if (!validateStep()) return;
|
|
|
|
|
|
|
|
|
|
setIsLoading(true);
|
2026-03-28 18:00:44 +00:00
|
|
|
|
2026-07-15 20:03:38 +03:00
|
|
|
const finalImagePaths = await ensureImagesUploaded();
|
|
|
|
|
if (finalImagePaths.length < 2) {
|
2026-08-06 22:26:35 +03:00
|
|
|
toast.error(t("addProperty.minImagesRequired"));
|
2026-07-15 20:03:38 +03:00
|
|
|
setIsLoading(false);
|
|
|
|
|
return;
|
|
|
|
|
}
|
2026-08-06 22:26:35 +03:00
|
|
|
const GovernorateToBackendCity = {
|
|
|
|
|
[Governorate.DAMASCUS]: "Damascus",
|
|
|
|
|
[Governorate.ALEPPO]: "Aleppo",
|
|
|
|
|
[Governorate.HOMS]: "Homs",
|
|
|
|
|
[Governorate.LATAKIA]: "Latakia",
|
|
|
|
|
[Governorate.DARAA]: "Daraa",
|
|
|
|
|
[Governorate.TARTOUS]: "Tartous",
|
|
|
|
|
[Governorate.SUWEIDA]: "Suweida",
|
|
|
|
|
[Governorate.DEIR_EZZOR]: "DeirEzzor",
|
|
|
|
|
[Governorate.RAQQA]: "Raqqa",
|
|
|
|
|
[Governorate.IDLIB]: "Idlib",
|
|
|
|
|
[Governorate.HASAKAH]: "Hasakah",
|
|
|
|
|
[Governorate.QAMISHLI]: "Qamishli",
|
|
|
|
|
[Governorate.RURAL_DAMASCUS]: "RuralDamascus",
|
|
|
|
|
};
|
2026-07-15 20:03:38 +03:00
|
|
|
|
2026-08-06 22:26:35 +03:00
|
|
|
const buildingTypeMap = {
|
|
|
|
|
apartment: BuildingType.APARTMENT,
|
|
|
|
|
villa: BuildingType.VILLA,
|
|
|
|
|
sweet: BuildingType.SWEET,
|
|
|
|
|
suite: BuildingType.SWEET,
|
|
|
|
|
room: BuildingType.ROOM,
|
|
|
|
|
studio: BuildingType.STUDIO,
|
|
|
|
|
office: BuildingType.OFFICE,
|
|
|
|
|
farms: BuildingType.FARMS,
|
|
|
|
|
shop: BuildingType.SHOP,
|
|
|
|
|
warehouse: BuildingType.WAREHOUSE,
|
|
|
|
|
};
|
2026-03-29 15:48:48 +00:00
|
|
|
|
|
|
|
|
const selectedServices = Object.entries(formData.services)
|
|
|
|
|
.filter(([, v]) => v)
|
2026-05-25 21:27:39 +03:00
|
|
|
.map(([k]) => k);
|
2026-03-28 18:00:44 +00:00
|
|
|
|
2026-03-29 15:48:48 +00:00
|
|
|
const selectedTerms = Object.entries(formData.terms)
|
|
|
|
|
.filter(([, v]) => v)
|
2026-05-25 21:27:39 +03:00
|
|
|
.map(([k]) => k);
|
2026-03-29 15:48:48 +00:00
|
|
|
|
2026-05-26 01:20:05 +03:00
|
|
|
const allTerms = [...new Set([...selectedTerms, ...customTerms])];
|
|
|
|
|
|
2026-08-06 22:26:35 +03:00
|
|
|
const isRent = purpose === "rent";
|
2026-05-25 23:18:03 +03:00
|
|
|
const details = {
|
2026-08-06 22:26:35 +03:00
|
|
|
description: formData.description || "",
|
2026-08-12 17:42:51 +03:00
|
|
|
commission: parseFloat(formData.commission) || 0,
|
2026-03-29 15:48:48 +00:00
|
|
|
services: selectedServices,
|
2026-08-06 22:26:35 +03:00
|
|
|
serviceDetails: selectedServices.reduce((acc, s) => ({ ...acc, [s]: formData.serviceDetails[s] || "in general" }), {}),
|
|
|
|
|
propertyCondition: formData.furnished ? "WithFurniture" : "WithoutFurniture",
|
2026-05-25 23:18:03 +03:00
|
|
|
floorNumber: parseInt(formData.floorNumber) || 0,
|
|
|
|
|
numberOfSalons: parseInt(formData.salons) || 0,
|
|
|
|
|
numberOfBalconies: parseInt(formData.balconies) || 0,
|
|
|
|
|
nearbyDistances: {
|
2026-08-06 22:26:35 +03:00
|
|
|
school: formData.nearbySchool || "",
|
|
|
|
|
hospital: formData.nearbyHospital || "",
|
|
|
|
|
restaurant: formData.nearbyRestaurant || "",
|
|
|
|
|
university: formData.nearbyUniversity || "",
|
|
|
|
|
park: formData.nearbyPark || "",
|
|
|
|
|
mall: formData.nearbyMall || "",
|
2026-08-11 02:29:54 +03:00
|
|
|
mosque: formData.neaebymosque || "",
|
2026-05-25 23:18:03 +03:00
|
|
|
},
|
|
|
|
|
};
|
|
|
|
|
|
2026-05-26 03:22:14 +03:00
|
|
|
if (isRent) {
|
|
|
|
|
details.terms = allTerms.reduce((acc, k) => ({ ...acc, [k]: true }), {});
|
2026-08-06 22:26:35 +03:00
|
|
|
details.displayType = formData.offerType === "both" ? "Both" : formData.offerType === "daily" ? "Daily" : "Monthly";
|
2026-05-26 03:22:14 +03:00
|
|
|
}
|
|
|
|
|
|
2026-08-06 22:26:35 +03:00
|
|
|
if (isRent && formData.propertyType === "room") {
|
2026-05-25 23:18:03 +03:00
|
|
|
details.room = {
|
2026-08-06 22:26:35 +03:00
|
|
|
areaType: formData.roomAreaType || "Private room",
|
2026-05-25 23:18:03 +03:00
|
|
|
peopleAllowed: formData.roomPeopleAllowed || String(formData.bedrooms),
|
2026-08-06 22:26:35 +03:00
|
|
|
furnitureDetails: formData.roomFurniture || "",
|
|
|
|
|
entranceType: formData.roomEntrance || "Shared entrance",
|
|
|
|
|
bathroomType: formData.roomBathroom || "Shared",
|
|
|
|
|
kitchenType: formData.roomKitchen || "Not available",
|
2026-05-25 23:18:03 +03:00
|
|
|
hasRestrictedOwnerAreas: formData.roomRestrictedAreas || false,
|
2026-08-06 22:26:35 +03:00
|
|
|
homeResidentsCount: formData.roomResidents || "",
|
|
|
|
|
currentPopulationGender: formData.roomGender || "Family",
|
|
|
|
|
languageDialect: formData.roomLanguage || "",
|
2026-05-25 23:18:03 +03:00
|
|
|
hasChildren: formData.roomChildren || false,
|
|
|
|
|
hasPets: formData.roomPets || false,
|
2026-08-06 22:26:35 +03:00
|
|
|
dedicatedTo: formData.roomDedicatedTo || "Everyone",
|
2026-05-25 23:18:03 +03:00
|
|
|
visitorsAllowed: formData.roomVisitors ?? true,
|
|
|
|
|
quietTimesEnabled: formData.roomQuietTimes ?? false,
|
2026-08-06 22:26:35 +03:00
|
|
|
quietTimes: formData.roomQuietTimesDetails || "",
|
2026-05-25 23:18:03 +03:00
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const detailsJSON = JSON.stringify(details);
|
2026-03-17 20:36:59 +03:00
|
|
|
|
2026-05-25 21:27:39 +03:00
|
|
|
const propInfo = {
|
2026-08-06 22:26:35 +03:00
|
|
|
cordsX: formData.lat ? String(formData.lat) : "",
|
|
|
|
|
cordsY: formData.lng ? String(formData.lng) : "",
|
|
|
|
|
// activityStatus: 1,
|
2026-07-15 20:03:38 +03:00
|
|
|
images: finalImagePaths,
|
2026-05-26 02:34:18 +03:00
|
|
|
address: `${formData.city} - ${formData.district} - ${formData.address}`.trim(),
|
2026-08-06 22:26:35 +03:00
|
|
|
description: formData.description || "",
|
2026-05-26 02:34:18 +03:00
|
|
|
numberOfBathRooms: formData.bathrooms || 0,
|
|
|
|
|
numberOfRooms: formData.bedrooms || 0,
|
|
|
|
|
numberOfBedRooms: formData.bedrooms || 0,
|
|
|
|
|
space: parseFloat(formData.space) || 0,
|
2026-08-06 22:26:35 +03:00
|
|
|
detailsJSON: detailsJSON || "{}",
|
2026-05-26 02:34:18 +03:00
|
|
|
buildingType: buildingTypeMap[formData.propertyType] ?? BuildingType.APARTMENT,
|
|
|
|
|
status: 0,
|
|
|
|
|
propertyType: formData.furnished ? RentPropertyCondition.WITH_FURNITURE : RentPropertyCondition.WITHOUT_FURNITURE,
|
2026-07-04 02:53:34 +03:00
|
|
|
documentType: formData.documentType,
|
|
|
|
|
governorate: formData.city,
|
2026-08-06 22:26:35 +03:00
|
|
|
city: CityEnum[formData.city],
|
2026-03-28 18:00:44 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
try {
|
2026-08-06 22:26:35 +03:00
|
|
|
if (purpose === "sale") {
|
2026-05-25 21:27:39 +03:00
|
|
|
const payload = {
|
2026-08-11 02:29:54 +03:00
|
|
|
propInfo,
|
|
|
|
|
price: parseFloat(formData.salePrice),
|
|
|
|
|
deposit: parseFloat(formData.deposit) || 0,
|
2026-05-25 21:27:39 +03:00
|
|
|
};
|
2026-08-12 17:42:51 +03:00
|
|
|
|
2026-05-25 21:27:39 +03:00
|
|
|
const res = await addSaleProperty(payload);
|
2026-08-12 17:42:51 +03:00
|
|
|
|
2026-08-06 22:26:35 +03:00
|
|
|
toast.success(t("toast.salePropertySuccess"));
|
2026-05-25 21:27:39 +03:00
|
|
|
} else {
|
|
|
|
|
const rentTypeMap = { daily: RentType.DAILY, monthly: RentType.MONTHLY, both: RentType.MONTHLY };
|
2026-07-04 12:24:13 +03:00
|
|
|
const payload = buildRentPropertyPayload({
|
2026-05-26 02:34:18 +03:00
|
|
|
propertyInformation: propInfo,
|
2026-07-04 12:24:13 +03:00
|
|
|
governorate: formData.city,
|
2026-08-06 22:26:35 +03:00
|
|
|
city: GovernorateToBackendCity[formData.city] ?? formData.city,
|
2026-07-04 12:24:13 +03:00
|
|
|
documentType: formData.documentType,
|
2026-05-26 02:34:18 +03:00
|
|
|
deposit: parseFloat(formData.deposit) || 0,
|
|
|
|
|
monthlyRent: parseFloat(formData.monthlyPrice) || 0,
|
|
|
|
|
dailyRent: parseFloat(formData.dailyPrice) || 0,
|
|
|
|
|
rating: 1,
|
|
|
|
|
currencyId: selectedCurrencyId,
|
|
|
|
|
rentType: rentTypeMap[formData.offerType] ?? RentType.MONTHLY,
|
|
|
|
|
type: formData.furnished ? RentPropertyType.FURNISHED : RentPropertyType.UNFURNISHED,
|
2026-08-06 22:26:35 +03:00
|
|
|
allowedPaymentPeriod: formData.allowedPaymentPeriod || "1.00:00:00",
|
2026-07-04 12:24:13 +03:00
|
|
|
});
|
2026-08-12 17:42:51 +03:00
|
|
|
|
2026-05-25 21:27:39 +03:00
|
|
|
const res = await addRentProperty(payload);
|
2026-08-12 17:42:51 +03:00
|
|
|
|
2026-08-06 22:26:35 +03:00
|
|
|
toast.success(t("toast.rentPropertySuccess"));
|
2026-05-25 21:27:39 +03:00
|
|
|
}
|
2026-03-17 20:36:59 +03:00
|
|
|
setTimeout(() => {
|
2026-08-06 22:26:35 +03:00
|
|
|
router.push("/owner/properties");
|
2026-03-17 20:36:59 +03:00
|
|
|
}, 1500);
|
2026-03-28 18:00:44 +00:00
|
|
|
} catch (err) {
|
2026-08-06 22:26:35 +03:00
|
|
|
console.error("[AddProperty] API error:", err);
|
|
|
|
|
toast.error(err.message || t("toast.propertyAddFailed"));
|
2026-03-28 18:00:44 +00:00
|
|
|
} finally {
|
|
|
|
|
setIsLoading(false);
|
|
|
|
|
}
|
2026-03-17 20:36:59 +03:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const fadeInUp = {
|
|
|
|
|
initial: { opacity: 0, y: 20 },
|
|
|
|
|
animate: { opacity: 1, y: 0 },
|
2026-08-06 22:26:35 +03:00
|
|
|
transition: { duration: 0.5 },
|
2026-03-17 20:36:59 +03:00
|
|
|
};
|
|
|
|
|
|
2026-08-06 22:26:35 +03:00
|
|
|
const currencySymbol = selectedCurrencyId === Currency.USD ? "$" : "SP";
|
2026-06-28 14:55:23 +03:00
|
|
|
|
2026-03-17 20:36:59 +03:00
|
|
|
return (
|
|
|
|
|
<div className="min-h-screen bg-gray-50 py-8">
|
|
|
|
|
<Toaster position="top-center" reverseOrder={false} />
|
2026-08-06 22:26:35 +03:00
|
|
|
|
2026-03-17 20:36:59 +03:00
|
|
|
<div className="container mx-auto px-4 max-w-4xl">
|
2026-07-15 20:03:38 +03:00
|
|
|
{isUploadingImages && (
|
|
|
|
|
<div className="mb-4 rounded-2xl border border-amber-200 bg-amber-50 px-4 py-3 text-sm text-amber-800 flex items-center justify-center gap-2">
|
|
|
|
|
<Loader2 className="w-4 h-4 animate-spin" />
|
2026-08-06 22:26:35 +03:00
|
|
|
<span>{t("addProperty.uploadingImagesMessage") || "جاري رفع الصور... يرجى الانتظار"}</span>
|
2026-07-15 20:03:38 +03:00
|
|
|
</div>
|
|
|
|
|
)}
|
2026-03-17 20:36:59 +03:00
|
|
|
<div className="mb-8">
|
|
|
|
|
<div className="flex items-center justify-between mb-4">
|
2026-08-06 22:26:35 +03:00
|
|
|
<Link href="/owner/properties" className="flex items-center gap-2 text-gray-600 hover:text-amber-600 transition-colors group">
|
|
|
|
|
<ArrowLeft className="w-4 h-4 group-hover:-translate-x-1 transition-transform" />
|
|
|
|
|
<span>{t("addProperty.backToProperties")}</span>
|
|
|
|
|
</Link>
|
2026-03-17 20:36:59 +03:00
|
|
|
<div className="flex items-center gap-2">
|
2026-08-06 22:26:35 +03:00
|
|
|
<span className="text-sm font-medium text-amber-600">{t("addProperty.stepCounter", { step, total: totalSteps })}</span>
|
2026-03-17 20:36:59 +03:00
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<div className="flex gap-2">
|
|
|
|
|
{[1, 2, 3, 4].map((s) => (
|
2026-08-06 22:26:35 +03:00
|
|
|
<motion.div key={s} className={`h-2 flex-1 rounded-full ${s <= step ? "bg-amber-500" : "bg-gray-200"}`} animate={{ scaleX: s <= step ? 1 : 0.5 }} />
|
2026-03-17 20:36:59 +03:00
|
|
|
))}
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<div className="flex justify-between mt-2 text-xs text-gray-500">
|
2026-08-06 22:26:35 +03:00
|
|
|
<span>{t("addProperty.stepInfo")}</span>
|
|
|
|
|
<span>{t("addProperty.stepDetails")}</span>
|
|
|
|
|
<span>{purpose === "sale" ? t("addProperty.stepSalePrice") : t("addProperty.stepRentPrice")}</span>
|
|
|
|
|
<span>{t("addProperty.stepLocation")}</span>
|
2026-03-17 20:36:59 +03:00
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<motion.div
|
|
|
|
|
key={step}
|
|
|
|
|
initial={{ opacity: 0, x: 20 }}
|
|
|
|
|
animate={{ opacity: 1, x: 0 }}
|
|
|
|
|
exit={{ opacity: 0, x: -20 }}
|
|
|
|
|
transition={{ duration: 0.3 }}
|
|
|
|
|
className="bg-white rounded-2xl shadow-xl p-6 md:p-8"
|
|
|
|
|
>
|
|
|
|
|
{step === 1 && (
|
|
|
|
|
<motion.div variants={fadeInUp} className="space-y-8">
|
|
|
|
|
<div className="text-center mb-6">
|
|
|
|
|
<div className="w-20 h-20 bg-amber-100 rounded-2xl flex items-center justify-center mx-auto mb-4">
|
|
|
|
|
<Home className="w-10 h-10 text-amber-600" />
|
|
|
|
|
</div>
|
2026-08-06 22:26:35 +03:00
|
|
|
<h2 className="text-2xl font-bold text-gray-900 mb-2">{t("addProperty.stepInfo")}</h2>
|
|
|
|
|
<p className="text-gray-600">{t("addProperty.propertyInfoSubtitle")}</p>
|
2026-03-17 20:36:59 +03:00
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<div>
|
2026-07-16 10:56:59 -07:00
|
|
|
<label className="block text-sm font-medium text-gray-700 mb-3">
|
2026-08-06 22:26:35 +03:00
|
|
|
{t("addProperty.propertyTypeLabel")} <span className="text-red-500">*</span>
|
|
|
|
|
</label>
|
2026-03-17 20:36:59 +03:00
|
|
|
<div className="grid grid-cols-2 md:grid-cols-4 gap-3">
|
|
|
|
|
{propertyTypes.map((type) => {
|
|
|
|
|
const Icon = type.icon;
|
|
|
|
|
return (
|
|
|
|
|
<button
|
|
|
|
|
key={type.id}
|
|
|
|
|
type="button"
|
2026-08-06 22:26:35 +03:00
|
|
|
onClick={() => setFormData({ ...formData, propertyType: type.id })}
|
2026-03-17 20:36:59 +03:00
|
|
|
className={`p-4 border rounded-xl flex flex-col items-center gap-2 transition-all ${
|
2026-08-11 02:29:54 +03:00
|
|
|
formData.propertyType === type.id ? "border-amber-500 bg-[#1E293B] text-amber-700" : "border-gray-200 hover:border-amber-200 hover:bg-amber-50/50"
|
2026-03-17 20:36:59 +03:00
|
|
|
}`}
|
|
|
|
|
>
|
|
|
|
|
<Icon className="w-6 h-6" />
|
|
|
|
|
<span className="text-sm font-medium">{type.label}</span>
|
|
|
|
|
</button>
|
|
|
|
|
);
|
|
|
|
|
})}
|
|
|
|
|
</div>
|
2026-08-06 22:26:35 +03:00
|
|
|
{errors.propertyType && <p className="text-red-500 text-sm mt-2">{errors.propertyType}</p>}
|
2026-03-17 20:36:59 +03:00
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<div>
|
2026-08-06 22:26:35 +03:00
|
|
|
<label className="block text-sm font-medium text-gray-700 mb-3">{t("addProperty.furnishedStatus")}</label>
|
2026-03-17 20:36:59 +03:00
|
|
|
<div className="flex gap-4">
|
|
|
|
|
<label className="flex items-center gap-2 p-3 border rounded-xl cursor-pointer hover:bg-gray-50 flex-1">
|
2026-08-06 22:26:35 +03:00
|
|
|
<input type="radio" name="furnished" checked={formData.furnished === true} onChange={() => setFormData({ ...formData, furnished: true })} className="w-4 h-4 text-amber-500" />
|
|
|
|
|
<span className="text-gray-700">{t("addProperty.furnished")}</span>
|
2026-03-17 20:36:59 +03:00
|
|
|
</label>
|
|
|
|
|
<label className="flex items-center gap-2 p-3 border rounded-xl cursor-pointer hover:bg-gray-50 flex-1">
|
2026-08-06 22:26:35 +03:00
|
|
|
<input type="radio" name="furnished" checked={formData.furnished === false} onChange={() => setFormData({ ...formData, furnished: false })} className="w-4 h-4 text-amber-500" />
|
|
|
|
|
<span className="text-gray-700">{t("addProperty.unfurnished")}</span>
|
2026-03-17 20:36:59 +03:00
|
|
|
</label>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
2026-07-04 02:53:34 +03:00
|
|
|
<div>
|
|
|
|
|
<label className="block text-sm font-medium text-gray-700 mb-2">
|
2026-08-06 22:26:35 +03:00
|
|
|
{t("addProperty.documentTypeLabel")} <span className="text-red-500">*</span>
|
2026-07-04 02:53:34 +03:00
|
|
|
</label>
|
|
|
|
|
<select
|
|
|
|
|
value={formData.documentType}
|
2026-08-06 22:26:35 +03:00
|
|
|
onChange={(e) => setFormData({ ...formData, documentType: e.target.value })}
|
2026-08-11 02:29:54 +03:00
|
|
|
className="w-full px-4 bg-[#1E293B] py-3 border border-gray-300 rounded-xl focus:outline-none focus:ring-2 focus:ring-amber-500"
|
2026-07-04 02:53:34 +03:00
|
|
|
>
|
|
|
|
|
{DocumentTypeList.map((type) => (
|
|
|
|
|
<option key={type} value={type}>
|
|
|
|
|
{getDocumentTypeLabel(type)}
|
|
|
|
|
</option>
|
|
|
|
|
))}
|
|
|
|
|
</select>
|
|
|
|
|
</div>
|
|
|
|
|
|
2026-03-17 20:36:59 +03:00
|
|
|
<div>
|
2026-08-06 22:26:35 +03:00
|
|
|
<label className="block text-sm font-medium text-gray-700 mb-2">{t("addProperty.additionalDescription")}</label>
|
2026-03-17 20:36:59 +03:00
|
|
|
<textarea
|
|
|
|
|
value={formData.description}
|
2026-08-06 22:26:35 +03:00
|
|
|
onChange={(e) => setFormData({ ...formData, description: e.target.value })}
|
2026-03-17 20:36:59 +03:00
|
|
|
rows="4"
|
|
|
|
|
className="w-full px-4 py-3 border border-gray-300 rounded-xl focus:outline-none focus:ring-2 focus:ring-amber-500"
|
2026-08-06 22:26:35 +03:00
|
|
|
placeholder={t("addProperty.descriptionPlaceholder")}
|
2026-03-17 20:36:59 +03:00
|
|
|
/>
|
|
|
|
|
</div>
|
|
|
|
|
</motion.div>
|
|
|
|
|
)}
|
|
|
|
|
|
|
|
|
|
{step === 2 && (
|
|
|
|
|
<motion.div variants={fadeInUp} className="space-y-8">
|
|
|
|
|
<div className="text-center mb-6">
|
|
|
|
|
<div className="w-20 h-20 bg-amber-100 rounded-2xl flex items-center justify-center mx-auto mb-4">
|
|
|
|
|
<Layers className="w-10 h-10 text-amber-600" />
|
|
|
|
|
</div>
|
2026-08-06 22:26:35 +03:00
|
|
|
<h2 className="text-2xl font-bold text-gray-900 mb-2">{t("addProperty.propertyDetailsTitle")}</h2>
|
|
|
|
|
<p className="text-gray-600">{t("addProperty.propertyDetailsSubtitle")}</p>
|
2026-03-17 20:36:59 +03:00
|
|
|
</div>
|
|
|
|
|
|
2026-05-25 23:18:03 +03:00
|
|
|
<div className="grid grid-cols-1 md:grid-cols-4 gap-6">
|
|
|
|
|
<div>
|
2026-08-06 22:26:35 +03:00
|
|
|
<label className="block text-sm font-medium text-gray-700 mb-2">{t("addProperty.space")}</label>
|
2026-05-25 23:18:03 +03:00
|
|
|
<div className="relative">
|
|
|
|
|
<Square className="absolute right-3 top-1/2 transform -translate-y-1/2 w-4 h-4 text-gray-400" />
|
|
|
|
|
<input
|
2026-08-06 22:26:35 +03:00
|
|
|
type="number"
|
|
|
|
|
min="0"
|
|
|
|
|
value={formData.space}
|
|
|
|
|
onChange={(e) => setFormData({ ...formData, space: e.target.value })}
|
2026-05-25 23:18:03 +03:00
|
|
|
className="w-full pr-10 pl-3 py-3 border border-gray-300 rounded-xl focus:outline-none focus:ring-2 focus:ring-amber-500"
|
2026-08-06 22:26:35 +03:00
|
|
|
placeholder={t("addProperty.spacePlaceholder")}
|
2026-05-25 23:18:03 +03:00
|
|
|
/>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
2026-03-17 20:36:59 +03:00
|
|
|
<div>
|
|
|
|
|
<label className="block text-sm font-medium text-gray-700 mb-2">
|
2026-08-06 22:26:35 +03:00
|
|
|
{t("addProperty.bedrooms")} <span className="text-red-500">*</span>
|
2026-03-17 20:36:59 +03:00
|
|
|
</label>
|
|
|
|
|
<div className="flex items-center gap-2">
|
2026-08-06 22:26:35 +03:00
|
|
|
<button type="button" onClick={decrementBedrooms} className="w-10 h-10 bg-gray-100 rounded-lg flex items-center justify-center hover:bg-gray-200 transition-colors">
|
2026-03-17 20:36:59 +03:00
|
|
|
<Minus className="w-4 h-4" />
|
|
|
|
|
</button>
|
2026-08-06 22:26:35 +03:00
|
|
|
<div className="flex-1 text-center font-bold text-xl">{formData.bedrooms}</div>
|
|
|
|
|
<button type="button" onClick={incrementBedrooms} className="w-10 h-10 bg-gray-100 rounded-lg flex items-center justify-center hover:bg-gray-200 transition-colors">
|
2026-03-17 20:36:59 +03:00
|
|
|
<Plus className="w-4 h-4" />
|
|
|
|
|
</button>
|
|
|
|
|
</div>
|
2026-08-06 22:26:35 +03:00
|
|
|
{errors.bedrooms && <p className="text-red-500 text-sm mt-1">{errors.bedrooms}</p>}
|
2026-03-17 20:36:59 +03:00
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<div>
|
|
|
|
|
<label className="block text-sm font-medium text-gray-700 mb-2">
|
2026-08-06 22:26:35 +03:00
|
|
|
{t("addProperty.bathrooms")} <span className="text-red-500">*</span>
|
2026-03-17 20:36:59 +03:00
|
|
|
</label>
|
|
|
|
|
<div className="flex items-center gap-2">
|
2026-08-06 22:26:35 +03:00
|
|
|
<button type="button" onClick={decrementBathrooms} className="w-10 h-10 bg-gray-100 rounded-lg flex items-center justify-center hover:bg-gray-200">
|
2026-03-17 20:36:59 +03:00
|
|
|
<Minus className="w-4 h-4" />
|
|
|
|
|
</button>
|
2026-08-06 22:26:35 +03:00
|
|
|
<div className="flex-1 text-center font-bold text-xl">{formData.bathrooms}</div>
|
|
|
|
|
<button type="button" onClick={incrementBathrooms} className="w-10 h-10 bg-gray-100 rounded-lg flex items-center justify-center hover:bg-gray-200">
|
2026-03-17 20:36:59 +03:00
|
|
|
<Plus className="w-4 h-4" />
|
|
|
|
|
</button>
|
|
|
|
|
</div>
|
2026-08-06 22:26:35 +03:00
|
|
|
{errors.bathrooms && <p className="text-red-500 text-sm mt-1">{errors.bathrooms}</p>}
|
2026-03-17 20:36:59 +03:00
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<div>
|
|
|
|
|
<label className="block text-sm font-medium text-gray-700 mb-2">
|
2026-08-06 22:26:35 +03:00
|
|
|
{t("addProperty.livingRooms")} <span className="text-red-500">*</span>
|
2026-03-17 20:36:59 +03:00
|
|
|
</label>
|
|
|
|
|
<div className="flex items-center gap-2">
|
2026-08-06 22:26:35 +03:00
|
|
|
<button type="button" onClick={decrementLivingRooms} className="w-10 h-10 bg-gray-100 rounded-lg flex items-center justify-center hover:bg-gray-200">
|
2026-03-17 20:36:59 +03:00
|
|
|
<Minus className="w-4 h-4" />
|
|
|
|
|
</button>
|
2026-08-06 22:26:35 +03:00
|
|
|
<div className="flex-1 text-center font-bold text-xl">{formData.livingRooms}</div>
|
|
|
|
|
<button type="button" onClick={incrementLivingRooms} className="w-10 h-10 bg-gray-100 rounded-lg flex items-center justify-center hover:bg-gray-200">
|
2026-03-17 20:36:59 +03:00
|
|
|
<Plus className="w-4 h-4" />
|
|
|
|
|
</button>
|
|
|
|
|
</div>
|
2026-08-06 22:26:35 +03:00
|
|
|
{errors.livingRooms && <p className="text-red-500 text-sm mt-1">{errors.livingRooms}</p>}
|
2026-03-17 20:36:59 +03:00
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
2026-05-25 23:18:03 +03:00
|
|
|
<div className="grid grid-cols-1 md:grid-cols-3 gap-4">
|
|
|
|
|
<div>
|
2026-08-06 22:26:35 +03:00
|
|
|
<label className="block text-sm font-medium text-gray-700 mb-2">{t("addProperty.floorNumber")}</label>
|
2026-05-25 23:18:03 +03:00
|
|
|
<input
|
|
|
|
|
type="number"
|
|
|
|
|
value={formData.floorNumber}
|
2026-08-06 22:26:35 +03:00
|
|
|
onChange={(e) => setFormData({ ...formData, floorNumber: e.target.value })}
|
2026-05-25 23:18:03 +03:00
|
|
|
className="w-full px-4 py-3 border border-gray-300 rounded-xl focus:outline-none focus:ring-2 focus:ring-amber-500"
|
2026-08-06 22:26:35 +03:00
|
|
|
placeholder={t("addProperty.floorPlaceholder")}
|
2026-05-25 23:18:03 +03:00
|
|
|
/>
|
|
|
|
|
</div>
|
|
|
|
|
<div>
|
2026-08-06 22:26:35 +03:00
|
|
|
<label className="block text-sm font-medium text-gray-700 mb-2">{t("addProperty.salons")}</label>
|
2026-05-25 23:18:03 +03:00
|
|
|
<input
|
|
|
|
|
type="number"
|
2026-06-28 14:55:23 +03:00
|
|
|
min="0"
|
2026-05-25 23:18:03 +03:00
|
|
|
value={formData.salons}
|
2026-08-06 22:26:35 +03:00
|
|
|
onChange={(e) => setFormData({ ...formData, salons: e.target.value })}
|
2026-05-25 23:18:03 +03:00
|
|
|
className="w-full px-4 py-3 border border-gray-300 rounded-xl focus:outline-none focus:ring-2 focus:ring-amber-500"
|
2026-08-06 22:26:35 +03:00
|
|
|
placeholder={t("addProperty.salonsPlaceholder")}
|
2026-05-25 23:18:03 +03:00
|
|
|
/>
|
|
|
|
|
</div>
|
|
|
|
|
<div>
|
2026-08-06 22:26:35 +03:00
|
|
|
<label className="block text-sm font-medium text-gray-700 mb-2">{t("addProperty.balconies")}</label>
|
2026-05-25 23:18:03 +03:00
|
|
|
<input
|
|
|
|
|
type="number"
|
2026-06-28 14:55:23 +03:00
|
|
|
min="0"
|
2026-05-25 23:18:03 +03:00
|
|
|
value={formData.balconies}
|
2026-08-06 22:26:35 +03:00
|
|
|
onChange={(e) => setFormData({ ...formData, balconies: e.target.value })}
|
2026-05-25 23:18:03 +03:00
|
|
|
className="w-full px-4 py-3 border border-gray-300 rounded-xl focus:outline-none focus:ring-2 focus:ring-amber-500"
|
2026-08-06 22:26:35 +03:00
|
|
|
placeholder={t("addProperty.balconiesPlaceholder")}
|
2026-05-25 23:18:03 +03:00
|
|
|
/>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
2026-03-17 20:36:59 +03:00
|
|
|
<div>
|
2026-08-06 22:26:35 +03:00
|
|
|
<h3 className="text-lg font-bold text-gray-900 mb-4">
|
|
|
|
|
{t("addProperty.servicesLabel")} <span className="text-red-500">*</span>
|
|
|
|
|
</h3>
|
2026-03-30 01:01:42 +00:00
|
|
|
<div className="space-y-3">
|
2026-03-17 20:36:59 +03:00
|
|
|
{serviceList.map((service) => {
|
|
|
|
|
const Icon = service.icon;
|
2026-03-30 01:01:42 +00:00
|
|
|
const isSelected = formData.services[service.id];
|
2026-03-17 20:36:59 +03:00
|
|
|
return (
|
2026-08-11 02:29:54 +03:00
|
|
|
<div key={service.id} className={`border rounded-xl transition-all ${isSelected ? "border-amber-500 bg-[#1E293B]" : "border-gray-200"}`}>
|
2026-03-30 01:01:42 +00:00
|
|
|
<label className="flex items-center gap-3 p-3 cursor-pointer">
|
2026-08-11 02:29:54 +03:00
|
|
|
<input type="checkbox" checked={isSelected} onChange={() => toggleService(service.id)} className="w-4 h-4 bg-[#1E293B] text-amber-500 rounded" />
|
2026-08-06 22:26:35 +03:00
|
|
|
<Icon className={`w-5 h-5 ${isSelected ? "text-amber-600" : "text-gray-400"}`} />
|
|
|
|
|
<span className={`text-sm font-medium ${isSelected ? "text-amber-700" : "text-gray-600"}`}>{service.label}</span>
|
2026-03-30 01:01:42 +00:00
|
|
|
</label>
|
|
|
|
|
{isSelected && (
|
2026-08-11 02:29:54 +03:00
|
|
|
<div className="px-3 pb-3 bg-[#1E293B]">
|
2026-03-30 01:01:42 +00:00
|
|
|
<input
|
|
|
|
|
type="text"
|
2026-08-06 22:26:35 +03:00
|
|
|
value={formData.serviceDetails[service.id] || ""}
|
2026-03-30 01:01:42 +00:00
|
|
|
onChange={(e) => updateServiceDetail(service.id, e.target.value)}
|
2026-08-11 02:29:54 +03:00
|
|
|
className="w-full px-3 py-2 border bg-[#1E293B] border-gray-200 rounded-lg text-sm focus:outline-none focus:ring-2 focus:ring-amber-500"
|
2026-08-06 22:26:35 +03:00
|
|
|
placeholder={t("addProperty.serviceDetailPlaceholder")}
|
2026-03-30 01:01:42 +00:00
|
|
|
/>
|
|
|
|
|
</div>
|
|
|
|
|
)}
|
|
|
|
|
</div>
|
2026-03-17 20:36:59 +03:00
|
|
|
);
|
|
|
|
|
})}
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
2026-08-11 02:29:54 +03:00
|
|
|
<div className="bg-[#1E293B] rounded-xl p-6 border border-gray-200 mt-6">
|
2026-06-28 15:16:32 +03:00
|
|
|
<h3 className="text-lg font-bold text-gray-800 mb-4 flex items-center gap-2">
|
|
|
|
|
<MapPin className="w-5 h-5 text-amber-600" />
|
2026-08-06 22:26:35 +03:00
|
|
|
{t("addProperty.nearbyServicesTitle")}
|
2026-06-28 15:16:32 +03:00
|
|
|
</h3>
|
|
|
|
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
|
|
|
|
<div>
|
2026-08-06 22:26:35 +03:00
|
|
|
<label className="block text-sm font-medium text-gray-700 mb-2">{t("addProperty.nearbySchoolLabel")}</label>
|
2026-06-28 15:16:32 +03:00
|
|
|
<input
|
|
|
|
|
type="number"
|
|
|
|
|
min="0"
|
|
|
|
|
step="0.1"
|
|
|
|
|
value={formData.nearbySchool}
|
2026-08-06 22:26:35 +03:00
|
|
|
onChange={(e) => setFormData({ ...formData, nearbySchool: e.target.value })}
|
2026-06-28 15:16:32 +03:00
|
|
|
className="w-full px-4 py-3 border border-gray-300 rounded-xl focus:outline-none focus:ring-2 focus:ring-amber-500"
|
2026-08-06 22:26:35 +03:00
|
|
|
placeholder={t("addProperty.nearbyPlaceholder")}
|
2026-06-28 15:16:32 +03:00
|
|
|
/>
|
|
|
|
|
</div>
|
|
|
|
|
<div>
|
2026-08-06 22:26:35 +03:00
|
|
|
<label className="block text-sm font-medium text-gray-700 mb-2">{t("addProperty.nearbyHospitalLabel")}</label>
|
2026-06-28 15:16:32 +03:00
|
|
|
<input
|
|
|
|
|
type="number"
|
|
|
|
|
min="0"
|
|
|
|
|
step="0.1"
|
|
|
|
|
value={formData.nearbyHospital}
|
2026-08-06 22:26:35 +03:00
|
|
|
onChange={(e) => setFormData({ ...formData, nearbyHospital: e.target.value })}
|
2026-06-28 15:16:32 +03:00
|
|
|
className="w-full px-4 py-3 border border-gray-300 rounded-xl focus:outline-none focus:ring-2 focus:ring-amber-500"
|
2026-08-06 22:26:35 +03:00
|
|
|
placeholder={t("addProperty.nearbyPlaceholder")}
|
2026-06-28 15:16:32 +03:00
|
|
|
/>
|
|
|
|
|
</div>
|
2026-08-11 02:29:54 +03:00
|
|
|
<div>
|
|
|
|
|
<label className="block text-sm font-medium text-gray-700 mb-2">{t("neaebymosque")}</label>
|
|
|
|
|
<input
|
|
|
|
|
type="number"
|
|
|
|
|
min="0"
|
|
|
|
|
step="0.1"
|
|
|
|
|
required
|
|
|
|
|
value={formData.neaebymosque}
|
|
|
|
|
onChange={(e) => setFormData({ ...formData, neaebymosque: e.target.value })}
|
|
|
|
|
className="w-full px-4 py-3 border border-gray-300 rounded-xl focus:outline-none focus:ring-2 focus:ring-amber-500"
|
|
|
|
|
placeholder={t("addProperty.nearbyPlaceholder")}
|
|
|
|
|
/>
|
|
|
|
|
</div>
|
2026-06-28 15:16:32 +03:00
|
|
|
<div>
|
2026-08-06 22:26:35 +03:00
|
|
|
<label className="block text-sm font-medium text-gray-700 mb-2">{t("addProperty.nearbyRestaurantLabel")}</label>
|
2026-06-28 15:16:32 +03:00
|
|
|
<input
|
|
|
|
|
type="number"
|
|
|
|
|
min="0"
|
|
|
|
|
step="0.1"
|
|
|
|
|
value={formData.nearbyRestaurant}
|
2026-08-06 22:26:35 +03:00
|
|
|
onChange={(e) => setFormData({ ...formData, nearbyRestaurant: e.target.value })}
|
2026-06-28 15:16:32 +03:00
|
|
|
className="w-full px-4 py-3 border border-gray-300 rounded-xl focus:outline-none focus:ring-2 focus:ring-amber-500"
|
2026-08-06 22:26:35 +03:00
|
|
|
placeholder={t("addProperty.nearbyPlaceholder")}
|
2026-06-28 15:16:32 +03:00
|
|
|
/>
|
|
|
|
|
</div>
|
|
|
|
|
<div>
|
2026-08-06 22:26:35 +03:00
|
|
|
<label className="block text-sm font-medium text-gray-700 mb-2">{t("addProperty.nearbyUniversityLabel")}</label>
|
2026-06-28 15:16:32 +03:00
|
|
|
<input
|
|
|
|
|
type="number"
|
|
|
|
|
min="0"
|
|
|
|
|
step="0.1"
|
|
|
|
|
value={formData.nearbyUniversity}
|
2026-08-06 22:26:35 +03:00
|
|
|
onChange={(e) => setFormData({ ...formData, nearbyUniversity: e.target.value })}
|
2026-06-28 15:16:32 +03:00
|
|
|
className="w-full px-4 py-3 border border-gray-300 rounded-xl focus:outline-none focus:ring-2 focus:ring-amber-500"
|
2026-08-06 22:26:35 +03:00
|
|
|
placeholder={t("addProperty.nearbyPlaceholder")}
|
2026-06-28 15:16:32 +03:00
|
|
|
/>
|
|
|
|
|
</div>
|
|
|
|
|
<div>
|
2026-08-06 22:26:35 +03:00
|
|
|
<label className="block text-sm font-medium text-gray-700 mb-2">{t("addProperty.nearbyParkLabel")}</label>
|
2026-06-28 15:16:32 +03:00
|
|
|
<input
|
|
|
|
|
type="number"
|
|
|
|
|
min="0"
|
|
|
|
|
step="0.1"
|
|
|
|
|
value={formData.nearbyPark}
|
2026-08-06 22:26:35 +03:00
|
|
|
onChange={(e) => setFormData({ ...formData, nearbyPark: e.target.value })}
|
2026-06-28 15:16:32 +03:00
|
|
|
className="w-full px-4 py-3 border border-gray-300 rounded-xl focus:outline-none focus:ring-2 focus:ring-amber-500"
|
2026-08-06 22:26:35 +03:00
|
|
|
placeholder={t("addProperty.nearbyPlaceholder")}
|
2026-06-28 15:16:32 +03:00
|
|
|
/>
|
|
|
|
|
</div>
|
|
|
|
|
<div>
|
2026-08-06 22:26:35 +03:00
|
|
|
<label className="block text-sm font-medium text-gray-700 mb-2">{t("addProperty.nearbyMallLabel")}</label>
|
2026-06-28 15:16:32 +03:00
|
|
|
<input
|
|
|
|
|
type="number"
|
|
|
|
|
min="0"
|
|
|
|
|
step="0.1"
|
|
|
|
|
value={formData.nearbyMall}
|
2026-08-06 22:26:35 +03:00
|
|
|
onChange={(e) => setFormData({ ...formData, nearbyMall: e.target.value })}
|
2026-06-28 15:16:32 +03:00
|
|
|
className="w-full px-4 py-3 border border-gray-300 rounded-xl focus:outline-none focus:ring-2 focus:ring-amber-500"
|
2026-08-06 22:26:35 +03:00
|
|
|
placeholder={t("addProperty.nearbyPlaceholder")}
|
2026-06-28 15:16:32 +03:00
|
|
|
/>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
2026-08-06 22:26:35 +03:00
|
|
|
{purpose === "rent" && (
|
2026-05-26 04:16:53 +03:00
|
|
|
<div>
|
2026-08-06 22:26:35 +03:00
|
|
|
<h3 className="text-lg font-bold text-gray-900 mb-4">{t("addProperty.termsTitle")}</h3>
|
2026-05-26 04:16:53 +03:00
|
|
|
<div className="grid grid-cols-2 md:grid-cols-3 gap-3">
|
|
|
|
|
{termsList.map((term) => {
|
|
|
|
|
const Icon = term.icon;
|
|
|
|
|
return (
|
|
|
|
|
<label
|
|
|
|
|
key={term.id}
|
|
|
|
|
className={`flex items-center gap-2 p-3 border rounded-xl cursor-pointer transition-all ${
|
2026-08-11 02:29:54 +03:00
|
|
|
formData.terms[term.id] ? "border-amber-500 bg-[#1E293B]" : "border-gray-200 hover:border-amber-200 hover:bg-amber-50/50"
|
2026-05-26 04:16:53 +03:00
|
|
|
}`}
|
2026-05-26 01:20:05 +03:00
|
|
|
>
|
2026-08-06 22:26:35 +03:00
|
|
|
<input type="checkbox" checked={formData.terms[term.id]} onChange={() => toggleTerm(term.id)} className="hidden" />
|
|
|
|
|
<Icon className={`w-5 h-5 ${formData.terms[term.id] ? "text-amber-600" : "text-gray-400"}`} />
|
|
|
|
|
<span className={`text-sm ${formData.terms[term.id] ? "text-amber-700" : "text-gray-600"}`}>{term.label}</span>
|
2026-05-26 04:16:53 +03:00
|
|
|
</label>
|
|
|
|
|
);
|
|
|
|
|
})}
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<div className="mt-4 p-4 border border-dashed border-gray-300 rounded-xl">
|
2026-08-06 22:26:35 +03:00
|
|
|
<p className="text-sm font-medium text-gray-700 mb-2">{t("addProperty.customTermsTitle")}</p>
|
2026-05-26 04:16:53 +03:00
|
|
|
<div className="flex gap-2">
|
|
|
|
|
<input
|
|
|
|
|
type="text"
|
|
|
|
|
value={customTermInput}
|
|
|
|
|
onChange={(e) => setCustomTermInput(e.target.value)}
|
2026-08-06 22:26:35 +03:00
|
|
|
onKeyDown={(e) => {
|
|
|
|
|
if (e.key === "Enter") {
|
|
|
|
|
e.preventDefault();
|
|
|
|
|
addCustomTerm();
|
|
|
|
|
}
|
|
|
|
|
}}
|
|
|
|
|
placeholder={t("addProperty.customTermPlaceholder")}
|
2026-05-26 04:16:53 +03:00
|
|
|
className="flex-1 px-3 py-2 border border-gray-300 rounded-lg text-sm focus:ring-2 focus:ring-amber-500 focus:border-transparent outline-none"
|
|
|
|
|
/>
|
|
|
|
|
<button
|
|
|
|
|
type="button"
|
|
|
|
|
onClick={addCustomTerm}
|
|
|
|
|
disabled={!customTermInput.trim()}
|
|
|
|
|
className="px-4 py-2 bg-amber-500 text-white rounded-lg text-sm font-medium hover:bg-amber-600 disabled:bg-gray-300 disabled:cursor-not-allowed transition-colors"
|
|
|
|
|
>
|
2026-08-06 22:26:35 +03:00
|
|
|
{t("addProperty.add")}
|
2026-05-26 04:16:53 +03:00
|
|
|
</button>
|
2026-05-26 01:20:05 +03:00
|
|
|
</div>
|
2026-05-26 04:16:53 +03:00
|
|
|
{customTerms.length > 0 && (
|
|
|
|
|
<div className="flex flex-wrap gap-2 mt-3">
|
|
|
|
|
{customTerms.map((term) => (
|
2026-08-06 22:26:35 +03:00
|
|
|
<span key={term} className="inline-flex items-center gap-1 px-3 py-1 bg-amber-100 text-amber-800 rounded-full text-sm">
|
2026-05-26 04:16:53 +03:00
|
|
|
{term}
|
2026-08-06 22:26:35 +03:00
|
|
|
<button type="button" onClick={() => removeCustomTerm(term)} className="hover:text-red-600 transition-colors">
|
2026-05-26 04:16:53 +03:00
|
|
|
<X className="w-3.5 h-3.5" />
|
|
|
|
|
</button>
|
|
|
|
|
</span>
|
|
|
|
|
))}
|
|
|
|
|
</div>
|
|
|
|
|
)}
|
|
|
|
|
</div>
|
2026-05-26 01:20:05 +03:00
|
|
|
</div>
|
2026-05-26 04:16:53 +03:00
|
|
|
)}
|
2026-03-17 20:36:59 +03:00
|
|
|
</motion.div>
|
|
|
|
|
)}
|
|
|
|
|
|
2026-08-06 22:26:35 +03:00
|
|
|
{step === 3 && purpose === "sale" && (
|
2026-05-25 21:27:39 +03:00
|
|
|
<motion.div variants={fadeInUp} className="space-y-8">
|
|
|
|
|
<div className="text-center mb-6">
|
2026-08-12 17:42:51 +03:00
|
|
|
<div className="w-20 h-20 bg-amber-400 rounded-2xl flex items-center justify-center mx-auto mb-4">
|
2026-05-25 21:27:39 +03:00
|
|
|
<DollarSign className="w-10 h-10 text-amber-600" />
|
|
|
|
|
</div>
|
2026-08-06 22:26:35 +03:00
|
|
|
<h2 className="text-2xl font-bold text-gray-900 mb-2">{t("addProperty.salePriceTitle")}</h2>
|
|
|
|
|
<p className="text-gray-600">{t("addProperty.salePriceSubtitle")}</p>
|
2026-05-25 21:27:39 +03:00
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<div>
|
|
|
|
|
<label className="block text-sm font-medium text-gray-700 mb-2">
|
2026-08-06 22:26:35 +03:00
|
|
|
{t("addProperty.salePriceLabel")} <span className="text-red-500">*</span>
|
2026-05-25 21:27:39 +03:00
|
|
|
</label>
|
|
|
|
|
<div className="relative">
|
2026-06-28 14:55:23 +03:00
|
|
|
<span className="absolute right-3 top-1/2 transform -translate-y-1/2 w-5 h-5 text-gray-400 font-bold text-sm flex items-center justify-center">{currencySymbol}</span>
|
2026-05-25 21:27:39 +03:00
|
|
|
<input
|
|
|
|
|
type="number"
|
2026-06-28 14:55:23 +03:00
|
|
|
min="0"
|
2026-08-06 22:26:35 +03:00
|
|
|
value={formData.salePrice || ""}
|
2026-06-28 14:55:23 +03:00
|
|
|
onChange={(e) => {
|
|
|
|
|
const val = e.target.value;
|
2026-08-06 22:26:35 +03:00
|
|
|
if (val === "" || parseFloat(val) >= 0) {
|
|
|
|
|
setFormData({ ...formData, salePrice: val });
|
2026-06-28 14:55:23 +03:00
|
|
|
}
|
|
|
|
|
}}
|
2026-08-06 22:26:35 +03:00
|
|
|
onKeyDown={(e) => {
|
|
|
|
|
if (["-", "e", "E", "+"].includes(e.key)) e.preventDefault();
|
|
|
|
|
}}
|
2026-08-11 02:29:54 +03:00
|
|
|
className={`w-full pr-12 pl-4 py-3 border rounded-xl focus:outline-none focus:ring-2 focus:ring-amber-500 ${errors.salePrice ? "border-red-500" : "border-gray-300"}`}
|
2026-08-06 22:26:35 +03:00
|
|
|
placeholder={t("addProperty.salePricePlaceholder")}
|
2026-05-25 21:27:39 +03:00
|
|
|
/>
|
|
|
|
|
</div>
|
2026-08-12 17:42:51 +03:00
|
|
|
|
|
|
|
|
<div className="mt-3">
|
|
|
|
|
<label className="block text-sm font-medium text-gray-700 mb-2">
|
|
|
|
|
{t("commission")} ({currencySymbol})
|
|
|
|
|
</label>
|
|
|
|
|
<div className="relative ">
|
|
|
|
|
<span className="absolute right-3 top-1/2 transform -translate-y-1/2 w-5 h-5 text-gray-400 font-bold text-sm flex items-center justify-center">{currencySymbol}</span>
|
|
|
|
|
<input
|
|
|
|
|
type="number"
|
|
|
|
|
min="0"
|
|
|
|
|
value={formData.commission || ""}
|
|
|
|
|
onChange={(e) => {
|
|
|
|
|
const val = e.target.value;
|
|
|
|
|
|
|
|
|
|
if (val === "" || parseFloat(val) >= 0) {
|
|
|
|
|
setFormData({ ...formData, commission: val });
|
|
|
|
|
}
|
|
|
|
|
(formData.commission);
|
|
|
|
|
}}
|
|
|
|
|
onKeyDown={(e) => {
|
|
|
|
|
if (["-", "e", "E", "+"].includes(e.key)) e.preventDefault();
|
|
|
|
|
}}
|
|
|
|
|
className="w-full pr-12 pl-4 py-3 border border-gray-300 rounded-xl focus:outline-none focus:ring-2 focus:ring-amber-500"
|
|
|
|
|
placeholder={t("addProperty.depositPlaceholder")}
|
|
|
|
|
/>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
2026-05-25 21:27:39 +03:00
|
|
|
{errors.salePrice && <p className="text-red-500 text-sm mt-1">{errors.salePrice}</p>}
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<div>
|
|
|
|
|
<label className="block text-sm font-medium text-gray-700 mb-2">
|
2026-08-06 22:26:35 +03:00
|
|
|
{t("addProperty.currencyLabel")} <span className="text-red-500">*</span>
|
2026-05-25 21:27:39 +03:00
|
|
|
</label>
|
2026-07-01 15:43:58 +03:00
|
|
|
<select
|
|
|
|
|
value={selectedCurrencyId}
|
|
|
|
|
onChange={(e) => setSelectedCurrencyId(parseInt(e.target.value))}
|
2026-08-11 02:29:54 +03:00
|
|
|
className="w-full px-4 py-3 border border-gray-300 bg-[#1E293B] rounded-xl focus:outline-none focus:ring-2 focus:ring-amber-500"
|
2026-07-01 15:43:58 +03:00
|
|
|
>
|
2026-08-06 22:26:35 +03:00
|
|
|
<option value={Currency.USD}>{t("addProperty.usdOption")}</option>
|
|
|
|
|
<option value={Currency.SYP}>{t("addProperty.sypOption")}</option>
|
2026-07-01 15:43:58 +03:00
|
|
|
</select>
|
2026-05-25 21:27:39 +03:00
|
|
|
</div>
|
|
|
|
|
</motion.div>
|
|
|
|
|
)}
|
|
|
|
|
|
2026-08-06 22:26:35 +03:00
|
|
|
{step === 3 && purpose === "rent" && (
|
2026-03-17 20:36:59 +03:00
|
|
|
<motion.div variants={fadeInUp} className="space-y-8">
|
|
|
|
|
<div className="text-center mb-6">
|
|
|
|
|
<div className="w-20 h-20 bg-amber-100 rounded-2xl flex items-center justify-center mx-auto mb-4">
|
|
|
|
|
<DollarSign className="w-10 h-10 text-amber-600" />
|
|
|
|
|
</div>
|
2026-08-06 22:26:35 +03:00
|
|
|
<h2 className="text-2xl font-bold text-gray-900 mb-2">{t("addProperty.offerAndPriceTitle")}</h2>
|
|
|
|
|
<p className="text-gray-600">{t("addProperty.offerAndPriceSubtitle")}</p>
|
2026-03-17 20:36:59 +03:00
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<div>
|
|
|
|
|
<label className="block text-sm font-medium text-gray-700 mb-3">
|
2026-08-06 22:26:35 +03:00
|
|
|
{t("addProperty.offerTypeLabel")} <span className="text-red-500">*</span>
|
2026-03-17 20:36:59 +03:00
|
|
|
</label>
|
|
|
|
|
<div className="grid grid-cols-2 md:grid-cols-4 gap-3">
|
|
|
|
|
{offerTypes.map((type) => {
|
|
|
|
|
const Icon = type.icon;
|
|
|
|
|
return (
|
|
|
|
|
<button
|
|
|
|
|
key={type.id}
|
|
|
|
|
type="button"
|
2026-08-06 22:26:35 +03:00
|
|
|
onClick={() => setFormData({ ...formData, offerType: type.id })}
|
2026-03-17 20:36:59 +03:00
|
|
|
className={`p-4 border rounded-xl flex flex-col items-center gap-2 transition-all ${
|
2026-08-11 02:29:54 +03:00
|
|
|
formData.offerType === type.id ? "border-amber-500 bg-[#1E293B] text-amber-700" : "border-gray-200 hover:border-amber-200 hover:bg-amber-50/50"
|
2026-03-17 20:36:59 +03:00
|
|
|
}`}
|
|
|
|
|
>
|
|
|
|
|
<Icon className="w-6 h-6" />
|
|
|
|
|
<span className="text-sm font-medium">{type.label}</span>
|
|
|
|
|
</button>
|
|
|
|
|
);
|
|
|
|
|
})}
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
2026-08-11 02:29:54 +03:00
|
|
|
{/* كمسيون */}
|
2026-03-29 15:48:48 +00:00
|
|
|
<div>
|
|
|
|
|
<label className="block text-sm font-medium text-gray-700 mb-2">
|
2026-08-06 22:26:35 +03:00
|
|
|
{t("addProperty.currencyLabel")} <span className="text-red-500">*</span>
|
2026-03-29 15:48:48 +00:00
|
|
|
</label>
|
|
|
|
|
<select
|
|
|
|
|
value={selectedCurrencyId}
|
|
|
|
|
onChange={(e) => setSelectedCurrencyId(parseInt(e.target.value))}
|
2026-08-11 02:29:54 +03:00
|
|
|
className="w-full px-4 py-3 border bg-[#1E293B] border-gray-300 rounded-xl focus:outline-none focus:ring-2 focus:ring-amber-500"
|
2026-03-29 15:48:48 +00:00
|
|
|
>
|
2026-08-06 22:26:35 +03:00
|
|
|
<option value={Currency.USD}>{t("addProperty.usdOption")}</option>
|
|
|
|
|
<option value={Currency.SYP}>{t("addProperty.sypOption")}</option>
|
2026-03-29 15:48:48 +00:00
|
|
|
</select>
|
|
|
|
|
</div>
|
2026-03-28 19:40:03 +00:00
|
|
|
|
2026-08-06 22:26:35 +03:00
|
|
|
{errors.offerType && <p className="text-red-500 text-sm mt-1">{errors.offerType}</p>}
|
2026-03-28 19:40:03 +00:00
|
|
|
|
2026-06-28 14:55:23 +03:00
|
|
|
{!formData.offerType && (
|
|
|
|
|
<div className="border-2 border-dashed border-gray-200 rounded-2xl p-8 text-center">
|
|
|
|
|
<DollarSign className="w-8 h-8 text-gray-300 mx-auto mb-3" />
|
2026-08-06 22:26:35 +03:00
|
|
|
<p className="text-gray-400 font-medium">{t("addProperty.selectOfferTypeFirst")}</p>
|
2026-06-28 14:55:23 +03:00
|
|
|
</div>
|
|
|
|
|
)}
|
2026-05-26 02:35:41 +03:00
|
|
|
|
2026-03-17 20:36:59 +03:00
|
|
|
<AnimatePresence mode="wait">
|
2026-08-06 22:26:35 +03:00
|
|
|
{formData.offerType === "daily" && (
|
|
|
|
|
<motion.div key="daily" initial={{ opacity: 0, height: 0 }} animate={{ opacity: 1, height: "auto" }} exit={{ opacity: 0, height: 0 }} className="space-y-4">
|
2026-03-17 20:36:59 +03:00
|
|
|
<div>
|
|
|
|
|
<label className="block text-sm font-medium text-gray-700 mb-2">
|
2026-08-06 22:26:35 +03:00
|
|
|
{t("addProperty.dailyPriceLabel")} ({currencySymbol}) <span className="text-red-500">*</span>
|
2026-03-17 20:36:59 +03:00
|
|
|
</label>
|
|
|
|
|
<div className="relative">
|
2026-06-28 14:55:23 +03:00
|
|
|
<span className="absolute right-3 top-1/2 transform -translate-y-1/2 w-5 h-5 text-gray-400 font-bold text-sm flex items-center justify-center">{currencySymbol}</span>
|
2026-03-17 20:36:59 +03:00
|
|
|
<input
|
|
|
|
|
type="number"
|
2026-06-28 14:55:23 +03:00
|
|
|
min="0"
|
2026-03-17 20:36:59 +03:00
|
|
|
value={formData.dailyPrice}
|
2026-06-28 14:55:23 +03:00
|
|
|
onChange={(e) => {
|
|
|
|
|
const val = e.target.value;
|
2026-08-06 22:26:35 +03:00
|
|
|
if (val === "" || parseFloat(val) >= 0) {
|
|
|
|
|
setFormData({ ...formData, dailyPrice: val });
|
2026-06-28 14:55:23 +03:00
|
|
|
}
|
|
|
|
|
}}
|
2026-08-06 22:26:35 +03:00
|
|
|
onKeyDown={(e) => {
|
|
|
|
|
if (["-", "e", "E", "+"].includes(e.key)) e.preventDefault();
|
|
|
|
|
}}
|
|
|
|
|
className={`w-full pr-12 pl-4 py-3 border rounded-xl focus:outline-none focus:ring-2 focus:ring-amber-500 ${errors.dailyPrice ? "border-red-500" : "border-gray-300"}`}
|
|
|
|
|
placeholder={t("addProperty.dailyPricePlaceholder")}
|
2026-03-17 20:36:59 +03:00
|
|
|
/>
|
|
|
|
|
</div>
|
2026-08-06 22:26:35 +03:00
|
|
|
{errors.dailyPrice && <p className="text-red-500 text-sm mt-1">{errors.dailyPrice}</p>}
|
2026-03-17 20:36:59 +03:00
|
|
|
</div>
|
2026-06-28 14:55:23 +03:00
|
|
|
<div>
|
|
|
|
|
<label className="block text-sm font-medium text-gray-700 mb-2">
|
2026-08-06 22:26:35 +03:00
|
|
|
{t("addProperty.depositLabel")} ({currencySymbol})
|
2026-06-28 14:55:23 +03:00
|
|
|
</label>
|
|
|
|
|
<div className="relative">
|
|
|
|
|
<span className="absolute right-3 top-1/2 transform -translate-y-1/2 w-5 h-5 text-gray-400 font-bold text-sm flex items-center justify-center">{currencySymbol}</span>
|
|
|
|
|
<input
|
|
|
|
|
type="number"
|
|
|
|
|
min="0"
|
2026-08-06 22:26:35 +03:00
|
|
|
value={formData.deposit || ""}
|
2026-06-28 14:55:23 +03:00
|
|
|
onChange={(e) => {
|
|
|
|
|
const val = e.target.value;
|
2026-08-06 22:26:35 +03:00
|
|
|
if (val === "" || parseFloat(val) >= 0) {
|
|
|
|
|
setFormData({ ...formData, deposit: val });
|
2026-06-28 14:55:23 +03:00
|
|
|
}
|
|
|
|
|
}}
|
2026-08-06 22:26:35 +03:00
|
|
|
onKeyDown={(e) => {
|
|
|
|
|
if (["-", "e", "E", "+"].includes(e.key)) e.preventDefault();
|
|
|
|
|
}}
|
2026-06-28 14:55:23 +03:00
|
|
|
className="w-full pr-12 pl-4 py-3 border border-gray-300 rounded-xl focus:outline-none focus:ring-2 focus:ring-amber-500"
|
2026-08-06 22:26:35 +03:00
|
|
|
placeholder={t("addProperty.depositPlaceholder")}
|
2026-06-28 14:55:23 +03:00
|
|
|
/>
|
|
|
|
|
</div>
|
2026-08-12 17:42:51 +03:00
|
|
|
|
|
|
|
|
{/* كمسيون */}
|
|
|
|
|
<div className="mt-3">
|
|
|
|
|
<label className="block text-sm font-medium text-gray-700 mb-2">
|
|
|
|
|
{t("commission")} ({currencySymbol})
|
|
|
|
|
</label>
|
|
|
|
|
<div className="relative ">
|
|
|
|
|
<span className="absolute right-3 top-1/2 transform -translate-y-1/2 w-5 h-5 text-gray-400 font-bold text-sm flex items-center justify-center">{currencySymbol}</span>
|
|
|
|
|
<input
|
|
|
|
|
type="number"
|
|
|
|
|
min="0"
|
|
|
|
|
value={formData.commission || ""}
|
|
|
|
|
onChange={(e) => {
|
|
|
|
|
const val = e.target.value;
|
|
|
|
|
if (val === "" || parseFloat(val) >= 0) {
|
|
|
|
|
setFormData({ ...formData, commission: val });
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
}}
|
|
|
|
|
onKeyDown={(e) => {
|
|
|
|
|
if (["-", "e", "E", "+"].includes(e.key)) e.preventDefault();
|
|
|
|
|
}}
|
|
|
|
|
className="w-full pr-12 pl-4 py-3 border border-gray-300 rounded-xl focus:outline-none focus:ring-2 focus:ring-amber-500"
|
|
|
|
|
placeholder={t("addProperty.depositPlaceholder")}
|
|
|
|
|
/>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
2026-06-28 14:55:23 +03:00
|
|
|
</div>
|
2026-03-17 20:36:59 +03:00
|
|
|
</motion.div>
|
|
|
|
|
)}
|
|
|
|
|
|
2026-08-06 22:26:35 +03:00
|
|
|
{formData.offerType === "monthly" && (
|
|
|
|
|
<motion.div key="monthly" initial={{ opacity: 0, height: 0 }} animate={{ opacity: 1, height: "auto" }} exit={{ opacity: 0, height: 0 }} className="space-y-4">
|
2026-03-17 20:36:59 +03:00
|
|
|
<div>
|
|
|
|
|
<label className="block text-sm font-medium text-gray-700 mb-2">
|
2026-08-06 22:26:35 +03:00
|
|
|
{t("addProperty.monthlyPriceLabel")} ({currencySymbol}) <span className="text-red-500">*</span>
|
2026-03-17 20:36:59 +03:00
|
|
|
</label>
|
|
|
|
|
<div className="relative">
|
2026-06-28 14:55:23 +03:00
|
|
|
<span className="absolute right-3 top-1/2 transform -translate-y-1/2 w-5 h-5 text-gray-400 font-bold text-sm flex items-center justify-center">{currencySymbol}</span>
|
2026-03-17 20:36:59 +03:00
|
|
|
<input
|
|
|
|
|
type="number"
|
2026-06-28 14:55:23 +03:00
|
|
|
min="0"
|
2026-03-17 20:36:59 +03:00
|
|
|
value={formData.monthlyPrice}
|
2026-06-28 14:55:23 +03:00
|
|
|
onChange={(e) => {
|
|
|
|
|
const val = e.target.value;
|
2026-08-06 22:26:35 +03:00
|
|
|
if (val === "" || parseFloat(val) >= 0) {
|
|
|
|
|
setFormData({ ...formData, monthlyPrice: val });
|
2026-06-28 14:55:23 +03:00
|
|
|
}
|
|
|
|
|
}}
|
2026-08-06 22:26:35 +03:00
|
|
|
onKeyDown={(e) => {
|
|
|
|
|
if (["-", "e", "E", "+"].includes(e.key)) e.preventDefault();
|
|
|
|
|
}}
|
|
|
|
|
className={`w-full pr-12 pl-4 py-3 border rounded-xl focus:outline-none focus:ring-2 focus:ring-amber-500 ${errors.monthlyPrice ? "border-red-500" : "border-gray-300"}`}
|
|
|
|
|
placeholder={t("addProperty.monthlyPricePlaceholder")}
|
2026-03-17 20:36:59 +03:00
|
|
|
/>
|
|
|
|
|
</div>
|
2026-08-06 22:26:35 +03:00
|
|
|
{errors.monthlyPrice && <p className="text-red-500 text-sm mt-1">{errors.monthlyPrice}</p>}
|
2026-03-17 20:36:59 +03:00
|
|
|
</div>
|
2026-06-28 14:55:23 +03:00
|
|
|
<div>
|
|
|
|
|
<label className="block text-sm font-medium text-gray-700 mb-2">
|
2026-08-06 22:26:35 +03:00
|
|
|
{t("addProperty.depositLabel")} ({currencySymbol})
|
2026-06-28 14:55:23 +03:00
|
|
|
</label>
|
|
|
|
|
<div className="relative">
|
|
|
|
|
<span className="absolute right-3 top-1/2 transform -translate-y-1/2 w-5 h-5 text-gray-400 font-bold text-sm flex items-center justify-center">{currencySymbol}</span>
|
|
|
|
|
<input
|
|
|
|
|
type="number"
|
|
|
|
|
min="0"
|
2026-08-06 22:26:35 +03:00
|
|
|
value={formData.deposit || ""}
|
2026-06-28 14:55:23 +03:00
|
|
|
onChange={(e) => {
|
|
|
|
|
const val = e.target.value;
|
2026-08-06 22:26:35 +03:00
|
|
|
if (val === "" || parseFloat(val) >= 0) {
|
|
|
|
|
setFormData({ ...formData, deposit: val });
|
2026-06-28 14:55:23 +03:00
|
|
|
}
|
|
|
|
|
}}
|
2026-08-06 22:26:35 +03:00
|
|
|
onKeyDown={(e) => {
|
|
|
|
|
if (["-", "e", "E", "+"].includes(e.key)) e.preventDefault();
|
|
|
|
|
}}
|
2026-06-28 14:55:23 +03:00
|
|
|
className="w-full pr-12 pl-4 py-3 border border-gray-300 rounded-xl focus:outline-none focus:ring-2 focus:ring-amber-500"
|
2026-08-06 22:26:35 +03:00
|
|
|
placeholder={t("addProperty.depositPlaceholder")}
|
2026-06-28 14:55:23 +03:00
|
|
|
/>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
2026-08-12 17:42:51 +03:00
|
|
|
<div className="mt-3">
|
|
|
|
|
<label className="block text-sm font-medium text-gray-700 mb-2">
|
|
|
|
|
{t("commission")} ({currencySymbol})
|
|
|
|
|
</label>
|
|
|
|
|
<div className="relative ">
|
|
|
|
|
<span className="absolute right-3 top-1/2 transform -translate-y-1/2 w-5 h-5 text-gray-400 font-bold text-sm flex items-center justify-center">{currencySymbol}</span>
|
|
|
|
|
<input
|
|
|
|
|
type="number"
|
|
|
|
|
min="0"
|
|
|
|
|
value={formData.commission || ""}
|
|
|
|
|
onChange={(e) => {
|
|
|
|
|
const val = e.target.value;
|
|
|
|
|
if (val === "" || parseFloat(val) >= 0) {
|
|
|
|
|
setFormData({ ...formData, commission: val });
|
|
|
|
|
}
|
|
|
|
|
}}
|
|
|
|
|
onKeyDown={(e) => {
|
|
|
|
|
if (["-", "e", "E", "+"].includes(e.key)) e.preventDefault();
|
|
|
|
|
}}
|
|
|
|
|
className="w-full pr-12 pl-4 py-3 border border-gray-300 rounded-xl focus:outline-none focus:ring-2 focus:ring-amber-500"
|
|
|
|
|
placeholder={t("addProperty.depositPlaceholder")}
|
|
|
|
|
/>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
2026-06-28 14:55:23 +03:00
|
|
|
</motion.div>
|
|
|
|
|
)}
|
|
|
|
|
|
2026-08-06 22:26:35 +03:00
|
|
|
{formData.offerType === "both" && (
|
|
|
|
|
<motion.div key="both" initial={{ opacity: 0, height: 0 }} animate={{ opacity: 1, height: "auto" }} exit={{ opacity: 0, height: 0 }} className="space-y-4">
|
2026-06-28 14:55:23 +03:00
|
|
|
<div>
|
|
|
|
|
<label className="block text-sm font-medium text-gray-700 mb-2">
|
2026-08-06 22:26:35 +03:00
|
|
|
{t("addProperty.dailyPriceLabel")} ({currencySymbol}) <span className="text-red-500">*</span>
|
2026-06-28 14:55:23 +03:00
|
|
|
</label>
|
|
|
|
|
<div className="relative">
|
|
|
|
|
<span className="absolute right-3 top-1/2 transform -translate-y-1/2 w-5 h-5 text-gray-400 font-bold text-sm flex items-center justify-center">{currencySymbol}</span>
|
|
|
|
|
<input
|
|
|
|
|
type="number"
|
|
|
|
|
min="0"
|
|
|
|
|
value={formData.dailyPrice}
|
|
|
|
|
onChange={(e) => {
|
|
|
|
|
const val = e.target.value;
|
2026-08-06 22:26:35 +03:00
|
|
|
if (val === "" || parseFloat(val) >= 0) {
|
|
|
|
|
setFormData({ ...formData, dailyPrice: val });
|
2026-06-28 14:55:23 +03:00
|
|
|
}
|
|
|
|
|
}}
|
2026-08-06 22:26:35 +03:00
|
|
|
onKeyDown={(e) => {
|
|
|
|
|
if (["-", "e", "E", "+"].includes(e.key)) e.preventDefault();
|
|
|
|
|
}}
|
|
|
|
|
className={`w-full pr-12 pl-4 py-3 border rounded-xl focus:outline-none focus:ring-2 focus:ring-amber-500 ${errors.dailyPrice ? "border-red-500" : "border-gray-300"}`}
|
|
|
|
|
placeholder={t("addProperty.dailyPricePlaceholder")}
|
2026-06-28 14:55:23 +03:00
|
|
|
/>
|
|
|
|
|
</div>
|
2026-08-06 22:26:35 +03:00
|
|
|
{errors.dailyPrice && <p className="text-red-500 text-sm mt-1">{errors.dailyPrice}</p>}
|
2026-06-28 14:55:23 +03:00
|
|
|
</div>
|
|
|
|
|
<div>
|
|
|
|
|
<label className="block text-sm font-medium text-gray-700 mb-2">
|
2026-08-06 22:26:35 +03:00
|
|
|
{t("addProperty.monthlyPriceLabel")} ({currencySymbol}) <span className="text-red-500">*</span>
|
2026-06-28 14:55:23 +03:00
|
|
|
</label>
|
|
|
|
|
<div className="relative">
|
|
|
|
|
<span className="absolute right-3 top-1/2 transform -translate-y-1/2 w-5 h-5 text-gray-400 font-bold text-sm flex items-center justify-center">{currencySymbol}</span>
|
|
|
|
|
<input
|
|
|
|
|
type="number"
|
|
|
|
|
min="0"
|
|
|
|
|
value={formData.monthlyPrice}
|
|
|
|
|
onChange={(e) => {
|
|
|
|
|
const val = e.target.value;
|
2026-08-06 22:26:35 +03:00
|
|
|
if (val === "" || parseFloat(val) >= 0) {
|
|
|
|
|
setFormData({ ...formData, monthlyPrice: val });
|
2026-06-28 14:55:23 +03:00
|
|
|
}
|
|
|
|
|
}}
|
2026-08-06 22:26:35 +03:00
|
|
|
onKeyDown={(e) => {
|
|
|
|
|
if (["-", "e", "E", "+"].includes(e.key)) e.preventDefault();
|
|
|
|
|
}}
|
|
|
|
|
className={`w-full pr-12 pl-4 py-3 border rounded-xl focus:outline-none focus:ring-2 focus:ring-amber-500 ${errors.monthlyPrice ? "border-red-500" : "border-gray-300"}`}
|
|
|
|
|
placeholder={t("addProperty.monthlyPricePlaceholder")}
|
2026-06-28 14:55:23 +03:00
|
|
|
/>
|
|
|
|
|
</div>
|
2026-08-06 22:26:35 +03:00
|
|
|
{errors.monthlyPrice && <p className="text-red-500 text-sm mt-1">{errors.monthlyPrice}</p>}
|
2026-06-28 14:55:23 +03:00
|
|
|
</div>
|
|
|
|
|
<div>
|
|
|
|
|
<label className="block text-sm font-medium text-gray-700 mb-2">
|
2026-08-06 22:26:35 +03:00
|
|
|
{t("addProperty.depositLabel")} ({currencySymbol})
|
2026-06-28 14:55:23 +03:00
|
|
|
</label>
|
|
|
|
|
<div className="relative">
|
|
|
|
|
<span className="absolute right-3 top-1/2 transform -translate-y-1/2 w-5 h-5 text-gray-400 font-bold text-sm flex items-center justify-center">{currencySymbol}</span>
|
|
|
|
|
<input
|
|
|
|
|
type="number"
|
|
|
|
|
min="0"
|
2026-08-06 22:26:35 +03:00
|
|
|
value={formData.deposit || ""}
|
2026-06-28 14:55:23 +03:00
|
|
|
onChange={(e) => {
|
|
|
|
|
const val = e.target.value;
|
2026-08-06 22:26:35 +03:00
|
|
|
if (val === "" || parseFloat(val) >= 0) {
|
|
|
|
|
setFormData({ ...formData, deposit: val });
|
2026-06-28 14:55:23 +03:00
|
|
|
}
|
|
|
|
|
}}
|
2026-08-06 22:26:35 +03:00
|
|
|
onKeyDown={(e) => {
|
|
|
|
|
if (["-", "e", "E", "+"].includes(e.key)) e.preventDefault();
|
|
|
|
|
}}
|
2026-06-28 14:55:23 +03:00
|
|
|
className="w-full pr-12 pl-4 py-3 border border-gray-300 rounded-xl focus:outline-none focus:ring-2 focus:ring-amber-500"
|
2026-08-06 22:26:35 +03:00
|
|
|
placeholder={t("addProperty.depositPlaceholder")}
|
2026-06-28 14:55:23 +03:00
|
|
|
/>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
2026-08-12 17:42:51 +03:00
|
|
|
<div className="mt-3">
|
|
|
|
|
<label className="block text-sm font-medium text-gray-700 mb-2">
|
|
|
|
|
{t("commission")} ({currencySymbol})
|
|
|
|
|
</label>
|
|
|
|
|
<div className="relative ">
|
|
|
|
|
<span className="absolute right-3 top-1/2 transform -translate-y-1/2 w-5 h-5 text-gray-400 font-bold text-sm flex items-center justify-center">{currencySymbol}</span>
|
|
|
|
|
<input
|
|
|
|
|
type="number"
|
|
|
|
|
min="0"
|
|
|
|
|
value={formData.commission || ""}
|
|
|
|
|
onChange={(e) => {
|
|
|
|
|
const val = e.target.value;
|
|
|
|
|
if (val === "" || parseFloat(val) >= 0) {
|
|
|
|
|
setFormData({ ...formData, commission: val });
|
|
|
|
|
}
|
|
|
|
|
}}
|
|
|
|
|
onKeyDown={(e) => {
|
|
|
|
|
if (["-", "e", "E", "+"].includes(e.key)) e.preventDefault();
|
|
|
|
|
}}
|
|
|
|
|
className="w-full pr-12 pl-4 py-3 border border-gray-300 rounded-xl focus:outline-none focus:ring-2 focus:ring-amber-500"
|
|
|
|
|
placeholder={t("addProperty.depositPlaceholder")}
|
|
|
|
|
/>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
2026-03-17 20:36:59 +03:00
|
|
|
</motion.div>
|
|
|
|
|
)}
|
|
|
|
|
</AnimatePresence>
|
|
|
|
|
</motion.div>
|
|
|
|
|
)}
|
|
|
|
|
|
2026-08-06 22:26:35 +03:00
|
|
|
{step === 4 && (
|
2026-08-11 02:29:54 +03:00
|
|
|
<motion.div variants={fadeInUp} className="space-y-8 ">
|
|
|
|
|
<div className="text-center mb-6 ">
|
2026-08-06 22:26:35 +03:00
|
|
|
<div className="w-20 h-20 bg-amber-100 rounded-2xl flex items-center justify-center mx-auto mb-4">
|
|
|
|
|
<MapPin className="w-10 h-10 text-amber-600" />
|
|
|
|
|
</div>
|
|
|
|
|
<h2 className="text-2xl font-bold text-gray-900 mb-2">{t("addProperty.locationAndImagesTitle")}</h2>
|
|
|
|
|
<p className="text-gray-600">{t("addProperty.locationAndImagesSubtitle")}</p>
|
|
|
|
|
</div>
|
2026-03-17 20:36:59 +03:00
|
|
|
|
2026-08-11 02:29:54 +03:00
|
|
|
<div className="rounded-3xl border border-amber-100 p-4 sm:p-5 md:p-6 shadow-sm">
|
2026-08-06 22:26:35 +03:00
|
|
|
<div className="flex flex-col gap-3 md:flex-row md:items-start md:justify-between mb-5">
|
|
|
|
|
<div>
|
|
|
|
|
<div className="flex items-center gap-2 text-amber-700 font-semibold mb-1">
|
|
|
|
|
<Navigation className="w-4 h-4" />
|
|
|
|
|
{t("addProperty.selectLocationTitle")}
|
|
|
|
|
</div>
|
|
|
|
|
<p className="text-sm text-gray-600">{t("addProperty.clickMapHint")}</p>
|
|
|
|
|
</div>
|
|
|
|
|
<div className="inline-flex items-center gap-2 rounded-full border border-amber-200 bg-white px-3 py-1.5 text-sm font-medium text-amber-700 shadow-sm">
|
|
|
|
|
<MapPin className="w-4 h-4" />
|
|
|
|
|
{t("addProperty.cityLabel")}
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
2026-03-17 20:36:59 +03:00
|
|
|
|
2026-08-06 22:26:35 +03:00
|
|
|
<div className="grid grid-cols-1 xl:grid-cols-[minmax(220px,260px)_1fr] gap-4 mb-5">
|
|
|
|
|
<div className="rounded-2xl border border-white/80 bg-white/90 p-4 shadow-sm">
|
|
|
|
|
<label className="block text-sm font-medium text-gray-700 mb-2">
|
|
|
|
|
{t("addProperty.cityLabel")} <span className="text-red-500">*</span>
|
|
|
|
|
</label>
|
|
|
|
|
<select
|
|
|
|
|
value={formData.city}
|
|
|
|
|
onChange={(e) => setFormData({ ...formData, city: e.target.value })}
|
|
|
|
|
className="w-full px-4 py-3 border border-gray-300 rounded-xl focus:outline-none focus:ring-2 focus:ring-amber-500 bg-white"
|
|
|
|
|
>
|
|
|
|
|
{GovernorateList.map((city) => (
|
|
|
|
|
<option key={city} value={city}>
|
|
|
|
|
{GovernorateLabels[city] || city}
|
|
|
|
|
</option>
|
|
|
|
|
))}
|
|
|
|
|
</select>
|
|
|
|
|
<p className="text-xs text-gray-500 mt-2">{t("addProperty.searchHint")}</p>
|
|
|
|
|
</div>
|
2026-07-04 02:53:34 +03:00
|
|
|
|
2026-08-06 22:26:35 +03:00
|
|
|
<div className="rounded-2xl border border-white/80 bg-white/90 p-4 shadow-sm">
|
|
|
|
|
<label className="block text-sm font-medium text-gray-700 mb-2">{t("addProperty.searchAddress")}</label>
|
|
|
|
|
<div className="flex flex-col sm:flex-row gap-2">
|
|
|
|
|
<div className="relative flex-1">
|
|
|
|
|
<input
|
|
|
|
|
type="text"
|
|
|
|
|
value={searchQuery}
|
|
|
|
|
onChange={(e) => setSearchQuery(e.target.value)}
|
|
|
|
|
onKeyPress={(e) => e.key === "Enter" && handleSearch()}
|
|
|
|
|
placeholder={t("addProperty.searchAddress")}
|
|
|
|
|
className="w-full px-4 py-3 border border-gray-300 rounded-xl focus:outline-none focus:ring-2 focus:ring-amber-500 pr-10"
|
|
|
|
|
/>
|
|
|
|
|
<Search className="absolute left-3 top-1/2 transform -translate-y-1/2 w-5 h-5 text-gray-400" />
|
|
|
|
|
</div>
|
|
|
|
|
<button onClick={handleSearch} className="px-5 py-3 bg-amber-500 text-white rounded-xl hover:bg-amber-600 transition-colors font-medium flex items-center justify-center gap-2">
|
|
|
|
|
<Search className="w-4 h-4" />
|
|
|
|
|
{t("addProperty.searchButton")}
|
|
|
|
|
</button>
|
|
|
|
|
</div>
|
2026-08-11 02:29:54 +03:00
|
|
|
<div className="mt-3 flex items-start gap-2 text-sm text-amber-700">
|
2026-08-06 22:26:35 +03:00
|
|
|
<Info className="w-4 h-4 mt-0.5 shrink-0" />
|
|
|
|
|
<span>{t("addProperty.clickMapHint")}</span>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
2026-03-17 20:36:59 +03:00
|
|
|
|
2026-08-06 22:26:35 +03:00
|
|
|
<div className="relative w-full h-96 rounded-2xl overflow-hidden border border-gray-200 shadow-inner">
|
|
|
|
|
<AddPropertyMap mapCenter={mapCenter} mapZoom={mapZoom} selectedLocation={selectedLocation} onMapClick={handleMapClick} onMarkerDragEnd={handleMarkerDragEnd} />
|
|
|
|
|
</div>
|
2026-03-17 20:36:59 +03:00
|
|
|
|
2026-08-06 22:26:35 +03:00
|
|
|
<div className="mt-4 flex flex-col gap-3 md:flex-row md:items-center md:justify-between">
|
|
|
|
|
{selectedLocation && !formData.lat ? (
|
|
|
|
|
<button
|
|
|
|
|
onClick={confirmLocation}
|
|
|
|
|
className="w-full md:w-auto bg-green-600 text-white py-3 px-5 rounded-xl font-medium hover:bg-green-700 transition-colors flex items-center justify-center gap-2 shadow-sm"
|
|
|
|
|
>
|
|
|
|
|
<CheckCircle className="w-5 h-5" />
|
|
|
|
|
{t("addProperty.confirmLocation")}
|
|
|
|
|
</button>
|
|
|
|
|
) : (
|
2026-08-11 02:29:54 +03:00
|
|
|
<div className="rounded-2xl border border-amber-100 bg-[#1E293B] px-4 py-3 text-sm text-amber-800 flex items-start gap-2">
|
2026-08-06 22:26:35 +03:00
|
|
|
<Info className="w-4 h-4 mt-0.5 shrink-0" />
|
|
|
|
|
<span>{t("addProperty.clickMapHint")}</span>
|
|
|
|
|
</div>
|
|
|
|
|
)}
|
2026-07-04 02:53:34 +03:00
|
|
|
|
2026-08-06 22:26:35 +03:00
|
|
|
{formData.lat && (
|
|
|
|
|
<div className="flex-1 rounded-2xl border border-green-200 bg-green-50 p-4">
|
|
|
|
|
<div className="flex items-center gap-2">
|
|
|
|
|
<CheckCircle className="w-5 h-5 text-green-600" />
|
|
|
|
|
<span className="text-green-800 font-medium">{t("addProperty.locationConfirmed")}:</span>
|
|
|
|
|
</div>
|
|
|
|
|
<p className="text-green-700 text-sm mt-2 line-clamp-2">{formData.address}</p>
|
|
|
|
|
</div>
|
|
|
|
|
)}
|
|
|
|
|
</div>
|
2026-03-17 20:36:59 +03:00
|
|
|
|
2026-08-06 22:26:35 +03:00
|
|
|
{errors.location && <p className="text-red-500 text-sm text-center mt-4">{errors.location}</p>}
|
|
|
|
|
</div>
|
2026-03-17 20:36:59 +03:00
|
|
|
|
2026-08-06 22:26:35 +03:00
|
|
|
<div>
|
|
|
|
|
<h3 className="text-lg font-bold text-gray-900 mb-4">{t("addProperty.imagesTitle")}</h3>
|
2026-03-17 20:36:59 +03:00
|
|
|
|
2026-08-06 22:26:35 +03:00
|
|
|
<div
|
|
|
|
|
onClick={() => fileInputRef.current?.click()}
|
|
|
|
|
className={`border-2 border-dashed rounded-xl p-8 text-center cursor-pointer transition-all ${
|
|
|
|
|
errors.images ? "border-red-500 bg-red-50" : "border-gray-300 hover:border-amber-500 hover:bg-amber-50"
|
|
|
|
|
}`}
|
|
|
|
|
>
|
|
|
|
|
<input ref={fileInputRef} type="file" multiple accept="image/*" onChange={(e) => handleImageUpload(e.target.files)} className="hidden" />
|
2026-06-28 14:55:23 +03:00
|
|
|
|
2026-08-06 22:26:35 +03:00
|
|
|
<Upload className="w-12 h-12 text-gray-400 mx-auto mb-3" />
|
|
|
|
|
<p className="text-gray-600 font-medium">{t("addProperty.uploadImages")}</p>
|
|
|
|
|
<p className="text-xs text-gray-500 mt-2">{t("addProperty.imageFormatHint")}</p>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<p className="text-sm text-gray-500 mt-2 mb-4 text-center">
|
|
|
|
|
{t("addProperty.uploadMultipleHintLine1")}
|
|
|
|
|
<br />
|
|
|
|
|
{t("addProperty.uploadMultipleHintLine2")}
|
|
|
|
|
</p>
|
|
|
|
|
|
|
|
|
|
{errors.images && <p className="text-red-500 text-sm text-center mt-2">{errors.images}</p>}
|
|
|
|
|
|
|
|
|
|
{imagePreviews.length > 0 && (
|
|
|
|
|
<div className="grid grid-cols-2 md:grid-cols-4 gap-4 mt-6">
|
|
|
|
|
{imagePreviews.map((preview, index) => (
|
|
|
|
|
<motion.div key={index} initial={{ opacity: 0, scale: 0.9 }} animate={{ opacity: 1, scale: 1 }} className="relative group aspect-square">
|
|
|
|
|
<Image src={preview} alt={`Property ${index + 1}`} fill className="object-cover rounded-lg" />
|
|
|
|
|
<button
|
|
|
|
|
onClick={() => removeImage(index)}
|
|
|
|
|
className="absolute -top-2 -right-2 w-6 h-6 bg-red-500 rounded-full flex items-center justify-center opacity-0 group-hover:opacity-100 transition-opacity"
|
|
|
|
|
>
|
|
|
|
|
<X className="w-4 h-4 text-white" />
|
|
|
|
|
</button>
|
|
|
|
|
</motion.div>
|
|
|
|
|
))}
|
|
|
|
|
</div>
|
|
|
|
|
)}
|
|
|
|
|
</div>
|
2026-06-28 14:55:23 +03:00
|
|
|
|
2026-08-06 22:26:35 +03:00
|
|
|
{step === 4 && (
|
|
|
|
|
<div className="border-t border-gray-200 pt-6 mt-8">
|
|
|
|
|
<div className="bg-gray-50 rounded-2xl p-5 border border-gray-200">
|
|
|
|
|
<p className="text-sm text-gray-700 leading-relaxed mb-4">{t("addProperty.ownerAgreement")}</p>
|
|
|
|
|
<label className="flex items-start gap-3 cursor-pointer">
|
|
|
|
|
<input type="checkbox" checked={agreeToTerms} onChange={(e) => setAgreeToTerms(e.target.checked)} className="w-5 h-5 mt-0.5 text-amber-500 rounded shrink-0" />
|
|
|
|
|
<span className="text-sm text-gray-600">
|
|
|
|
|
{t("addProperty.agreeToText")}{" "}
|
|
|
|
|
<Link href="/terms" target="_blank" className="text-amber-600 underline hover:text-amber-700">
|
|
|
|
|
{t("addProperty.termsOfUseText")}
|
|
|
|
|
</Link>
|
|
|
|
|
</span>
|
|
|
|
|
</label>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
)}
|
|
|
|
|
</motion.div>
|
|
|
|
|
)}
|
2026-03-17 20:36:59 +03:00
|
|
|
|
|
|
|
|
<div className="flex gap-3 mt-8 pt-6 border-t border-gray-200">
|
|
|
|
|
{step > 1 && (
|
2026-08-06 22:26:35 +03:00
|
|
|
<button onClick={handleBack} className="flex-1 py-3 px-4 bg-gray-100 text-gray-700 rounded-xl font-medium hover:bg-gray-200 transition-colors flex items-center justify-center gap-2">
|
2026-03-17 20:36:59 +03:00
|
|
|
<ChevronRight className="w-5 h-5" />
|
2026-08-06 22:26:35 +03:00
|
|
|
{t("addProperty.previous")}
|
2026-03-17 20:36:59 +03:00
|
|
|
</button>
|
|
|
|
|
)}
|
2026-08-06 22:26:35 +03:00
|
|
|
|
2026-03-17 20:36:59 +03:00
|
|
|
{step < totalSteps ? (
|
|
|
|
|
<button
|
|
|
|
|
onClick={handleNext}
|
2026-08-06 22:26:35 +03:00
|
|
|
className={`flex-1 py-3 px-4 bg-amber-500 text-white rounded-xl font-medium hover:bg-amber-600 transition-colors flex items-center justify-center gap-2 ${step === 1 ? "w-full" : ""}`}
|
2026-03-17 20:36:59 +03:00
|
|
|
>
|
2026-08-06 22:26:35 +03:00
|
|
|
{t("addProperty.next")}
|
2026-03-17 20:36:59 +03:00
|
|
|
<ChevronLeft className="w-5 h-5" />
|
|
|
|
|
</button>
|
|
|
|
|
) : (
|
|
|
|
|
<button
|
|
|
|
|
onClick={handleSubmit}
|
2026-06-28 14:55:23 +03:00
|
|
|
disabled={isLoading || !agreeToTerms}
|
2026-07-15 20:03:38 +03:00
|
|
|
className="flex-1 py-3 px-4 bg-linear-to-r from-amber-500 to-amber-600 text-white rounded-xl font-medium hover:from-amber-600 hover:to-amber-700 transition-all disabled:opacity-50 flex items-center justify-center gap-2"
|
2026-03-17 20:36:59 +03:00
|
|
|
>
|
|
|
|
|
{isLoading ? (
|
|
|
|
|
<>
|
|
|
|
|
<Loader2 className="w-5 h-5 animate-spin" />
|
2026-08-06 22:26:35 +03:00
|
|
|
{t("addProperty.saving")}
|
2026-03-17 20:36:59 +03:00
|
|
|
</>
|
|
|
|
|
) : (
|
|
|
|
|
<>
|
|
|
|
|
<Save className="w-5 h-5" />
|
2026-08-06 22:26:35 +03:00
|
|
|
{t("addProperty.saveProperty")}
|
2026-03-17 20:36:59 +03:00
|
|
|
</>
|
|
|
|
|
)}
|
|
|
|
|
</button>
|
|
|
|
|
)}
|
|
|
|
|
</div>
|
|
|
|
|
</motion.div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
);
|
2026-08-06 22:26:35 +03:00
|
|
|
}
|