fix(frontend): auth error handler 5xx

This commit is contained in:
sky5454 2026-04-08 20:38:31 +08:00
parent 4d6145abc2
commit 13c9a2087a

View file

@ -1,6 +1,6 @@
import { Outlet, createRootRoute, useRouterState } from "@tanstack/react-router"
import { TanStackRouterDevtools } from "@tanstack/react-router-devtools"
import { useEffect } from "react"
import { useEffect, useState } from "react"
import { getLauncherAuthStatus } from "@/api/launcher-auth"
import { AppLayout } from "@/components/app-layout"
@ -30,6 +30,8 @@ const RootLayout = () => {
(m) => m.routeId === "/launcher-login" || m.routeId === "/launcher-setup",
)
const [authError, setAuthError] = useState<string | null>(null)
// Session guard: proactively check auth status on every page load.
// This catches the case where ?token= auto-login bypassed the login/setup UI.
useEffect(() => {
@ -43,12 +45,16 @@ const RootLayout = () => {
}
})
.catch((err: unknown) => {
// For real HTTP errors (e.g. 503 when the auth store is unavailable),
// redirect to login so the user can re-authenticate rather than being
// silently stranded on the dashboard. Network failures (no response)
// are left alone — launcherFetch handles the 401 redirect on real calls.
if (err instanceof Error && /^status \d+$/.test(err.message)) {
// On 401/403, redirect to login — the session is invalid.
// On 5xx (e.g. 503 when the auth store is unavailable) or network errors,
// do NOT redirect: a subsequent successful login would loop straight back here.
// launcherFetch handles 401 on real API calls regardless.
if (err instanceof Error && /^status 40[13]$/.test(err.message)) {
globalThis.location.assign("/launcher-login")
} else {
setAuthError(
err instanceof Error ? err.message : "Auth service unavailable, please try to delete the launcher-auth.db at picoclaw home directory and restart the application.",
)
}
})
}, [isAuthPage])
@ -70,10 +76,24 @@ const RootLayout = () => {
}
return (
<AppLayout>
<Outlet />
{import.meta.env.DEV ? <TanStackRouterDevtools /> : null}
</AppLayout>
<>
{authError && (
<div className="bg-destructive text-destructive-foreground fixed inset-x-0 top-0 z-[100] flex items-center justify-between px-4 py-2 text-sm shadow-md">
<span>Auth service error: {authError}</span>
<button
className="ml-4 opacity-70 hover:opacity-100"
onClick={() => setAuthError(null)}
aria-label="Dismiss"
>
</button>
</div>
)}
<AppLayout>
<Outlet />
{import.meta.env.DEV ? <TanStackRouterDevtools /> : null}
</AppLayout>
</>
)
}