fixing add property

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(() => { useEffect(() => {
// Fetch available currencies
getCurrencies().then((data) => { getCurrencies().then((data) => {
if (Array.isArray(data) && data.length > 0) { if (Array.isArray(data) && data.length > 0) {
setCurrencies(data); setCurrencies(data);
@ -278,7 +277,7 @@ export default function AddPropertyPage() {
}; };
const handleGeolocation = () => { const handleGeolocation = () => {
if (!navigator.geolocation) { if (!navigator.geolocation) {
toast.error(t('toast.geolocationNotSupported')); toast.error(t('toast.geolocationNotSupported'));
return; return;
} }
@ -291,7 +290,7 @@ if (!navigator.geolocation) {
setMapCenter([latitude, longitude]); setMapCenter([latitude, longitude]);
setMapZoom(18); setMapZoom(18);
try { try {
const response = await fetch( const response = await fetch(
`https://nominatim.openstreetmap.org/reverse?format=json&lat=${latitude}&lon=${longitude}&accept-language=ar` `https://nominatim.openstreetmap.org/reverse?format=json&lat=${latitude}&lon=${longitude}&accept-language=ar`
); );
@ -319,57 +318,59 @@ try {
); );
}; };
const handleMapClick = async (coords) => { const handleMapClick = async (coords) => {
try { try {
const [lat, lng] = coords; const [lat, lng] = coords;
toast.loading(t('addProperty.locationSelecting'), { id: 'location' }); toast.loading(t('addProperty.locationSelecting'), { id: 'location' });
const response = await fetch( const response = await fetch(
`https://nominatim.openstreetmap.org/reverse?format=json&lat=${lat}&lon=${lng}&accept-language=ar` `https://nominatim.openstreetmap.org/reverse?format=json&lat=${lat}&lon=${lng}&accept-language=ar`
); );
const data = await response.json(); const data = await response.json();
setSelectedLocation({ setSelectedLocation({
lat: lat, lat: lat,
lng: lng, 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') address: data.display_name || t('addProperty.defaultAddress')
}); });
} catch (error) {
setMapZoom(18); setSelectedLocation({
toast.success(t('toast.locationSelectedSuccess'), { id: 'location' }); lat,
lng,
} catch (error) { address: t('addProperty.defaultAddress')
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 = () => { const confirmLocation = () => {
if (selectedLocation) { if (selectedLocation) {
setFormData({ setFormData({
@ -414,7 +415,6 @@ const handleMarkerDragEnd = async (lat, lng) => {
continue; continue;
} }
// Show preview
const reader = new FileReader(); const reader = new FileReader();
reader.onloadend = () => { reader.onloadend = () => {
setImagePreviews(prev => [...prev, reader.result]); setImagePreviews(prev => [...prev, reader.result]);
@ -426,7 +426,6 @@ const handleMarkerDragEnd = async (lat, lng) => {
images: [...prev.images, file] images: [...prev.images, file]
})); }));
// Upload to server immediately
try { try {
const path = await uploadPicture(file); const path = await uploadPicture(file);
setUploadedImagePaths(prev => [...prev, path]); setUploadedImagePaths(prev => [...prev, path]);
@ -648,7 +647,6 @@ const handleMarkerDragEnd = async (lat, lng) => {
return; 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 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) const selectedServices = Object.entries(formData.services)
@ -750,7 +748,7 @@ const handleMarkerDragEnd = async (lat, lng) => {
currencyId: selectedCurrencyId, currencyId: selectedCurrencyId,
rentType: rentTypeMap[formData.offerType] ?? RentType.MONTHLY, rentType: rentTypeMap[formData.offerType] ?? RentType.MONTHLY,
type: formData.furnished ? RentPropertyType.FURNISHED : RentPropertyType.UNFURNISHED, type: formData.furnished ? RentPropertyType.FURNISHED : RentPropertyType.UNFURNISHED,
allowedPaymentPeriod: formData.allowedPaymentPeriod || '', allowedPaymentPeriod: formData.allowedPaymentPeriod || '1.00:00:00',
}); });
const res = await addRentProperty(payload); const res = await addRentProperty(payload);
toast.success(t('toast.rentPropertySuccess')); toast.success(t('toast.rentPropertySuccess'));
@ -787,7 +785,7 @@ const handleMarkerDragEnd = async (lat, lng) => {
)} )}
<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
href="/owner/properties" href="/owner/properties"
className="flex items-center gap-2 text-gray-600 hover:text-amber-600 transition-colors group" 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>
<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> {t('addProperty.propertyTypeLabel')} <span className="text-red-500">*</span>
</label> </label>
<div className="grid grid-cols-2 md:grid-cols-4 gap-3"> <div className="grid grid-cols-2 md:grid-cols-4 gap-3">
@ -1226,7 +1224,6 @@ const handleMarkerDragEnd = async (lat, lng) => {
})} })}
</div> </div>
{/* Custom Terms */}
<div className="mt-4 p-4 border border-dashed border-gray-300 rounded-xl"> <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> <p className="text-sm font-medium text-gray-700 mb-2">{t('addProperty.customTermsTitle')}</p>
<div className="flex gap-2"> <div className="flex gap-2">
@ -1360,7 +1357,6 @@ const handleMarkerDragEnd = async (lat, lng) => {
</div> </div>
</div> </div>
{/* Currency dropdown */}
<div> <div>
<label className="block text-sm font-medium text-gray-700 mb-2"> <label className="block text-sm font-medium text-gray-700 mb-2">
{t('addProperty.currencyLabel')} <span className="text-red-500">*</span> {t('addProperty.currencyLabel')} <span className="text-red-500">*</span>
@ -1375,7 +1371,6 @@ const handleMarkerDragEnd = async (lat, lng) => {
</select> </select>
</div> </div>
{/* Price and deposit fields - conditional on offerType */}
{errors.offerType && ( {errors.offerType && (
<p className="text-red-500 text-sm mt-1">{errors.offerType}</p> <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}`}`; return `${base.replace(/\/$/, "")}${endpoint.startsWith("/") ? endpoint : `/${endpoint}`}`;
} }
/**
* Generic API fetch — attaches auth token, unwraps { data } envelope
*/
async function apiFetch(endpoint, options = {}) { async function apiFetch(endpoint, options = {}) {
const token = AuthService.getToken(); 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) { async function authFetch(endpoint, body, token = null) {
const headers = {}; const headers = {};
@ -193,8 +187,6 @@ async function reportFetch(endpoint, body) {
}; };
} }
// ─── Rent Properties ───
export async function getRentProperties() { export async function getRentProperties() {
return apiFetch("/RentProperties/GetRentProperties"); return apiFetch("/RentProperties/GetRentProperties");
} }
@ -213,8 +205,6 @@ export async function getRentPropertyLocations(params = {}) {
); );
} }
// ─── Sale Properties ───
export async function getSaleProperties() { export async function getSaleProperties() {
return apiFetch("/SaleProperties/GetSaleProperties"); return apiFetch("/SaleProperties/GetSaleProperties");
} }
@ -225,14 +215,10 @@ export async function getSaleProperty(id) {
return items.find((p) => p.id == id) || items[0]; return items.find((p) => p.id == id) || items[0];
} }
// ─── Properties (generic) ───
export async function getProperty(id) { export async function getProperty(id) {
return apiFetch(`/Properties/Get/${id}`); return apiFetch(`/Properties/Get/${id}`);
} }
// ─── Recommendations ───
export async function getRecommendations() { export async function getRecommendations() {
return apiFetch("/Recommendations/GetRecommendations"); return apiFetch("/Recommendations/GetRecommendations");
} }
@ -241,8 +227,6 @@ export async function getTopRecommendations(count = 10) {
return apiFetch(`/Recommendations/GetTopRecommendations?count=${count}`); return apiFetch(`/Recommendations/GetTopRecommendations?count=${count}`);
} }
// ─── Reservations ───
export async function getAvailableDateRanges( export async function getAvailableDateRanges(
propertyId, propertyId,
fromDate = null, fromDate = null,
@ -291,8 +275,6 @@ export async function bookReservation(propertyInfoId, startDate, endDate) {
}); });
} }
// ─── Terms ───
export async function getARTerms() { export async function getARTerms() {
return apiFetch("/Configuration/GetARTerms"); return apiFetch("/Configuration/GetARTerms");
} }
@ -301,8 +283,6 @@ export async function getENTerms() {
return apiFetch("/Configuration/GetENTerms"); return apiFetch("/Configuration/GetENTerms");
} }
// ─── Profile ───
export async function getCustomerByUserId(userId) { export async function getCustomerByUserId(userId) {
return apiFetch(`/Customer/GetByUserId/${userId}`); return apiFetch(`/Customer/GetByUserId/${userId}`);
} }
@ -311,69 +291,34 @@ export async function getOwnerByUserId(userId) {
return apiFetch(`/Owner/GetByUserId/${userId}`); return apiFetch(`/Owner/GetByUserId/${userId}`);
} }
// ─── Properties ───
export async function getMyRentListings() { export async function getMyRentListings() {
return apiFetch("/RentProperties/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 = {}) { export function buildRentPropertyPayload(data = {}) {
const propertyInformation = data?.propertyInformation || {}; const propertyInformation = data?.propertyInformation || {};
const rawCityValue = const rawCityValue =
data?.city ?? data?.city ??
data?.governorate ?? data?.governorate ??
propertyInformation?.city ?? propertyInformation?.city ??
propertyInformation?.governorate ?? propertyInformation?.governorate ??
""; 1;
const normalizedCityValue = normalizeLocationValue(rawCityValue);
const cityInt = parseInt(rawCityValue, 10) || 1;
const documentTypeValue = const documentTypeValue =
data?.documentType ?? propertyInformation?.documentType ?? ""; data?.documentType ?? propertyInformation?.documentType ?? 1;
return { return {
...data, ...data,
city: normalizedCityValue, city: cityInt,
governorate: normalizedCityValue, governorate: cityInt,
documentType: documentTypeValue, documentType: documentTypeValue,
propertyInformation: { propertyInformation: {
...propertyInformation, ...propertyInformation,
city: normalizedCityValue, city: cityInt,
governorate: normalizeLocationValue(propertyInformation?.governorate ?? normalizedCityValue), governorate: cityInt,
documentType: documentTypeValue, documentType: documentTypeValue,
}, },
}; };
@ -382,14 +327,14 @@ export function buildRentPropertyPayload(data = {}) {
export async function addRentProperty(data) { export async function addRentProperty(data) {
return apiFetch("/RentProperties/AddRentProperty", { return apiFetch("/RentProperties/AddRentProperty", {
method: "POST", method: "POST",
body: { rentPropertyDto: buildRentPropertyPayload(data) }, body: buildRentPropertyPayload(data),
}); });
} }
export async function editRentProperty(id, data) { export async function editRentProperty(id, data) {
return apiFetch(`/RentProperties/EditRentProperty/${id}`, { return apiFetch(`/RentProperties/EditRentProperty/${id}`, {
method: "PUT", method: "PUT",
body: { rentPropertyDto: buildRentPropertyPayload(data) }, body: buildRentPropertyPayload(data),
}); });
} }
@ -429,8 +374,6 @@ export async function updateSalePropertyStatus(id, status) {
}); });
} }
// ─── Currencies ───
export async function getCurrencies() { export async function getCurrencies() {
return apiFetch("/Currency/GetAll"); return apiFetch("/Currency/GetAll");
} }
@ -439,8 +382,6 @@ export async function getPaymentTypes() {
return apiFetch("/PaymentType/GetAll"); return apiFetch("/PaymentType/GetAll");
} }
// ─── Files ───
export async function uploadPicture(file) { export async function uploadPicture(file) {
const formData = new FormData(); const formData = new FormData();
formData.append("image", file); formData.append("image", file);
@ -474,8 +415,6 @@ export async function uploadPicture(file) {
} }
} }
// ─── Auth: Registration ───
async function multipartAuthFetch(endpoint, formData) { async function multipartAuthFetch(endpoint, formData) {
const token = AuthService.getToken(); const token = AuthService.getToken();
@ -571,8 +510,6 @@ export async function addCustomer(data, frontImage = null, backImage = null) {
return multipartAuthFetch("/Customer/Add", formData); return multipartAuthFetch("/Customer/Add", formData);
} }
// ─── Auth: Login ───
export async function loginWithEmail(credential, password) { export async function loginWithEmail(credential, password) {
return authFetch("/Auth/LogInWithEmail", { return authFetch("/Auth/LogInWithEmail", {
credential, credential,
@ -591,8 +528,6 @@ export async function loginWithPhone(credential, password) {
}); });
} }
// ─── Auth: OTP ───
export async function sendEmailOTP() { export async function sendEmailOTP() {
return apiFetch("/Auth/SendEmailOTP", { method: "POST" }); return apiFetch("/Auth/SendEmailOTP", { method: "POST" });
} }
@ -619,8 +554,6 @@ export async function verifyPhone(code) {
); );
} }
// ─── Helpers ───
export function isEmail(value) { export function isEmail(value) {
return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value); return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value);
} }
@ -629,8 +562,6 @@ export function isPhoneNumber(value) {
return /^\+?\d{7,15}$/.test(value.replace(/[\s\-()]/g, "")); return /^\+?\d{7,15}$/.test(value.replace(/[\s\-()]/g, ""));
} }
// ─── Favorites ───
export async function getUserFavoriteProperties() { export async function getUserFavoriteProperties() {
return apiFetch("/FavoriteProperty/GetUserFavoriteProperties"); return apiFetch("/FavoriteProperty/GetUserFavoriteProperties");
} }
@ -649,8 +580,6 @@ export async function getUserNotifications() {
return apiFetch("/Notifications/GetUserNotifications"); return apiFetch("/Notifications/GetUserNotifications");
} }
// ─── Booking/Reservation Management ───
export async function confirmDepositPayment(bookingId) { export async function confirmDepositPayment(bookingId) {
return apiFetch("/Reservations/ConfirmDepositPayment", { return apiFetch("/Reservations/ConfirmDepositPayment", {
method: "POST", method: "POST",
@ -706,8 +635,6 @@ export async function updateBookingStatus(bookingId, status) {
}); });
} }
// ─── Owner / Reservations ───
export async function getOwnerReservationRequests() { export async function getOwnerReservationRequests() {
return apiFetch("/Reservations/GetOwnerResevationRequests"); return apiFetch("/Reservations/GetOwnerResevationRequests");
} }
@ -729,8 +656,6 @@ export async function ownerConfirmReservation(id) {
}); });
} }
// ─── Payments ───
export async function getMyTransaction() { export async function getMyTransaction() {
return apiFetch("/Customer/GetMyTransaction"); return apiFetch("/Customer/GetMyTransaction");
} }
@ -754,7 +679,6 @@ export async function payDeposit(data) {
formData.append("Comment", ""); formData.append("Comment", "");
} }
// صورة الوصل لطريقة الدفع Haram
if (data.paymentImage) { if (data.paymentImage) {
formData.append("TransactionType", data.paymentImage); formData.append("TransactionType", data.paymentImage);
formData.append("paymentImage", data.paymentImage); formData.append("paymentImage", data.paymentImage);
@ -766,8 +690,6 @@ export async function payDeposit(data) {
}); });
} }
// ─── Owner Contact & Stats ───
export async function getOwnerContactInformation(propertyInformationId) { export async function getOwnerContactInformation(propertyInformationId) {
return apiFetch( return apiFetch(
`/Owner/GetOwnerContactInformation?propertyInformationId=${propertyInformationId}`, `/Owner/GetOwnerContactInformation?propertyInformationId=${propertyInformationId}`,
@ -778,8 +700,6 @@ export async function getOwnerStatistics() {
return apiFetch("/Statistics/GetOwnerStatistics"); return apiFetch("/Statistics/GetOwnerStatistics");
} }
// ─── Agent Registration ───
export async function registerRealEstateAgent(formData) { export async function registerRealEstateAgent(formData) {
const token = AuthService.getToken(); const token = AuthService.getToken();
@ -811,8 +731,6 @@ export async function registerRealEstateAgent(formData) {
}; };
} }
// ─── Change Password ───
export async function changePassword(oldPassword, newPassword) { export async function changePassword(oldPassword, newPassword) {
return apiFetch( return apiFetch(
`/User/ChangePassword?oldPassword=${encodeURIComponent(oldPassword)}&newPassword=${encodeURIComponent(newPassword)}`, `/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) { export async function requestForgetPasswordOtp(email) {
return apiFetch(`/User/ForgetPassword?email=${encodeURIComponent(email)}`, { return apiFetch(`/User/ForgetPassword?email=${encodeURIComponent(email)}`, {
method: "POST", method: "POST",
@ -839,14 +755,10 @@ export async function verifyForgetPasswordOtp(email, code, newPassword) {
); );
} }
// ─── Reset Password (token flow) ───
export async function resetPassword(token) { export async function resetPassword(token) {
return apiFetch(`/Auth/ResetPassword?token=${encodeURIComponent(token)}`); return apiFetch(`/Auth/ResetPassword?token=${encodeURIComponent(token)}`);
} }
// ─── Delete Account ───
export async function deleteMyAccount(password) { export async function deleteMyAccount(password) {
return apiFetch( return apiFetch(
`/User/DeleteMyAccount?password=${encodeURIComponent(password)}`, `/User/DeleteMyAccount?password=${encodeURIComponent(password)}`,
@ -856,8 +768,6 @@ export async function deleteMyAccount(password) {
); );
} }
// ─── Set FCM Token ───
export async function setFCMToken(token, deviceType = 2) { export async function setFCMToken(token, deviceType = 2) {
return apiFetch("/User/SetFCMToken", { return apiFetch("/User/SetFCMToken", {
method: "POST", method: "POST",
@ -865,8 +775,6 @@ export async function setFCMToken(token, deviceType = 2) {
}); });
} }
// ─── Filter Rent Properties ───
export async function filterRentProperties(params = {}) { export async function filterRentProperties(params = {}) {
const qs = new URLSearchParams(); const qs = new URLSearchParams();
Object.entries(params).forEach(([k, v]) => { Object.entries(params).forEach(([k, v]) => {
@ -878,8 +786,6 @@ export async function filterRentProperties(params = {}) {
); );
} }
// ─── Reports ───
export async function sendGeneralReport(subject, reportBody) { export async function sendGeneralReport(subject, reportBody) {
return reportFetch("/Reports/SendGeneralReport", { return reportFetch("/Reports/SendGeneralReport", {
subject, subject,
@ -922,8 +828,6 @@ export async function updateSaleReport(id, data) {
}); });
} }
// ─── Terms (Add or Update) ───
export async function addOrUpdateTerms(terms) { export async function addOrUpdateTerms(terms) {
return apiFetch("/Terms/AddOrUpdateTerms", { return apiFetch("/Terms/AddOrUpdateTerms", {
method: "POST", method: "POST",