Migrate AuthContext

This commit is contained in:
2025-05-08 21:01:20 +02:00
parent 1e350bb0cf
commit 1a06c31705
4 changed files with 137 additions and 149 deletions

View File

@@ -1,25 +1,15 @@
import {
API_BASE_URL,
User,
LoginRequest,
LoginResponse,
isLoginResponse,
isUser,
} from '../types/authApi';
import { API_BASE_URL, User, LoginRequest, isUser } from '../types/authApi';
import { apiCall } from './api';
/**
* Logs in a user with email and password
* @param {string} email - The user's email
* @param {string} password - The user's password
* @returns {Promise<LoginResponse>} A promise that resolves to the login response
* @returns {Promise<User>} A promise that resolves to the user
* @throws {Error} If the API call fails or returns an invalid response
* @throws {Error} If the login fails
*/
export const login = async (
email: string,
password: string
): Promise<LoginResponse> => {
export const login = async (email: string, password: string): Promise<User> => {
const loginData: LoginRequest = { email, password };
const response = await apiCall(`${API_BASE_URL}/auth/login`, {
method: 'POST',
@@ -27,11 +17,11 @@ export const login = async (
});
const data = await response.json();
if (!isLoginResponse(data)) {
throw new Error('Invalid login response received from API');
if (!('user' in data) || !isUser(data.user)) {
throw new Error('Invalid login response from API');
}
return data;
return data.user;
};
/**

View File

@@ -1,108 +0,0 @@
import React, {
createContext,
useContext,
useState,
useCallback,
useEffect,
} from 'react';
import { notifications } from '@mantine/notifications';
import * as authApi from '../api/auth';
const AuthContext = createContext(null);
export const AuthProvider = ({ children }) => {
const [user, setUser] = useState(null);
const [loading, setLoading] = useState(true);
const [initialized, setInitialized] = useState(false);
// Load user data on mount
useEffect(() => {
const initializeAuth = async () => {
try {
const userData = await authApi.getCurrentUser();
setUser(userData);
} catch (error) {
console.error('Failed to initialize auth:', error);
} finally {
setLoading(false);
setInitialized(true);
}
};
initializeAuth();
}, []);
const login = useCallback(async (email, password) => {
try {
const { user: userData } = await authApi.login(email, password);
setUser(userData);
notifications.show({
title: 'Success',
message: 'Logged in successfully',
color: 'green',
});
return true;
} catch (error) {
console.error('Login failed:', error);
notifications.show({
title: 'Error',
message: error.message || 'Login failed',
color: 'red',
});
return false;
}
}, []);
const logout = useCallback(async () => {
try {
await authApi.logout();
} catch (error) {
console.error('Logout failed:', error);
} finally {
setUser(null);
}
}, []);
const refreshToken = useCallback(async () => {
try {
const success = await authApi.refreshToken();
if (!success) {
await logout();
}
return success;
} catch (error) {
console.error('Token refresh failed:', error);
await logout();
return false;
}
}, [logout]);
const refreshUser = useCallback(async () => {
try {
const userData = await authApi.getCurrentUser();
setUser(userData);
} catch (error) {
console.error('Failed to refresh user data:', error);
}
}, []);
const value = {
user,
loading,
initialized,
login,
logout,
refreshToken,
refreshUser,
};
return <AuthContext.Provider value={value}>{children}</AuthContext.Provider>;
};
export const useAuth = () => {
const context = useContext(AuthContext);
if (!context) {
throw new Error('useAuth must be used within an AuthProvider');
}
return context;
};

View File

@@ -0,0 +1,131 @@
import React, {
createContext,
useContext,
useState,
useCallback,
useEffect,
} from 'react';
import { notifications } from '@mantine/notifications';
import {
login as apiLogin,
logout as apiLogout,
refreshToken as apiRefreshToken,
getCurrentUser,
} from '@/api/auth';
import { User } from '@/types/authApi';
interface AuthContextType {
user: User | null;
loading: boolean;
initialized: boolean;
login: (email: string, password: string) => Promise<boolean>;
logout: () => Promise<void>;
refreshToken: () => Promise<boolean>;
refreshUser: () => Promise<void>;
}
const AuthContext = createContext<AuthContextType | null>(null);
interface AuthProviderProps {
children: React.ReactNode;
}
export const AuthProvider: React.FC<AuthProviderProps> = ({ children }) => {
const [user, setUser] = useState<User | null>(null);
const [loading, setLoading] = useState<boolean>(true);
const [initialized, setInitialized] = useState<boolean>(false);
// Load user data on mount
useEffect(() => {
const initializeAuth = async (): Promise<void> => {
try {
const userData = await getCurrentUser();
setUser(userData);
} catch (error) {
console.error('Failed to initialize auth:', error);
} finally {
setLoading(false);
setInitialized(true);
}
};
initializeAuth();
}, []);
const login = useCallback(
async (email: string, password: string): Promise<boolean> => {
try {
const userData = await apiLogin(email, password);
setUser(userData);
notifications.show({
title: 'Success',
message: 'Logged in successfully',
color: 'green',
});
return true;
} catch (error) {
console.error('Login failed:', error);
notifications.show({
title: 'Error',
message: error instanceof Error ? error.message : 'Login failed',
color: 'red',
});
return false;
}
},
[]
);
const logout = useCallback(async (): Promise<void> => {
try {
await apiLogout();
} catch (error) {
console.error('Logout failed:', error);
} finally {
setUser(null);
}
}, []);
const refreshToken = useCallback(async (): Promise<boolean> => {
try {
const success = await apiRefreshToken();
if (!success) {
await logout();
}
return success;
} catch (error) {
console.error('Token refresh failed:', error);
await logout();
return false;
}
}, [logout]);
const refreshUser = useCallback(async (): Promise<void> => {
try {
const userData = await getCurrentUser();
setUser(userData);
} catch (error) {
console.error('Failed to refresh user data:', error);
}
}, []);
const value: AuthContextType = {
user,
loading,
initialized,
login,
logout,
refreshToken,
refreshUser,
};
return <AuthContext.Provider value={value}>{children}</AuthContext.Provider>;
};
export const useAuth = (): AuthContextType => {
const context = useContext(AuthContext);
if (!context) {
throw new Error('useAuth must be used within an AuthProvider');
}
return context;
};

View File

@@ -72,31 +72,6 @@ export interface LoginRequest {
password: string;
}
/**
* Login response from the API
*/
export interface LoginResponse {
user: User;
sessionId: string;
expiresAt: string;
}
/**
* Type guard to check if a value is a valid LoginResponse
*/
export function isLoginResponse(value: unknown): value is LoginResponse {
return (
typeof value === 'object' &&
value !== null &&
'user' in value &&
isUser((value as LoginResponse).user) &&
'sessionId' in value &&
typeof (value as LoginResponse).sessionId === 'string' &&
'expiresAt' in value &&
typeof (value as LoginResponse).expiresAt === 'string'
);
}
/**
* API call options extending the standard RequestInit
*/