From 1f419dc0785b5eb43a2f32be8cdc9f7e7ad22d42 Mon Sep 17 00:00:00 2001 From: canvas-bot Date: Mon, 15 Jun 2026 01:25:37 +0400 Subject: [PATCH] fix(login): persona click signs in directly + friendly error copy - Persona chips (Mariana CEO, Aisha HR, Rohan IT) now run dev-login on click instead of just populating the email field. Closes the dogfood failure 'ceo_persona_dev_login_lands_on_landing'. - api.ts: replace 'Login failed: 502 ...502 Bad Gateway...' with a human-readable friendlyAuthError() mapping (502/504/503/404/401) so a regional store manager sees 'auth service temporarily down' not an HTTP status dump. Aligns with the business-as-code mandate. - api.test: ping() failure assertion widened to match the new copy. 31/31 vitest tests pass. tsc + vite build green. --- src/lib/api.test.ts | 2 +- src/lib/api.ts | 16 +++++++++++----- src/scenes/Login.tsx | 17 +++++++++++++++-- 3 files changed, 27 insertions(+), 8 deletions(-) diff --git a/src/lib/api.test.ts b/src/lib/api.test.ts index abfc7de..ec3f5d9 100644 --- a/src/lib/api.test.ts +++ b/src/lib/api.test.ts @@ -56,7 +56,7 @@ describe("api client", () => { ]); const r = await api.ping(); expect(r.ok).toBe(false); - expect(r.reason).toMatch(/401/); + expect(r.reason).toMatch(/rejected|401|sign-in/i); }); it("workItems() returns the items array", async () => { diff --git a/src/lib/api.ts b/src/lib/api.ts index 9387b07..3f5dbf5 100644 --- a/src/lib/api.ts +++ b/src/lib/api.ts @@ -19,6 +19,14 @@ const DEFAULT_CONFIG: ApiConfig = { const TOKEN_KEY = "fm.mc.token.v1"; +function friendlyAuthError(status: number, kind: string): string { + if (status === 502 || status === 504) return `${kind} unavailable: the EA2 backend is not reachable from this environment right now. Try again shortly.`; + if (status === 503) return `${kind} unavailable: the auth service is temporarily down. Try again shortly.`; + if (status === 404) return `${kind} is not wired to this environment yet.`; + if (status === 401 || status === 403) return `${kind} rejected: check the email and password.`; + return `${kind} failed (HTTP ${status}). Try again or contact the operator on call.`; +} + let inflightLogin: Promise | null = null; async function login(cfg: ApiConfig, signal?: AbortSignal): Promise { @@ -30,7 +38,7 @@ async function login(cfg: ApiConfig, signal?: AbortSignal): Promise { body: JSON.stringify({ email: cfg.email }), signal, }); - if (!r.ok) throw new Error(`dev-login ${r.status}`); + if (!r.ok) throw new Error(friendlyAuthError(r.status, "Developer sign-in")); const body = (await r.json()) as { access_token: string }; sessionStorage.setItem(TOKEN_KEY, body.access_token); return body.access_token; @@ -213,8 +221,7 @@ export const api = { }; const r = await instrumentedFetch("POST", `${this.config.baseUrl}/api/v1/auth/login`, init, { email, password }); if (!r.ok) { - const text = await r.text().catch(() => ""); - throw new Error(`Login failed: ${r.status} ${text.slice(0, 100)}`); + throw new Error(friendlyAuthError(r.status, "Sign-in")); } const data = await r.json() as { access_token: string; refresh_token?: string }; this.setBearer(data.access_token); @@ -230,8 +237,7 @@ export const api = { }; const r = await instrumentedFetch("POST", `${this.config.baseUrl}/api/v1/auth/dev-login`, init, { email }); if (!r.ok) { - const text = await r.text().catch(() => ""); - throw new Error(`Dev-login failed: ${r.status} ${text.slice(0, 100)}`); + throw new Error(friendlyAuthError(r.status, "Developer sign-in")); } const data = await r.json() as { access_token: string; refresh_token?: string }; this.setBearer(data.access_token); diff --git a/src/scenes/Login.tsx b/src/scenes/Login.tsx index d944183..c0ae28f 100644 --- a/src/scenes/Login.tsx +++ b/src/scenes/Login.tsx @@ -206,9 +206,22 @@ export default function Login() { key={p.key} type="button" className="persona-chip" - onClick={() => setEmail(p.email)} + onClick={async () => { + setEmail(p.email); + if (!devLoginEnabled) return; + setLoading(true); + setError(null); + try { + await loginAs(p.email, undefined, { method: "dev" }); + setScene("landing"); + } catch (err) { + setError((err as Error).message); + } finally { + setLoading(false); + } + }} disabled={loading} - title={p.email} + title={`Sign in as ${p.title}`} > {p.title} -- 2.54.0