Login: evitar pantalla en blanco al arrancar
- ErrorBoundary global: si un modulo falla al renderizar se muestra un mensaje amable con recargar en lugar de pantalla en blanco. - El bootstrap de auth ya no se queda cargando para siempre: si /auth/me no responde en 20 s (red lenta o tunel caido) se muestra una pantalla de reintento. Si la red fallo se conserva la sesion; solo se descarta el token cuando el servidor contesta (401).
This commit is contained in:
@@ -59,10 +59,18 @@ const MENU_ROLES: Record<string, FrontendRole[]> = {
|
||||
'/chat': ['admin', 'medico', 'recepcion', 'enfermeria', 'cosmetologia', 'psicologa', 'farmacia', 'gerencia', 'lectura'],
|
||||
};
|
||||
|
||||
// Si /auth/me no responde en este tiempo (red lenta o caída), se muestra la
|
||||
// pantalla de reintento en lugar del spinner eterno ("pantalla en blanco").
|
||||
const BOOT_TIMEOUT_MS = 20000;
|
||||
|
||||
interface AuthContextValue {
|
||||
user: FrontendUser | null;
|
||||
token: string | null;
|
||||
loading: boolean;
|
||||
// Error de arranque (servidor no responde): la app muestra Reintentar
|
||||
// en lugar de quedarse cargando para siempre.
|
||||
bootError: string | null;
|
||||
retryBoot: () => void;
|
||||
login: (login: string, password: string) => Promise<FrontendUser>;
|
||||
logout: () => Promise<void>;
|
||||
hasRole: (min: FrontendRole) => boolean;
|
||||
@@ -75,6 +83,9 @@ export const AuthProvider: FC<{ children: ReactNode }> = ({ children }) => {
|
||||
const [user, setUser] = useState<FrontendUser | null>(null);
|
||||
const [token, setToken] = useState<string | null>(() => localStorage.getItem(TOKEN_KEY));
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [bootError, setBootError] = useState<string | null>(null);
|
||||
// Cada reintento de arranque re-ejecuta la validación del token.
|
||||
const [bootAttempt, setBootAttempt] = useState(0);
|
||||
// Matriz dinámica de permisos por rol (cache en localStorage; fallback a MENU_ROLES)
|
||||
const [menuRoles, setMenuRoles] = useState<Record<string, FrontendRole[]> | null>(() => {
|
||||
try {
|
||||
@@ -109,6 +120,7 @@ export const AuthProvider: FC<{ children: ReactNode }> = ({ children }) => {
|
||||
// Validar token al arrancar
|
||||
useEffect(() => {
|
||||
let alive = true;
|
||||
let timer: number | undefined;
|
||||
const t = localStorage.getItem(TOKEN_KEY);
|
||||
if (!t) {
|
||||
setLoading(false);
|
||||
@@ -119,6 +131,13 @@ export const AuthProvider: FC<{ children: ReactNode }> = ({ children }) => {
|
||||
if (cached) {
|
||||
try { setUser(JSON.parse(cached)); } catch { /* ignore */ }
|
||||
}
|
||||
// Red de seguridad: si el servidor no responde (red lenta/caída del
|
||||
// túnel), mostrar pantalla de reintento en vez del spinner eterno.
|
||||
timer = window.setTimeout(() => {
|
||||
if (!alive) return;
|
||||
setBootError('El servidor no responde. Revisa tu conexión e intenta de nuevo.');
|
||||
setLoading(false);
|
||||
}, BOOT_TIMEOUT_MS);
|
||||
odooApi.me()
|
||||
.then((res) => {
|
||||
if (!alive) return;
|
||||
@@ -129,11 +148,30 @@ export const AuthProvider: FC<{ children: ReactNode }> = ({ children }) => {
|
||||
} else {
|
||||
clear();
|
||||
}
|
||||
setBootError(null);
|
||||
})
|
||||
.catch(() => { if (alive) clear(); })
|
||||
.finally(() => { if (alive) setLoading(false); });
|
||||
return () => { alive = false; };
|
||||
}, [clear, refreshMenuRoles]);
|
||||
.catch((err) => {
|
||||
if (!alive) return;
|
||||
// Solo se descarta la sesión si el servidor contestó (token inválido,
|
||||
// 401). Si la red falló se conserva el token para el reintento.
|
||||
if (err?.response) clear();
|
||||
else setBootError('No se pudo conectar con el servidor. Revisa tu conexión e intenta de nuevo.');
|
||||
})
|
||||
.finally(() => {
|
||||
if (timer) window.clearTimeout(timer);
|
||||
if (alive) setLoading(false);
|
||||
});
|
||||
return () => {
|
||||
alive = false;
|
||||
if (timer) window.clearTimeout(timer);
|
||||
};
|
||||
}, [clear, refreshMenuRoles, bootAttempt]);
|
||||
|
||||
const retryBoot = useCallback(() => {
|
||||
setBootError(null);
|
||||
setLoading(true);
|
||||
setBootAttempt((n) => n + 1);
|
||||
}, []);
|
||||
|
||||
const login = async (login: string, password: string): Promise<FrontendUser> => {
|
||||
const res = await odooApi.login(login, password);
|
||||
@@ -182,7 +220,7 @@ export const AuthProvider: FC<{ children: ReactNode }> = ({ children }) => {
|
||||
};
|
||||
|
||||
return (
|
||||
<AuthContext.Provider value={{ user, token, loading, login, logout, hasRole, canSee }}>
|
||||
<AuthContext.Provider value={{ user, token, loading, bootError, retryBoot, login, logout, hasRole, canSee }}>
|
||||
{children}
|
||||
</AuthContext.Provider>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user