diff --git a/app/register/agent/page.js b/app/register/agent/page.js
index c5e76a9..59f3f60 100644
--- a/app/register/agent/page.js
+++ b/app/register/agent/page.js
@@ -1,5 +1,3 @@
-// app/register/agent/page.js
-
'use client';
import { useState, useRef, useMemo } from 'react';
@@ -9,11 +7,17 @@ import Link from 'next/link';
import Image from 'next/image';
import {
User, Mail, Phone, Lock, Eye, EyeOff, MessageCircle,
- Camera, X, CheckCircle, XCircle, ArrowLeft, Building,
- Loader2, Shield, KeyRound, MapPin, FileText, BadgeCheck, Briefcase
+ Camera, X, CheckCircle, XCircle, ArrowLeft,
+ Loader2, Shield, KeyRound, MapPin, FileText,
+ BadgeCheck, Briefcase, Calendar
} from 'lucide-react';
import toast, { Toaster } from 'react-hot-toast';
-import { registerRealEstateAgent, loginWithEmail, sendEmailOTP, verifyEmail } from '../../utils/api';
+import {
+ registerRealEstateAgent,
+ loginWithEmail,
+ sendEmailOTP,
+ verifyEmail
+} from '../../utils/api';
import AuthService from '../../services/AuthService';
export default function AgentRegisterPage() {
@@ -38,69 +42,97 @@ export default function AgentRegisterPage() {
agreeTerms: false
});
- const [idImages, setIdImages] = useState({ front: null, back: null, license: null });
- const [idImagePreviews, setIdImagePreviews] = useState({ front: '', back: '', license: '' });
+ const [idImages, setIdImages] = useState({
+ front: null,
+ back: null,
+ license: null
+ });
+
+ const [idImagePreviews, setIdImagePreviews] = useState({
+ front: '',
+ back: '',
+ license: ''
+ });
+
const [otpCode, setOtpCode] = useState('');
const [errors, setErrors] = useState({});
const fileInputFrontRef = useRef(null);
const fileInputBackRef = useRef(null);
- const fileInputLicenseRef = useRef(null);
const fileInputLicenseStep3Ref = useRef(null);
- const handleImageUpload = (side, file) => {
- if (!file) return;
- if (!file.type.startsWith('image/')) {
- toast.error('الرجاء اختيار صورة صالحة');
- return;
- }
- if (file.size > 5 * 1024 * 1024) {
- toast.error('حجم الصورة يجب أن يكون أقل من 5 ميجابايت');
- return;
- }
- const reader = new FileReader();
- reader.onloadend = () => {
- setIdImagePreviews(prev => ({ ...prev, [side]: reader.result }));
- };
- reader.readAsDataURL(file);
- setIdImages(prev => ({ ...prev, [side]: file }));
- toast.success('تم رفع الصورة بنجاح', { style: { background: '#dcfce7', color: '#166534' } });
- };
-
const validateEmail = (email) => /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email);
const validatePhone = (phone) => /^(09|05)[0-9]{8}$/.test(phone);
+ const handleImageUpload = (side, file) => {
+ if (!file) return;
+
+ if (!file.type.startsWith('image/')) {
+ toast.error('الرجاء اختيار صورة صالحة');
+ return;
+ }
+
+ if (file.size > 5 * 1024 * 1024) {
+ toast.error('حجم الصورة يجب أن يكون أقل من 5 ميجابايت');
+ return;
+ }
+
+ const reader = new FileReader();
+ reader.onloadend = () => {
+ setIdImagePreviews((prev) => ({ ...prev, [side]: reader.result }));
+ };
+ reader.readAsDataURL(file);
+ setIdImages((prev) => ({ ...prev, [side]: file }));
+
+ toast.success('تم رفع الصورة بنجاح', {
+ style: { background: '#dcfce7', color: '#166534' }
+ });
+ };
+
const validateStep1 = () => {
const newErrors = {};
+
if (!formData.firstName) newErrors.firstName = 'الاسم الأول مطلوب';
if (!formData.lastName) newErrors.lastName = 'اسم العائلة مطلوب';
+
if (!formData.email) newErrors.email = 'البريد الإلكتروني مطلوب';
else if (!validateEmail(formData.email)) newErrors.email = 'البريد الإلكتروني غير صالح';
+
if (!formData.phone) newErrors.phone = 'رقم الهاتف مطلوب';
else if (!validatePhone(formData.phone)) newErrors.phone = 'رقم الهاتف غير صالح (يجب أن يبدأ 09 أو 05)';
+
if (!formData.password) newErrors.password = 'كلمة المرور مطلوبة';
else if (formData.password.length < 6) newErrors.password = 'كلمة المرور يجب أن تكون 6 أحرف على الأقل';
- if (formData.password !== formData.confirmPassword) newErrors.confirmPassword = 'كلمات المرور غير متطابقة';
+
+ if (!formData.confirmPassword) newErrors.confirmPassword = 'تأكيد كلمة المرور مطلوب';
+ else if (formData.password !== formData.confirmPassword) newErrors.confirmPassword = 'كلمات المرور غير متطابقة';
+
setErrors(newErrors);
return Object.keys(newErrors).length === 0;
};
const validateStep2 = () => {
const newErrors = {};
+
if (!formData.nationalNumber) newErrors.nationalNumber = 'الرقم الوطني مطلوب';
+
if (!formData.whatsapp) newErrors.whatsapp = 'رقم الواتساب مطلوب';
else if (!validatePhone(formData.whatsapp)) newErrors.whatsapp = 'رقم الواتساب غير صالح (يجب أن يبدأ 09 أو 05)';
+
if (!idImages.front) newErrors.front = 'صورة الوجه الأمامي للهوية مطلوبة';
if (!idImages.back) newErrors.back = 'صورة الوجه الخلفي للهوية مطلوبة';
+
setErrors(newErrors);
return Object.keys(newErrors).length === 0;
};
const validateStep3 = () => {
const newErrors = {};
+
if (!formData.agencyAddress) newErrors.agencyAddress = 'عنوان الوكالة مطلوب';
if (!formData.licenseNumber) newErrors.licenseNumber = 'رقم الترخيص مطلوب';
if (!idImages.license) newErrors.license = 'صورة الترخيص مطلوبة';
+
setErrors(newErrors);
return Object.keys(newErrors).length === 0;
};
@@ -120,8 +152,34 @@ export default function AgentRegisterPage() {
}
};
+ const buildFormData = () => {
+ const fd = new FormData();
+
+ // مطابق للـ API
+ fd.append('Address', formData.agencyAddress);
+ fd.append('LicenseNumber', formData.licenseNumber);
+
+ fd.append('Owner.FirstName', formData.firstName);
+ fd.append('Owner.LastName', formData.lastName);
+ fd.append('Owner.Email', formData.email);
+ fd.append('Owner.Password', formData.password);
+ fd.append('Owner.PhoneNumber', formData.phone);
+ fd.append('Owner.Phone', formData.phone);
+ fd.append('Owner.NationalNumber', formData.nationalNumber);
+ fd.append('Owner.WhatsAppNumber', formData.whatsapp);
+ fd.append('Owner.Language', '0');
+
+ // مطابق لأسماء الحقول الظاهرة في swagger
+ if (idImages.front) fd.append('FrontIdCarImage', idImages.front);
+ if (idImages.back) fd.append('RearIdCarImage', idImages.back);
+ if (idImages.license) fd.append('license', idImages.license);
+
+ return fd;
+ };
+
const handleSubmit = async (e) => {
e.preventDefault();
+
if (!formData.agreeTerms) {
toast.error('يجب الموافقة على الشروط والأحكام');
return;
@@ -129,48 +187,43 @@ export default function AgentRegisterPage() {
setIsLoading(true);
try {
- const fd = new FormData();
- fd.append('FirstName', formData.firstName);
- fd.append('LastName', formData.lastName);
- fd.append('Email', formData.email);
- fd.append('PhoneNumber', formData.phone);
- fd.append('WhatsAppNumber', formData.whatsapp);
- fd.append('NationalNumber', formData.nationalNumber);
- fd.append('Password', formData.password);
- fd.append('AgencyAddress', formData.agencyAddress);
- fd.append('LicenseNumber', formData.licenseNumber);
- fd.append('Language', '0');
-
- if (idImages.front) fd.append('FrontIdCarImagePath', idImages.front);
- if (idImages.back) fd.append('RearIdCarImagePath', idImages.back);
- if (idImages.license) fd.append('LicenseImagePath', idImages.license);
+ const fd = buildFormData();
const res = await registerRealEstateAgent(fd);
- if (res.ok || res.status === 200) {
- const tempToken = res.data;
+
+ if (res?.ok || res?.status === 200) {
+ const tempToken = res?.data;
if (tempToken) AuthService.addToken(tempToken);
- toast.success(res.message || 'تم إنشاء الحساب! يرجى التحقق من بريدك الإلكتروني', { duration: 4000 });
+
+ toast.success(res?.message || 'تم إنشاء الحساب! يرجى التحقق من بريدك الإلكتروني', {
+ duration: 4000
+ });
const loginRes = await loginWithEmail(formData.email, formData.password);
- if (loginRes.status === 206) {
- const otpToken = loginRes.data;
+
+ if (loginRes?.status === 206) {
+ const otpToken = loginRes?.data;
if (otpToken) AuthService.addToken(otpToken);
- toast(loginRes.message || 'تم إرسال رمز التحقق إلى بريدك الإلكتروني', { icon: '📧' });
+
+ toast(loginRes?.message || 'تم إرسال رمز التحقق إلى بريدك الإلكتروني', {
+ icon: '📧'
+ });
setShowOtpModal(true);
- } else if (loginRes.status === 200) {
- const loginToken = loginRes.data;
+ } else if (loginRes?.status === 200) {
+ const loginToken = loginRes?.data;
if (loginToken) AuthService.addToken(loginToken);
- toast.success(loginRes.message || 'تم تسجيل الدخول بنجاح!');
+
+ toast.success(loginRes?.message || 'تم تسجيل الدخول بنجاح!');
router.push('/');
} else {
toast.success('تم إنشاء الحساب بنجاح! يرجى تسجيل الدخول');
setTimeout(() => router.push('/login'), 1500);
}
} else {
- toast.error(res.message || res.data?.message || 'فشل في إنشاء الحساب');
+ toast.error(res?.message || res?.data?.message || 'فشل في إنشاء الحساب');
}
} catch (err) {
- toast.error(err.message || 'حدث خطأ أثناء التسجيل');
+ toast.error(err?.message || 'حدث خطأ أثناء التسجيل');
} finally {
setIsLoading(false);
}
@@ -181,19 +234,21 @@ export default function AgentRegisterPage() {
toast.error('يرجى إدخال رمز التحقق');
return;
}
+
setIsLoading(true);
try {
const res = await verifyEmail(otpCode);
- if (res.status === 200) {
+
+ if (res?.status === 200) {
AuthService.deleteToken();
- toast.success(res.message || 'تم التحقق من البريد الإلكتروني بنجاح!');
+ toast.success(res?.message || 'تم التحقق من البريد الإلكتروني بنجاح!');
setShowOtpModal(false);
setTimeout(() => router.push('/login'), 1500);
} else {
- toast.error(res.message || res.data?.message || 'رمز التحقق غير صحيح');
+ toast.error(res?.message || res?.data?.message || 'رمز التحقق غير صحيح');
}
} catch (err) {
- toast.error(err.message || 'حدث خطأ أثناء التحقق');
+ toast.error(err?.message || 'حدث خطأ أثناء التحقق');
} finally {
setIsLoading(false);
}
@@ -223,29 +278,70 @@ export default function AgentRegisterPage() {
const backgroundElements = useMemo(() => {
const circles = [
- { style: { top: '20%', right: '20%', width: '256px', height: '256px' }, className: 'bg-purple-500/5' },
- { style: { bottom: '20%', left: '20%', width: '320px', height: '320px' }, className: 'bg-amber-500/5' },
- { style: { top: '50%', left: '50%', width: '384px', height: '384px', transform: 'translate(-50%, -50%)' }, className: 'bg-purple-500/5' },
+ {
+ style: { top: '20%', right: '20%', width: '256px', height: '256px' },
+ className: 'bg-purple-500/5'
+ },
+ {
+ style: { bottom: '20%', left: '20%', width: '320px', height: '320px' },
+ className: 'bg-amber-500/5'
+ },
+ {
+ style: {
+ top: '50%',
+ left: '50%',
+ width: '384px',
+ height: '384px',
+ transform: 'translate(-50%, -50%)'
+ },
+ className: 'bg-purple-500/5'
+ }
];
+
const dots = Array.from({ length: 10 }).map((_, i) => ({
left: `${5 + i * 10}%`,
- top: `${10 + (i * 7) % 80}%`,
+ top: `${10 + ((i * 7) % 80)}%`,
size: `${80 + (i % 5) * 15}px`
}));
+
return (
<>
{circles.map((circle, i) => (
-
+
))}
{dots.map((dot, i) => (
-
+
))}
>
);
}, []);
- const stepTitles = ['المعلومات الأساسية', 'معلومات الهوية', 'معلومات الوكالة', 'تأكيد التسجيل'];
- const stepDescriptions = ['أدخل معلوماتك الأساسية', 'يرجى رفع صور الهوية للتحقق', 'أدخل بيانات الوكالة والترخيص', 'راجع معلوماتك قبل الإرسال'];
+ const stepTitles = [
+ 'المعلومات الأساسية',
+ 'معلومات الهوية',
+ 'معلومات الوكالة',
+ 'تأكيد التسجيل'
+ ];
+
+ const stepDescriptions = [
+ 'أدخل معلوماتك الأساسية',
+ 'يرجى رفع صور الهوية للتحقق',
+ 'أدخل بيانات الوكالة والترخيص',
+ 'راجع معلوماتك قبل الإرسال'
+ ];
const renderStepFields = () => {
switch (step) {
@@ -254,93 +350,210 @@ export default function AgentRegisterPage() {
<>
-
+
- {errors.firstName &&
{errors.firstName}
}
+ {errors.firstName && (
+
{errors.firstName}
+ )}
+
-
-
{ setFormData({...formData, lastName: e.target.value}); setErrors({...errors, lastName: null}); }}
- className={`w-full px-4 py-3 bg-white/5 border rounded-xl focus:outline-none focus:ring-2 focus:ring-purple-500 focus:border-transparent text-white placeholder-gray-500 transition-all ${errors.lastName ? 'border-red-500' : 'border-gray-700'}`}
- placeholder="اسم العائلة" />
- {errors.lastName &&
{errors.lastName}
}
+
+
{
+ setFormData({ ...formData, lastName: e.target.value });
+ setErrors({ ...errors, lastName: null });
+ }}
+ className={`w-full px-4 py-3 bg-white/5 border rounded-xl focus:outline-none focus:ring-2 focus:ring-purple-500 focus:border-transparent text-white placeholder-gray-500 transition-all ${
+ errors.lastName ? 'border-red-500' : 'border-gray-700'
+ }`}
+ placeholder="اسم العائلة"
+ />
+ {errors.lastName && (
+
{errors.lastName}
+ )}
-
+
{errors.email && {errors.email}
}
-
+
{errors.phone && {errors.phone}
}
-
+
{errors.password && {errors.password}
}
-
+
- {errors.confirmPassword && {errors.confirmPassword}
}
+ {errors.confirmPassword && (
+ {errors.confirmPassword}
+ )}
>
);
@@ -349,65 +562,171 @@ export default function AgentRegisterPage() {
return (
<>
-
+
- {errors.nationalNumber && {errors.nationalNumber}
}
+ {errors.nationalNumber && (
+ {errors.nationalNumber}
+ )}
-
+
{errors.whatsapp && {errors.whatsapp}
}
-
- fileInputFrontRef.current?.click()}
- className={`relative border-2 border-dashed rounded-xl p-6 text-center cursor-pointer transition-all ${idImagePreviews.front ? 'border-green-500 bg-green-500/10' : errors.front ? 'border-red-500 bg-red-500/10' : 'border-gray-700 hover:border-purple-500 hover:bg-white/5'}`}>
-
handleImageUpload('front', e.target.files?.[0])} className="hidden" />
+
+
fileInputFrontRef.current?.click()}
+ className={`relative border-2 border-dashed rounded-xl p-6 text-center cursor-pointer transition-all ${
+ idImagePreviews.front
+ ? 'border-green-500 bg-green-500/10'
+ : errors.front
+ ? 'border-red-500 bg-red-500/10'
+ : 'border-gray-700 hover:border-purple-500 hover:bg-white/5'
+ }`}
+ >
+
handleImageUpload('front', e.target.files?.[0])}
+ className="hidden"
+ />
+
{idImagePreviews.front ? (
-
-
- ) : (<>
اضغط لرفع الصورة
JPEG, PNG, JPG • حتى 5MB
>)}
+ ) : (
+ <>
+
+
اضغط لرفع الصورة
+
JPEG, PNG, JPG • حتى 5MB
+ >
+ )}
{errors.front &&
{errors.front}
}
-
- fileInputBackRef.current?.click()}
- className={`relative border-2 border-dashed rounded-xl p-6 text-center cursor-pointer transition-all ${idImagePreviews.back ? 'border-green-500 bg-green-500/10' : errors.back ? 'border-red-500 bg-red-500/10' : 'border-gray-700 hover:border-purple-500 hover:bg-white/5'}`}>
-
handleImageUpload('back', e.target.files?.[0])} className="hidden" />
+
+
fileInputBackRef.current?.click()}
+ className={`relative border-2 border-dashed rounded-xl p-6 text-center cursor-pointer transition-all ${
+ idImagePreviews.back
+ ? 'border-green-500 bg-green-500/10'
+ : errors.back
+ ? 'border-red-500 bg-red-500/10'
+ : 'border-gray-700 hover:border-purple-500 hover:bg-white/5'
+ }`}
+ >
+
handleImageUpload('back', e.target.files?.[0])}
+ className="hidden"
+ />
+
{idImagePreviews.back ? (
-
- { e.stopPropagation(); setIdImages(prev => ({...prev, back: null})); setIdImagePreviews(prev => ({...prev, back: ''})); }}
- className="absolute -top-2 -right-2 w-6 h-6 bg-red-500 rounded-full flex items-center justify-center hover:bg-red-600">
+
+ {
+ e.stopPropagation();
+ setIdImages((prev) => ({ ...prev, back: null }));
+ setIdImagePreviews((prev) => ({ ...prev, back: '' }));
+ }}
+ className="absolute -top-2 -right-2 w-6 h-6 bg-red-500 rounded-full flex items-center justify-center hover:bg-red-600"
+ >
- ) : (<>
اضغط لرفع الصورة
JPEG, PNG, JPG • حتى 5MB
>)}
+ ) : (
+ <>
+
+
اضغط لرفع الصورة
+
JPEG, PNG, JPG • حتى 5MB
+ >
+ )}
{errors.back &&
{errors.back}
}
@@ -418,47 +737,119 @@ export default function AgentRegisterPage() {
return (
<>
-
+
- {errors.agencyAddress && {errors.agencyAddress}
}
+ {errors.agencyAddress && (
+ {errors.agencyAddress}
+ )}
-
+
- {errors.licenseNumber && {errors.licenseNumber}
}
+ {errors.licenseNumber && (
+ {errors.licenseNumber}
+ )}
-
- fileInputLicenseStep3Ref.current?.click()}
- className={`relative border-2 border-dashed rounded-xl p-6 text-center cursor-pointer transition-all ${idImagePreviews.license ? 'border-green-500 bg-green-500/10' : errors.license ? 'border-red-500 bg-red-500/10' : 'border-gray-700 hover:border-purple-500 hover:bg-white/5'}`}>
-
handleImageUpload('license', e.target.files?.[0])} className="hidden" />
+
+
fileInputLicenseStep3Ref.current?.click()}
+ className={`relative border-2 border-dashed rounded-xl p-6 text-center cursor-pointer transition-all ${
+ idImagePreviews.license
+ ? 'border-green-500 bg-green-500/10'
+ : errors.license
+ ? 'border-red-500 bg-red-500/10'
+ : 'border-gray-700 hover:border-purple-500 hover:bg-white/5'
+ }`}
+ >
+
handleImageUpload('license', e.target.files?.[0])}
+ className="hidden"
+ />
+
{idImagePreviews.license ? (
-
- { e.stopPropagation(); setIdImages(prev => ({...prev, license: null})); setIdImagePreviews(prev => ({...prev, license: ''})); }}
- className="absolute -top-2 -right-2 w-6 h-6 bg-red-500 rounded-full flex items-center justify-center hover:bg-red-600">
+
+ {
+ e.stopPropagation();
+ setIdImages((prev) => ({ ...prev, license: null }));
+ setIdImagePreviews((prev) => ({ ...prev, license: '' }));
+ }}
+ className="absolute -top-2 -right-2 w-6 h-6 bg-red-500 rounded-full flex items-center justify-center hover:bg-red-600"
+ >
- ) : (<>
اضغط لرفع صورة الترخيص
JPEG, PNG, JPG • حتى 5MB
>)}
+ ) : (
+ <>
+
+
اضغط لرفع صورة الترخيص
+
JPEG, PNG, JPG • حتى 5MB
+ >
+ )}
{errors.license &&
{errors.license}
}
@@ -474,10 +865,22 @@ export default function AgentRegisterPage() {
المعلومات الأساسية
-
الاسم الأول: {formData.firstName}
-
اسم العائلة: {formData.lastName}
-
البريد الإلكتروني: {formData.email}
-
رقم الهاتف: {formData.phone}
+
+ الاسم الأول:{' '}
+ {formData.firstName}
+
+
+ اسم العائلة:{' '}
+ {formData.lastName}
+
+
+ البريد الإلكتروني:{' '}
+ {formData.email}
+
+
+ رقم الهاتف:{' '}
+ {formData.phone}
+
@@ -487,19 +890,38 @@ export default function AgentRegisterPage() {
معلومات الهوية
-
الرقم الوطني: {formData.nationalNumber}
-
رقم الواتساب: {formData.whatsapp}
+
+ الرقم الوطني:{' '}
+ {formData.nationalNumber}
+
+
+ رقم الواتساب:{' '}
+ {formData.whatsapp}
+
+
{idImagePreviews.front && (
)}
{idImagePreviews.back && (
)}
@@ -512,23 +934,48 @@ export default function AgentRegisterPage() {
معلومات الوكالة
-
عنوان الوكالة: {formData.agencyAddress}
-
رقم الترخيص: {formData.licenseNumber}
+
+ عنوان الوكالة:{' '}
+ {formData.agencyAddress}
+
+
+ رقم الترخيص:{' '}
+ {formData.licenseNumber}
+
+
{idImagePreviews.license && (
)}
- setFormData({...formData, agreeTerms: e.target.checked})}
- className="w-4 h-4 rounded border-gray-600 bg-white/5 text-purple-500 focus:ring-purple-500" required />
+ setFormData({ ...formData, agreeTerms: e.target.checked })}
+ className="w-4 h-4 rounded border-gray-600 bg-white/5 text-purple-500 focus:ring-purple-500"
+ required
+ />
@@ -544,31 +991,57 @@ export default function AgentRegisterPage() {
{backgroundElements}
-
+
-
-
+
+
+
+
العودة
خطوة {step} من 4
{[1, 2, 3, 4].map((s) => (
- = s ? 'bg-purple-500' : 'bg-gray-700'}`} animate={{ scaleX: step >= s ? 1 : 0.5 }} />
+ = s ? 'bg-purple-500' : 'bg-gray-700'}`}
+ animate={{ scaleX: step >= s ? 1 : 0.5 }}
+ />
))}
-
+
-
-
-
+
+
+
{stepTitles[step - 1]}
@@ -577,24 +1050,53 @@ export default function AgentRegisterPage() {
-
{ e.preventDefault(); handleNextStep(); } : handleSubmit}
- className="space-y-6">
+ {
+ e.preventDefault();
+ handleNextStep();
+ } : handleSubmit}
+ className="space-y-6"
+ >
{renderStepFields()}
{step > 1 && (
- { setStep(step - 1); window.scrollTo({ top: 0, behavior: 'smooth' }); }}
- className="flex-1 py-3 px-4 bg-white/5 border border-gray-700 rounded-xl text-gray-300 hover:bg-white/10 transition-colors">السابق
+ {
+ setStep(step - 1);
+ window.scrollTo({ top: 0, behavior: 'smooth' });
+ }}
+ className="flex-1 py-3 px-4 bg-white/5 border border-gray-700 rounded-xl text-gray-300 hover:bg-white/10 transition-colors"
+ >
+ السابق
+
)}
+
{step < 4 ? (
-
+
التالي
) : (
-
- {isLoading ? (جاري التسجيل...
) : 'إنشاء حساب وكيل'}
+
+ {isLoading ? (
+
+
+ جاري التسجيل...
+
+ ) : (
+ 'إنشاء حساب وكيل'
+ )}
)}
@@ -605,10 +1107,18 @@ export default function AgentRegisterPage() {
{showOtpModal && (
-
-
+
+
@@ -617,26 +1127,48 @@ export default function AgentRegisterPage() {
تم إرسال رمز التحقق إلى
{formData.email}
+
-
+
-
setOtpCode(e.target.value)}
className="w-full pr-12 pl-4 py-3 bg-white/5 border border-gray-700 rounded-xl focus:outline-none focus:ring-2 focus:ring-purple-500 text-white text-center tracking-[0.5em] text-xl"
- placeholder="------" />
+ placeholder="------"
+ />
+
-
- {isLoading ? <>جاري التحقق...> : 'تحقق'}
+
+ {isLoading ? (
+ <>
+
+ جاري التحقق...
+ >
+ ) : (
+ 'تحقق'
+ )}
-
+
+
إعادة إرسال الرمز
@@ -645,4 +1177,4 @@ export default function AgentRegisterPage() {
);
-}
+}
\ No newline at end of file
diff --git a/app/utils/api.js b/app/utils/api.js
index 419a503..8a31a74 100644
--- a/app/utils/api.js
+++ b/app/utils/api.js
@@ -453,6 +453,10 @@ import AuthService from '../services/AuthService';
const API_BASE = process.env.NEXT_PUBLIC_API_URL || 'https://45.93.137.91.nip.io/api';
+function isFormData(value) {
+ return typeof FormData !== 'undefined' && value instanceof FormData;
+}
+
/**
* Generic API fetch — attaches auth token, unwraps { data } envelope
*/
@@ -460,14 +464,24 @@ async function apiFetch(endpoint, options = {}) {
const token = AuthService.getToken();
const headers = {
- 'Content-Type': 'application/json',
...(token && { Authorization: `Bearer ${token}` }),
- ...options.headers,
+ ...(options.headers || {}),
};
+ const hasBody = options.body != null;
+ const bodyIsFormData = isFormData(options.body);
+
+ if (hasBody && !bodyIsFormData && !headers['Content-Type'] && !headers['content-type']) {
+ headers['Content-Type'] = 'application/json';
+ }
+
const res = await fetch(`${API_BASE}${endpoint}`, {
...options,
headers,
+ body:
+ hasBody && !bodyIsFormData && typeof options.body !== 'string'
+ ? JSON.stringify(options.body)
+ : options.body,
});
if (!res.ok && res.status !== 206) {
@@ -493,7 +507,13 @@ 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 = { 'Content-Type': 'application/json' };
+ const headers = {};
+
+ const bodyIsFormData = isFormData(body);
+ if (!bodyIsFormData) {
+ headers['Content-Type'] = 'application/json';
+ }
+
if (token) {
headers['Authorization'] = `Bearer ${token}`;
}
@@ -501,11 +521,12 @@ async function authFetch(endpoint, body, token = null) {
const res = await fetch(`${API_BASE}${endpoint}`, {
method: 'POST',
headers,
- body: JSON.stringify(body),
+ body: bodyIsFormData ? body : JSON.stringify(body),
});
const text = await res.text();
let data = null;
+
try {
data = text ? JSON.parse(text) : null;
if (data && typeof data === 'object' && 'data' in data) {
@@ -515,7 +536,7 @@ async function authFetch(endpoint, body, token = null) {
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 };
}
@@ -547,7 +568,7 @@ export async function getSaleProperties() {
export async function getSaleProperty(id) {
const items = await apiFetch('/SaleProperties/GetSaleProperties');
if (!Array.isArray(items)) return items;
- return items.find(p => p.id == id) || items[0];
+ return items.find((p) => p.id == id) || items[0];
}
// ─── Properties (generic) ───
@@ -596,15 +617,13 @@ export async function checkAvailability(propertyId, fromDate = null, toDate = nu
}
export async function bookReservation(propertyInfoId, startDate, endDate) {
- const payload = {
- propertyInfoId,
- startDate,
- endDate,
- };
-
return apiFetch('/Reservations/BookReservation/book', {
method: 'POST',
- body: JSON.stringify(payload),
+ body: {
+ propertyInfoId,
+ startDate,
+ endDate,
+ },
});
}
@@ -627,34 +646,34 @@ 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',
- body: JSON.stringify(data),
+ body: data,
});
}
export async function editRentProperty(id, data) {
return apiFetch(`/RentProperties/EditRentProperty/${id}`, {
method: 'PUT',
- body: JSON.stringify(data),
+ body: data,
});
}
export async function editSaleProperty(id, data) {
return apiFetch(`/SaleProperties/EditSaleProperty/${id}`, {
method: 'PUT',
- body: JSON.stringify(data),
+ body: data,
});
}
export async function addSaleProperty(data) {
return apiFetch('/SaleProperties/AddSaleProperty', {
method: 'POST',
- body: JSON.stringify(data),
+ body: data,
});
}
@@ -677,6 +696,7 @@ export async function getCurrencies() {
export async function uploadPicture(file) {
const formData = new FormData();
formData.append('image', file);
+
const token = AuthService.getToken();
const res = await fetch(`${API_BASE}/Files/UploadPicture`, {
@@ -702,8 +722,13 @@ export async function uploadPicture(file) {
// ─── Auth: Registration ───
async function multipartAuthFetch(endpoint, formData) {
+ const token = AuthService.getToken();
+
const res = await fetch(`${API_BASE}${endpoint}`, {
method: 'POST',
+ headers: {
+ ...(token && { Authorization: `Bearer ${token}` }),
+ },
body: formData,
});
@@ -729,8 +754,7 @@ export async function addOwner(data, frontImage = null, backImage = null, licens
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 || '';
@@ -842,7 +866,7 @@ export async function getUserNotifications() {
export async function confirmDepositPayment(bookingId) {
return apiFetch('/Reservations/ConfirmDepositPayment', {
method: 'POST',
- body: JSON.stringify({ bookingId }),
+ body: { bookingId },
});
}
@@ -890,7 +914,7 @@ export async function adminConfirmDeposit(reservationId, adminId, comment = null
export async function updateBookingStatus(bookingId, status) {
return apiFetch('/Reservations/UpdateStatus', {
method: 'PUT',
- body: JSON.stringify({ bookingId, status }),
+ body: { bookingId, status },
});
}
@@ -903,7 +927,7 @@ export async function getOwnerReservationRequests() {
export async function getOwnerReservationsByStatuses(filterStatuses) {
return apiFetch('/Reservations/GetAllReservationsByStateForOwner', {
method: 'POST',
- body: JSON.stringify({ filterStatuses }),
+ body: { filterStatuses },
});
}
@@ -922,14 +946,16 @@ export async function ownerConfirmReservation(id) {
export async function payDeposit(data) {
return apiFetch('/Reservations/PayDeposit/pay-deposit', {
method: 'POST',
- body: JSON.stringify(data),
+ body: data,
});
}
// ─── Owner Contact & Stats ───
export async function getOwnerContactInformation(propertyInformationId) {
- return apiFetch(`/Owner/GetOwnerContactInformation?propertyInformationId=${propertyInformationId}`);
+ return apiFetch(
+ `/Owner/GetOwnerContactInformation?propertyInformationId=${propertyInformationId}`
+ );
}
export async function getOwnerStatistics() {
@@ -940,28 +966,42 @@ export async function getOwnerStatistics() {
export async function registerRealEstateAgent(formData) {
const token = AuthService.getToken();
+
const res = await fetch(`${API_BASE}/RealEstateAgent/Add`, {
method: 'POST',
- headers: { ...(token && { Authorization: `Bearer ${token}` }) },
+ headers: {
+ ...(token && { Authorization: `Bearer ${token}` }),
+ },
body: formData,
});
+
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;
}
- 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 || (typeof data === 'string' ? data : null),
+ };
}
// ─── Change Password ───
export async function changePassword(oldPassword, newPassword) {
- return apiFetch(`/User/ChangePassword?oldPassword=${encodeURIComponent(oldPassword)}&newPassword=${encodeURIComponent(newPassword)}`, {
- method: 'PUT',
- });
+ return apiFetch(
+ `/User/ChangePassword?oldPassword=${encodeURIComponent(oldPassword)}&newPassword=${encodeURIComponent(newPassword)}`,
+ {
+ method: 'PUT',
+ }
+ );
}
// ─── Forget Password (OTP flow) ───
@@ -971,9 +1011,12 @@ export async function requestForgetPasswordOtp(email) {
}
export async function verifyForgetPasswordOtp(email, code, newPassword) {
- return apiFetch(`/User/VerifyForgetPasswordOTP?email=${encodeURIComponent(email)}&code=${encodeURIComponent(code)}&newPassword=${encodeURIComponent(newPassword)}`, {
- method: 'POST',
- });
+ return apiFetch(
+ `/User/VerifyForgetPasswordOTP?email=${encodeURIComponent(email)}&code=${encodeURIComponent(code)}&newPassword=${encodeURIComponent(newPassword)}`,
+ {
+ method: 'POST',
+ }
+ );
}
// ─── Reset Password (token flow) ───
@@ -995,7 +1038,7 @@ export async function deleteMyAccount(password) {
export async function setFCMToken(token, deviceType = 2) {
return apiFetch('/User/SetFCMToken', {
method: 'POST',
- body: JSON.stringify({ token, deviceType }),
+ body: { token, deviceType },
});
}
@@ -1003,7 +1046,9 @@ 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); });
+ Object.entries(params).forEach(([k, v]) => {
+ if (v != null && v !== '') qs.set(k, v);
+ });
const query = qs.toString();
return apiFetch(`/RentProperties/FilterRentProperties${query ? `?${query}` : ''}`);
}
@@ -1013,35 +1058,35 @@ export async function filterRentProperties(params = {}) {
export async function submitReport(subject, body) {
return apiFetch('/Reports', {
method: 'POST',
- body: JSON.stringify({ subject, body }),
+ body: { subject, body },
});
}
export async function submitReservationReport(data) {
return apiFetch('/ReservationReports', {
method: 'POST',
- body: JSON.stringify(data),
+ body: data,
});
}
export async function updateReservationReport(id, data) {
return apiFetch(`/ReservationReports/${id}`, {
method: 'PUT',
- body: JSON.stringify(data),
+ body: data,
});
}
export async function submitSaleReport(data) {
return apiFetch('/SaleReports', {
method: 'POST',
- body: JSON.stringify(data),
+ body: data,
});
}
export async function updateSaleReport(id, data) {
return apiFetch(`/SaleReports/${id}`, {
method: 'PUT',
- body: JSON.stringify(data),
+ body: data,
});
}
@@ -1050,6 +1095,6 @@ export async function updateSaleReport(id, data) {
export async function addTerm(name, description) {
return apiFetch('/Terms', {
method: 'POST',
- body: JSON.stringify({ name, description }),
+ body: { name, description },
});
-}
+}
\ No newline at end of file