Spaces:
Sleeping
Sleeping
| import { useState } from 'react'; | |
| import { useNavigate } from 'react-router-dom'; | |
| import { Button } from '../ui/button'; | |
| import { Input } from '../ui/input'; | |
| import { Label } from '../ui/label'; | |
| import { Textarea } from '../ui/textarea'; | |
| import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '../ui/card'; | |
| import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '../ui/select'; | |
| import { Alert, AlertDescription } from '../ui/alert'; | |
| import { CheckCircle2, User, DollarSign, Award, FileText } from 'lucide-react'; | |
| import { useAuth } from '../../lib/authContext'; | |
| import { useLanguage } from '../../lib/languageContext'; | |
| import { LanguageSwitcher } from '../LanguageSwitcher'; | |
| import { getApiBaseUrl } from '../../lib/api/config'; | |
| export function CompleteProfile() { | |
| const { user, updateProfile } = useAuth(); | |
| const { language } = useLanguage(); | |
| const navigate = useNavigate(); | |
| const [loading, setLoading] = useState(false); | |
| const [error, setError] = useState<string | null>(null); | |
| const [submitted, setSubmitted] = useState(false); | |
| const parseErrorResponse = async (res: Response) => { | |
| let errorMsg = language === 'ar' ? 'فشل الإرسال' : 'Submission failed'; | |
| try { | |
| const json = await res.clone().json(); | |
| if (json?.message) return json.message; | |
| if (json?.error) return json.error; | |
| if (Array.isArray(json?.errors) && json.errors.length > 0) { | |
| return json.errors.map((err: any) => err.defaultMessage || err.message || String(err)).join(', '); | |
| } | |
| if (json?.errors) return JSON.stringify(json.errors); | |
| } catch { | |
| // ignore invalid JSON | |
| } | |
| try { | |
| const text = await res.text(); | |
| if (text) return text; | |
| } catch { | |
| // ignore text read errors | |
| } | |
| return errorMsg; | |
| }; | |
| // Student fields | |
| const [quranLevel, setQuranLevel] = useState(''); | |
| const [preferredLanguage, setPreferredLanguage] = useState(''); | |
| const [preferredAccent, setPreferredAccent] = useState(''); | |
| const [profilePhotoUrl, setProfilePhotoUrl] = useState(''); | |
| // Sheikh fields | |
| const [bio, setBio] = useState(''); | |
| const [specialization, setSpecialization] = useState(''); | |
| const [yearsOfExperience, setYearsOfExperience] = useState(''); | |
| const [hourlyRate, setHourlyRate] = useState(''); | |
| const [certificateUrl, setCertificateUrl] = useState(''); | |
| const [sheikhProfilePhotoUrl, setSheikhProfilePhotoUrl] = useState(''); | |
| // const t = translations[language].auth; | |
| const isRTL = language === 'ar'; | |
| const handleSubmit = async (e: React.FormEvent) => { | |
| e.preventDefault(); | |
| setLoading(true); | |
| setError(null); | |
| try { | |
| const token = localStorage.getItem('authToken'); | |
| if (!token) { | |
| throw new Error( | |
| language === 'ar' | |
| ? 'لا يوجد رمز مصادقة' | |
| : 'No authentication token found' | |
| ); | |
| } | |
| const BASE_URL = getApiBaseUrl(); | |
| if (user?.role === 'student') { | |
| // Validate student fields | |
| if (!quranLevel || !preferredLanguage || !preferredAccent) { | |
| setError( | |
| language === 'ar' | |
| ? 'يرجى ملء جميع الحقول المطلوبة' | |
| : 'Please fill in all required fields' | |
| ); | |
| setLoading(false); | |
| return; | |
| } | |
| const payload = { | |
| quranLevel, | |
| preferredLanguage, | |
| preferredAccent, | |
| profilePhotoUrl: profilePhotoUrl || '' | |
| }; | |
| const res = await fetch(`${BASE_URL}/api/student/profile/complete`, { | |
| method: 'POST', | |
| headers: { | |
| 'Content-Type': 'application/json', | |
| 'Authorization': `Bearer ${token}` | |
| }, | |
| body: JSON.stringify(payload), | |
| }); | |
| if (!res.ok) { | |
| const errorMsg = await parseErrorResponse(res); | |
| throw new Error(errorMsg); | |
| } | |
| // Update context and navigate | |
| updateProfile({ profileCompleted: true }); | |
| navigate('/student/dashboard'); | |
| } else if (user?.role === 'sheikh') { | |
| // Validate sheikh fields | |
| if (!bio || !specialization || !yearsOfExperience || !hourlyRate) { | |
| setError( | |
| language === 'ar' | |
| ? 'يرجى ملء جميع الحقول المطلوبة (السيرة الذاتية، التخصص، سنوات الخبرة، السعر بالساعة)' | |
| : 'Please fill in all required fields (Bio, Specialization, Years of Experience, Hourly Rate)' | |
| ); | |
| setLoading(false); | |
| return; | |
| } | |
| const payload = { | |
| bio, | |
| specialization, | |
| yearsOfExperience: parseInt(yearsOfExperience), | |
| hourlyRate: parseFloat(hourlyRate), | |
| certificateUrl: certificateUrl || '', | |
| profilePhotoUrl: sheikhProfilePhotoUrl || '' | |
| }; | |
| const res = await fetch(`${BASE_URL}/api/sheikh/profile/complete`, { | |
| method: 'POST', | |
| headers: { | |
| 'Content-Type': 'application/json', | |
| 'Authorization': `Bearer ${token}` | |
| }, | |
| body: JSON.stringify(payload), | |
| }); | |
| if (!res.ok) { | |
| const errorMsg = await parseErrorResponse(res); | |
| throw new Error(errorMsg); | |
| } | |
| // Show success screen for sheikh | |
| setSubmitted(true); | |
| } | |
| } catch (err: any) { | |
| console.error('Profile submission error:', err); | |
| setError(err.message || ( | |
| language === 'ar' | |
| ? 'حدث خطأ أثناء الإرسال. حاول مرة أخرى.' | |
| : 'Error submitting profile. Please try again.' | |
| )); | |
| } finally { | |
| setLoading(false); | |
| } | |
| }; | |
| // Success screen for sheikh | |
| if (user?.role === 'sheikh' && submitted) { | |
| return ( | |
| <div className="min-h-screen flex items-center justify-center bg-gradient-to-br from-emerald-50 to-teal-50 p-4"> | |
| <div className="absolute top-4 right-4 z-10"> | |
| <LanguageSwitcher /> | |
| </div> | |
| <Card className={`w-full max-w-md ${isRTL ? 'rtl' : 'ltr'}`} dir={isRTL ? 'rtl' : 'ltr'}> | |
| <CardHeader className="text-center"> | |
| <div className="flex justify-center mb-4"> | |
| <div className="w-16 h-16 bg-emerald-600 rounded-full flex items-center justify-center"> | |
| <CheckCircle2 className="h-8 w-8 text-white" /> | |
| </div> | |
| </div> | |
| <CardTitle> | |
| {language === 'ar' ? 'تم إرسال الطلب بنجاح!' : 'Application Submitted Successfully!'} | |
| </CardTitle> | |
| <CardDescription> | |
| {language === 'ar' | |
| ? 'ملفك الشخصي قيد المراجعة من قبل فريق الإدارة' | |
| : 'Your profile is under review by our admin team'} | |
| </CardDescription> | |
| </CardHeader> | |
| <CardContent className="space-y-4"> | |
| <Alert> | |
| <AlertDescription className={isRTL ? 'text-right' : ''}> | |
| {language === 'ar' | |
| ? 'تم إرسال طلبك وهو حاليًا في حالة الموافقة المعلقة. سيقوم فريق الإدارة بمراجعة بياناتك خلال 3-5 أيام عمل. سوف تتلقى إشعارًا عبر البريد الإلكتروني بمجرد الموافقة على طلبك.' | |
| : 'Your application has been submitted and is currently in PENDING approval status. Our admin team will review your credentials within 3-5 business days. You will receive an email notification once your application is approved.'} | |
| </AlertDescription> | |
| </Alert> | |
| <Button | |
| onClick={() => { | |
| updateProfile({ profileCompleted: true }); | |
| navigate('/sheikh/interview'); | |
| }} | |
| className="w-full bg-emerald-600 hover:bg-emerald-700" | |
| > | |
| {language === 'ar' ? 'الانتقال إلى صفحة الانتظار' : 'Go to Waiting Page'} | |
| </Button> | |
| </CardContent> | |
| </Card> | |
| </div> | |
| ); | |
| } | |
| // Main form | |
| return ( | |
| <div className="min-h-screen bg-gradient-to-br from-emerald-50 to-teal-50 p-4 py-8"> | |
| <div className="absolute top-4 right-4 z-10"> | |
| <LanguageSwitcher /> | |
| </div> | |
| <Card className={`w-full max-w-2xl mx-auto ${isRTL ? 'rtl' : 'ltr'}`} dir={isRTL ? 'rtl' : 'ltr'}> | |
| <CardHeader> | |
| <CardTitle className={isRTL ? 'text-right' : ''}> | |
| {language === 'ar' ? 'أكمل ملفك الشخصي' : 'Complete Your Profile'} | |
| </CardTitle> | |
| <CardDescription className={isRTL ? 'text-right' : ''}> | |
| {user?.role === 'student' | |
| ? (language === 'ar' | |
| ? 'أخبرنا عن نفسك لتخصيص تجربة التعلم الخاصة بك' | |
| : 'Tell us about yourself to personalize your learning experience') | |
| : (language === 'ar' | |
| ? 'قدم بياناتك وقم برفع الملفات المطلوبة لبدء التدريس' | |
| : 'Provide your credentials and upload required files to start teaching')} | |
| </CardDescription> | |
| </CardHeader> | |
| <CardContent> | |
| <form onSubmit={handleSubmit} className="space-y-6"> | |
| {/* STUDENT FIELDS */} | |
| {user?.role === 'student' ? ( | |
| <> | |
| {/* Quran Level */} | |
| <div className="space-y-2"> | |
| <Label htmlFor="quranLevel" className={isRTL ? 'text-right block' : ''}> | |
| {language === 'ar' ? 'مستوى القرآن' : 'Quran Level'} | |
| <span className="text-red-500 ml-1">*</span> | |
| </Label> | |
| <Select value={quranLevel} onValueChange={setQuranLevel} required dir={isRTL ? 'rtl' : 'ltr'}> | |
| <SelectTrigger className={isRTL ? 'text-right' : ''}> | |
| <SelectValue placeholder={language === 'ar' ? 'اختر مستواك' : 'Select your Quran level'} /> | |
| </SelectTrigger> | |
| <SelectContent> | |
| <SelectItem value="beginner"> | |
| {language === 'ar' ? 'مبتدئ' : 'Beginner'} | |
| </SelectItem> | |
| <SelectItem value="intermediate"> | |
| {language === 'ar' ? 'متوسط' : 'Intermediate'} | |
| </SelectItem> | |
| <SelectItem value="advanced"> | |
| {language === 'ar' ? 'متقدم' : 'Advanced'} | |
| </SelectItem> | |
| </SelectContent> | |
| </Select> | |
| </div> | |
| {/* Preferred Language */} | |
| <div className="space-y-2"> | |
| <Label htmlFor="language" className={isRTL ? 'text-right block' : ''}> | |
| {language === 'ar' ? 'اللغة المفضلة' : 'Preferred Language'} | |
| <span className="text-red-500 ml-1">*</span> | |
| </Label> | |
| <Select value={preferredLanguage} onValueChange={setPreferredLanguage} required dir={isRTL ? 'rtl' : 'ltr'}> | |
| <SelectTrigger className={isRTL ? 'text-right' : ''}> | |
| <SelectValue placeholder={language === 'ar' ? 'اختر اللغة' : 'Select language'} /> | |
| </SelectTrigger> | |
| <SelectContent> | |
| <SelectItem value="arabic">{language === 'ar' ? 'العربية' : 'Arabic'}</SelectItem> | |
| <SelectItem value="english">{language === 'ar' ? 'الإنجليزية' : 'English'}</SelectItem> | |
| <SelectItem value="urdu">{language === 'ar' ? 'الأردية' : 'Urdu'}</SelectItem> | |
| <SelectItem value="french">{language === 'ar' ? 'الفرنسية' : 'French'}</SelectItem> | |
| </SelectContent> | |
| </Select> | |
| </div> | |
| {/* Preferred Accent */} | |
| <div className="space-y-2"> | |
| <Label htmlFor="accent" className={isRTL ? 'text-right block' : ''}> | |
| {language === 'ar' ? 'اللهجة المفضلة' : 'Preferred Accent'} | |
| <span className="text-red-500 ml-1">*</span> | |
| </Label> | |
| <Select value={preferredAccent} onValueChange={setPreferredAccent} required dir={isRTL ? 'rtl' : 'ltr'}> | |
| <SelectTrigger className={isRTL ? 'text-right' : ''}> | |
| <SelectValue placeholder={language === 'ar' ? 'اختر اللهجة' : 'Select accent'} /> | |
| </SelectTrigger> | |
| <SelectContent> | |
| <SelectItem value="egyptian">{language === 'ar' ? 'مصرية' : 'Egyptian'}</SelectItem> | |
| <SelectItem value="saudi">{language === 'ar' ? 'سعودية (حجازية)' : 'Saudi (Hijazi)'}</SelectItem> | |
| <SelectItem value="levantine">{language === 'ar' ? 'شامية' : 'Levantine'}</SelectItem> | |
| <SelectItem value="maghrebi">{language === 'ar' ? 'مغربية' : 'Maghrebi'}</SelectItem> | |
| </SelectContent> | |
| </Select> | |
| </div> | |
| {/* Profile Photo URL */} | |
| <div className="space-y-2"> | |
| <Label htmlFor="profilePhotoUrl" className={isRTL ? 'text-right block' : ''}> | |
| {language === 'ar' ? 'رابط الصورة الشخصية (اختياري)' : 'Profile Photo URL (optional)'} | |
| </Label> | |
| <div className="relative"> | |
| <User className={`absolute top-3 h-4 w-4 text-muted-foreground ${isRTL ? 'right-3' : 'left-3'}`} /> | |
| <Input | |
| id="profilePhotoUrl" | |
| type="url" | |
| placeholder="https://example.com/photo.jpg" | |
| value={profilePhotoUrl} | |
| onChange={(e) => setProfilePhotoUrl(e.target.value)} | |
| className={isRTL ? 'pr-10 text-right' : 'pl-10'} | |
| dir="ltr" | |
| /> | |
| </div> | |
| </div> | |
| </> | |
| ) : ( | |
| /* SHEIKH FIELDS */ | |
| <> | |
| {/* Specialization */} | |
| <div className="space-y-2"> | |
| <Label htmlFor="specialization" className={isRTL ? 'text-right block' : ''}> | |
| {language === 'ar' ? 'التخصص' : 'Specialization'} | |
| <span className="text-red-500 ml-1">*</span> | |
| </Label> | |
| <div className="relative"> | |
| <Award className={`absolute top-3 h-4 w-4 text-muted-foreground ${isRTL ? 'right-3' : 'left-3'}`} /> | |
| <Input | |
| id="specialization" | |
| placeholder={language === 'ar' ? 'مثال: تلاوة القرآن، التجويد، الحفظ' : 'e.g., Quranic Recitation, Tajweed, Memorization'} | |
| value={specialization} | |
| onChange={(e) => setSpecialization(e.target.value)} | |
| className={isRTL ? 'pr-10 text-right' : 'pl-10'} | |
| required | |
| /> | |
| </div> | |
| </div> | |
| {/* Years of Experience */} | |
| <div className="space-y-2"> | |
| <Label htmlFor="yearsOfExperience" className={isRTL ? 'text-right block' : ''}> | |
| {language === 'ar' ? 'سنوات الخبرة' : 'Years of Experience'} | |
| <span className="text-red-500 ml-1">*</span> | |
| </Label> | |
| <Input | |
| id="yearsOfExperience" | |
| type="number" | |
| min="0" | |
| placeholder={language === 'ar' ? 'مثال: 10' : 'e.g., 10'} | |
| value={yearsOfExperience} | |
| onChange={(e) => setYearsOfExperience(e.target.value)} | |
| className={isRTL ? 'text-right' : ''} | |
| required | |
| /> | |
| </div> | |
| {/* Hourly Rate */} | |
| <div className="space-y-2"> | |
| <Label htmlFor="hourlyRate" className={isRTL ? 'text-right block' : ''}> | |
| {language === 'ar' ? 'السعر بالساعة ($)' : 'Hourly Rate ($)'} | |
| <span className="text-red-500 ml-1">*</span> | |
| </Label> | |
| <div className="relative"> | |
| <DollarSign className={`absolute top-3 h-4 w-4 text-muted-foreground ${isRTL ? 'right-3' : 'left-3'}`} /> | |
| <Input | |
| id="hourlyRate" | |
| type="number" | |
| step="0.01" | |
| min="0" | |
| placeholder={language === 'ar' ? 'مثال: 25.00' : 'e.g., 25.00'} | |
| value={hourlyRate} | |
| onChange={(e) => setHourlyRate(e.target.value)} | |
| className={isRTL ? 'pr-10 text-right' : 'pl-10'} | |
| dir="ltr" | |
| required | |
| /> | |
| </div> | |
| </div> | |
| {/* Bio */} | |
| <div className="space-y-2"> | |
| <Label htmlFor="bio" className={isRTL ? 'text-right block' : ''}> | |
| {language === 'ar' ? 'السيرة الذاتية / نبذة عنك' : 'Bio / About You'} | |
| <span className="text-red-500 ml-1">*</span> | |
| </Label> | |
| <div className="relative"> | |
| <FileText className={`absolute top-3 h-4 w-4 text-muted-foreground ${isRTL ? 'right-3' : 'left-3'}`} /> | |
| <Textarea | |
| id="bio" | |
| placeholder={language === 'ar' | |
| ? 'أخبر الطلاب عن نفسك، منهجك التعليمي، ومؤهلاتك...' | |
| : 'Tell students about yourself, your teaching approach, and qualifications...'} | |
| value={bio} | |
| onChange={(e) => setBio(e.target.value)} | |
| className={`min-h-24 ${isRTL ? 'pr-10 text-right' : 'pl-10'}`} | |
| required | |
| /> | |
| </div> | |
| </div> | |
| {/* Certificate URL */} | |
| <div className="space-y-2"> | |
| <Label htmlFor="certificateUrl" className={isRTL ? 'text-right block' : ''}> | |
| {language === 'ar' ? 'رابط الشهادة (اختياري)' : 'Certificate URL (optional)'} | |
| </Label> | |
| <Input | |
| id="certificateUrl" | |
| type="url" | |
| placeholder="https://example.com/certificate.pdf" | |
| value={certificateUrl} | |
| onChange={(e) => setCertificateUrl(e.target.value)} | |
| className={isRTL ? 'text-right' : ''} | |
| dir="ltr" | |
| /> | |
| </div> | |
| {/* Profile Photo URL */} | |
| <div className="space-y-2"> | |
| <Label htmlFor="sheikhProfilePhotoUrl" className={isRTL ? 'text-right block' : ''}> | |
| {language === 'ar' ? 'رابط الصورة الشخصية (اختياري)' : 'Profile Photo URL (optional)'} | |
| </Label> | |
| <div className="relative"> | |
| <User className={`absolute top-3 h-4 w-4 text-muted-foreground ${isRTL ? 'right-3' : 'left-3'}`} /> | |
| <Input | |
| id="sheikhProfilePhotoUrl" | |
| type="url" | |
| placeholder="https://example.com/photo.jpg" | |
| value={sheikhProfilePhotoUrl} | |
| onChange={(e) => setSheikhProfilePhotoUrl(e.target.value)} | |
| className={isRTL ? 'pr-10 text-right' : 'pl-10'} | |
| dir="ltr" | |
| /> | |
| </div> | |
| </div> | |
| </> | |
| )} | |
| {/* Error Message */} | |
| {error && ( | |
| <div className="bg-red-50 border border-red-200 rounded-lg p-3"> | |
| <p className={`text-red-800 text-sm ${isRTL ? 'text-right' : ''}`}> | |
| {error} | |
| </p> | |
| </div> | |
| )} | |
| {/* Submit Button */} | |
| <Button | |
| type="submit" | |
| className="w-full bg-emerald-600 hover:bg-emerald-700" | |
| disabled={loading} | |
| > | |
| {loading ? ( | |
| <span className="flex items-center gap-2"> | |
| <span className="animate-spin">⏳</span> | |
| {language === 'ar' ? 'جاري الإرسال...' : 'Submitting...'} | |
| </span> | |
| ) : ( | |
| language === 'ar' ? 'إكمال الملف الشخصي' : 'Complete Profile' | |
| )} | |
| </Button> | |
| </form> | |
| </CardContent> | |
| </Card> | |
| </div> | |
| ); | |
| } |