This commit is contained in:
@ -1239,6 +1239,7 @@ const resources = {
|
|||||||
"addProperty.monthlyPriceRequired": "Monthly price is required",
|
"addProperty.monthlyPriceRequired": "Monthly price is required",
|
||||||
"addProperty.minImagesRequired": "At least 2 images required",
|
"addProperty.minImagesRequired": "At least 2 images required",
|
||||||
"addProperty.maxImagesError": "Maximum 5 images allowed",
|
"addProperty.maxImagesError": "Maximum 5 images allowed",
|
||||||
|
"addProperty.uploadingImagesMessage": "Uploading images, please wait...",
|
||||||
"addProperty.dailyAndMonthly": "Daily & Monthly Rent",
|
"addProperty.dailyAndMonthly": "Daily & Monthly Rent",
|
||||||
|
|
||||||
/* ─── Toast Messages ─── */
|
/* ─── Toast Messages ─── */
|
||||||
@ -2900,6 +2901,7 @@ const resources = {
|
|||||||
"addProperty.monthlyPriceRequired": "السعر الشهري مطلوب",
|
"addProperty.monthlyPriceRequired": "السعر الشهري مطلوب",
|
||||||
"addProperty.minImagesRequired": "يجب رفع صورتين على الأقل",
|
"addProperty.minImagesRequired": "يجب رفع صورتين على الأقل",
|
||||||
"addProperty.maxImagesError": "يمكن رفع 5 صور كحد أقصى",
|
"addProperty.maxImagesError": "يمكن رفع 5 صور كحد أقصى",
|
||||||
|
"addProperty.uploadingImagesMessage": "جاري رفع الصور، يرجى الانتظار...",
|
||||||
"addProperty.dailyAndMonthly": "إيجار يومي وشهري",
|
"addProperty.dailyAndMonthly": "إيجار يومي وشهري",
|
||||||
|
|
||||||
/* ─── Toast Messages ─── */
|
/* ─── Toast Messages ─── */
|
||||||
|
|||||||
@ -174,6 +174,7 @@ export default function AddPropertyPage() {
|
|||||||
const [errors, setErrors] = useState({});
|
const [errors, setErrors] = useState({});
|
||||||
|
|
||||||
const [isLoading, setIsLoading] = useState(false);
|
const [isLoading, setIsLoading] = useState(false);
|
||||||
|
const [isUploadingImages, setIsUploadingImages] = useState(false);
|
||||||
const [agreeToTerms, setAgreeToTerms] = useState(false);
|
const [agreeToTerms, setAgreeToTerms] = useState(false);
|
||||||
|
|
||||||
const fileInputRef = useRef(null);
|
const fileInputRef = useRef(null);
|
||||||
@ -602,11 +603,51 @@ const handleMarkerDragEnd = async (lat, lng) => {
|
|||||||
window.scrollTo({ top: 0, behavior: 'smooth' });
|
window.scrollTo({ top: 0, behavior: 'smooth' });
|
||||||
};
|
};
|
||||||
|
|
||||||
|
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) {
|
||||||
|
console.error('[AddProperty] Image upload failed:', err);
|
||||||
|
toast.error(`${t('toast.imageUploadFailed')}: ${file.name}`);
|
||||||
|
setIsUploadingImages(false);
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
setUploadedImagePaths(finalPaths);
|
||||||
|
setIsUploadingImages(false);
|
||||||
|
return finalPaths;
|
||||||
|
};
|
||||||
|
|
||||||
const handleSubmit = async () => {
|
const handleSubmit = async () => {
|
||||||
if (!validateStep()) return;
|
if (!validateStep()) return;
|
||||||
|
|
||||||
setIsLoading(true);
|
setIsLoading(true);
|
||||||
|
|
||||||
|
const finalImagePaths = await ensureImagesUploaded();
|
||||||
|
if (finalImagePaths.length < 2) {
|
||||||
|
toast.error(t('addProperty.minImagesRequired'));
|
||||||
|
setIsLoading(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// Map UI property type to API BuildingType enum
|
// Map UI property type to API BuildingType enum
|
||||||
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 };
|
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 };
|
||||||
|
|
||||||
@ -670,14 +711,14 @@ const handleMarkerDragEnd = async (lat, lng) => {
|
|||||||
const propInfo = {
|
const propInfo = {
|
||||||
cordsX: formData.lat ? String(formData.lat) : '',
|
cordsX: formData.lat ? String(formData.lat) : '',
|
||||||
cordsY: formData.lng ? String(formData.lng) : '',
|
cordsY: formData.lng ? String(formData.lng) : '',
|
||||||
images: uploadedImagePaths,
|
images: finalImagePaths,
|
||||||
address: `${formData.city} - ${formData.district} - ${formData.address}`.trim(),
|
address: `${formData.city} - ${formData.district} - ${formData.address}`.trim(),
|
||||||
description: formData.description || '',
|
description: formData.description || '',
|
||||||
numberOfBathRooms: formData.bathrooms || 0,
|
numberOfBathRooms: formData.bathrooms || 0,
|
||||||
numberOfRooms: formData.bedrooms || 0,
|
numberOfRooms: formData.bedrooms || 0,
|
||||||
numberOfBedRooms: formData.bedrooms || 0,
|
numberOfBedRooms: formData.bedrooms || 0,
|
||||||
space: parseFloat(formData.space) || 0,
|
space: parseFloat(formData.space) || 0,
|
||||||
detailsJSON,
|
detailsJSON: detailsJSON || '{}',
|
||||||
buildingType: buildingTypeMap[formData.propertyType] ?? BuildingType.APARTMENT,
|
buildingType: buildingTypeMap[formData.propertyType] ?? BuildingType.APARTMENT,
|
||||||
status: 0,
|
status: 0,
|
||||||
propertyType: formData.furnished ? RentPropertyCondition.WITH_FURNITURE : RentPropertyCondition.WITHOUT_FURNITURE,
|
propertyType: formData.furnished ? RentPropertyCondition.WITH_FURNITURE : RentPropertyCondition.WITHOUT_FURNITURE,
|
||||||
@ -738,6 +779,12 @@ const handleMarkerDragEnd = async (lat, lng) => {
|
|||||||
<Toaster position="top-center" reverseOrder={false} />
|
<Toaster position="top-center" reverseOrder={false} />
|
||||||
|
|
||||||
<div className="container mx-auto px-4 max-w-4xl">
|
<div className="container mx-auto px-4 max-w-4xl">
|
||||||
|
{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" />
|
||||||
|
<span>{t('addProperty.uploadingImagesMessage') || 'جاري رفع الصور... يرجى الانتظار'}</span>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
<div className="mb-8">
|
<div className="mb-8">
|
||||||
<div className="flex items-center justify-between mb-4">
|
<div className="flex items-center justify-between mb-4">
|
||||||
<Link
|
<Link
|
||||||
@ -1561,7 +1608,7 @@ const handleMarkerDragEnd = async (lat, lng) => {
|
|||||||
<p className="text-gray-600">{t('addProperty.locationAndImagesSubtitle')}</p>
|
<p className="text-gray-600">{t('addProperty.locationAndImagesSubtitle')}</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="rounded-3xl border border-amber-100 bg-gradient-to-br from-amber-50 via-white to-orange-50 p-4 sm:p-5 md:p-6 shadow-sm">
|
<div className="rounded-3xl border border-amber-100 bg-linear-to-br from-amber-50 via-white to-orange-50 p-4 sm:p-5 md:p-6 shadow-sm">
|
||||||
<div className="flex flex-col gap-3 md:flex-row md:items-start md:justify-between mb-5">
|
<div className="flex flex-col gap-3 md:flex-row md:items-start md:justify-between mb-5">
|
||||||
<div>
|
<div>
|
||||||
<div className="flex items-center gap-2 text-amber-700 font-semibold mb-1">
|
<div className="flex items-center gap-2 text-amber-700 font-semibold mb-1">
|
||||||
@ -1782,7 +1829,7 @@ const handleMarkerDragEnd = async (lat, lng) => {
|
|||||||
<button
|
<button
|
||||||
onClick={handleSubmit}
|
onClick={handleSubmit}
|
||||||
disabled={isLoading || !agreeToTerms}
|
disabled={isLoading || !agreeToTerms}
|
||||||
className="flex-1 py-3 px-4 bg-gradient-to-r from-amber-500 to-amber-600 text-white rounded-xl font-medium hover:from-amber-600 hover:to-amber-700 transition-all disabled:opacity-50 flex items-center justify-center gap-2"
|
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"
|
||||||
>
|
>
|
||||||
{isLoading ? (
|
{isLoading ? (
|
||||||
<>
|
<>
|
||||||
|
|||||||
Reference in New Issue
Block a user