// import React, { createContext, useContext, useState, useEffect } from 'react'; // import { User, Streak, RecitationStats } from '../types'; // interface AuthContextType { // user: User | null; // loading: boolean; // signIn: (email: string, password: string) => Promise; // signInWithGoogle: (idToken: string, role: 'STUDENT' | 'SHEIKH') => Promise; // signUp: ( // email: string, // password: string, // fullName: string, // role: 'student' | 'sheikh', // phone: string, // gender: string, // country: string, // birthDate: string, // ) => Promise; // signOut: () => Promise; // updateProfile: (updates: Partial) => void; // fetchStreak: () => Promise; // fetchPracticeStats: () => Promise; // } // const AuthContext = createContext(undefined); // const BASE = `http://${window.location.hostname}:8080`; // export function AuthProvider({ children }: { children: React.ReactNode }) { // const [user, setUser] = useState(null); // const [loading, setLoading] = useState(true); // // ── Helper: Save user to storage ── // const saveUserToStorage = (userData: User | null, token?: string) => { // if (userData) { // localStorage.setItem('currentUser', JSON.stringify(userData)); // if (token) { // localStorage.setItem('authToken', token); // } // } else { // localStorage.removeItem('currentUser'); // if (token === undefined) { // localStorage.removeItem('authToken'); // } // } // }; // // ── Restore session on app load ────────────────────────────── // useEffect(() => { // const checkSession = async () => { // const savedUser = localStorage.getItem('currentUser'); // const token = localStorage.getItem('authToken'); // if (savedUser && token) { // try { // const parsedUser: User = JSON.parse(savedUser); // const res = await fetch(`${BASE}/api/auth/verify-profile`, { // headers: { // 'Content-Type': 'application/json', // Authorization: `Bearer ${token}`, // }, // }); // if (res.ok) { // const profileData = await res.json(); // const role = parsedUser.role?.toLowerCase(); // let profileCompleted = false; // if (role === 'student') { // profileCompleted = // profileData.profileCompleted === true && // profileData.profileStatus === 'COMPLETE'; // } else if (role === 'sheikh') { // profileCompleted = // profileData.profileCompleted === true && // (profileData.profileStatus === 'COMPLETE' || // profileData.profileStatus === 'PENDING_APPROVAL' || // profileData.profileStatus === 'UNDER_REVIEW'); // } else { // profileCompleted = profileData.profileCompleted === true; // } // const updatedUser = { ...parsedUser, profileCompleted }; // setUser(updatedUser); // saveUserToStorage(updatedUser); // } else { // localStorage.removeItem('currentUser'); // localStorage.removeItem('authToken'); // } // } catch (err) { // console.error('Failed to verify session:', err); // localStorage.removeItem('currentUser'); // localStorage.removeItem('authToken'); // } // } // setLoading(false); // }; // checkSession(); // }, []); // // ── signIn — email/password ────────────────────────────────── // const signIn = async (email: string, password: string): Promise => { // setLoading(true); // try { // const loginRes = await fetch(`${BASE}/api/auth/login`, { // method: 'POST', // headers: { 'Content-Type': 'application/json' }, // body: JSON.stringify({ email, password }), // }); // if (!loginRes.ok) { // const err = await loginRes.json().catch(() => ({})); // throw new Error(err.message || 'Login failed'); // } // const data = await loginRes.json(); // const { token, role: rawRole, user: userData } = data; // let profileCompleted = false; // const profileRes = await fetch(`${BASE}/api/auth/verify-profile`, { // headers: { // 'Content-Type': 'application/json', // Authorization: `Bearer ${token}`, // }, // }); // if (profileRes.ok) { // const profileData = await profileRes.json(); // const role = rawRole?.toLowerCase(); // if (role === 'student') { // profileCompleted = // profileData.profileCompleted === true && // profileData.profileStatus === 'COMPLETE'; // } else if (role === 'sheikh') { // const approvalStatus = data.sheikhApprovalStatus?.toUpperCase(); // profileCompleted = // profileData.profileCompleted === true && // ( // profileData.profileStatus === 'COMPLETE' || // approvalStatus === 'APPROVED' || // approvalStatus === 'PENDING' || // approvalStatus === 'UNDER_REVIEW' // ); // } else { // profileCompleted = true; // } // } // const user: User = { // id: userData.id.toString(), // email: userData.email, // name: userData.fullName, // role: (rawRole ?? userData.role ?? 'student').toLowerCase() as // 'student' | 'sheikh' | 'admin', // profileCompleted, // createdAt: new Date(), // phone: userData.phone, // fullName: userData.fullName, // sheikhApprovalStatus: data.sheikhApprovalStatus, // }; // setUser(user); // saveUserToStorage(user, token); // return user; // } finally { // setLoading(false); // } // }; // // ── signInWithGoogle — Google OAuth ────────────────────────── // const signInWithGoogle = async ( // token: string, // دي ممكن تكون access_token أو id_token // role: 'STUDENT' | 'SHEIKH', // ): Promise => { // setLoading(true); // try { // // الـ API محتاجة idToken // const res = await fetch(`${BASE}/api/auth/google`, { // method: 'POST', // headers: { 'Content-Type': 'application/json' }, // body: JSON.stringify({ idToken: token, role }), // ← هنا token بدل idToken // }); // if (!res.ok) { // const err = await res.json().catch(() => ({})); // throw new Error(err.message || 'Google sign-in failed'); // } // const data = await res.json(); // const { token, role: rawRole, user: userData } = data; // let profileCompleted = false; // const profileRes = await fetch(`${BASE}/api/auth/verify-profile`, { // headers: { // 'Content-Type': 'application/json', // Authorization: `Bearer ${token}`, // }, // }); // if (profileRes.ok) { // const profileData = await profileRes.json(); // const normalizedRole = rawRole?.toLowerCase(); // if (normalizedRole === 'student') { // profileCompleted = // profileData.profileCompleted === true && // profileData.profileStatus === 'COMPLETE'; // } else if (normalizedRole === 'sheikh') { // const approvalStatus = data.sheikhApprovalStatus?.toUpperCase(); // profileCompleted = // profileData.profileCompleted === true && // ( // profileData.profileStatus === 'COMPLETE' || // approvalStatus === 'APPROVED' || // approvalStatus === 'PENDING' || // approvalStatus === 'UNDER_REVIEW' // ); // } else { // profileCompleted = true; // } // } // const user: User = { // id: userData.id.toString(), // email: userData.email, // name: userData.fullName, // role: (rawRole ?? userData.role ?? 'student').toLowerCase() as // 'student' | 'sheikh' | 'admin', // profileCompleted, // createdAt: new Date(), // phone: userData.phone, // fullName: userData.fullName, // sheikhApprovalStatus: data.sheikhApprovalStatus, // }; // setUser(user); // saveUserToStorage(user, token); // return user; // } finally { // setLoading(false); // } // }; // const signUp = async ( // email: string, // password: string, // fullName: string, // role: 'student' | 'sheikh', // phone: string, // gender: string, // country: string, // birthDate: string, // ) => { // setLoading(true); // try { // const endpoint = // role === 'student' // ? `${BASE}/api/auth/register/student` // : `${BASE}/api/auth/register/sheikh`; // const res = await fetch(endpoint, { // method: 'POST', // headers: { 'Content-Type': 'application/json' }, // body: JSON.stringify({ fullName, email, password, phone, gender, country, birthDate }), // }); // if (!res.ok) { // const err = await res.json().catch(() => ({})); // throw new Error(err.message || 'Registration failed'); // } // const data = await res.json(); // const { token, user: userData } = data; // const user: User = { // id: userData.id.toString(), // email: userData.email, // name: userData.fullName, // role, // profileCompleted: false, // createdAt: new Date(), // phone: userData.phone, // fullName: userData.fullName, // }; // setUser(user); // saveUserToStorage(user, token); // } finally { // setLoading(false); // } // }; // const signOut = async () => { // try { // const token = localStorage.getItem('authToken'); // if (token) { // await fetch(`${BASE}/api/auth/logout`, { // method: 'POST', // headers: { // 'Content-Type': 'application/json', // Authorization: `Bearer ${token}`, // }, // }); // } // } catch (err) { // console.error('Logout API call failed:', err); // } // setUser(null); // localStorage.removeItem('currentUser'); // localStorage.removeItem('authToken'); // }; // const updateProfile = (updates: Partial) => { // if (user) { // const updated = { ...user, ...updates }; // setUser(updated); // saveUserToStorage(updated); // } // }; // const fetchStreak = async (): Promise => { // try { // const token = localStorage.getItem('authToken'); // if (!token) return null; // const res = await fetch(`${BASE}/api/auth/streak`, { // headers: { // 'Content-Type': 'application/json', // Authorization: `Bearer ${token}`, // }, // }); // if (!res.ok) return null; // const data = await res.json(); // return { // userId: data.userId, // currentStreak: data.currentStreak, // longestStreak: data.longestStreak, // lastActivityDate: data.lastActivityDate ? new Date(data.lastActivityDate) : null, // }; // } catch { // return null; // } // }; // const fetchPracticeStats = async (): Promise => { // try { // const token = localStorage.getItem('authToken'); // if (!token) return null; // const res = await fetch(`${BASE}/api/auth/practice/stats`, { // headers: { // 'Content-Type': 'application/json', // Authorization: `Bearer ${token}`, // }, // }); // if (!res.ok) return null; // const data = await res.json(); // return { // userId: data.userId, // totalRecitations: data.totalRecitations, // averageAccuracy: data.averageAccuracy, // }; // } catch { // return null; // } // }; // return ( // // {children} // // ); // } // export function useAuth() { // const ctx = useContext(AuthContext); // if (!ctx) throw new Error('useAuth must be used within an AuthProvider'); // return ctx; // } import React, { createContext, useContext, useState, useEffect } from 'react'; import { User, Streak, RecitationStats } from '../types'; interface AuthContextType { user: User | null; loading: boolean; signIn: (email: string, password: string) => Promise; signInWithGoogle: (idToken: string, role: 'STUDENT' | 'SHEIKH') => Promise; signUp: ( email: string, password: string, fullName: string, role: 'student' | 'sheikh', phone: string, gender: string, country: string, birthDate: string, ) => Promise; signOut: () => Promise; updateProfile: (updates: Partial) => void; fetchStreak: () => Promise; fetchPracticeStats: () => Promise; } const AuthContext = createContext(undefined); import { getApiBaseUrl } from './api/config'; const BASE = getApiBaseUrl(); export function AuthProvider({ children }: { children: React.ReactNode }) { const [user, setUser] = useState(null); const [loading, setLoading] = useState(true); // ── Restore session on app load ────────────────────────────── useEffect(() => { const checkSession = async () => { const savedUser = localStorage.getItem('currentUser'); const token = localStorage.getItem('authToken'); if (savedUser && token) { try { const parsedUser: User = JSON.parse(savedUser); const res = await fetch(`${BASE}/api/auth/verify-profile`, { headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${token}`, }, }); if (res.ok) { const profileData = await res.json(); const role = parsedUser.role?.toLowerCase(); let profileCompleted = false; if (role === 'student') { profileCompleted = profileData.profileCompleted === true && profileData.profileStatus === 'COMPLETE'; } else if (role === 'sheikh') { profileCompleted = profileData.profileCompleted === true && (profileData.profileStatus === 'COMPLETE' || profileData.profileStatus === 'PENDING_APPROVAL' || profileData.profileStatus === 'UNDER_REVIEW'); } else { profileCompleted = profileData.profileCompleted === true; } const updatedUser = { ...parsedUser, profileCompleted }; setUser(updatedUser); localStorage.setItem('currentUser', JSON.stringify(updatedUser)); } else { localStorage.removeItem('currentUser'); localStorage.removeItem('authToken'); } } catch (err) { console.error('Failed to verify session:', err); localStorage.removeItem('currentUser'); localStorage.removeItem('authToken'); } } setLoading(false); }; checkSession(); }, []); // ── signIn — email/password ────────────────────────────────── const signIn = async (email: string, password: string): Promise => { setLoading(true); try { const loginRes = await fetch(`${BASE}/api/auth/login`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ email, password }), }); if (!loginRes.ok) { const err = await loginRes.json().catch(() => ({})); throw new Error(err.message || 'Login failed'); } const data = await loginRes.json(); const { token, role: rawRole, user: userData } = data; let profileCompleted = false; const profileRes = await fetch(`${BASE}/api/auth/verify-profile`, { headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${token}`, }, }); if (profileRes.ok) { const profileData = await profileRes.json(); const role = rawRole?.toLowerCase(); if (role === 'student') { profileCompleted = profileData.profileCompleted === true && profileData.profileStatus === 'COMPLETE'; } else if (role === 'sheikh') { const approvalStatus = data.sheikhApprovalStatus?.toUpperCase(); profileCompleted = profileData.profileCompleted === true && ( profileData.profileStatus === 'COMPLETE' || approvalStatus === 'APPROVED' || approvalStatus === 'PENDING' || approvalStatus === 'UNDER_REVIEW' ); } else { profileCompleted = true; } } const user: User = { id: userData.id.toString(), email: userData.email, name: userData.fullName, role: (rawRole ?? userData.role ?? 'student').toLowerCase() as 'student' | 'sheikh' | 'admin', profileCompleted, createdAt: new Date(), phone: userData.phone, fullName: userData.fullName, sheikhApprovalStatus: data.sheikhApprovalStatus, }; setUser(user); localStorage.setItem('currentUser', JSON.stringify(user)); localStorage.setItem('authToken', token); return user; } finally { setLoading(false); } }; // ── signInWithGoogle — Google OAuth ────────────────────────── // // Call this after getting the idToken from Google Identity Services. // Pass the role the user selected on the role-selection page. // // Returns the User object so the caller can decide navigation: // // const user = await signInWithGoogle(idToken, selectedRole); // // Navigation rules: // • student + profileCompleted → /student/dashboard // • student + !profileCompleted → /complete-profile/student // • sheikh + approved → /sheikh/dashboard // • sheikh + pending/review → /sheikh/interview // • sheikh + !profileCompleted → /complete-profile/sheikh // • admin → /admin/dashboard // const signInWithGoogle = async ( idToken: string, role: 'STUDENT' | 'SHEIKH', ): Promise => { setLoading(true); try { // 1. Send Google ID token + chosen role to backend const res = await fetch(`${BASE}/api/auth/google`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ idToken, role }), }); if (!res.ok) { const err = await res.json().catch(() => ({})); throw new Error(err.message || 'Google sign-in failed'); } const data = await res.json(); // data: { token, role, message, user: { id, fullName, email, phone, role } } const { token, role: rawRole, user: userData } = data; // 2. Check profile completion status let profileCompleted = false; const profileRes = await fetch(`${BASE}/api/auth/verify-profile`, { headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${token}`, }, }); if (profileRes.ok) { const profileData = await profileRes.json(); const normalizedRole = rawRole?.toLowerCase(); if (normalizedRole === 'student') { profileCompleted = profileData.profileCompleted === true && profileData.profileStatus === 'COMPLETE'; } else if (normalizedRole === 'sheikh') { const approvalStatus = data.sheikhApprovalStatus?.toUpperCase(); profileCompleted = profileData.profileCompleted === true && ( profileData.profileStatus === 'COMPLETE' || approvalStatus === 'APPROVED' || approvalStatus === 'PENDING' || approvalStatus === 'UNDER_REVIEW' ); } else { profileCompleted = true; } } // 3. Build User object const user: User = { id: userData.id.toString(), email: userData.email, name: userData.fullName, role: (rawRole ?? userData.role ?? 'student').toLowerCase() as 'student' | 'sheikh' | 'admin', profileCompleted, createdAt: new Date(), phone: userData.phone, fullName: userData.fullName, sheikhApprovalStatus: data.sheikhApprovalStatus, }; setUser(user); localStorage.setItem('currentUser', JSON.stringify(user)); localStorage.setItem('authToken', token); return user; } finally { setLoading(false); } }; const signUp = async ( email: string, password: string, fullName: string, role: 'student' | 'sheikh', phone: string, gender: string, country: string, birthDate: string, ) => { setLoading(true); try { const endpoint = role === 'student' ? `${BASE}/api/auth/register/student` : `${BASE}/api/auth/register/sheikh`; const res = await fetch(endpoint, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ fullName, email, password, phone, gender, country, birthDate }), }); if (!res.ok) { const err = await res.json().catch(() => ({})); throw new Error(err.message || 'Registration failed'); } const data = await res.json(); const { token, user: userData } = data; const user: User = { id: userData.id.toString(), email: userData.email, name: userData.fullName, role, profileCompleted: false, createdAt: new Date(), phone: userData.phone, fullName: userData.fullName, }; setUser(user); localStorage.setItem('currentUser', JSON.stringify(user)); localStorage.setItem('authToken', token); } finally { setLoading(false); } }; const signOut = async () => { try { const token = localStorage.getItem('authToken'); if (token) { await fetch(`${BASE}/api/auth/logout`, { method: 'POST', headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${token}`, }, }); } } catch (err) { console.error('Logout API call failed:', err); } setUser(null); localStorage.removeItem('currentUser'); localStorage.removeItem('authToken'); }; const updateProfile = (updates: Partial) => { if (user) { const updated = { ...user, ...updates }; setUser(updated); localStorage.setItem('currentUser', JSON.stringify(updated)); } }; const fetchStreak = async (): Promise => { try { const token = localStorage.getItem('authToken'); if (!token) return null; const res = await fetch(`${BASE}/api/auth/streak`, { headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${token}`, }, }); if (!res.ok) return null; const data = await res.json(); return { userId: data.userId, currentStreak: data.currentStreak, longestStreak: data.longestStreak, lastActivityDate: data.lastActivityDate ? new Date(data.lastActivityDate) : null, }; } catch { return null; } }; const fetchPracticeStats = async (): Promise => { try { const token = localStorage.getItem('authToken'); if (!token) return null; const res = await fetch(`${BASE}/api/auth/practice/stats`, { headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${token}`, }, }); if (!res.ok) return null; const data = await res.json(); return { userId: data.userId, totalRecitations: data.totalRecitations, averageAccuracy: data.averageAccuracy, }; } catch { return null; } }; return ( {children} ); } export function useAuth() { const ctx = useContext(AuthContext); if (!ctx) throw new Error('useAuth must be used within an AuthProvider'); return ctx; }