Spaces:
Sleeping
Sleeping
| import { useState, useEffect, ChangeEvent, CSSProperties, ReactNode } from "react"; | |
| import { useNavigate } from "react-router-dom"; | |
| import { useAuth } from "../../lib/authContext"; | |
| import { getApiBaseUrl } from "../../lib/api/config"; | |
| // ββ Types βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| type StatusType = "success" | "error" | "loading"; | |
| interface Status { | |
| text: string; | |
| type: StatusType; | |
| } | |
| interface SheikhProfileForm { | |
| birthDate: string; | |
| birthTime: string; | |
| country: string; | |
| gender: string; | |
| phone: string; | |
| bio: string; | |
| specialization: string; | |
| yearsOfExperience: string; | |
| hourlyRate: string; | |
| certificateUrl: string; | |
| profilePhotoUrl: string; | |
| } | |
| interface SheikhProfileBody { | |
| birthDateTime: string; | |
| country: string; | |
| gender: string; | |
| phone: string | null; | |
| bio: string | null; | |
| specialization: string | null; | |
| yearsOfExperience: number; | |
| hourlyRate: number; | |
| certificateUrl: string | null; | |
| profilePhotoUrl: string | null; | |
| } | |
| interface FormGroupProps { | |
| label: string; | |
| required?: boolean; | |
| children: ReactNode; | |
| } | |
| // ββ Constants βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| const BACKEND_URL = `${getApiBaseUrl()}/api/auth/complete-google-profile/sheikh`; | |
| const COUNTRIES: string[] = [ | |
| "Egypt", "Saudi Arabia", "UAE", "Jordan", "Kuwait", "Qatar", | |
| "Bahrain", "Oman", "Libya", "Tunisia", "Morocco", "Algeria", "Sudan", "Other", | |
| ]; | |
| const STATUS_COLORS: Record<StatusType, { bg: string; color: string }> = { | |
| success: { bg: "#d4edda", color: "#155724" }, | |
| error: { bg: "#f8d7da", color: "#721c24" }, | |
| loading: { bg: "#fff3cd", color: "#856404" }, | |
| }; | |
| // ββ Sub-component βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| function FormGroup({ label, required = false, children }: FormGroupProps) { | |
| return ( | |
| <div style={{ marginBottom: 16, flex: 1 }}> | |
| <label style={styles.label}> | |
| {label} {required && <span style={{ color: "#e53935" }}>*</span>} | |
| </label> | |
| {children} | |
| </div> | |
| ); | |
| } | |
| // ββ Component βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| // sheikh/interview | |
| export default function CompleteSheikhProfile() { | |
| const navigate = useNavigate(); | |
| const { updateProfile } = useAuth(); | |
| const [hasToken, setHasToken] = useState<boolean>(true); | |
| const [loading, setLoading] = useState<boolean>(false); | |
| const [status, setStatus] = useState<Status | null>(null); | |
| const [response, setResponse] = useState<string | null>(null); | |
| const [form, setForm] = useState<SheikhProfileForm>({ | |
| birthDate: "", | |
| birthTime: "00:00", | |
| country: "", | |
| gender: "", | |
| phone: "", | |
| bio: "", | |
| specialization: "", | |
| yearsOfExperience: "", | |
| hourlyRate: "", | |
| certificateUrl: "", | |
| profilePhotoUrl: "", | |
| }); | |
| useEffect(() => { | |
| setHasToken(!!localStorage.getItem("authToken")); | |
| }, []); | |
| function handleChange(e: ChangeEvent<HTMLInputElement | HTMLSelectElement | HTMLTextAreaElement>): void { | |
| const { id, value } = e.target; | |
| setForm((prev) => ({ ...prev, [id]: value })); | |
| } | |
| async function submitProfile(): Promise<void> { | |
| const jwt = localStorage.getItem("authToken"); | |
| if (!jwt) { setStatus({ text: "β No authentication token found. Please sign in first.", type: "error" }); return; } | |
| if (!form.birthDate){ setStatus({ text: "β Birth date is required", type: "error" }); return; } | |
| if (!form.country) { setStatus({ text: "β Country is required", type: "error" }); return; } | |
| if (!form.gender) { setStatus({ text: "β Gender is required", type: "error" }); return; } | |
| const birthDateTime = `${form.birthDate}T${form.birthTime || "00:00"}:00`; | |
| const body: SheikhProfileBody = { | |
| birthDateTime, | |
| country: form.country, | |
| gender: form.gender, | |
| phone: form.phone || null, | |
| bio: form.bio || null, | |
| specialization: form.specialization || null, | |
| yearsOfExperience: parseInt(form.yearsOfExperience) || 0, | |
| hourlyRate: parseFloat(form.hourlyRate) || 0, | |
| certificateUrl: form.certificateUrl || null, | |
| profilePhotoUrl: form.profilePhotoUrl || null, | |
| }; | |
| setLoading(true); | |
| setStatus({ text: "β³ Saving profile...", type: "loading" }); | |
| try { | |
| const res = await fetch(BACKEND_URL, { | |
| method: "POST", | |
| headers: { "Content-Type": "application/json", Authorization: "Bearer " + jwt }, | |
| body: JSON.stringify(body), | |
| }); | |
| const text = await res.text(); | |
| setResponse(text); | |
| if (res.ok) { | |
| setStatus({ text: "β Sheikh profile submitted! Waiting for admin approval.", type: "success" }); | |
| updateProfile({ profileCompleted: true }); | |
| setTimeout(() => navigate("/sheikh/interview", { replace: true }), 1000); | |
| } else { | |
| setStatus({ text: "β Error: " + text, type: "error" }); | |
| setLoading(false); | |
| } | |
| } catch (err: unknown) { | |
| const message = err instanceof Error ? err.message : "Unknown error"; | |
| setStatus({ text: "β Failed: " + message, type: "error" }); | |
| setLoading(false); | |
| } | |
| } | |
| return ( | |
| <div style={styles.page}> | |
| <div style={styles.container}> | |
| {/* Header */} | |
| <div style={styles.header}> | |
| <div style={styles.icon}>π</div> | |
| <h1 style={styles.h1}>Complete Sheikh Profile</h1> | |
| <p style={styles.subtitle}>Fill in your details to apply as a sheikh</p> | |
| </div> | |
| <div style={{ textAlign: "center", marginBottom: 16 }}> | |
| <span style={styles.roleBadgeSheikh}>SHEIKH</span> | |
| </div> | |
| <div style={styles.pendingNote}> | |
| βΉοΈ After submitting, your profile will be reviewed by the admin. You will receive an interview invitation once approved. | |
| </div> | |
| {/* No token warning */} | |
| {!hasToken && ( | |
| <div style={styles.warning}> | |
| β οΈ No JWT found. Please{" "} | |
| <a href="/auth/google" style={styles.link}>sign in with Google first</a>. | |
| </div> | |
| )} | |
| {/* Personal Info */} | |
| <div style={styles.sectionTitle}>π€ Personal Information</div> | |
| <FormGroup label="Birth Date" required> | |
| <input type="date" id="birthDate" value={form.birthDate} onChange={handleChange} style={styles.input} /> | |
| </FormGroup> | |
| <div style={styles.formRow}> | |
| <FormGroup label="Country" required> | |
| <select id="country" value={form.country} onChange={handleChange} style={styles.input}> | |
| <option value="">Select country...</option> | |
| {COUNTRIES.map((c) => <option key={c} value={c}>{c}</option>)} | |
| </select> | |
| </FormGroup> | |
| <FormGroup label="Gender" required> | |
| <select id="gender" value={form.gender} onChange={handleChange} style={styles.input}> | |
| <option value="">Select gender...</option> | |
| <option value="Male">Male</option> | |
| <option value="Female">Female</option> | |
| </select> | |
| </FormGroup> | |
| </div> | |
| <FormGroup label="Phone Number"> | |
| <input type="tel" id="phone" value={form.phone} onChange={handleChange} placeholder="e.g. 01012345678" style={styles.input} /> | |
| </FormGroup> | |
| {/* Sheikh Profile */} | |
| <div style={styles.sectionTitle}>π Sheikh Profile</div> | |
| <FormGroup label="Bio"> | |
| <textarea | |
| id="bio" | |
| value={form.bio} | |
| onChange={handleChange} | |
| placeholder="Tell students about yourself, your teaching style and experience..." | |
| style={{ ...styles.input, resize: "vertical", minHeight: 80 }} | |
| /> | |
| </FormGroup> | |
| <FormGroup label="Specialization"> | |
| <input type="text" id="specialization" value={form.specialization} onChange={handleChange} placeholder="e.g. Tajweed, Memorization, Tafseer" style={styles.input} /> | |
| </FormGroup> | |
| <div style={styles.formRow}> | |
| <FormGroup label="Years of Experience"> | |
| <input type="number" id="yearsOfExperience" value={form.yearsOfExperience} onChange={handleChange} placeholder="e.g. 5" min={0} max={50} style={styles.input} /> | |
| </FormGroup> | |
| <FormGroup label="Hourly Rate (USD)"> | |
| <input type="number" id="hourlyRate" value={form.hourlyRate} onChange={handleChange} placeholder="e.g. 20.00" min={0} step={0.01} style={styles.input} /> | |
| </FormGroup> | |
| </div> | |
| <FormGroup label="Certificate URL"> | |
| <input type="url" id="certificateUrl" value={form.certificateUrl} onChange={handleChange} placeholder="https://... link to your Ijazah or certificate" style={styles.input} /> | |
| </FormGroup> | |
| <FormGroup label="Profile Photo URL"> | |
| <input type="url" id="profilePhotoUrl" value={form.profilePhotoUrl} onChange={handleChange} placeholder="https://... (leave empty to keep Google photo)" style={styles.input} /> | |
| </FormGroup> | |
| <button | |
| style={{ ...styles.submitBtn, ...(loading ? styles.submitBtnDisabled : {}) }} | |
| onClick={submitProfile} | |
| disabled={loading} | |
| > | |
| β Submit Sheikh Profile | |
| </button> | |
| {status && ( | |
| <div style={{ ...styles.statusBadge, background: STATUS_COLORS[status.type].bg, color: STATUS_COLORS[status.type].color }}> | |
| {status.text} | |
| </div> | |
| )} | |
| </div> | |
| </div> | |
| ); | |
| } | |
| // ββ Styles ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| const styles: Record<string, CSSProperties> = { | |
| page: { fontFamily: "'Segoe UI', sans-serif", background: "#f0f4f8", minHeight: "100vh", display: "flex", alignItems: "center", justifyContent: "center", padding: "40px 20px" }, | |
| container: { background: "white", borderRadius: 16, padding: 40, width: 600, boxShadow: "0 4px 24px rgba(0,0,0,0.10)" }, | |
| header: { textAlign: "center", marginBottom: 32 }, | |
| icon: { fontSize: 48, marginBottom: 12 }, | |
| h1: { color: "#1a6b3c", fontSize: 24, marginBottom: 6 }, | |
| subtitle: { color: "#777", fontSize: 14 }, | |
| roleBadgeSheikh: { display: "inline-block", background: "#fff3e0", color: "#e65100", padding: "4px 12px", borderRadius: 20, fontSize: 12, fontWeight: 700, marginBottom: 16 }, | |
| pendingNote: { background: "#e3f2fd", border: "1px solid #90caf9", borderRadius: 8, padding: "12px 16px", fontSize: 13, color: "#1565c0", marginBottom: 20 }, | |
| warning: { background: "#fff3cd", border: "1px solid #ffc107", borderRadius: 8, padding: "12px 16px", fontSize: 13, color: "#856404", marginBottom: 20 }, | |
| link: { color: "#1a6b3c", fontWeight: 600 }, | |
| sectionTitle: { fontSize: 13, fontWeight: 700, color: "#1a6b3c", textTransform: "uppercase", letterSpacing: 1, margin: "24px 0 12px", paddingBottom: 6, borderBottom: "2px solid #e8f5e9" }, | |
| formRow: { display: "flex", gap: 16 }, | |
| label: { display: "block", fontSize: 13, fontWeight: 600, color: "#444", marginBottom: 6 }, | |
| input: { width: "100%", padding: "11px 14px", border: "1.5px solid #ddd", borderRadius: 8, fontSize: 14, color: "#333", outline: "none", background: "white", fontFamily: "inherit", boxSizing: "border-box" }, | |
| submitBtn: { width: "100%", padding: 14, background: "#1a6b3c", color: "white", border: "none", borderRadius: 10, fontSize: 16, fontWeight: 600, cursor: "pointer", marginTop: 24 }, | |
| submitBtnDisabled: { background: "#aaa", cursor: "not-allowed" }, | |
| statusBadge: { padding: "12px 16px", borderRadius: 8, fontSize: 14, fontWeight: 500, marginTop: 16, textAlign: "center" }, | |
| responseBox: { background: "#1e1e1e", borderRadius: 10, padding: 16, marginTop: 16 }, | |
| responseTitle: { color: "#4ec9b0", fontSize: 12, marginBottom: 8, textTransform: "uppercase" }, | |
| responsePre: { color: "#d4d4d4", fontSize: 12, fontFamily: "'Courier New', monospace", whiteSpace: "pre-wrap", wordBreak: "break-all" }, | |
| }; | |