This commit is contained in:
2026-03-16 11:00:16 -04:00
parent 7c47f2d2eb
commit b1b8eb297e
45 changed files with 3701 additions and 3736 deletions

View File

@@ -1,4 +1,4 @@
import { createContext, useContext, useState, useEffect, ReactNode } from 'react';
import { createContext, useContext, useState, useEffect, ReactNode, useCallback } from 'react';
import { useNavigate } from 'react-router-dom';
import { useLogin, useLogout, useGetMe } from '../generated/anthoLumeAPIV1';
@@ -9,7 +9,7 @@ interface AuthState {
}
interface AuthContextType extends AuthState {
login: (username: string, password: string) => Promise<void>;
login: (_username: string, _password: string) => Promise<void>;
logout: () => void;
}
@@ -24,7 +24,7 @@ export function AuthProvider({ children }: { children: ReactNode }) {
const loginMutation = useLogin();
const logoutMutation = useLogout();
// Always call /me to check authentication status
const { data: meData, error: meError, isLoading: meLoading } = useGetMe();
@@ -32,50 +32,56 @@ export function AuthProvider({ children }: { children: ReactNode }) {
// Update auth state based on /me endpoint response
useEffect(() => {
if (meLoading) {
// Still checking authentication
setAuthState((prev) => ({ ...prev, isCheckingAuth: true }));
} else if (meData?.data) {
// User is authenticated
setAuthState({
isAuthenticated: true,
user: meData.data,
isCheckingAuth: false,
});
} else if (meError) {
// User is not authenticated or error occurred
setAuthState({
isAuthenticated: false,
user: null,
isCheckingAuth: false,
});
}
setAuthState(prev => {
if (meLoading) {
// Still checking authentication
return { ...prev, isCheckingAuth: true };
} else if (meData?.data) {
// User is authenticated
return {
isAuthenticated: true,
user: meData.data,
isCheckingAuth: false,
};
} else if (meError) {
// User is not authenticated or error occurred
return {
isAuthenticated: false,
user: null,
isCheckingAuth: false,
};
}
return prev;
});
}, [meData, meError, meLoading]);
const login = async (username: string, password: string) => {
try {
const response = await loginMutation.mutateAsync({
data: {
username,
password,
},
});
const login = useCallback(
async (username: string, password: string) => {
try {
const response = await loginMutation.mutateAsync({
data: {
username,
password,
},
});
// The backend uses session-based authentication, so no token to store
// The session cookie is automatically set by the browser
setAuthState({
isAuthenticated: true,
user: response.data,
isCheckingAuth: false,
});
// The backend uses session-based authentication, so no token to store
// The session cookie is automatically set by the browser
setAuthState({
isAuthenticated: true,
user: response.data,
isCheckingAuth: false,
});
navigate('/');
} catch (err) {
throw new Error('Login failed');
}
};
navigate('/');
} catch (_error) {
throw new Error('Login failed');
}
},
[loginMutation, navigate]
);
const logout = () => {
const logout = useCallback(() => {
logoutMutation.mutate(undefined, {
onSuccess: () => {
setAuthState({
@@ -86,12 +92,10 @@ export function AuthProvider({ children }: { children: ReactNode }) {
navigate('/login');
},
});
};
}, [logoutMutation, navigate]);
return (
<AuthContext.Provider value={{ ...authState, login, logout }}>
{children}
</AuthContext.Provider>
<AuthContext.Provider value={{ ...authState, login, logout }}>{children}</AuthContext.Provider>
);
}
@@ -101,4 +105,4 @@ export function useAuth() {
throw new Error('useAuth must be used within an AuthProvider');
}
return context;
}
}

View File

@@ -20,4 +20,4 @@ export function ProtectedRoute({ children }: ProtectedRouteProps) {
}
return children;
}
}

View File

@@ -4,24 +4,24 @@ const TOKEN_KEY = 'antholume_token';
// Request interceptor to add auth token to requests
axios.interceptors.request.use(
(config) => {
config => {
const token = localStorage.getItem(TOKEN_KEY);
if (token && config.headers) {
config.headers.Authorization = `Bearer ${token}`;
}
return config;
},
(error) => {
error => {
return Promise.reject(error);
}
);
// Response interceptor to handle auth errors
axios.interceptors.response.use(
(response) => {
response => {
return response;
},
(error) => {
error => {
if (error.response?.status === 401) {
// Clear token on auth failure
localStorage.removeItem(TOKEN_KEY);
@@ -32,4 +32,4 @@ axios.interceptors.response.use(
}
);
export default axios;
export default axios;