Compare commits

...

7 Commits

Author SHA1 Message Date
61e527fab3 removing console.log
All checks were successful
Build frontend / build (push) Successful in 54s
2026-06-17 06:46:57 -07:00
d0b626ac8c Merge branch 'main' of http://45.93.137.91:3000/Rahaf/SweetHome
All checks were successful
Build frontend / build (push) Successful in 1m20s
2026-06-17 06:01:32 -07:00
792bed6250 remove profile editing keep 2026-06-17 06:01:24 -07:00
bf45a48504 fixing the build
All checks were successful
Build frontend / build (push) Successful in 59s
2026-06-16 19:03:15 +03:00
eaa4206b0b fixing the build
Some checks failed
Build frontend / build (push) Failing after 50s
2026-06-16 18:58:48 +03:00
c5d9ad7b70 Merge branch 'main' of http://45.93.137.91:3000/Rahaf/SweetHome 2026-06-16 18:58:06 +03:00
46945464ae fixing the build 2026-06-16 18:57:46 +03:00
12 changed files with 455 additions and 672 deletions

View File

@ -91,7 +91,6 @@ export default function NotificationHandler() {
// This MUST be synchronous from a user gesture
const permission = await Notification.requestPermission();
console.log("[FCM] Permission result:", permission);
if (permission === "granted") {
await setupFCM();

View File

@ -84,24 +84,15 @@ export default function LoginPage() {
try {
const loginFn = loginMethod === "email" ? loginWithEmail : loginWithPhone;
console.log(
"[Login] Attempting login via",
loginMethod,
":",
formData.credential,
);
const result = await loginFn(formData.credential, formData.password);
console.log("[Login] Response status:", result.status);
if (result.status === 200) {
const token =
typeof result.data === "string"
? result.data
: result.data?.token || result.data?.accessToken;
AuthService.addToken(token);
console.log("[Login] Token stored");
// Fetch user profile to get full name
const authUser = AuthService.getUser();
@ -119,7 +110,6 @@ export default function LoginPage() {
email: profile.email || authUser.email,
phone: profile.phone || profile.phoneNumber || authUser.phone,
});
console.log("[Login] User profile cached");
}
} catch (err) {
console.warn("[Login] Failed to fetch profile:", err);
@ -127,7 +117,6 @@ export default function LoginPage() {
}
const userRole = AuthService.isOwner() ? "owner" : "customer";
console.log("[Login] User role:", userRole);
setIsSuccess(true);
toast.success("تم تسجيل الدخول بنجاح!", {
@ -138,14 +127,12 @@ export default function LoginPage() {
router.push("/");
}, 1500);
} else if (result.status === 206) {
console.log("[Login] 206 — OTP required");
const tempToken =
typeof result.data === "string"
? result.data
: result.data?.token || result.data?.accessToken;
if (tempToken) {
AuthService.addToken(tempToken);
console.log("[Login] Temp token stored for OTP");
}
toast("يرجى إدخال رمز التحقق", {
icon: "🔐",
@ -159,7 +146,6 @@ export default function LoginPage() {
} else {
await sendPhoneOTP();
}
console.log("[Login] OTP sent successfully");
} catch (otpErr) {
console.warn("[Login] OTP send failed, proceeding anyway:", otpErr);
}
@ -197,10 +183,8 @@ export default function LoginPage() {
try {
const verifyFn = loginMethod === "email" ? verifyEmail : verifyPhone;
console.log("[OTP] Verifying code:", otpCode);
const result = await verifyFn(otpCode);
console.log("[OTP] Verify response status:", result.status);
if (result.ok) {
const finalToken =
@ -209,7 +193,6 @@ export default function LoginPage() {
: result.data?.token || result.data?.accessToken;
if (finalToken && typeof finalToken === "string") {
AuthService.addToken(finalToken);
console.log("[OTP] Final token stored");
}
setIsSuccess(true);
@ -218,7 +201,6 @@ export default function LoginPage() {
});
setTimeout(() => {
console.log("[OTP] Redirecting to home");
router.push("/");
}, 1500);
} else {
@ -235,7 +217,6 @@ export default function LoginPage() {
const resendOTP = async () => {
try {
console.log("[OTP] Resending OTP via", loginMethod);
if (loginMethod === "email") {
await sendEmailOTP();
} else {
@ -677,7 +658,6 @@ export default function LoginPage() {
setStep("login");
setOtpCode("");
setOtpError("");
console.log("[OTP] Going back to login");
}}
className="text-gray-400 hover:text-white transition-colors"
>

View File

@ -695,7 +695,6 @@ export default function OwnerBookingsPage() {
>
<OwnerBookingCalendar
property={{ bookings }}
onDateSelect={(date) => console.log('Date selected:', date)}
/>
</motion.div>
)}

View File

@ -234,7 +234,6 @@ export default function AddPropertyPage() {
getCurrencies().then((data) => {
if (Array.isArray(data) && data.length > 0) {
setCurrencies(data);
console.log('[AddProperty] Currencies loaded:', data);
}
}).catch((err) => {
console.warn('[AddProperty] Failed to load currencies:', err);
@ -382,7 +381,6 @@ const handleMapClick = async (coords) => {
const handleImageUpload = async (files) => {
const newImages = Array.from(files);
console.log('[AddProperty] handleImageUpload called with', newImages.length, 'files');
if (formData.images.length + newImages.length > 10) {
toast.error('يمكنك رفع 10 صور كحد أقصى');
@ -416,7 +414,6 @@ const handleMapClick = async (coords) => {
try {
const path = await uploadPicture(file);
setUploadedImagePaths(prev => [...prev, path]);
console.log('[AddProperty] Image uploaded:', path);
} catch (err) {
console.error('[AddProperty] Image upload failed:', err);
toast.error('فشل رفع الصورة: ' + file.name);
@ -675,9 +672,7 @@ const handleMapClick = async (coords) => {
price: parseFloat(formData.salePrice) || 0,
currencyId: selectedCurrencyId,
};
console.log('[AddProperty] Sale payload:', JSON.stringify(payload, null, 2));
const res = await addSaleProperty(payload);
console.log('[AddProperty] Sale API response:', res);
toast.success('تم إضافة عقار للبيع بنجاح!');
} else {
const rentTypeMap = { daily: RentType.DAILY, monthly: RentType.MONTHLY, both: RentType.MONTHLY };
@ -692,9 +687,7 @@ const handleMapClick = async (coords) => {
type: formData.furnished ? RentPropertyType.FURNISHED : RentPropertyType.UNFURNISHED,
allowedPaymentPeriod: formData.allowedPaymentPeriod || '',
};
console.log('[AddProperty] Rent payload:', JSON.stringify(payload, null, 2));
const res = await addRentProperty(payload);
console.log('[AddProperty] Rent API response:', res);
toast.success('تم إضافة عقار للإيجار بنجاح!');
}
setTimeout(() => {

View File

@ -1191,8 +1191,6 @@ export default function OwnerPropertiesPage() {
}
try {
console.log("[OwnerProperties] Fetching listings for user:", userId);
const [rentData, saleData] = await Promise.allSettled([
getMyRentListings(),
getMySaleListings(),
@ -1215,13 +1213,6 @@ export default function OwnerPropertiesPage() {
: []
: [];
console.log(
"[OwnerProperties] Rent:",
rentList.length,
"Sale:",
saleList.length,
);
const normalizeServices = (details) => {
const rawServices = details.services || {};
const serviceList = Array.isArray(rawServices)

View File

@ -459,9 +459,7 @@ export default function HomePage() {
lng: p.location.lng,
address: p.location.address
}))}
onPropertyClick={(property) => {
console.log('Property clicked:', property);
}}
onPropertyClick={() => {}}
/>
) : (
<div className="h-[400px] flex flex-col items-center justify-center bg-gray-50">

View File

@ -105,7 +105,6 @@
// }
'use client';
import { useEffect, useState } from 'react';
@ -118,9 +117,108 @@ const ENDPOINTS = {
en: '/Configuration/GetENPrivacyPolicy',
};
function tryParseJson(value) {
let current = value;
for (let i = 0; i < 3; i += 1) {
if (typeof current !== 'string') break;
const trimmed = current.trim();
if (!trimmed) return trimmed;
try {
current = JSON.parse(trimmed);
} catch {
return current;
}
}
return current;
}
function normalizeItem(item, index) {
if (!item || typeof item !== 'object') return null;
const title =
typeof item.title === 'string' ? item.title.trim() : '';
const description =
typeof item.description === 'string' ? item.description.trim() : '';
if (!title && !description) return null;
return {
id: index + 1,
title,
description,
};
}
function normalizePolicyContent(raw) {
const decoded = tryParseJson(raw);
if (Array.isArray(decoded)) {
const items = decoded.map((item, index) => normalizeItem(item, index)).filter(Boolean);
return {
items,
rawText: typeof raw === 'string' ? raw.trim() : '',
};
}
if (decoded && typeof decoded === 'object') {
const possibleArray = Array.isArray(decoded.items)
? decoded.items
: Array.isArray(decoded.data)
? decoded.data
: null;
if (possibleArray) {
const items = possibleArray.map((item, index) => normalizeItem(item, index)).filter(Boolean);
return {
items,
rawText: typeof raw === 'string' ? raw.trim() : '',
};
}
const singleItem = normalizeItem(decoded, 0);
if (singleItem) {
return {
items: [singleItem],
rawText: typeof raw === 'string' ? raw.trim() : '',
};
}
}
if (typeof decoded === 'string') {
const cleaned = decoded.replace(/\\r\\n/g, '\n').replace(/\\n/g, '\n').trim();
const matches = [...cleaned.matchAll(/"title"\s*:\s*"([^"]+)"\s*,\s*"description"\s*:\s*"([^"]+)"/g)];
if (matches.length > 0) {
return {
items: matches.map((match, index) => ({
id: index + 1,
title: match[1].trim(),
description: match[2].trim(),
})),
rawText: cleaned,
};
}
return {
items: [],
rawText: cleaned,
};
}
return {
items: [],
rawText: '',
};
}
export default function PrivacyPage() {
const [language, setLanguage] = useState('ar');
const [policyText, setPolicyText] = useState('');
const [policyItems, setPolicyItems] = useState([]);
const [rawPolicyText, setRawPolicyText] = useState('');
const [loading, setLoading] = useState(false);
const [error, setError] = useState('');
@ -146,13 +244,14 @@ export default function PrivacyPage() {
}
const text = await response.text();
const normalized = normalizePolicyContent(text);
console.log('API RESPONSE:', text);
setPolicyText(text.trim());
setPolicyItems(normalized.items);
setRawPolicyText(normalized.rawText);
} catch (err) {
if (err.name !== 'AbortError') {
setPolicyText('');
if (err?.name !== 'AbortError') {
setPolicyItems([]);
setRawPolicyText('');
setError(
language === 'ar'
? 'تعذر تحميل النص من الخادم.'
@ -207,9 +306,37 @@ export default function PrivacyPage() {
)}
<div className="rounded-2xl border border-gray-200 bg-white p-6 shadow-sm">
<div className="whitespace-pre-wrap leading-8 text-gray-800">
{policyText}
</div>
{policyItems.length > 0 ? (
<div className="space-y-4">
{policyItems.map((item) => (
<div
key={item.id}
className="rounded-2xl border border-gray-200 bg-gray-50 p-5"
>
{item.title && (
<div className="mb-3 flex items-start gap-3">
<div className="mt-0.5 flex h-8 w-8 shrink-0 items-center justify-center rounded-full bg-amber-100 text-sm font-bold text-amber-700">
{item.id}
</div>
<h3 className="text-base font-bold text-gray-900">
{item.title}
</h3>
</div>
)}
{item.description && (
<p className="text-sm leading-8 text-gray-700 whitespace-pre-wrap">
{item.description}
</p>
)}
</div>
))}
</div>
) : (
<div className="whitespace-pre-wrap leading-8 text-gray-800">
{rawPolicyText}
</div>
)}
</div>
</div>
</div>

View File

@ -1,29 +1,22 @@
'use client';
import { useState, useEffect, useRef } from 'react';
import { motion, AnimatePresence } from 'framer-motion';
import { useState, useEffect } from 'react';
import { motion } from 'framer-motion';
import { useRouter } from 'next/navigation';
import Link from 'next/link';
import Image from 'next/image';
import {
User,
Mail,
Phone,
MessageCircle,
Camera,
Save,
Check,
X,
CheckCircle,
AlertCircle,
ArrowLeft,
Building,
Home,
Calendar,
MapPin,
Edit,
Loader2,
Upload,
Check,
Pencil
} from 'lucide-react';
import toast, { Toaster } from 'react-hot-toast';
@ -34,9 +27,9 @@ export default function ProfilePage() {
const router = useRouter();
const [user, setUser] = useState(null);
const [isLoading, setIsLoading] = useState(true);
const [isSaving, setIsSaving] = useState(false);
const [editingField, setEditingField] = useState(null);
const [editingBio, setEditingBio] = useState(false);
const [bioDraft, setBioDraft] = useState('');
const [formData, setFormData] = useState({
name: '',
@ -48,20 +41,7 @@ export default function ProfilePage() {
joinedDate: ''
});
const [tempValues, setTempValues] = useState({});
const [avatar, setAvatar] = useState(null);
const [avatarPreview, setAvatarPreview] = useState('');
const [errors, setErrors] = useState({});
const fileInputRef = useRef(null);
const inputRefs = {
name: useRef(null),
email: useRef(null),
phone: useRef(null),
whatsapp: useRef(null),
location: useRef(null),
bio: useRef(null)
};
useEffect(() => {
const authUser = AuthService.getUser();
@ -74,15 +54,11 @@ export default function ProfilePage() {
role: AuthService.isOwner() ? 'owner' : 'customer',
};
setUser(userData);
console.log('[Profile] User from JWT:', userData);
// Fetch full profile from API using user ID (SID from JWT)
async function fetchProfile() {
try {
const fetchFn = userData.role === 'owner' ? getOwnerByUserId : getCustomerByUserId;
console.log('[Profile] Fetching profile via', userData.role === 'owner' ? 'Owner' : 'Customer', 'GetByUserId:', userData.id);
const profile = await fetchFn(userData.id);
console.log('[Profile] API profile:', profile);
if (profile) {
const profileData = {
@ -97,16 +73,15 @@ export default function ProfilePage() {
: new Date().toLocaleDateString('ar-SA', { month: 'long', year: 'numeric' }),
};
setFormData(profileData);
setTempValues(profileData);
setBioDraft(profileData.bio);
localStorage.setItem('userProfile', JSON.stringify(profileData));
setIsLoading(false);
return;
}
} catch (err) {
console.warn('[Profile] API fetch failed, falling back to JWT/localStorage:', err);
// Ignore API errors and fall back to local data.
}
// Fallback to JWT + localStorage
const savedProfile = localStorage.getItem('userProfile');
let profileData;
if (savedProfile) {
@ -123,7 +98,7 @@ export default function ProfilePage() {
};
}
setFormData(profileData);
setTempValues(profileData);
setBioDraft(profileData.bio || '');
setIsLoading(false);
}
@ -138,114 +113,22 @@ export default function ProfilePage() {
}
}, [router]);
useEffect(() => {
if (editingField && inputRefs[editingField]?.current) {
inputRefs[editingField].current.focus();
}
}, [editingField]);
const handleImageUpload = (file) => {
if (!file) return;
if (!file.type.startsWith('image/')) {
toast.error('الرجاء اختيار صورة صالحة');
return;
}
if (file.size > 2 * 1024 * 1024) {
toast.error('حجم الصورة يجب أن يكون أقل من 2 ميجابايت');
return;
}
const reader = new FileReader();
reader.onloadend = () => {
setAvatarPreview(reader.result);
localStorage.setItem('userAvatar', reader.result);
toast.success('تم تغيير الصورة بنجاح');
};
reader.readAsDataURL(file);
const startBioEditing = () => {
setBioDraft(formData.bio || '');
setEditingBio(true);
};
const validateField = (field, value) => {
if (field === 'email' && value && !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value)) {
return 'البريد الإلكتروني غير صالح';
}
if ((field === 'phone' || field === 'whatsapp') && value && !/^(09|05)[0-9]{8}$/.test(value)) {
return 'رقم الهاتف غير صالح (يجب أن يبدأ 09 أو 05)';
}
if (field === 'name' && !value?.trim()) {
return 'الاسم مطلوب';
}
return null;
const cancelBioEditing = () => {
setBioDraft(formData.bio || '');
setEditingBio(false);
};
const startEditing = (field) => {
setEditingField(field);
setTempValues(prev => ({ ...prev, [field]: formData[field] }));
setErrors(prev => ({ ...prev, [field]: null }));
};
const cancelEditing = () => {
setEditingField(null);
setTempValues({});
setErrors({});
};
const saveField = (field) => {
const value = tempValues[field];
const error = validateField(field, value);
if (error) {
setErrors(prev => ({ ...prev, [field]: error }));
return;
}
const updatedData = { ...formData, [field]: value };
const saveBio = () => {
const updatedData = { ...formData, bio: bioDraft };
setFormData(updatedData);
localStorage.setItem('userProfile', JSON.stringify(updatedData));
if (field === 'name') {
const updatedUser = { ...user, name: value };
setUser(updatedUser);
}
setEditingField(null);
setTempValues({});
toast.success(`تم تحديث ${getFieldLabel(field)} بنجاح`);
};
const handleKeyDown = (e, field) => {
if (e.key === 'Enter') {
saveField(field);
} else if (e.key === 'Escape') {
cancelEditing();
}
};
const getFieldLabel = (field) => {
const labels = {
name: 'الاسم',
email: 'البريد الإلكتروني',
phone: 'رقم الهاتف',
whatsapp: 'رقم الواتساب',
location: 'الموقع',
bio: 'نبذة عني'
};
return labels[field] || field;
};
const getFieldIcon = (field) => {
const icons = {
name: User,
email: Mail,
phone: Phone,
whatsapp: MessageCircle,
location: MapPin,
bio: User
};
const Icon = icons[field];
return Icon ? <Icon className="w-5 h-5 text-gray-400" /> : null;
setEditingBio(false);
toast.success('تم تحديث نبذة عني بنجاح');
};
const fadeInUp = {
@ -307,8 +190,7 @@ export default function ProfilePage() {
<div className="px-8 pb-8">
<div className="flex justify-center -mt-16 mb-6">
<div className="relative group">
<div className="w-32 h-32 rounded-full border-4 border-white bg-gradient-to-br from-amber-500 to-amber-600 flex items-center justify-center text-white text-4xl font-bold shadow-xl overflow-hidden cursor-pointer"
onClick={() => fileInputRef.current?.click()}>
<div className="w-32 h-32 rounded-full border-4 border-white bg-gradient-to-br from-amber-500 to-amber-600 flex items-center justify-center text-white text-4xl font-bold shadow-xl overflow-hidden">
{avatarPreview ? (
<img
src={avatarPreview}
@ -319,107 +201,17 @@ export default function ProfilePage() {
formData.name?.charAt(0).toUpperCase() || 'U'
)}
</div>
<button
onClick={() => fileInputRef.current?.click()}
className="absolute bottom-0 right-0 w-8 h-8 bg-amber-500 rounded-full flex items-center justify-center text-white hover:bg-amber-600 transition-colors shadow-lg"
title="تغيير الصورة"
>
<Camera className="w-4 h-4" />
</button>
<input
ref={fileInputRef}
type="file"
accept="image/*"
onChange={(e) => handleImageUpload(e.target.files?.[0])}
className="hidden"
/>
</div>
</div>
<div className="text-center mb-6">
<div className="flex items-center justify-center gap-2">
{editingField === 'name' ? (
<div className="flex items-center gap-2">
<input
ref={inputRefs.name}
type="text"
value={tempValues.name || ''}
onChange={(e) => setTempValues({...tempValues, name: e.target.value})}
onKeyDown={(e) => handleKeyDown(e, 'name')}
className="text-3xl font-bold text-center border-b-2 border-amber-500 outline-none px-2 py-1 w-64"
placeholder="الاسم الكامل"
/>
<button
onClick={() => saveField('name')}
className="p-2 bg-green-500 text-white rounded-full hover:bg-green-600 transition-colors"
title="حفظ"
>
<Check className="w-4 h-4" />
</button>
<button
onClick={cancelEditing}
className="p-2 bg-red-500 text-white rounded-full hover:bg-red-600 transition-colors"
title="إلغاء"
>
<X className="w-4 h-4" />
</button>
</div>
) : (
<>
<h1 className="text-3xl font-bold text-gray-900">{formData.name}</h1>
<button
onClick={() => startEditing('name')}
className="p-2 text-gray-400 hover:text-amber-500 transition-colors"
title="تعديل الاسم"
>
<Pencil className="w-4 h-4" />
</button>
</>
)}
<h1 className="text-3xl font-bold text-gray-900">{formData.name}</h1>
</div>
{errors.name && (
<p className="text-red-500 text-sm mt-1">{errors.name}</p>
)}
<div className="flex items-center justify-center gap-2 text-gray-500 mt-2">
<MapPin className="w-4 h-4" />
{editingField === 'location' ? (
<div className="flex items-center gap-2">
<input
ref={inputRefs.location}
type="text"
value={tempValues.location || ''}
onChange={(e) => setTempValues({...tempValues, location: e.target.value})}
onKeyDown={(e) => handleKeyDown(e, 'location')}
className="border-b border-amber-500 outline-none px-2 py-1"
placeholder="الموقع"
/>
<button
onClick={() => saveField('location')}
className="text-green-500 hover:text-green-600"
>
<Check className="w-4 h-4" />
</button>
<button
onClick={cancelEditing}
className="text-red-500 hover:text-red-600"
>
<X className="w-4 h-4" />
</button>
</div>
) : (
<>
<span>{formData.location || 'الموقع غير محدد'}</span>
<button
onClick={() => startEditing('location')}
className="text-gray-400 hover:text-amber-500 transition-colors"
>
<Pencil className="w-3 h-3" />
</button>
</>
)}
<span>{formData.location || 'الموقع غير محدد'}</span>
</div>
<div className="flex items-center justify-center gap-2 text-gray-500 mt-1">
@ -434,53 +226,11 @@ export default function ProfilePage() {
<label className="text-sm font-medium text-gray-600">
البريد الإلكتروني
</label>
{editingField !== 'email' && (
<button
onClick={() => startEditing('email')}
className="opacity-0 group-hover:opacity-100 transition-opacity text-gray-400 hover:text-amber-500"
>
<Pencil className="w-4 h-4" />
</button>
)}
</div>
{editingField === 'email' ? (
<div className="space-y-2">
<div className="flex gap-2">
<input
ref={inputRefs.email}
type="email"
value={tempValues.email || ''}
onChange={(e) => setTempValues({...tempValues, email: e.target.value})}
onKeyDown={(e) => handleKeyDown(e, 'email')}
className="flex-1 px-3 py-2 border rounded-lg focus:ring-2 focus:ring-amber-500 focus:border-transparent"
placeholder="example@domain.com"
/>
<button
onClick={() => saveField('email')}
className="p-2 bg-green-500 text-white rounded-lg hover:bg-green-600"
title="حفظ"
>
<Check className="w-4 h-4" />
</button>
<button
onClick={cancelEditing}
className="p-2 bg-red-500 text-white rounded-lg hover:bg-red-600"
title="إلغاء"
>
<X className="w-4 h-4" />
</button>
</div>
{errors.email && (
<p className="text-red-500 text-xs">{errors.email}</p>
)}
</div>
) : (
<div className="flex items-center gap-2 text-gray-900">
<Mail className="w-5 h-5 text-gray-400" />
<span>{formData.email}</span>
</div>
)}
<div className="flex items-center gap-2 text-gray-900">
<Mail className="w-5 h-5 text-gray-400" />
<span>{formData.email}</span>
</div>
</div>
<div className="bg-gray-50 p-4 rounded-xl group">
@ -488,51 +238,11 @@ export default function ProfilePage() {
<label className="text-sm font-medium text-gray-600">
رقم الهاتف
</label>
{editingField !== 'phone' && (
<button
onClick={() => startEditing('phone')}
className="opacity-0 group-hover:opacity-100 transition-opacity text-gray-400 hover:text-amber-500"
>
<Pencil className="w-4 h-4" />
</button>
)}
</div>
{editingField === 'phone' ? (
<div className="space-y-2">
<div className="flex gap-2">
<input
ref={inputRefs.phone}
type="tel"
value={tempValues.phone || ''}
onChange={(e) => setTempValues({...tempValues, phone: e.target.value})}
onKeyDown={(e) => handleKeyDown(e, 'phone')}
className="flex-1 px-3 py-2 border rounded-lg focus:ring-2 focus:ring-amber-500 focus:border-transparent"
placeholder="09XXXXXXXX"
/>
<button
onClick={() => saveField('phone')}
className="p-2 bg-green-500 text-white rounded-lg hover:bg-green-600"
>
<Check className="w-4 h-4" />
</button>
<button
onClick={cancelEditing}
className="p-2 bg-red-500 text-white rounded-lg hover:bg-red-600"
>
<X className="w-4 h-4" />
</button>
</div>
{errors.phone && (
<p className="text-red-500 text-xs">{errors.phone}</p>
)}
</div>
) : (
<div className="flex items-center gap-2 text-gray-900">
<Phone className="w-5 h-5 text-gray-400" />
<span>{formData.phone || 'غير محدد'}</span>
</div>
)}
<div className="flex items-center gap-2 text-gray-900">
<Phone className="w-5 h-5 text-gray-400" />
<span>{formData.phone || 'غير محدد'}</span>
</div>
</div>
<div className="bg-gray-50 p-4 rounded-xl group">
@ -540,51 +250,11 @@ export default function ProfilePage() {
<label className="text-sm font-medium text-gray-600">
رقم الواتساب
</label>
{editingField !== 'whatsapp' && (
<button
onClick={() => startEditing('whatsapp')}
className="opacity-0 group-hover:opacity-100 transition-opacity text-gray-400 hover:text-amber-500"
>
<Pencil className="w-4 h-4" />
</button>
)}
</div>
{editingField === 'whatsapp' ? (
<div className="space-y-2">
<div className="flex gap-2">
<input
ref={inputRefs.whatsapp}
type="tel"
value={tempValues.whatsapp || ''}
onChange={(e) => setTempValues({...tempValues, whatsapp: e.target.value})}
onKeyDown={(e) => handleKeyDown(e, 'whatsapp')}
className="flex-1 px-3 py-2 border rounded-lg focus:ring-2 focus:ring-amber-500 focus:border-transparent"
placeholder="09XXXXXXXX"
/>
<button
onClick={() => saveField('whatsapp')}
className="p-2 bg-green-500 text-white rounded-lg hover:bg-green-600"
>
<Check className="w-4 h-4" />
</button>
<button
onClick={cancelEditing}
className="p-2 bg-red-500 text-white rounded-lg hover:bg-red-600"
>
<X className="w-4 h-4" />
</button>
</div>
{errors.whatsapp && (
<p className="text-red-500 text-xs">{errors.whatsapp}</p>
)}
</div>
) : (
<div className="flex items-center gap-2 text-gray-900">
<MessageCircle className="w-5 h-5 text-gray-400" />
<span>{formData.whatsapp || 'غير محدد'}</span>
</div>
)}
<div className="flex items-center gap-2 text-gray-900">
<MessageCircle className="w-5 h-5 text-gray-400" />
<span>{formData.whatsapp || 'غير محدد'}</span>
</div>
</div>
<div className="bg-gray-50 p-4 rounded-xl">
@ -612,9 +282,9 @@ export default function ProfilePage() {
<label className="text-sm font-medium text-gray-600">
نبذة عني
</label>
{editingField !== 'bio' && (
{!editingBio && (
<button
onClick={() => startEditing('bio')}
onClick={startBioEditing}
className="opacity-0 group-hover:opacity-100 transition-opacity text-gray-400 hover:text-amber-500"
>
<Pencil className="w-4 h-4" />
@ -622,26 +292,26 @@ export default function ProfilePage() {
)}
</div>
{editingField === 'bio' ? (
{editingBio ? (
<div className="space-y-2">
<textarea
ref={inputRefs.bio}
value={tempValues.bio || ''}
onChange={(e) => setTempValues({...tempValues, bio: e.target.value})}
value={bioDraft}
onChange={(e) => setBioDraft(e.target.value)}
rows="4"
autoFocus
className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-amber-500 focus:border-transparent"
placeholder="اكتب نبذة عن نفسك..."
/>
<div className="flex justify-end gap-2">
<button
onClick={() => saveField('bio')}
onClick={saveBio}
className="px-4 py-2 bg-green-500 text-white rounded-lg hover:bg-green-600 flex items-center gap-2"
>
<Check className="w-4 h-4" />
حفظ
</button>
<button
onClick={cancelEditing}
onClick={cancelBioEditing}
className="px-4 py-2 bg-red-500 text-white rounded-lg hover:bg-red-600 flex items-center gap-2"
>
<X className="w-4 h-4" />

View File

@ -616,10 +616,9 @@
// app/register/tenant/page.js
'use client';
import { useState, useMemo } from 'react';
import { useState, useEffect, useMemo } from 'react';
import { motion, AnimatePresence } from 'framer-motion';
import { useRouter } from 'next/navigation';
import Link from 'next/link';
@ -697,7 +696,6 @@ export default function TenantRegisterPage() {
}
setIsLoading(true);
console.log('[CustomerRegister] Submitting customer registration...');
const payload = {
firstName: formData.firstName,
@ -714,7 +712,6 @@ export default function TenantRegisterPage() {
try {
// لا توجد صور للمستأجر
const res = await addCustomer(payload, null, null);
console.log('[CustomerRegister] addCustomer response:', res);
if (res.status === 200 || res.ok) {
const tempToken = res.data;

View File

@ -252,9 +252,6 @@
'use client';
import { useState } from 'react';
@ -371,7 +368,6 @@ export default function SettingsPage() {
throw new Error(responseText || `فشل إرسال البلاغ (HTTP ${res.status})`);
}
console.log('Send report success:', responseText);
toast.success(responseText || 'تم إرسال البلاغ بنجاح');
setShowReportDialog(false);
setReportSubject('');

View File

@ -373,99 +373,20 @@
// // ─── Booking/Reservation Management ───
// export async function confirmDepositPayment(bookingId) {
// return apiFetch('/Reservations/ConfirmDepositPayment', {
// method: 'POST',
// body: JSON.stringify({ bookingId }),
// });
// }
// export async function adminConfirmDeposit(reservationId, adminId, comment = null) {
// const token = AuthService.getToken();
// const endpoint = `${API_BASE}/Reservations/AdminConfirmDeposit/admin-confirm-deposit`;
// const normalizedComment =
// typeof comment === 'string' && comment.trim()
// ? comment.trim()
// : null;
// const payload = {
// reservationId,
// adminId,
// comment: normalizedComment,
// };
// console.log('[API] AdminConfirmDeposit request', {
// method: 'PUT',
// endpoint,
// payload,
// adminIdSource: 'jwt-user-id',
// hasToken: Boolean(token),
// tokenPreview: token ? `${token.slice(0, 18)}...${token.slice(-8)}` : null,
// });
// const res = await fetch(endpoint, {
// method: 'PUT',
// headers: {
// 'Content-Type': 'application/json',
// ...(token && { Authorization: `Bearer ${token}` }),
// },
// body: JSON.stringify(payload),
// });
// const text = await res.text();
// let data = null;
// console.log('[API] AdminConfirmDeposit raw response', {
// status: res.status,
// ok: res.ok,
// endpoint,
// rawText: text,
// });
// try {
// data = text ? JSON.parse(text) : null;
// if (data && typeof data === 'object' && 'data' in data) {
// data = data.data;
// }
// } catch {
// data = text;
// }
// const message = typeof data === 'object' && data?.message ? data.message : null;
// console.log('[API] AdminConfirmDeposit parsed response', {
// status: res.status,
// ok: res.ok,
// message,
// data,
// });
// return { status: res.status, data, ok: res.ok, message };
// }
// export async function updateBookingStatus(bookingId, status) {
// return apiFetch('/Reservations/UpdateStatus', {
// method: 'PUT',
// body: JSON.stringify({ bookingId, status }),
// });
// }
import AuthService from '../services/AuthService';
const API_BASE = process.env.NEXT_PUBLIC_API_URL || 'https://45.93.137.91.nip.io/api';
const REPORT_API_BASE = process.env.NEXT_PUBLIC_REPORT_API_URL || 'http://45.93.137.91/api';
import AuthService from "../services/AuthService";
const API_BASE =
process.env.NEXT_PUBLIC_API_URL || "https://45.93.137.91.nip.io/api";
const REPORT_API_BASE =
process.env.NEXT_PUBLIC_REPORT_API_URL || "http://45.93.137.91/api";
function isFormData(value) {
return typeof FormData !== 'undefined' && value instanceof FormData;
return typeof FormData !== "undefined" && value instanceof FormData;
}
class ApiBlockedError extends Error {
constructor(message = 'Your account is blocked') {
constructor(message = "Your account is blocked") {
super(message);
this.name = 'ApiBlockedError';
this.name = "ApiBlockedError";
this.status = 451;
}
}
@ -475,8 +396,11 @@ export function isApiBlockedError(error) {
}
function redirectToBlockedPage() {
if (typeof window !== 'undefined' && window.location.pathname !== '/blocked') {
window.location.replace('/blocked');
if (
typeof window !== "undefined" &&
window.location.pathname !== "/blocked"
) {
window.location.replace("/blocked");
}
}
@ -488,7 +412,7 @@ function assertNotBlocked(response) {
}
function buildApiUrl(base, endpoint) {
return `${base.replace(/\/$/, '')}${endpoint.startsWith('/') ? endpoint : `/${endpoint}`}`;
return `${base.replace(/\/$/, "")}${endpoint.startsWith("/") ? endpoint : `/${endpoint}`}`;
}
/**
@ -505,32 +429,37 @@ async function apiFetch(endpoint, options = {}) {
const hasBody = options.body != null;
const bodyIsFormData = isFormData(options.body);
if (hasBody && !bodyIsFormData && !headers['Content-Type'] && !headers['content-type']) {
headers['Content-Type'] = 'application/json';
if (
hasBody &&
!bodyIsFormData &&
!headers["Content-Type"] &&
!headers["content-type"]
) {
headers["Content-Type"] = "application/json";
}
const url = `${API_BASE}${endpoint}`;
console.log('API Request:', url);
console.log('API Method:', options.method || 'GET');
console.log('API Body:', hasBody ? options.body : null);
console.log("API Request:", url);
console.log("API Method:", options.method || "GET");
console.log("API Body:", hasBody ? options.body : null);
const res = await fetch(url, {
...options,
headers,
body:
hasBody && !bodyIsFormData && typeof options.body !== 'string'
hasBody && !bodyIsFormData && typeof options.body !== "string"
? JSON.stringify(options.body)
: options.body,
});
console.log('API Response Status:', res.status);
console.log('API Response OK:', res.ok);
console.log("API Response Status:", res.status);
console.log("API Response OK:", res.ok);
assertNotBlocked(res);
if (!res.ok && res.status !== 206) {
const text = await res.text().catch(() => '');
console.error('API Error Response:', text || res.statusText);
const text = await res.text().catch(() => "");
console.error("API Error Response:", text || res.statusText);
throw new Error(`API ${res.status}: ${text || res.statusText}`);
}
@ -539,7 +468,7 @@ async function apiFetch(endpoint, options = {}) {
try {
const json = JSON.parse(text);
if (json && typeof json === 'object' && 'data' in json) {
if (json && typeof json === "object" && "data" in json) {
return json.data;
}
return json;
@ -556,15 +485,15 @@ async function authFetch(endpoint, body, token = null) {
const bodyIsFormData = isFormData(body);
if (!bodyIsFormData) {
headers['Content-Type'] = 'application/json';
headers["Content-Type"] = "application/json";
}
if (token) {
headers['Authorization'] = `Bearer ${token}`;
headers["Authorization"] = `Bearer ${token}`;
}
const res = await fetch(`${API_BASE}${endpoint}`, {
method: 'POST',
method: "POST",
headers,
body: bodyIsFormData ? body : JSON.stringify(body),
});
@ -576,23 +505,29 @@ async function authFetch(endpoint, body, token = null) {
try {
data = text ? JSON.parse(text) : null;
if (data && typeof data === 'object' && 'data' in data) {
if (data && typeof data === "object" && "data" in data) {
data = data.data;
}
} catch {
data = text;
}
const message = typeof data === 'object' && data?.message ? data.message : null;
const message =
typeof data === "object" && data?.message ? data.message : null;
return { status: res.status, data, ok: res.ok || res.status === 206, message };
return {
status: res.status,
data,
ok: res.ok || res.status === 206,
message,
};
}
async function reportFetch(endpoint, body) {
const res = await fetch(buildApiUrl(REPORT_API_BASE, endpoint), {
method: 'POST',
method: "POST",
headers: {
'Content-Type': 'application/json',
"Content-Type": "application/json",
},
body: JSON.stringify(body),
});
@ -604,22 +539,28 @@ async function reportFetch(endpoint, body) {
try {
data = text ? JSON.parse(text) : null;
if (data && typeof data === 'object' && 'data' in data) {
if (data && typeof data === "object" && "data" in data) {
data = data.data;
}
} catch {
data = text;
}
const message = typeof data === 'object' && data?.message ? data.message : null;
const message =
typeof data === "object" && data?.message ? data.message : null;
return { status: res.status, data, ok: res.ok || res.status === 206, message };
return {
status: res.status,
data,
ok: res.ok || res.status === 206,
message,
};
}
// ─── Rent Properties ───
export async function getRentProperties() {
return apiFetch('/RentProperties/GetRentProperties');
return apiFetch("/RentProperties/GetRentProperties");
}
export async function getRentProperty(id) {
@ -628,20 +569,22 @@ export async function getRentProperty(id) {
export async function getRentPropertyLocations(params = {}) {
const qs = new URLSearchParams();
if (params.maxOffset != null) qs.set('maxOffset', params.maxOffset);
if (params.minOffset != null) qs.set('minOffset', params.minOffset);
if (params.maxOffset != null) qs.set("maxOffset", params.maxOffset);
if (params.minOffset != null) qs.set("minOffset", params.minOffset);
const query = qs.toString();
return apiFetch(`/RentProperties/GetRentPropertiesLocations${query ? `?${query}` : ''}`);
return apiFetch(
`/RentProperties/GetRentPropertiesLocations${query ? `?${query}` : ""}`,
);
}
// ─── Sale Properties ───
export async function getSaleProperties() {
return apiFetch('/SaleProperties/GetSaleProperties');
return apiFetch("/SaleProperties/GetSaleProperties");
}
export async function getSaleProperty(id) {
const items = await apiFetch('/SaleProperties/GetSaleProperties');
const items = await apiFetch("/SaleProperties/GetSaleProperties");
if (!Array.isArray(items)) return items;
return items.find((p) => p.id == id) || items[0];
}
@ -655,7 +598,7 @@ export async function getProperty(id) {
// ─── Recommendations ───
export async function getRecommendations() {
return apiFetch('/Recommendations/GetRecommendations');
return apiFetch("/Recommendations/GetRecommendations");
}
export async function getTopRecommendations(count = 10) {
@ -664,36 +607,46 @@ export async function getTopRecommendations(count = 10) {
// ─── Reservations ───
export async function getAvailableDateRanges(propertyId, fromDate = null, toDate = null) {
export async function getAvailableDateRanges(
propertyId,
fromDate = null,
toDate = null,
) {
const qs = new URLSearchParams();
if (fromDate) qs.set('fromDate', fromDate);
if (toDate) qs.set('toDate', toDate);
if (fromDate) qs.set("fromDate", fromDate);
if (toDate) qs.set("toDate", toDate);
const query = qs.toString();
return apiFetch(
`/Reservations/GetAvailableDates/available/${propertyId}${query ? `?${query}` : ''}`
`/Reservations/GetAvailableDates/available/${propertyId}${query ? `?${query}` : ""}`,
);
}
export async function getReservations() {
return apiFetch('/Reservations/GetAllReservations');
return apiFetch("/Reservations/GetAllReservations");
}
export async function getReservation(id) {
return apiFetch(`/Reservations/GetReservation?id=${id}`);
}
export async function checkAvailability(propertyId, fromDate = null, toDate = null) {
export async function checkAvailability(
propertyId,
fromDate = null,
toDate = null,
) {
const qs = new URLSearchParams();
if (fromDate) qs.set('fromDate', fromDate);
if (toDate) qs.set('toDate', toDate);
if (fromDate) qs.set("fromDate", fromDate);
if (toDate) qs.set("toDate", toDate);
const query = qs.toString();
return apiFetch(`/Reservations/GetAvailable/${propertyId}${query ? `?${query}` : ''}`);
return apiFetch(
`/Reservations/GetAvailable/${propertyId}${query ? `?${query}` : ""}`,
);
}
export async function bookReservation(propertyInfoId, startDate, endDate) {
return apiFetch('/Reservations/BookReservation/book', {
method: 'POST',
return apiFetch("/Reservations/BookReservation/book", {
method: "POST",
body: {
propertyInfoId,
startDate,
@ -705,11 +658,11 @@ export async function bookReservation(propertyInfoId, startDate, endDate) {
// ─── Terms ───
export async function getARTerms() {
return apiFetch('/Configuration/GetARTerms');
return apiFetch("/Configuration/GetARTerms");
}
export async function getENTerms() {
return apiFetch('/Configuration/GetENTerms');
return apiFetch("/Configuration/GetENTerms");
}
// ─── Profile ───
@ -725,39 +678,39 @@ export async function getOwnerByUserId(userId) {
// ─── Properties ───
export async function getMyRentListings() {
return apiFetch('/RentProperties/GetMyRentListings');
return apiFetch("/RentProperties/GetMyRentListings");
}
export async function addRentProperty(data) {
return apiFetch('/RentProperties/AddRentProperty', {
method: 'POST',
return apiFetch("/RentProperties/AddRentProperty", {
method: "POST",
body: data,
});
}
export async function editRentProperty(id, data) {
return apiFetch(`/RentProperties/EditRentProperty/${id}`, {
method: 'PUT',
method: "PUT",
body: data,
});
}
export async function editSaleProperty(id, data) {
return apiFetch(`/SaleProperties/EditSaleProperty/${id}`, {
method: 'PUT',
method: "PUT",
body: data,
});
}
export async function addSaleProperty(data) {
return apiFetch('/SaleProperties/AddSaleProperty', {
method: 'POST',
return apiFetch("/SaleProperties/AddSaleProperty", {
method: "POST",
body: data,
});
}
export async function getMySaleListings() {
return apiFetch('/SaleProperties/GetMySaleListings');
return apiFetch("/SaleProperties/GetMySaleListings");
}
export async function getSalePropertyById(id) {
@ -766,14 +719,14 @@ export async function getSalePropertyById(id) {
export async function updateRentPropertyStatus(id, status) {
return apiFetch(`/RentProperties/UpdateStatus/${id}`, {
method: 'PUT',
method: "PUT",
body: { status },
});
}
export async function updateSalePropertyStatus(id, status) {
return apiFetch(`/SaleProperties/UpdateStatus/${id}`, {
method: 'PUT',
method: "PUT",
body: { status },
});
}
@ -781,19 +734,19 @@ export async function updateSalePropertyStatus(id, status) {
// ─── Currencies ───
export async function getCurrencies() {
return apiFetch('/Currency/GetAll');
return apiFetch("/Currency/GetAll");
}
// ─── Files ───
export async function uploadPicture(file) {
const formData = new FormData();
formData.append('image', file);
formData.append("image", file);
const token = AuthService.getToken();
const res = await fetch(`${API_BASE}/Files/UploadPicture`, {
method: 'POST',
method: "POST",
headers: {
...(token && { Authorization: `Bearer ${token}` }),
},
@ -820,7 +773,7 @@ async function multipartAuthFetch(endpoint, formData) {
const token = AuthService.getToken();
const res = await fetch(`${API_BASE}${endpoint}`, {
method: 'POST',
method: "POST",
headers: {
...(token && { Authorization: `Bearer ${token}` }),
},
@ -834,100 +787,129 @@ async function multipartAuthFetch(endpoint, formData) {
try {
data = text ? JSON.parse(text) : null;
if (data && typeof data === 'object' && 'data' in data) {
if (data && typeof data === "object" && "data" in data) {
data = data.data;
}
} catch {
data = text;
}
return { status: res.status, data, ok: res.ok || res.status === 206, message: data?.message };
return {
status: res.status,
data,
ok: res.ok || res.status === 206,
message: data?.message,
};
}
export async function addOwner(data, frontImage = null, backImage = null, licenseImage = null) {
export async function addOwner(
data,
frontImage = null,
backImage = null,
licenseImage = null,
) {
const formData = new FormData();
formData.append('FirstName', data.firstName || data.FirstName || '');
formData.append('LastName', data.lastName || data.LastName || '');
formData.append('Email', data.email || data.Email || '');
formData.append("FirstName", data.firstName || data.FirstName || "");
formData.append("LastName", data.lastName || data.LastName || "");
formData.append("Email", data.email || data.Email || "");
const phoneValue = data.phone || data.phoneNumber || data.Phone || data.PhoneNumber || '';
const phoneValue =
data.phone || data.phoneNumber || data.Phone || data.PhoneNumber || "";
const whatsappValue =
data.whatsAppNumber || data.whatsapp || data.WhatsAppNumber || data.WhatsApp || '';
data.whatsAppNumber ||
data.whatsapp ||
data.WhatsAppNumber ||
data.WhatsApp ||
"";
formData.append('PhoneNumber', phoneValue);
formData.append('Phone', phoneValue);
formData.append('WhatsAppNumber', whatsappValue);
formData.append("PhoneNumber", phoneValue);
formData.append("Phone", phoneValue);
formData.append("WhatsAppNumber", whatsappValue);
formData.append('NationalNumber', data.nationalNumber || data.NationalNumber || '');
formData.append('Password', data.password || data.Password || '');
formData.append('Type', String(data.type ?? data.ownerType ?? data.Type ?? 0));
formData.append('Language', String(data.language ?? data.Language ?? 1));
formData.append(
"NationalNumber",
data.nationalNumber || data.NationalNumber || "",
);
formData.append("Password", data.password || data.Password || "");
formData.append(
"Type",
String(data.type ?? data.ownerType ?? data.Type ?? 0),
);
formData.append("Language", String(data.language ?? data.Language ?? 1));
if (frontImage) formData.append('FrontIdCarImagePath', frontImage);
if (backImage) formData.append('RearIdCarImagePath', backImage);
if (licenseImage) formData.append('LicenseImagePath', licenseImage);
if (frontImage) formData.append("FrontIdCarImagePath", frontImage);
if (backImage) formData.append("RearIdCarImagePath", backImage);
if (licenseImage) formData.append("LicenseImagePath", licenseImage);
return multipartAuthFetch('/Owner/Add', formData);
return multipartAuthFetch("/Owner/Add", formData);
}
export async function addCustomer(data, frontImage = null, backImage = null) {
const formData = new FormData();
formData.append('FirstName', data.firstName || data.FirstName || '');
formData.append('LastName', data.lastName || data.LastName || '');
formData.append('Email', data.email || '');
formData.append('PhoneNumber', data.phoneNumber || '');
formData.append('WhatsAppNumber', data.whatsAppNumber || '');
formData.append('Phone', data.phone || '');
formData.append('NationalNumber', data.nationalNumber || '');
formData.append('Password', data.password || '');
formData.append('Type', String(data.customerType ?? data.Type ?? 0));
formData.append('Language', '0');
formData.append("FirstName", data.firstName || data.FirstName || "");
formData.append("LastName", data.lastName || data.LastName || "");
formData.append("Email", data.email || "");
formData.append("PhoneNumber", data.phoneNumber || "");
formData.append("WhatsAppNumber", data.whatsAppNumber || "");
formData.append("Phone", data.phone || "");
formData.append("NationalNumber", data.nationalNumber || "");
formData.append("Password", data.password || "");
formData.append("Type", String(data.customerType ?? data.Type ?? 0));
formData.append("Language", "0");
if (frontImage) formData.append('FrontIdCarImagePath', frontImage);
if (backImage) formData.append('RearIdCarImagePath', backImage);
if (frontImage) formData.append("FrontIdCarImagePath", frontImage);
if (backImage) formData.append("RearIdCarImagePath", backImage);
return multipartAuthFetch('/Customer/Add', formData);
return multipartAuthFetch("/Customer/Add", formData);
}
// ─── Auth: Login ───
export async function loginWithEmail(credential, password) {
return authFetch('/Auth/LogInWithEmail', {
return authFetch("/Auth/LogInWithEmail", {
credential,
password,
device: 0,
appVersion: '',
appVersion: "",
});
}
export async function loginWithPhone(credential, password) {
return authFetch('/Auth/LogInWithPhoneNumber', {
return authFetch("/Auth/LogInWithPhoneNumber", {
credential,
password,
device: 0,
appVersion: '',
appVersion: "",
});
}
// ─── Auth: OTP ───
export async function sendEmailOTP() {
return apiFetch('/Auth/SendEmailOTP', { method: 'POST' });
return apiFetch("/Auth/SendEmailOTP", { method: "POST" });
}
export async function sendPhoneOTP() {
return apiFetch('/Auth/SendPhoneNumberOTP', { method: 'POST' });
return apiFetch("/Auth/SendPhoneNumberOTP", { method: "POST" });
}
export async function verifyEmail(code) {
const token = AuthService.getToken();
return authFetch(`/Auth/VerifyEmail?code=${encodeURIComponent(code)}`, {}, token);
return authFetch(
`/Auth/VerifyEmail?code=${encodeURIComponent(code)}`,
{},
token,
);
}
export async function verifyPhone(code) {
const token = AuthService.getToken();
return authFetch(`/Auth/VerifyPhoneNumber?code=${encodeURIComponent(code)}`, {}, token);
return authFetch(
`/Auth/VerifyPhoneNumber?code=${encodeURIComponent(code)}`,
{},
token,
);
}
// ─── Helpers ───
@ -937,39 +919,83 @@ export function isEmail(value) {
}
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() {
return apiFetch('/FavoriteProperty/GetUserFavoriteProperties');
return apiFetch("/FavoriteProperty/GetUserFavoriteProperties");
}
export async function addFavoriteProperty(propId) {
return apiFetch(`/FavoriteProperty/Add?propId=${propId}`, { method: 'POST' });
return apiFetch(`/FavoriteProperty/Add?propId=${propId}`, { method: "POST" });
}
export async function removeFavoriteProperty(favePropId) {
return apiFetch(`/FavoriteProperty/Remove?favePropId=${favePropId}`, { method: 'DELETE' });
return apiFetch(`/FavoriteProperty/Remove?favePropId=${favePropId}`, {
method: "DELETE",
});
}
export async function getUserNotifications() {
return apiFetch('/Notifications/GetUserNotifications');
return apiFetch("/Notifications/GetUserNotifications");
}
// ─── Booking/Reservation Management ───
export async function confirmDepositPayment(bookingId) {
return apiFetch('/Reservations/ConfirmDepositPayment', {
method: 'POST',
return apiFetch("/Reservations/ConfirmDepositPayment", {
method: "POST",
body: { bookingId },
});
}
export async function adminConfirmDeposit(
reservationId,
adminId,
comment = null,
) {
const token = AuthService.getToken();
const endpoint = `${API_BASE}/Reservations/AdminConfirmDeposit/admin-confirm-deposit`;
const normalizedComment =
typeof comment === "string" && comment.trim() ? comment.trim() : null;
const payload = {
reservationId,
adminId,
comment: normalizedComment,
};
const res = await fetch(endpoint, {
method: "PUT",
headers: {
"Content-Type": "application/json",
...(token && { Authorization: `Bearer ${token}` }),
},
body: JSON.stringify(payload),
});
const text = await res.text();
let data = null;
try {
data = text ? JSON.parse(text) : null;
if (data && typeof data === "object" && "data" in data) {
data = data.data;
}
} catch {
data = text;
}
const message =
typeof data === "object" && data?.message ? data.message : null;
return { status: res.status, data, ok: res.ok, message };
}
export async function updateBookingStatus(bookingId, status) {
return apiFetch('/Reservations/UpdateStatus', {
method: 'PUT',
return apiFetch("/Reservations/UpdateStatus", {
method: "PUT",
body: { bookingId, status },
});
}
@ -977,31 +1003,31 @@ export async function updateBookingStatus(bookingId, status) {
// ─── Owner / Reservations ───
export async function getOwnerReservationRequests() {
return apiFetch('/Reservations/GetOwnerResevationRequests');
return apiFetch("/Reservations/GetOwnerResevationRequests");
}
export async function getOwnerReservationsByStatuses(filterStatuses) {
return apiFetch('/Reservations/GetAllReservationsByStateForOwner', {
method: 'POST',
return apiFetch("/Reservations/GetAllReservationsByStateForOwner", {
method: "POST",
body: { filterStatuses },
});
}
export async function getUserReservations() {
return apiFetch('/Reservations/GetUserResevations');
return apiFetch("/Reservations/GetUserResevations");
}
export async function ownerConfirmReservation(id) {
return apiFetch(`/Reservations/OwnerConfirmReservation/owner-confirm/${id}`, {
method: 'PUT',
method: "PUT",
});
}
// ─── Payments ───
export async function payDeposit(data) {
return apiFetch('/Reservations/PayDeposit/pay-deposit', {
method: 'POST',
return apiFetch("/Reservations/PayDeposit/pay-deposit", {
method: "POST",
body: data,
});
}
@ -1010,12 +1036,12 @@ export async function payDeposit(data) {
export async function getOwnerContactInformation(propertyInformationId) {
return apiFetch(
`/Owner/GetOwnerContactInformation?propertyInformationId=${propertyInformationId}`
`/Owner/GetOwnerContactInformation?propertyInformationId=${propertyInformationId}`,
);
}
export async function getOwnerStatistics() {
return apiFetch('/Statistics/GetOwnerStatistics');
return apiFetch("/Statistics/GetOwnerStatistics");
}
// ─── Agent Registration ───
@ -1024,7 +1050,7 @@ export async function registerRealEstateAgent(formData) {
const token = AuthService.getToken();
const res = await fetch(`${API_BASE}/RealEstateAgent/Add`, {
method: 'POST',
method: "POST",
headers: {
...(token && { Authorization: `Bearer ${token}` }),
},
@ -1038,7 +1064,7 @@ export async function registerRealEstateAgent(formData) {
try {
data = text ? JSON.parse(text) : null;
if (data && typeof data === 'object' && 'data' in data) data = data.data;
if (data && typeof data === "object" && "data" in data) data = data.data;
} catch {
data = text;
}
@ -1047,7 +1073,7 @@ export async function registerRealEstateAgent(formData) {
status: res.status,
data,
ok: res.ok || res.status === 206,
message: data?.message || (typeof data === 'string' ? data : null),
message: data?.message || (typeof data === "string" ? data : null),
};
}
@ -1057,23 +1083,25 @@ export async function changePassword(oldPassword, newPassword) {
return apiFetch(
`/User/ChangePassword?oldPassword=${encodeURIComponent(oldPassword)}&newPassword=${encodeURIComponent(newPassword)}`,
{
method: 'PUT',
}
method: "PUT",
},
);
}
// ─── Forget Password (OTP flow) ───
export async function requestForgetPasswordOtp(email) {
return apiFetch(`/User/ForgetPassword?email=${encodeURIComponent(email)}`, { method: 'POST' });
return apiFetch(`/User/ForgetPassword?email=${encodeURIComponent(email)}`, {
method: "POST",
});
}
export async function verifyForgetPasswordOtp(email, code, newPassword) {
return apiFetch(
`/User/VerifyForgetPasswordOTP?email=${encodeURIComponent(email)}&code=${encodeURIComponent(code)}&newPassword=${encodeURIComponent(newPassword)}`,
{
method: 'POST',
}
method: "POST",
},
);
}
@ -1086,16 +1114,19 @@ export async function resetPassword(token) {
// ─── Delete Account ───
export async function deleteMyAccount(password) {
return apiFetch(`/User/DeleteMyAccount?password=${encodeURIComponent(password)}`, {
method: 'DELETE',
});
return apiFetch(
`/User/DeleteMyAccount?password=${encodeURIComponent(password)}`,
{
method: "DELETE",
},
);
}
// ─── Set FCM Token ───
export async function setFCMToken(token, deviceType = 2) {
return apiFetch('/User/SetFCMToken', {
method: 'POST',
return apiFetch("/User/SetFCMToken", {
method: "POST",
body: { token, deviceType },
});
}
@ -1105,52 +1136,54 @@ export async function setFCMToken(token, deviceType = 2) {
export async function filterRentProperties(params = {}) {
const qs = new URLSearchParams();
Object.entries(params).forEach(([k, v]) => {
if (v != null && v !== '') qs.set(k, v);
if (v != null && v !== "") qs.set(k, v);
});
const query = qs.toString();
return apiFetch(`/RentProperties/FilterRentProperties${query ? `?${query}` : ''}`);
return apiFetch(
`/RentProperties/FilterRentProperties${query ? `?${query}` : ""}`,
);
}
// ─── Reports ───
export async function sendGeneralReport(subject, reportBody) {
return reportFetch('/Reports/SendGeneralReport', {
return reportFetch("/Reports/SendGeneralReport", {
subject,
body: reportBody,
});
}
export async function submitReport(subject, body) {
return apiFetch('/Reports/SendGeneralReport', {
method: 'POST',
return apiFetch("/Reports/SendGeneralReport", {
method: "POST",
body: { subject, body },
});
}
export async function submitReservationReport(data) {
return apiFetch('/ReservationReports', {
method: 'POST',
return apiFetch("/ReservationReports", {
method: "POST",
body: data,
});
}
export async function updateReservationReport(id, data) {
return apiFetch(`/ReservationReports/${id}`, {
method: 'PUT',
method: "PUT",
body: data,
});
}
export async function submitSaleReport(data) {
return apiFetch('/SaleReports', {
method: 'POST',
return apiFetch("/SaleReports", {
method: "POST",
body: data,
});
}
export async function updateSaleReport(id, data) {
return apiFetch(`/SaleReports/${id}`, {
method: 'PUT',
method: "PUT",
body: data,
});
}
@ -1158,8 +1191,8 @@ export async function updateSaleReport(id, data) {
// ─── Terms (Add or Update) ───
export async function addOrUpdateTerms(terms) {
return apiFetch('/Terms/AddOrUpdateTerms', {
method: 'POST',
return apiFetch("/Terms/AddOrUpdateTerms", {
method: "POST",
body: terms,
});
}