Kyle Pope 3d7166740e Fix lock screen flash, theme flicker, and lock state gating
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>
2026-03-12 19:56:05 +08:00

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;