fixing add property
All checks were successful
Build frontend / build (push) Successful in 57s

This commit is contained in:
Beilin-b
2026-07-16 10:56:59 -07:00
parent 2374e80380
commit ca6b12bfb9
2 changed files with 69 additions and 170 deletions

View File

@ -227,7 +227,6 @@ export default function AddPropertyPage() {
};
useEffect(() => {
// Fetch available currencies
getCurrencies().then((data) => {
if (Array.isArray(data) && data.length > 0) {
setCurrencies(data);
@ -278,7 +277,7 @@ export default function AddPropertyPage() {
};
const handleGeolocation = () => {
if (!navigator.geolocation) {
if (!navigator.geolocation) {
toast.error(t('toast.geolocationNotSupported'));
return;
}
@ -291,7 +290,7 @@ if (!navigator.geolocation) {
setMapCenter([latitude, longitude]);
setMapZoom(18);
try {
try {
const response = await fetch(
`https://nominatim.openstreetmap.org/reverse?format=json&lat=${latitude}&lon=${longitude}&accept-language=ar`
);
@ -319,57 +318,59 @@ try {
);
};
const handleMapClick = async (coords) => {
try {
const [lat, lng] = coords;
const handleMapClick = async (coords) => {
try {
const [lat, lng] = coords;
toast.loading(t('addProperty.locationSelecting'), { id: 'location' });
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();
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,
setSelectedLocation({
lat: lat,
lng: lng,
address: data.display_name || t('addProperty.defaultAddress')
});
setMapZoom(18);
toast.success(t('toast.locationSelectedSuccess'), { id: 'location' });
} catch (error) {
console.error(t('addProperty.locationError'), error);
const [lat, lng] = coords;
setSelectedLocation({
lat: lat,
lng: lng,
address: t('addProperty.defaultAddress')
});
setMapZoom(18);
toast.success(t('toast.locationConfirmed'), { id: 'location' });
}
};
const handleMarkerDragEnd = async (lat, lng) => {
try {
const response = await fetch(
`https://nominatim.openstreetmap.org/reverse?format=json&lat=${lat}&lon=${lng}&accept-language=ar`
);
const data = await response.json();
setSelectedLocation({
lat,
lng,
address: data.display_name || t('addProperty.defaultAddress')
});
});
} catch (error) {
setSelectedLocation({
lat,
lng,
address: t('addProperty.defaultAddress')
});
}
};
setMapZoom(18);
toast.success(t('toast.locationSelectedSuccess'), { id: 'location' });
} catch (error) {
console.error(t('addProperty.locationError'), error);
const [lat, lng] = coords;
setSelectedLocation({
lat: lat,
lng: lng,
address: t('addProperty.defaultAddress')
});
setMapZoom(18);
toast.success(t('toast.locationConfirmed'), { id: 'location' });
}
};
const handleMarkerDragEnd = async (lat, lng) => {
try {
const response = await fetch(
`https://nominatim.openstreetmap.org/reverse?format=json&lat=${lat}&lon=${lng}&accept-language=ar`
);
const data = await response.json();
setSelectedLocation({
lat,
lng,
address: data.display_name || t('addProperty.defaultAddress')
});
} catch (error) {
setSelectedLocation({
lat,
lng,
address: t('addProperty.defaultAddress')
});
}
};
const confirmLocation = () => {
if (selectedLocation) {
setFormData({
@ -414,7 +415,6 @@ const handleMarkerDragEnd = async (lat, lng) => {
continue;
}
// Show preview
const reader = new FileReader();
reader.onloadend = () => {
setImagePreviews(prev => [...prev, reader.result]);
@ -426,7 +426,6 @@ const handleMarkerDragEnd = async (lat, lng) => {
images: [...prev.images, file]
}));
// Upload to server immediately
try {
const path = await uploadPicture(file);
setUploadedImagePaths(prev => [...prev, path]);
@ -648,7 +647,6 @@ const handleMarkerDragEnd = async (lat, lng) => {
return;
}
// 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 selectedServices = Object.entries(formData.services)
@ -750,7 +748,7 @@ const handleMarkerDragEnd = async (lat, lng) => {
currencyId: selectedCurrencyId,
rentType: rentTypeMap[formData.offerType] ?? RentType.MONTHLY,
type: formData.furnished ? RentPropertyType.FURNISHED : RentPropertyType.UNFURNISHED,
allowedPaymentPeriod: formData.allowedPaymentPeriod || '',
allowedPaymentPeriod: formData.allowedPaymentPeriod || '1.00:00:00',
});
const res = await addRentProperty(payload);
toast.success(t('toast.rentPropertySuccess'));
@ -787,7 +785,7 @@ const handleMarkerDragEnd = async (lat, lng) => {
)}
<div className="mb-8">
<div className="flex items-center justify-between mb-4">
<Link
<Link
href="/owner/properties"
className="flex items-center gap-2 text-gray-600 hover:text-amber-600 transition-colors group"
>
@ -838,7 +836,7 @@ const handleMarkerDragEnd = async (lat, lng) => {
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-3">
<label className="block text-sm font-medium text-gray-700 mb-3">
{t('addProperty.propertyTypeLabel')} <span className="text-red-500">*</span>
</label>
<div className="grid grid-cols-2 md:grid-cols-4 gap-3">
@ -1226,7 +1224,6 @@ const handleMarkerDragEnd = async (lat, lng) => {
})}
</div>
{/* Custom Terms */}
<div className="mt-4 p-4 border border-dashed border-gray-300 rounded-xl">
<p className="text-sm font-medium text-gray-700 mb-2">{t('addProperty.customTermsTitle')}</p>
<div className="flex gap-2">
@ -1360,7 +1357,6 @@ const handleMarkerDragEnd = async (lat, lng) => {
</div>
</div>
{/* Currency dropdown */}
<div>
<label className="block text-sm font-medium text-gray-700 mb-2">
{t('addProperty.currencyLabel')} <span className="text-red-500">*</span>
@ -1375,7 +1371,6 @@ const handleMarkerDragEnd = async (lat, lng) => {
</select>
</div>
{/* Price and deposit fields - conditional on offerType */}
{errors.offerType && (
<p className="text-red-500 text-sm mt-1">{errors.offerType}</p>
)}

View File

@ -39,9 +39,6 @@ function buildApiUrl(base, endpoint) {
return `${base.replace(/\/$/, "")}${endpoint.startsWith("/") ? endpoint : `/${endpoint}`}`;
}
/**
* Generic API fetch — attaches auth token, unwraps { data } envelope
*/
async function apiFetch(endpoint, options = {}) {
const token = AuthService.getToken();
@ -103,9 +100,6 @@ async function apiFetch(endpoint, options = {}) {
}
}
/**
* Auth fetch — returns full { status, data, ok } for status-code handling
*/
async function authFetch(endpoint, body, token = null) {
const headers = {};
@ -193,8 +187,6 @@ async function reportFetch(endpoint, body) {
};
}
// ─── Rent Properties ───
export async function getRentProperties() {
return apiFetch("/RentProperties/GetRentProperties");
}
@ -213,8 +205,6 @@ export async function getRentPropertyLocations(params = {}) {
);
}
// ─── Sale Properties ───
export async function getSaleProperties() {
return apiFetch("/SaleProperties/GetSaleProperties");
}
@ -225,14 +215,10 @@ export async function getSaleProperty(id) {
return items.find((p) => p.id == id) || items[0];
}
// ─── Properties (generic) ───
export async function getProperty(id) {
return apiFetch(`/Properties/Get/${id}`);
}
// ─── Recommendations ───
export async function getRecommendations() {
return apiFetch("/Recommendations/GetRecommendations");
}
@ -241,8 +227,6 @@ export async function getTopRecommendations(count = 10) {
return apiFetch(`/Recommendations/GetTopRecommendations?count=${count}`);
}
// ─── Reservations ───
export async function getAvailableDateRanges(
propertyId,
fromDate = null,
@ -291,8 +275,6 @@ export async function bookReservation(propertyInfoId, startDate, endDate) {
});
}
// ─── Terms ───
export async function getARTerms() {
return apiFetch("/Configuration/GetARTerms");
}
@ -301,8 +283,6 @@ export async function getENTerms() {
return apiFetch("/Configuration/GetENTerms");
}
// ─── Profile ───
export async function getCustomerByUserId(userId) {
return apiFetch(`/Customer/GetByUserId/${userId}`);
}
@ -311,69 +291,34 @@ export async function getOwnerByUserId(userId) {
return apiFetch(`/Owner/GetByUserId/${userId}`);
}
// ─── Properties ───
export async function getMyRentListings() {
return apiFetch("/RentProperties/GetMyRentListings");
}
function normalizeLocationValue(value) {
if (value === null || value === undefined || value === "") return "";
const normalizedValue = String(value).trim();
const map = {
"دمشق": "Damascus",
"حلب": "Aleppo",
"حمص": "Homs",
"اللاذقية": "Latakia",
"درعا": "Daraa",
"طرطوس": "Tartous",
"السويداء": "Suweida",
"دير الزور": "DeirEzzor",
"الرقة": "Raqqa",
"إدلب": "Idlib",
"الحسكة": "Hasakah",
"القامشلي": "Qamishli",
"ريف دمشق": "RuralDamascus",
Damascus: "Damascus",
Aleppo: "Aleppo",
Homs: "Homs",
Latakia: "Latakia",
Daraa: "Daraa",
Tartous: "Tartous",
Sweida: "Suweida",
DeirEzzor: "DeirEzzor",
Raqqa: "Raqqa",
Idlib: "Idlib",
Hasakah: "Hasakah",
Qamishli: "Qamishli",
RuralDamascus: "RuralDamascus",
};
return map[normalizedValue] ?? normalizedValue;
}
export function buildRentPropertyPayload(data = {}) {
const propertyInformation = data?.propertyInformation || {};
const rawCityValue =
data?.city ??
data?.governorate ??
propertyInformation?.city ??
propertyInformation?.governorate ??
"";
const normalizedCityValue = normalizeLocationValue(rawCityValue);
1;
const cityInt = parseInt(rawCityValue, 10) || 1;
const documentTypeValue =
data?.documentType ?? propertyInformation?.documentType ?? "";
data?.documentType ?? propertyInformation?.documentType ?? 1;
return {
...data,
city: normalizedCityValue,
governorate: normalizedCityValue,
city: cityInt,
governorate: cityInt,
documentType: documentTypeValue,
propertyInformation: {
...propertyInformation,
city: normalizedCityValue,
governorate: normalizeLocationValue(propertyInformation?.governorate ?? normalizedCityValue),
city: cityInt,
governorate: cityInt,
documentType: documentTypeValue,
},
};
@ -382,14 +327,14 @@ export function buildRentPropertyPayload(data = {}) {
export async function addRentProperty(data) {
return apiFetch("/RentProperties/AddRentProperty", {
method: "POST",
body: { rentPropertyDto: buildRentPropertyPayload(data) },
body: buildRentPropertyPayload(data),
});
}
export async function editRentProperty(id, data) {
return apiFetch(`/RentProperties/EditRentProperty/${id}`, {
method: "PUT",
body: { rentPropertyDto: buildRentPropertyPayload(data) },
body: buildRentPropertyPayload(data),
});
}
@ -429,8 +374,6 @@ export async function updateSalePropertyStatus(id, status) {
});
}
// ─── Currencies ───
export async function getCurrencies() {
return apiFetch("/Currency/GetAll");
}
@ -439,8 +382,6 @@ export async function getPaymentTypes() {
return apiFetch("/PaymentType/GetAll");
}
// ─── Files ───
export async function uploadPicture(file) {
const formData = new FormData();
formData.append("image", file);
@ -474,8 +415,6 @@ export async function uploadPicture(file) {
}
}
// ─── Auth: Registration ───
async function multipartAuthFetch(endpoint, formData) {
const token = AuthService.getToken();
@ -571,8 +510,6 @@ export async function addCustomer(data, frontImage = null, backImage = null) {
return multipartAuthFetch("/Customer/Add", formData);
}
// ─── Auth: Login ───
export async function loginWithEmail(credential, password) {
return authFetch("/Auth/LogInWithEmail", {
credential,
@ -591,8 +528,6 @@ export async function loginWithPhone(credential, password) {
});
}
// ─── Auth: OTP ───
export async function sendEmailOTP() {
return apiFetch("/Auth/SendEmailOTP", { method: "POST" });
}
@ -619,8 +554,6 @@ export async function verifyPhone(code) {
);
}
// ─── Helpers ───
export function isEmail(value) {
return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value);
}
@ -629,8 +562,6 @@ export function isPhoneNumber(value) {
return /^\+?\d{7,15}$/.test(value.replace(/[\s\-()]/g, ""));
}
// ─── Favorites ───
export async function getUserFavoriteProperties() {
return apiFetch("/FavoriteProperty/GetUserFavoriteProperties");
}
@ -649,8 +580,6 @@ export async function getUserNotifications() {
return apiFetch("/Notifications/GetUserNotifications");
}
// ─── Booking/Reservation Management ───
export async function confirmDepositPayment(bookingId) {
return apiFetch("/Reservations/ConfirmDepositPayment", {
method: "POST",
@ -706,8 +635,6 @@ export async function updateBookingStatus(bookingId, status) {
});
}
// ─── Owner / Reservations ───
export async function getOwnerReservationRequests() {
return apiFetch("/Reservations/GetOwnerResevationRequests");
}
@ -729,8 +656,6 @@ export async function ownerConfirmReservation(id) {
});
}
// ─── Payments ───
export async function getMyTransaction() {
return apiFetch("/Customer/GetMyTransaction");
}
@ -754,7 +679,6 @@ export async function payDeposit(data) {
formData.append("Comment", "");
}
// صورة الوصل لطريقة الدفع Haram
if (data.paymentImage) {
formData.append("TransactionType", data.paymentImage);
formData.append("paymentImage", data.paymentImage);
@ -766,8 +690,6 @@ export async function payDeposit(data) {
});
}
// ─── Owner Contact & Stats ───
export async function getOwnerContactInformation(propertyInformationId) {
return apiFetch(
`/Owner/GetOwnerContactInformation?propertyInformationId=${propertyInformationId}`,
@ -778,8 +700,6 @@ export async function getOwnerStatistics() {
return apiFetch("/Statistics/GetOwnerStatistics");
}
// ─── Agent Registration ───
export async function registerRealEstateAgent(formData) {
const token = AuthService.getToken();
@ -811,8 +731,6 @@ export async function registerRealEstateAgent(formData) {
};
}
// ─── Change Password ───
export async function changePassword(oldPassword, newPassword) {
return apiFetch(
`/User/ChangePassword?oldPassword=${encodeURIComponent(oldPassword)}&newPassword=${encodeURIComponent(newPassword)}`,
@ -822,8 +740,6 @@ export async function changePassword(oldPassword, newPassword) {
);
}
// ─── Forget Password (OTP flow) ───
export async function requestForgetPasswordOtp(email) {
return apiFetch(`/User/ForgetPassword?email=${encodeURIComponent(email)}`, {
method: "POST",
@ -839,14 +755,10 @@ export async function verifyForgetPasswordOtp(email, code, newPassword) {
);
}
// ─── Reset Password (token flow) ───
export async function resetPassword(token) {
return apiFetch(`/Auth/ResetPassword?token=${encodeURIComponent(token)}`);
}
// ─── Delete Account ───
export async function deleteMyAccount(password) {
return apiFetch(
`/User/DeleteMyAccount?password=${encodeURIComponent(password)}`,
@ -856,8 +768,6 @@ export async function deleteMyAccount(password) {
);
}
// ─── Set FCM Token ───
export async function setFCMToken(token, deviceType = 2) {
return apiFetch("/User/SetFCMToken", {
method: "POST",
@ -865,8 +775,6 @@ export async function setFCMToken(token, deviceType = 2) {
});
}
// ─── Filter Rent Properties ───
export async function filterRentProperties(params = {}) {
const qs = new URLSearchParams();
Object.entries(params).forEach(([k, v]) => {
@ -878,8 +786,6 @@ export async function filterRentProperties(params = {}) {
);
}
// ─── Reports ───
export async function sendGeneralReport(subject, reportBody) {
return reportFetch("/Reports/SendGeneralReport", {
subject,
@ -922,8 +828,6 @@ export async function updateSaleReport(id, data) {
});
}
// ─── Terms (Add or Update) ───
export async function addOrUpdateTerms(terms) {
return apiFetch("/Terms/AddOrUpdateTerms", {
method: "POST",