Gate dashboard rendering on isLockResolved to prevent content flash before lock state is known. Remove animate-fade-in from LockOverlay so it renders instantly. Always write accent color to localStorage (even default cyan) to prevent theme flash on reload. Resolve lock state on auth query error to avoid permanent blank screen. Lift mobileOpen state above lock gate to survive lock/unlock cycles. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
41 lines
1.3 KiB
TypeScript
41 lines
1.3 KiB
TypeScript
import axios from 'axios';
|
|
|
|
const api = axios.create({
|
|
baseURL: '/api',
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
'X-Requested-With': 'XMLHttpRequest',
|
|
},
|
|
withCredentials: true,
|
|
});
|
|
|
|
api.interceptors.response.use(
|
|
(response) => response,
|
|
(error) => {
|
|
if (error.response?.status === 401) {
|
|
const url = error.config?.url || '';
|
|
// Don't redirect on auth endpoints — they legitimately return 401
|
|
const authEndpoints = ['/auth/login', '/auth/register', '/auth/setup', '/auth/verify-password', '/auth/change-password'];
|
|
if (!authEndpoints.some(ep => url.startsWith(ep))) {
|
|
window.location.href = '/login';
|
|
}
|
|
}
|
|
// 423 = session is locked server-side — trigger lock screen
|
|
if (error.response?.status === 423) {
|
|
window.dispatchEvent(new CustomEvent('umbra:session-locked'));
|
|
}
|
|
return Promise.reject(error);
|
|
}
|
|
);
|
|
|
|
export function getErrorMessage(error: unknown, fallback: string): string {
|
|
if (axios.isAxiosError(error) && error.response?.data?.detail) {
|
|
const detail = error.response.data.detail;
|
|
if (typeof detail === 'string') return detail;
|
|
if (Array.isArray(detail)) return detail.map((d: { msg?: string }) => d.msg || '').join(', ');
|
|
}
|
|
return fallback;
|
|
}
|
|
|
|
export default api;
|