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() { <>
- +
- +
- { setFormData({...formData, firstName: e.target.value}); setErrors({...errors, firstName: null}); }} - className={`w-full pr-12 pl-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.firstName ? 'border-red-500' : 'border-gray-700'}`} - placeholder="الاسم الأول" /> + { + setFormData({ ...formData, firstName: e.target.value }); + setErrors({ ...errors, firstName: null }); + }} + className={`w-full pr-12 pl-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.firstName ? 'border-red-500' : 'border-gray-700' + }`} + placeholder="الاسم الأول" + />
- {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}

+ )}
- +
- +
- { setFormData({...formData, email: e.target.value}); setErrors({...errors, email: null}); }} - className={`w-full pr-12 pl-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.email ? 'border-red-500' : 'border-gray-700'}`} - placeholder="أدخل بريدك الإلكتروني" /> + { + setFormData({ ...formData, email: e.target.value }); + setErrors({ ...errors, email: null }); + }} + className={`w-full pr-12 pl-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.email ? 'border-red-500' : 'border-gray-700' + }`} + placeholder="أدخل بريدك الإلكتروني" + />
{errors.email &&

{errors.email}

}
- +
- +
- { setFormData({...formData, phone: e.target.value}); setErrors({...errors, phone: null}); }} - className={`w-full pr-12 pl-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.phone ? 'border-red-500' : 'border-gray-700'}`} - placeholder="أدخل رقم هاتفك" /> + { + setFormData({ ...formData, phone: e.target.value }); + setErrors({ ...errors, phone: null }); + }} + className={`w-full pr-12 pl-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.phone ? 'border-red-500' : 'border-gray-700' + }`} + placeholder="أدخل رقم هاتفك" + />
{errors.phone &&

{errors.phone}

}
- +
- +
- { setFormData({...formData, password: e.target.value}); setErrors({...errors, password: null}); }} - className={`w-full pr-12 pl-12 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.password ? 'border-red-500' : 'border-gray-700'}`} - placeholder="أدخل كلمة المرور" /> -
{errors.password &&

{errors.password}

}
- +
- +
- { setFormData({...formData, confirmPassword: e.target.value}); setErrors({...errors, confirmPassword: null}); }} - className={`w-full pr-12 pl-12 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.confirmPassword ? 'border-red-500' : 'border-gray-700'}`} - placeholder="أعد إدخال كلمة المرور" /> - {formData.confirmPassword && (
- {formData.password === formData.confirmPassword ? : } + {formData.password === formData.confirmPassword ? ( + + ) : ( + + )}
)}
- {errors.confirmPassword &&

{errors.confirmPassword}

} + {errors.confirmPassword && ( +

{errors.confirmPassword}

+ )}
); @@ -349,65 +562,171 @@ export default function AgentRegisterPage() { return ( <> - +
- +
- { setFormData({...formData, nationalNumber: e.target.value}); setErrors({...errors, nationalNumber: null}); }} - className={`w-full pr-12 pl-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.nationalNumber ? 'border-red-500' : 'border-gray-700'}`} - placeholder="أدخل الرقم الوطني" /> + { + setFormData({ ...formData, nationalNumber: e.target.value }); + setErrors({ ...errors, nationalNumber: null }); + }} + className={`w-full pr-12 pl-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.nationalNumber ? 'border-red-500' : 'border-gray-700' + }`} + placeholder="أدخل الرقم الوطني" + />
- {errors.nationalNumber &&

{errors.nationalNumber}

} + {errors.nationalNumber && ( +

{errors.nationalNumber}

+ )}
- +
- +
- { setFormData({...formData, whatsapp: e.target.value}); setErrors({...errors, whatsapp: null}); }} - className={`w-full pr-12 pl-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.whatsapp ? 'border-red-500' : 'border-gray-700'}`} - placeholder="أدخل رقم الواتساب" /> + { + setFormData({ ...formData, whatsapp: e.target.value }); + setErrors({ ...errors, whatsapp: null }); + }} + className={`w-full pr-12 pl-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.whatsapp ? 'border-red-500' : 'border-gray-700' + }`} + placeholder="أدخل رقم الواتساب" + />
{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 ? (
- Front ID -
- ) : (<>

اضغط لرفع الصورة

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 ? (
- Back ID -
- ) : (<>

اضغط لرفع الصورة

JPEG, PNG, JPG • حتى 5MB

)} + ) : ( + <> + +

اضغط لرفع الصورة

+

JPEG, PNG, JPG • حتى 5MB

+ + )}
{errors.back &&

{errors.back}

} @@ -418,47 +737,119 @@ export default function AgentRegisterPage() { return ( <> - +
- +
- { setFormData({...formData, agencyAddress: e.target.value}); setErrors({...errors, agencyAddress: null}); }} - className={`w-full pr-12 pl-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.agencyAddress ? 'border-red-500' : 'border-gray-700'}`} - placeholder="أدخل عنوان الوكالة" /> + { + setFormData({ ...formData, agencyAddress: e.target.value }); + setErrors({ ...errors, agencyAddress: null }); + }} + className={`w-full pr-12 pl-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.agencyAddress ? 'border-red-500' : 'border-gray-700' + }`} + placeholder="أدخل عنوان الوكالة" + />
- {errors.agencyAddress &&

{errors.agencyAddress}

} + {errors.agencyAddress && ( +

{errors.agencyAddress}

+ )}
- +
- +
- { setFormData({...formData, licenseNumber: e.target.value}); setErrors({...errors, licenseNumber: null}); }} - className={`w-full pr-12 pl-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.licenseNumber ? 'border-red-500' : 'border-gray-700'}`} - placeholder="أدخل رقم الترخيص العقاري" /> + { + setFormData({ ...formData, licenseNumber: e.target.value }); + setErrors({ ...errors, licenseNumber: null }); + }} + className={`w-full pr-12 pl-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.licenseNumber ? 'border-red-500' : 'border-gray-700' + }`} + placeholder="أدخل رقم الترخيص العقاري" + />
- {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 ? (
- License -
- ) : (<>

اضغط لرفع صورة الترخيص

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 && (
- Front ID + Front ID

الوجه الأمامي

)} {idImagePreviews.back && (
- Back ID + Back ID

الوجه الخلفي

)} @@ -512,23 +934,48 @@ export default function AgentRegisterPage() { معلومات الوكالة
-
عنوان الوكالة: {formData.agencyAddress}
-
رقم الترخيص: {formData.licenseNumber}
+
+ عنوان الوكالة:{' '} + {formData.agencyAddress} +
+
+ رقم الترخيص:{' '} + {formData.licenseNumber} +
+ {idImagePreviews.license && (
- License + 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 && ( - + )} + {step < 4 ? ( - ) : ( - )} @@ -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="------" + />
+
-
- @@ -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