Quran_Tech_Server / F_Pro /src /components /auth /CompleteSheikhProfile.tsx
aboalaa147's picture
Initial deployment
eb6a2f9
Raw
History Blame Contribute Delete
12.8 kB
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" },
};