Quran_Tech_Server / F_Pro /src /lib /authContext.tsx
aboalaa147's picture
Initial deployment
eb6a2f9
Raw
History Blame Contribute Delete
26.1 kB
// 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;
}