From a9b4fd86ff044be083e7967c5b4c294041db6e46 Mon Sep 17 00:00:00 2001 From: riyadhafraa <49757212-riyadhafraa@users.noreply.replit.com> Date: Tue, 19 May 2026 11:48:16 +0000 Subject: [PATCH] Task #615: surface real upload errors in shared upload hook MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The admin Edit Service image picker and the executive-meetings logo uploader on the Mac deployment were both failing with a generic toast ("Failed to generate upload URL" / "فشل رفع الصورة") that swallowed the actual server cause, making the iPhone-reported failure impossible to diagnose from the UI alone. Step 1 of the task: improve the diagnostic surface in `lib/object-storage-web/src/use-upload.ts` so both call sites (which already toast `err.message`) show what actually went wrong: - `requestUploadUrl`: include the server's `error`/`message` field AND the HTTP status code in the thrown Error. - `uploadToPresignedUrl`: distinguish network-level fetch failures (CORS / unreachable PUBLIC_BASE_URL host — the most likely Mac cause) from HTTP failures, and include the host + body snippet + status in each case. No backend or call-site changes; error semantics at the hook boundary are preserved (still rejects with Error, still invokes onError, still returns null from uploadFile). After this commit lands and is published to Gitea + redeployed on the Mac, re-trying the upload will reveal the real cause in the toast, which feeds Step 2 (root-cause fix) of Task #615. The follow-up "publish #615 to Gitea + Mac redeploy" lives in Task #614 (already proposed; blocked by concurrency). Code review (architect): PASS. Optional follow-up noted — `getUploadParameters` (Uppy path) still has the generic error message; not used by the two failing uploaders, left for later. --- lib/object-storage-web/src/use-upload.ts | 48 +++++++++++++++++++----- 1 file changed, 39 insertions(+), 9 deletions(-) diff --git a/lib/object-storage-web/src/use-upload.ts b/lib/object-storage-web/src/use-upload.ts index 7ec850e4..7808fa66 100644 --- a/lib/object-storage-web/src/use-upload.ts +++ b/lib/object-storage-web/src/use-upload.ts @@ -75,7 +75,14 @@ export function useUpload(options: UseUploadOptions = {}) { if (!response.ok) { const errorData = await response.json().catch(() => ({})); - throw new Error(errorData.error || "Failed to get upload URL"); + const serverMsg = + (typeof errorData?.error === "string" && errorData.error) || + (typeof errorData?.message === "string" && errorData.message) || + ""; + const detail = serverMsg + ? `${serverMsg} (HTTP ${response.status})` + : `HTTP ${response.status} ${response.statusText || ""}`.trim(); + throw new Error(`Failed to get upload URL: ${detail}`); } return response.json(); @@ -85,16 +92,39 @@ export function useUpload(options: UseUploadOptions = {}) { const uploadToPresignedUrl = useCallback( async (file: File, uploadURL: string): Promise => { - const response = await fetch(uploadURL, { - method: "PUT", - body: file, - headers: { - "Content-Type": file.type || "application/octet-stream", - }, - }); + let response: Response; + try { + response = await fetch(uploadURL, { + method: "PUT", + body: file, + headers: { + "Content-Type": file.type || "application/octet-stream", + }, + }); + } catch (err) { + // Network-level failure (CORS, DNS, unreachable host). This is + // the typical symptom when PUBLIC_BASE_URL on the server points + // at an origin the browser can't actually reach (e.g. localhost + // while the user is on Tailscale). + const msg = err instanceof Error ? err.message : String(err); + let host = ""; + try { + host = new URL(uploadURL).host; + } catch { + /* ignore */ + } + throw new Error( + `Failed to upload file to storage: network error reaching ${host || "the storage endpoint"} — ${msg}`, + ); + } if (!response.ok) { - throw new Error("Failed to upload file to storage"); + const bodyText = await response.text().catch(() => ""); + const snippet = bodyText.slice(0, 200).trim(); + const detail = snippet + ? `${snippet} (HTTP ${response.status})` + : `HTTP ${response.status} ${response.statusText || ""}`.trim(); + throw new Error(`Failed to upload file to storage: ${detail}`); } }, []