Spaces:
Sleeping
Sleeping
| // 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<User>; | |
| // signInWithGoogle: (idToken: string, role: 'STUDENT' | 'SHEIKH') => Promise<User>; | |
| // signUp: ( | |
| // email: string, | |
| // password: string, | |
| // fullName: string, | |
| // role: 'student' | 'sheikh', | |
| // phone: string, | |
| // gender: string, | |
| // country: string, | |
| // birthDate: string, | |
| // ) => Promise<void>; | |
| // signOut: () => Promise<void>; | |
| // updateProfile: (updates: Partial<User>) => void; | |
| // fetchStreak: () => Promise<Streak | null>; | |
| // fetchPracticeStats: () => Promise<RecitationStats | null>; | |
| // } | |
| // const AuthContext = createContext<AuthContextType | undefined>(undefined); | |
| // const BASE = `http://${window.location.hostname}:8080`; | |
| // export function AuthProvider({ children }: { children: React.ReactNode }) { | |
| // const [user, setUser] = useState<User | null>(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<User> => { | |
| // 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<User> => { | |
| // 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<User>) => { | |
| // if (user) { | |
| // const updated = { ...user, ...updates }; | |
| // setUser(updated); | |
| // saveUserToStorage(updated); | |
| // } | |
| // }; | |
| // const fetchStreak = async (): Promise<Streak | null> => { | |
| // 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<RecitationStats | null> => { | |
| // 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 ( | |
| // <AuthContext.Provider | |
| // value={{ | |
| // user, | |
| // loading, | |
| // signIn, | |
| // signInWithGoogle, | |
| // signUp, | |
| // signOut, | |
| // updateProfile, | |
| // fetchStreak, | |
| // fetchPracticeStats, | |
| // }} | |
| // > | |
| // {children} | |
| // </AuthContext.Provider> | |
| // ); | |
| // } | |
| // 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<User>; | |
| signInWithGoogle: (idToken: string, role: 'STUDENT' | 'SHEIKH') => Promise<User>; | |
| signUp: ( | |
| email: string, | |
| password: string, | |
| fullName: string, | |
| role: 'student' | 'sheikh', | |
| phone: string, | |
| gender: string, | |
| country: string, | |
| birthDate: string, | |
| ) => Promise<void>; | |
| signOut: () => Promise<void>; | |
| updateProfile: (updates: Partial<User>) => void; | |
| fetchStreak: () => Promise<Streak | null>; | |
| fetchPracticeStats: () => Promise<RecitationStats | null>; | |
| } | |
| const AuthContext = createContext<AuthContextType | undefined>(undefined); | |
| import { getApiBaseUrl } from './api/config'; | |
| const BASE = getApiBaseUrl(); | |
| export function AuthProvider({ children }: { children: React.ReactNode }) { | |
| const [user, setUser] = useState<User | null>(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<User> => { | |
| 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<User> => { | |
| 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<User>) => { | |
| if (user) { | |
| const updated = { ...user, ...updates }; | |
| setUser(updated); | |
| localStorage.setItem('currentUser', JSON.stringify(updated)); | |
| } | |
| }; | |
| const fetchStreak = async (): Promise<Streak | null> => { | |
| 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<RecitationStats | null> => { | |
| 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 ( | |
| <AuthContext.Provider | |
| value={{ | |
| user, | |
| loading, | |
| signIn, | |
| signInWithGoogle, | |
| signUp, | |
| signOut, | |
| updateProfile, | |
| fetchStreak, | |
| fetchPracticeStats, | |
| }} | |
| > | |
| {children} | |
| </AuthContext.Provider> | |
| ); | |
| } | |
| export function useAuth() { | |
| const ctx = useContext(AuthContext); | |
| if (!ctx) throw new Error('useAuth must be used within an AuthProvider'); | |
| return ctx; | |
| } |