From a147f5cffaef71f247733506ecfdaf29fcc50166 Mon Sep 17 00:00:00 2001 From: Christophe Vila Date: Mon, 27 Jul 2026 09:41:34 +0200 Subject: [PATCH] fix(frontend): show a friendly message when the backend is unreachable request()'s fetch() call throwing (network down, connection refused, offline) previously propagated the raw browser error text (e.g. "TypeError: Failed to fetch"). Centralizing the friendly message here, rather than in each call site, means every existing catch((e) => showError(String(e))) pattern gets it for free. --- frontend/src/api/client.ts | 21 ++++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) 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