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.
User asked to remove the hardcoded "TeaBoy" branding and let admins
change the system name. Also completes the in-progress service image
upload work via App Storage.
Changes:
- New app_settings table (single row id=1) with siteNameAr/siteNameEn
- New /settings endpoints: GET (public) + PATCH (admin)
- Atomic ensureSettingsRow via INSERT ... ON CONFLICT DO NOTHING
to avoid race conditions
- New SiteSettingsPanel tab in admin page (Arabic + English inputs)
- New useAppName hook reads settings, updates document.title, falls
back to defaults while loading
- Login + register pages now display the dynamic site name
- Service image upload (App Storage) wired via useUpload + presigned
GCS URL flow; admin component ServiceImageUploader
- Storage routes: /storage/uploads/request-url and /storage/objects/*
now require auth (closes previously-open endpoints flagged by review)
- Added AppSettings/UpdateAppSettingsBody + storage schemas to
openapi.yaml; regenerated client and zod
- Exposed UploadResponse from @workspace/object-storage-web; added
composite:true so it can be referenced by teaboy-os tsconfig
Validation: typechecks pass for api-server and teaboy-os; settings GET
returns row; upload URL endpoint returns 401 without auth.