// contexts/AuthContext.tsx
import React, { createContext, useContext, useReducer, useEffect } from 'react';
import { AuthService } from '../services/AuthService';
import { User, LoginCredentials, SignupCredentials } from '../types/auth';
interface AuthState {
user: User | null;
isLoading: boolean;
isAuthenticated: boolean;
error: string | null;
}
interface AuthContextType extends AuthState {
login: (credentials: LoginCredentials) => Promise<void>;
signup: (credentials: SignupCredentials) => Promise<void>;
logout: () => void;
refreshToken: () => Promise<void>;
clearError: () => void;
}
const AuthContext = createContext<AuthContextType | undefined>(undefined);
type AuthAction =
| { type: 'AUTH_START' }
| { type: 'AUTH_SUCCESS'; payload: User }
| { type: 'AUTH_ERROR'; payload: string }
| { type: 'AUTH_LOGOUT' }
| { type: 'CLEAR_ERROR' };
const authReducer = (state: AuthState, action: AuthAction): AuthState => {
switch (action.type) {
case 'AUTH_START':
return { ...state, isLoading: true, error: null };
case 'AUTH_SUCCESS':
return {
...state,
user: action.payload,
isAuthenticated: true,
isLoading: false,
error: null,
};
case 'AUTH_ERROR':
return {
...state,
user: null,
isAuthenticated: false,
isLoading: false,
error: action.payload,
};
case 'AUTH_LOGOUT':
return {
...state,
user: null,
isAuthenticated: false,
isLoading: false,
error: null,
};
case 'CLEAR_ERROR':
return { ...state, error: null };
default:
return state;
}
};
export function AuthProvider({ children }: { children: React.ReactNode }) {
const [state, dispatch] = useReducer(authReducer, {
user: null,
isLoading: true,
isAuthenticated: false,
error: null,
});
// Initialize auth state on app load
useEffect(() => {
initializeAuth();
}, []);
const initializeAuth = async () => {
try {
const token = localStorage.getItem('accessToken');
if (!token) {
dispatch({ type: 'AUTH_LOGOUT' });
return;
}
// Verify token and get user data
const user = await AuthService.getCurrentUser();
dispatch({ type: 'AUTH_SUCCESS', payload: user });
} catch (error) {
console.error('Auth initialization failed:', error);
localStorage.removeItem('accessToken');
localStorage.removeItem('refreshToken');
dispatch({ type: 'AUTH_LOGOUT' });
}
};
const login = async (credentials: LoginCredentials) => {
dispatch({ type: 'AUTH_START' });
try {
const { user, accessToken, refreshToken } = await AuthService.login(credentials);
// Store tokens
localStorage.setItem('accessToken', accessToken);
localStorage.setItem('refreshToken', refreshToken);
dispatch({ type: 'AUTH_SUCCESS', payload: user });
} catch (error) {
const message = error instanceof Error ? error.message : 'Login failed';
dispatch({ type: 'AUTH_ERROR', payload: message });
throw error;
}
};
const signup = async (credentials: SignupCredentials) => {
dispatch({ type: 'AUTH_START' });
try {
const { user, accessToken, refreshToken } = await AuthService.signup(credentials);
localStorage.setItem('accessToken', accessToken);
localStorage.setItem('refreshToken', refreshToken);
dispatch({ type: 'AUTH_SUCCESS', payload: user });
} catch (error) {
const message = error instanceof Error ? error.message : 'Signup failed';
dispatch({ type: 'AUTH_ERROR', payload: message });
throw error;
}
};
const logout = () => {
localStorage.removeItem('accessToken');
localStorage.removeItem('refreshToken');
dispatch({ type: 'AUTH_LOGOUT' });
};
const refreshToken = async () => {
try {
const refreshToken = localStorage.getItem('refreshToken');
if (!refreshToken) {
throw new Error('No refresh token available');
}
const { accessToken: newAccessToken, user } = await AuthService.refreshToken(refreshToken);
localStorage.setItem('accessToken', newAccessToken);
if (user) {
dispatch({ type: 'AUTH_SUCCESS', payload: user });
}
} catch (error) {
console.error('Token refresh failed:', error);
logout();
throw error;
}
};
const clearError = () => {
dispatch({ type: 'CLEAR_ERROR' });
};
return (
<AuthContext.Provider value={{
...state,
login,
signup,
logout,
refreshToken,
clearError,
}}>
{children}
</AuthContext.Provider>
);
}
export function useAuth() {
const context = useContext(AuthContext);
if (context === undefined) {
throw new Error('useAuth must be used within an AuthProvider');
}
return context;
}