diff --git a/frontend/src/api/client.ts b/frontend/src/api/client.ts index 41004d8..42c83bb 100644 --- a/frontend/src/api/client.ts +++ b/frontend/src/api/client.ts @@ -16,11 +16,22 @@ import type { export const BASE_URL = import.meta.env.VITE_API_BASE_URL ?? "http://localhost:8080"; async function request(path: string, init?: RequestInit): Promise { - const res = await fetch(`${BASE_URL}${path}`, { - headers: { "Content-Type": "application/json" }, - credentials: "include", - ...init, - }); + let res: Response; + try { + res = await fetch(`${BASE_URL}${path}`, { + headers: { "Content-Type": "application/json" }, + credentials: "include", + ...init, + }); + } catch { + // fetch() itself rejecting (not a non-2xx response, which is handled + // below) means the backend genuinely isn't reachable -- offline, + // connection refused, CORS, etc. The raw browser error text here + // (e.g. "TypeError: Failed to fetch") is not something to show a user; + // every caller's catch-and-showError already displays whatever this + // throws, so the friendly text only needs to be written once, here. + throw new Error("Can't reach the server — check your connection."); + } if (res.status === 401 && path !== "/api/session/me") { // An established session expired mid-use (not the initial "am I logged // in" check, which LoginGate itself interprets) -- reload so LoginGate