752 lines
21 KiB
TypeScript
752 lines
21 KiB
TypeScript
import React, { createContext, useContext, useState, useCallback, useEffect, useRef } from 'react';
|
|
import * as Types from '../types/types';
|
|
import { ApiClient, CreateCandidateRequest, CreateEmployerRequest } from '../services/api-client';
|
|
import { formatApiRequest, toCamelCase } from '../types/conversion';
|
|
|
|
// ============================
|
|
// Types and Interfaces
|
|
// ============================
|
|
|
|
|
|
interface AuthState {
|
|
user: Types.User | null;
|
|
guest: Types.Guest | null;
|
|
isAuthenticated: boolean;
|
|
isLoading: boolean;
|
|
isInitializing: boolean;
|
|
error: string | null;
|
|
mfaResponse: Types.MFARequestResponse | null;
|
|
}
|
|
|
|
interface LoginRequest {
|
|
login: string; // email or username
|
|
password: string;
|
|
}
|
|
|
|
interface MFAVerificationRequest {
|
|
email: string;
|
|
code: string;
|
|
deviceId: string;
|
|
rememberDevice?: boolean;
|
|
}
|
|
|
|
interface EmailVerificationRequest {
|
|
token: string;
|
|
}
|
|
|
|
interface ResendVerificationRequest {
|
|
email: string;
|
|
}
|
|
|
|
interface PasswordResetRequest {
|
|
email: string;
|
|
}
|
|
|
|
// ============================
|
|
// Token Storage Constants
|
|
// ============================
|
|
|
|
const TOKEN_STORAGE = {
|
|
ACCESS_TOKEN: 'accessToken',
|
|
REFRESH_TOKEN: 'refreshToken',
|
|
USER_DATA: 'userData',
|
|
TOKEN_EXPIRY: 'tokenExpiry',
|
|
GUEST_DATA: 'guestData',
|
|
PENDING_VERIFICATION_EMAIL: 'pendingVerificationEmail'
|
|
} as const;
|
|
|
|
// ============================
|
|
// JWT Utilities
|
|
// ============================
|
|
|
|
function parseJwtPayload(token: string): any {
|
|
try {
|
|
const base64Url = token.split('.')[1];
|
|
const base64 = base64Url.replace(/-/g, '+').replace(/_/g, '/');
|
|
const jsonPayload = decodeURIComponent(
|
|
atob(base64)
|
|
.split('')
|
|
.map(c => '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2))
|
|
.join('')
|
|
);
|
|
return JSON.parse(jsonPayload);
|
|
} catch (error) {
|
|
console.error('Failed to parse JWT token:', error);
|
|
return null;
|
|
}
|
|
}
|
|
|
|
function isTokenExpired(token: string): boolean {
|
|
const payload = parseJwtPayload(token);
|
|
if (!payload || !payload.exp) {
|
|
return true;
|
|
}
|
|
|
|
// Check if token expires within the next 5 minutes (buffer time)
|
|
const expiryTime = payload.exp * 1000; // Convert to milliseconds
|
|
const bufferTime = 5 * 60 * 1000; // 5 minutes
|
|
const currentTime = Date.now();
|
|
|
|
return currentTime >= (expiryTime - bufferTime);
|
|
}
|
|
|
|
// ============================
|
|
// Storage Utilities with Date Conversion
|
|
// ============================
|
|
|
|
function clearStoredAuth(): void {
|
|
localStorage.removeItem(TOKEN_STORAGE.ACCESS_TOKEN);
|
|
localStorage.removeItem(TOKEN_STORAGE.REFRESH_TOKEN);
|
|
localStorage.removeItem(TOKEN_STORAGE.USER_DATA);
|
|
localStorage.removeItem(TOKEN_STORAGE.TOKEN_EXPIRY);
|
|
}
|
|
|
|
function prepareUserDataForStorage(user: Types.User): string {
|
|
try {
|
|
// Convert dates to ISO strings for storage
|
|
const userForStorage = formatApiRequest(user);
|
|
return JSON.stringify(userForStorage);
|
|
} catch (error) {
|
|
console.error('Failed to prepare user data for storage:', error);
|
|
return JSON.stringify(user); // Fallback to direct serialization
|
|
}
|
|
}
|
|
|
|
function parseStoredUserData(userDataStr: string): Types.User | null {
|
|
try {
|
|
const rawUserData = JSON.parse(userDataStr);
|
|
|
|
// Convert the data using toCamelCase which handles date conversion
|
|
const convertedData = toCamelCase<Types.User>(rawUserData);
|
|
|
|
return convertedData;
|
|
} catch (error) {
|
|
console.error('Failed to parse stored user data:', error);
|
|
return null;
|
|
}
|
|
}
|
|
|
|
function storeAuthData(authResponse: Types.AuthResponse): void {
|
|
localStorage.setItem(TOKEN_STORAGE.ACCESS_TOKEN, authResponse.accessToken);
|
|
localStorage.setItem(TOKEN_STORAGE.REFRESH_TOKEN, authResponse.refreshToken);
|
|
localStorage.setItem(TOKEN_STORAGE.USER_DATA, prepareUserDataForStorage(authResponse.user));
|
|
localStorage.setItem(TOKEN_STORAGE.TOKEN_EXPIRY, authResponse.expiresAt.toString());
|
|
}
|
|
|
|
function getStoredAuthData(): {
|
|
accessToken: string | null;
|
|
refreshToken: string | null;
|
|
userData: Types.User | null;
|
|
expiresAt: number | null;
|
|
} {
|
|
const accessToken = localStorage.getItem(TOKEN_STORAGE.ACCESS_TOKEN);
|
|
const refreshToken = localStorage.getItem(TOKEN_STORAGE.REFRESH_TOKEN);
|
|
const userDataStr = localStorage.getItem(TOKEN_STORAGE.USER_DATA);
|
|
const expiryStr = localStorage.getItem(TOKEN_STORAGE.TOKEN_EXPIRY);
|
|
|
|
let userData: Types.User | null = null;
|
|
let expiresAt: number | null = null;
|
|
|
|
try {
|
|
if (userDataStr) {
|
|
userData = parseStoredUserData(userDataStr);
|
|
}
|
|
if (expiryStr) {
|
|
expiresAt = parseInt(expiryStr, 10);
|
|
}
|
|
} catch (error) {
|
|
console.error('Failed to parse stored auth data:', error);
|
|
clearStoredAuth();
|
|
}
|
|
|
|
return { accessToken, refreshToken, userData, expiresAt };
|
|
}
|
|
|
|
function updateStoredUserData(user: Types.User): void {
|
|
try {
|
|
localStorage.setItem(TOKEN_STORAGE.USER_DATA, prepareUserDataForStorage(user));
|
|
} catch (error) {
|
|
console.error('Failed to update stored user data:', error);
|
|
}
|
|
}
|
|
|
|
// ============================
|
|
// Guest Session Utilities
|
|
// ============================
|
|
|
|
function createGuestSession(): Types.Guest {
|
|
const sessionId = `guest_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
|
|
const guest: Types.Guest = {
|
|
sessionId,
|
|
createdAt: new Date(),
|
|
lastActivity: new Date(),
|
|
ipAddress: 'unknown',
|
|
userAgent: navigator.userAgent
|
|
};
|
|
|
|
try {
|
|
localStorage.setItem(TOKEN_STORAGE.GUEST_DATA, JSON.stringify(formatApiRequest(guest)));
|
|
} catch (error) {
|
|
console.error('Failed to store guest session:', error);
|
|
}
|
|
|
|
return guest;
|
|
}
|
|
|
|
function getStoredGuestData(): Types.Guest | null {
|
|
try {
|
|
const guestDataStr = localStorage.getItem(TOKEN_STORAGE.GUEST_DATA);
|
|
if (guestDataStr) {
|
|
return toCamelCase<Types.Guest>(JSON.parse(guestDataStr));
|
|
}
|
|
} catch (error) {
|
|
console.error('Failed to parse stored guest data:', error);
|
|
localStorage.removeItem(TOKEN_STORAGE.GUEST_DATA);
|
|
}
|
|
return null;
|
|
}
|
|
|
|
// ============================
|
|
// Main Authentication Hook
|
|
// ============================
|
|
|
|
function useAuthenticationLogic() {
|
|
const [authState, setAuthState] = useState<AuthState>({
|
|
user: null,
|
|
guest: null,
|
|
isAuthenticated: false,
|
|
isLoading: false,
|
|
isInitializing: true,
|
|
error: null,
|
|
mfaResponse: null,
|
|
});
|
|
|
|
const [apiClient] = useState(() => new ApiClient());
|
|
const initializationCompleted = useRef(false);
|
|
|
|
// Token refresh function
|
|
const refreshAccessToken = useCallback(async (refreshToken: string): Promise<Types.AuthResponse | null> => {
|
|
try {
|
|
const response = await apiClient.refreshToken(refreshToken);
|
|
return response;
|
|
} catch (error) {
|
|
console.error('Token refresh failed:', error);
|
|
return null;
|
|
}
|
|
}, [apiClient]);
|
|
|
|
// Initialize authentication state
|
|
const initializeAuth = useCallback(async () => {
|
|
if (initializationCompleted.current) {
|
|
return;
|
|
}
|
|
|
|
try {
|
|
// Initialize guest session first
|
|
let guest = getStoredGuestData();
|
|
if (!guest) {
|
|
guest = createGuestSession();
|
|
}
|
|
|
|
const stored = getStoredAuthData();
|
|
|
|
// If no stored tokens, user is not authenticated but has guest session
|
|
if (!stored.accessToken || !stored.refreshToken || !stored.userData) {
|
|
setAuthState({
|
|
user: null,
|
|
guest,
|
|
isAuthenticated: false,
|
|
isLoading: false,
|
|
isInitializing: false,
|
|
error: null,
|
|
mfaResponse: null,
|
|
});
|
|
return;
|
|
}
|
|
|
|
// Check if access token is expired
|
|
if (isTokenExpired(stored.accessToken)) {
|
|
console.log('Access token expired, attempting refresh...');
|
|
|
|
const refreshResult = await refreshAccessToken(stored.refreshToken);
|
|
|
|
if (refreshResult) {
|
|
storeAuthData(refreshResult);
|
|
apiClient.setAuthToken(refreshResult.accessToken);
|
|
|
|
setAuthState({
|
|
user: refreshResult.user,
|
|
guest,
|
|
isAuthenticated: true,
|
|
isLoading: false,
|
|
isInitializing: false,
|
|
error: null,
|
|
mfaResponse: null
|
|
});
|
|
|
|
console.log('Token refreshed successfully');
|
|
} else {
|
|
console.log('Token refresh failed, clearing stored auth');
|
|
clearStoredAuth();
|
|
apiClient.clearAuthToken();
|
|
|
|
setAuthState({
|
|
user: null,
|
|
guest,
|
|
isAuthenticated: false,
|
|
isLoading: false,
|
|
isInitializing: false,
|
|
error: null,
|
|
mfaResponse: null
|
|
});
|
|
}
|
|
} else {
|
|
// Access token is still valid
|
|
apiClient.setAuthToken(stored.accessToken);
|
|
|
|
setAuthState({
|
|
user: stored.userData,
|
|
guest,
|
|
isAuthenticated: true,
|
|
isLoading: false,
|
|
isInitializing: false,
|
|
error: null,
|
|
mfaResponse: null
|
|
});
|
|
|
|
console.log('Restored authentication from stored tokens');
|
|
}
|
|
} catch (error) {
|
|
console.error('Error initializing auth:', error);
|
|
clearStoredAuth();
|
|
apiClient.clearAuthToken();
|
|
|
|
const guest = createGuestSession();
|
|
setAuthState({
|
|
user: null,
|
|
guest,
|
|
isAuthenticated: false,
|
|
isLoading: false,
|
|
isInitializing: false,
|
|
error: null,
|
|
mfaResponse: null
|
|
});
|
|
} finally {
|
|
initializationCompleted.current = true;
|
|
}
|
|
}, [apiClient, refreshAccessToken]);
|
|
|
|
// Run initialization on mount
|
|
useEffect(() => {
|
|
initializeAuth();
|
|
}, [initializeAuth]);
|
|
|
|
// Set up automatic token refresh
|
|
useEffect(() => {
|
|
if (!authState.isAuthenticated) {
|
|
return;
|
|
}
|
|
|
|
const stored = getStoredAuthData();
|
|
if (!stored.expiresAt) {
|
|
return;
|
|
}
|
|
|
|
const expiryTime = stored.expiresAt * 1000;
|
|
const currentTime = Date.now();
|
|
const timeUntilExpiry = expiryTime - currentTime - (5 * 60 * 1000); // 5 minute buffer
|
|
|
|
if (timeUntilExpiry <= 0) {
|
|
initializeAuth();
|
|
return;
|
|
}
|
|
|
|
const refreshTimer = setTimeout(() => {
|
|
console.log('Auto-refreshing token before expiry...');
|
|
initializeAuth();
|
|
}, timeUntilExpiry);
|
|
|
|
return () => clearTimeout(refreshTimer);
|
|
}, [authState.isAuthenticated, initializeAuth]);
|
|
|
|
// Enhanced login with MFA support
|
|
const login = useCallback(async (loginData: LoginRequest): Promise<boolean> => {
|
|
setAuthState(prev => ({ ...prev, isLoading: true, error: null, mfaResponse: null, mfaData: null }));
|
|
|
|
try {
|
|
const result = await apiClient.login({
|
|
login: loginData.login,
|
|
password: loginData.password,
|
|
});
|
|
|
|
if ('mfaRequired' in result) {
|
|
// MFA required for new device
|
|
setAuthState(prev => ({
|
|
...prev,
|
|
isLoading: false,
|
|
mfaResponse: result,
|
|
}));
|
|
return false; // Login not complete yet
|
|
} else {
|
|
// Normal login success
|
|
const authResponse: Types.AuthResponse = result;
|
|
storeAuthData(authResponse);
|
|
apiClient.setAuthToken(authResponse.accessToken);
|
|
|
|
setAuthState(prev => ({
|
|
...prev,
|
|
user: authResponse.user,
|
|
isAuthenticated: true,
|
|
isLoading: false,
|
|
error: null,
|
|
mfaResponse: null,
|
|
}));
|
|
|
|
console.log('Login successful');
|
|
return true;
|
|
}
|
|
} catch (error: any) {
|
|
const errorMessage = error instanceof Error ? error.message : 'Network error occurred. Please try again.';
|
|
setAuthState(prev => ({
|
|
...prev,
|
|
isLoading: false,
|
|
error: errorMessage,
|
|
mfaResponse: null,
|
|
}));
|
|
return false;
|
|
}
|
|
}, [apiClient]);
|
|
|
|
// MFA verification
|
|
const verifyMFA = useCallback(async (mfaData: MFAVerificationRequest): Promise<boolean> => {
|
|
setAuthState(prev => ({ ...prev, isLoading: true, error: null }));
|
|
|
|
try {
|
|
const result = await apiClient.verifyMFA(mfaData);
|
|
|
|
if (result.accessToken) {
|
|
const authResponse: Types.AuthResponse = result;
|
|
storeAuthData(authResponse);
|
|
apiClient.setAuthToken(authResponse.accessToken);
|
|
|
|
setAuthState(prev => ({
|
|
...prev,
|
|
user: authResponse.user,
|
|
isAuthenticated: true,
|
|
isLoading: false,
|
|
error: null,
|
|
mfaResponse: null,
|
|
}));
|
|
|
|
console.log('MFA verification successful');
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
} catch (error) {
|
|
const errorMessage = error instanceof Error ? error.message : 'MFA verification failed';
|
|
console.log(errorMessage);
|
|
setAuthState(prev => ({
|
|
...prev,
|
|
isLoading: false,
|
|
error: errorMessage
|
|
}));
|
|
return false;
|
|
}
|
|
}, [apiClient]);
|
|
|
|
// Resend MFA code
|
|
const resendMFACode = useCallback(async (email: string, deviceId: string, deviceName: string): Promise<boolean> => {
|
|
setAuthState(prev => ({ ...prev, isLoading: true, error: null }));
|
|
|
|
try {
|
|
await apiClient.requestMFA({
|
|
email,
|
|
password: '', // This would need to be stored securely or re-entered
|
|
deviceId,
|
|
deviceName,
|
|
});
|
|
|
|
setAuthState(prev => ({ ...prev, isLoading: false }));
|
|
return true;
|
|
} catch (error) {
|
|
const errorMessage = error instanceof Error ? error.message : 'Failed to resend MFA code';
|
|
setAuthState(prev => ({
|
|
...prev,
|
|
isLoading: false,
|
|
error: errorMessage
|
|
}));
|
|
return false;
|
|
}
|
|
}, [apiClient]);
|
|
|
|
// Clear MFA state
|
|
const clearMFA = useCallback(() => {
|
|
setAuthState(prev => ({
|
|
...prev,
|
|
mfaResponse: null,
|
|
error: null
|
|
}));
|
|
}, []);
|
|
|
|
// Email verification
|
|
const verifyEmail = useCallback(async (verificationData: EmailVerificationRequest): Promise<{ message: string; userType: string } | null> => {
|
|
setAuthState(prev => ({ ...prev, isLoading: true, error: null }));
|
|
|
|
try {
|
|
const result = await apiClient.verifyEmail(verificationData);
|
|
setAuthState(prev => ({ ...prev, isLoading: false }));
|
|
return {
|
|
message: result.message || 'Email verified successfully',
|
|
userType: result.userType || 'user'
|
|
};
|
|
} catch (error) {
|
|
const errorMessage = error instanceof Error ? error.message : 'Email verification failed';
|
|
setAuthState(prev => ({
|
|
...prev,
|
|
isLoading: false,
|
|
error: errorMessage
|
|
}));
|
|
return null;
|
|
}
|
|
}, [apiClient]);
|
|
|
|
// Resend email verification
|
|
const resendEmailVerification = useCallback(async (email: string): Promise<boolean> => {
|
|
setAuthState(prev => ({ ...prev, isLoading: true, error: null }));
|
|
|
|
try {
|
|
await apiClient.resendVerificationEmail({ email });
|
|
setAuthState(prev => ({ ...prev, isLoading: false }));
|
|
return true;
|
|
} catch (error) {
|
|
const errorMessage = error instanceof Error ? error.message : 'Failed to resend verification email';
|
|
setAuthState(prev => ({
|
|
...prev,
|
|
isLoading: false,
|
|
error: errorMessage
|
|
}));
|
|
return false;
|
|
}
|
|
}, [apiClient]);
|
|
|
|
// Store pending verification email
|
|
const setPendingVerificationEmail = useCallback((email: string) => {
|
|
localStorage.setItem(TOKEN_STORAGE.PENDING_VERIFICATION_EMAIL, email);
|
|
}, []);
|
|
|
|
// Get pending verification email
|
|
const getPendingVerificationEmail = useCallback((): string | null => {
|
|
return localStorage.getItem(TOKEN_STORAGE.PENDING_VERIFICATION_EMAIL);
|
|
}, []);
|
|
|
|
const logout = useCallback(() => {
|
|
clearStoredAuth();
|
|
apiClient.clearAuthToken();
|
|
|
|
// Create new guest session after logout
|
|
const guest = createGuestSession();
|
|
|
|
setAuthState(prev => ({
|
|
...prev,
|
|
user: null,
|
|
guest,
|
|
isAuthenticated: false,
|
|
isLoading: false,
|
|
error: null,
|
|
mfaResponse: null,
|
|
}));
|
|
|
|
console.log('User logged out');
|
|
}, [apiClient]);
|
|
|
|
const updateUserData = useCallback((updatedUser: Types.User) => {
|
|
updateStoredUserData(updatedUser);
|
|
setAuthState(prev => ({
|
|
...prev,
|
|
user: updatedUser
|
|
}));
|
|
console.log('User data updated');
|
|
}, []);
|
|
|
|
const createCandidateAccount = useCallback(async (candidateData: CreateCandidateRequest): Promise<boolean> => {
|
|
setAuthState(prev => ({ ...prev, isLoading: true, error: null }));
|
|
|
|
try {
|
|
const candidate = await apiClient.createCandidate(candidateData);
|
|
console.log('Candidate created:', candidate);
|
|
|
|
// Store email for potential verification resend
|
|
setPendingVerificationEmail(candidateData.email);
|
|
|
|
setAuthState(prev => ({ ...prev, isLoading: false }));
|
|
return true;
|
|
} catch (error) {
|
|
const errorMessage = error instanceof Error ? error.message : 'Account creation failed';
|
|
setAuthState(prev => ({
|
|
...prev,
|
|
isLoading: false,
|
|
error: errorMessage
|
|
}));
|
|
return false;
|
|
}
|
|
}, [apiClient, setPendingVerificationEmail]);
|
|
|
|
const createEmployerAccount = useCallback(async (employerData: CreateEmployerRequest): Promise<boolean> => {
|
|
setAuthState(prev => ({ ...prev, isLoading: true, error: null }));
|
|
|
|
try {
|
|
const employer = await apiClient.createEmployer(employerData);
|
|
console.log('Employer created:', employer);
|
|
|
|
// Store email for potential verification resend
|
|
setPendingVerificationEmail(employerData.email);
|
|
|
|
setAuthState(prev => ({ ...prev, isLoading: false }));
|
|
return true;
|
|
} catch (error) {
|
|
const errorMessage = error instanceof Error ? error.message : 'Account creation failed';
|
|
setAuthState(prev => ({
|
|
...prev,
|
|
isLoading: false,
|
|
error: errorMessage
|
|
}));
|
|
return false;
|
|
}
|
|
}, [apiClient, setPendingVerificationEmail]);
|
|
|
|
const requestPasswordReset = useCallback(async (email: string): Promise<boolean> => {
|
|
setAuthState(prev => ({ ...prev, isLoading: true, error: null }));
|
|
|
|
try {
|
|
await apiClient.requestPasswordReset({ email });
|
|
setAuthState(prev => ({ ...prev, isLoading: false }));
|
|
return true;
|
|
} catch (error) {
|
|
const errorMessage = error instanceof Error ? error.message : 'Password reset request failed';
|
|
setAuthState(prev => ({
|
|
...prev,
|
|
isLoading: false,
|
|
error: errorMessage
|
|
}));
|
|
return false;
|
|
}
|
|
}, [apiClient]);
|
|
|
|
const refreshAuth = useCallback(async (): Promise<boolean> => {
|
|
const stored = getStoredAuthData();
|
|
if (!stored.refreshToken) {
|
|
return false;
|
|
}
|
|
|
|
setAuthState(prev => ({ ...prev, isLoading: true, error: null }));
|
|
|
|
const refreshResult = await refreshAccessToken(stored.refreshToken);
|
|
|
|
if (refreshResult) {
|
|
storeAuthData(refreshResult);
|
|
apiClient.setAuthToken(refreshResult.accessToken);
|
|
|
|
setAuthState(prev => ({
|
|
...prev,
|
|
user: refreshResult.user,
|
|
isAuthenticated: true,
|
|
isLoading: false,
|
|
error: null
|
|
}));
|
|
|
|
return true;
|
|
} else {
|
|
logout();
|
|
return false;
|
|
}
|
|
}, [refreshAccessToken, logout]);
|
|
|
|
return {
|
|
...authState,
|
|
apiClient,
|
|
login,
|
|
logout,
|
|
verifyMFA,
|
|
resendMFACode,
|
|
clearMFA,
|
|
verifyEmail,
|
|
resendEmailVerification,
|
|
setPendingVerificationEmail,
|
|
getPendingVerificationEmail,
|
|
createCandidateAccount,
|
|
createEmployerAccount,
|
|
requestPasswordReset,
|
|
refreshAuth,
|
|
updateUserData
|
|
};
|
|
}
|
|
|
|
// ============================
|
|
// Context Provider
|
|
// ============================
|
|
|
|
const AuthContext = createContext<ReturnType<typeof useAuthenticationLogic> | null>(null);
|
|
|
|
function AuthProvider({ children }: { children: React.ReactNode }) {
|
|
const auth = useAuthenticationLogic();
|
|
|
|
return (
|
|
<AuthContext.Provider value={auth}>
|
|
{children}
|
|
</AuthContext.Provider>
|
|
);
|
|
}
|
|
|
|
function useAuth() {
|
|
const context = useContext(AuthContext);
|
|
if (!context) {
|
|
throw new Error('useAuth must be used within an AuthProvider');
|
|
}
|
|
return context;
|
|
}
|
|
|
|
// ============================
|
|
// Protected Route Component
|
|
// ============================
|
|
|
|
interface ProtectedRouteProps {
|
|
children: React.ReactNode;
|
|
fallback?: React.ReactNode;
|
|
requiredUserType?: Types.UserType;
|
|
}
|
|
|
|
function ProtectedRoute({
|
|
children,
|
|
fallback = <div>Please log in to access this page.</div>,
|
|
requiredUserType
|
|
}: ProtectedRouteProps) {
|
|
const { isAuthenticated, isInitializing, user } = useAuth();
|
|
|
|
// Show loading while checking stored tokens
|
|
if (isInitializing) {
|
|
return <div>Loading...</div>;
|
|
}
|
|
|
|
// Not authenticated
|
|
if (!isAuthenticated) {
|
|
return <>{fallback}</>;
|
|
}
|
|
|
|
// Check user type if required
|
|
if (requiredUserType && user?.userType !== requiredUserType) {
|
|
return <div>Access denied. Required user type: {requiredUserType}</div>;
|
|
}
|
|
|
|
return <>{children}</>;
|
|
}
|
|
|
|
export type {
|
|
AuthState, LoginRequest, MFAVerificationRequest, EmailVerificationRequest, ResendVerificationRequest, PasswordResetRequest
|
|
}
|
|
|
|
export type { CreateCandidateRequest, CreateEmployerRequest } from '../services/api-client';
|
|
|
|
export {
|
|
useAuthenticationLogic, AuthProvider, useAuth, ProtectedRoute
|
|
} |