From 464f9bfdadee93ee9491c883ef3d632b779f58e6 Mon Sep 17 00:00:00 2001 From: canvas-bot Date: Mon, 15 Jun 2026 01:31:06 +0400 Subject: [PATCH] fix(login): show backend-health banner before the operator types MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Pre-checks /api/v1/auth/me on mount. The status code IS the signal: - 200/401/403 → backend up, login can succeed - 502/504 → proxy down (canvas nginx → demo unreachable) - 503 → auth service is down upstream Surfaces a yellow banner above the form when proxy or auth is down so a regional store manager doesn't type their password into a flow that's guaranteed to fail. Matches the friendlyAuthError() copy from PR #3. 31/31 vitest tests pass. --- src/index.css | 12 ++++++++++++ src/scenes/Login.tsx | 24 ++++++++++++++++++++++++ 2 files changed, 36 insertions(+) diff --git a/src/index.css b/src/index.css index 1dc9ca0..e349f5a 100644 --- a/src/index.css +++ b/src/index.css @@ -2237,3 +2237,15 @@ select.studio-input { background: var(--bp-paper); } @media (max-width: 700px) { .tab-badge { min-width: 14px; height: 12px; font-size: 8px; padding: 0 3px; } } + +.login-banner { + padding: 10px 12px; + margin-bottom: 12px; + border: 1px solid var(--bp-line, #b08a4a); + background: var(--bg-2, #fff6e0); + color: var(--text-1, #5a3a00); + font-size: 11.5px; + line-height: 1.45; + letter-spacing: 0.02em; +} +.login-banner-warn { border-color: #b08a4a; background: #fff6e0; color: #5a3a00; } diff --git a/src/scenes/Login.tsx b/src/scenes/Login.tsx index c0ae28f..9bd4197 100644 --- a/src/scenes/Login.tsx +++ b/src/scenes/Login.tsx @@ -12,6 +12,7 @@ export default function Login() { const buildAllowsDev = (import.meta.env.VITE_ENABLE_DEV_LOGIN ?? "true") !== "false"; const [devLoginEnabled, setDevLoginEnabled] = useState(buildAllowsDev); const [ssoState, setSsoState] = useState<"unknown" | "ready" | "not-configured" | "not-wired">("unknown"); + const [backendState, setBackendState] = useState<"unknown" | "up" | "auth-down" | "proxy-down">("unknown"); const [loading, setLoading] = useState(false); const [error, setError] = useState(null); @@ -30,6 +31,18 @@ export default function Login() { else setSsoState("not-wired"); }) .catch(() => setSsoState("not-wired")); + // Backend health precheck. /api/v1/auth/me with no token should return 401 + // when the backend is up. 502/504 means the proxy is down; 503 means the + // auth service is down. We surface this so the operator knows BEFORE they + // type their password. + fetch("/api/v1/auth/me", { method: "GET" }) + .then((r) => { + if (r.status === 401 || r.status === 403 || r.status === 200) setBackendState("up"); + else if (r.status === 502 || r.status === 504) setBackendState("proxy-down"); + else if (r.status === 503) setBackendState("auth-down"); + else setBackendState("up"); + }) + .catch(() => setBackendState("proxy-down")); }, [buildAllowsDev]); const handleSubmit = async (e: React.FormEvent) => { @@ -106,6 +119,17 @@ export default function Login() {
MISSION CONTROL VERIFICATION REQUIRED
+ {backendState === "proxy-down" && ( +
+ The FlowMaster backend is not reachable from this environment right now. Sign-in will fail until it is restored. +
+ )} + {backendState === "auth-down" && ( +
+ The authentication service is temporarily down. Sign-in will fail until it is restored. +
+ )} + {error &&
{error}
}
-- 2.54.0