From 249bd7125ffe679458fef155f1df33c94fd44917 Mon Sep 17 00:00:00 2001 From: henrique221 Date: Fri, 19 Jun 2026 19:35:12 -0300 Subject: [PATCH] fix(export): send credentials and surface server error on USFM export MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit useExportUsfm omitted credentials: 'include' — the only API call in the app that did. It worked only because the export endpoint was unauthenticated; once fluent-api requires auth (eten-tech-foundation/fluent-api#189) the request would 401 and the editor export would break. - Add credentials: 'include' so the session cookie is sent. - Parse the JSON error body (supports both { message } and the USFM route's { error } shape) and surface it, so ExportProjectDialog shows the actual reason (e.g. 'Invalid book IDs') instead of a hardcoded 'Export Failed'. Refs eten-tech-foundation/fluent-api#195 --- .../components/ExportProjectDialog.tsx | 2 +- src/features/projects/hooks/useExportUsfm.ts | 18 +++++++++++++++++- 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/src/features/projects/components/ExportProjectDialog.tsx b/src/features/projects/components/ExportProjectDialog.tsx index c4c27f21..640ff51f 100644 --- a/src/features/projects/components/ExportProjectDialog.tsx +++ b/src/features/projects/components/ExportProjectDialog.tsx @@ -115,7 +115,7 @@ export const ExportProjectDialog: React.FC = ({ onClose(); } catch (err) { - setError('Export Failed'); + setError(err instanceof Error ? err.message : 'Export Failed'); Logger.logException(err, { context: 'Export failed', projectUnitId, diff --git a/src/features/projects/hooks/useExportUsfm.ts b/src/features/projects/hooks/useExportUsfm.ts index 71955ef2..5c3edbd5 100644 --- a/src/features/projects/hooks/useExportUsfm.ts +++ b/src/features/projects/hooks/useExportUsfm.ts @@ -7,16 +7,32 @@ interface ExportUsfmPayload { bookIds: number[]; } +const DEFAULT_EXPORT_ERROR = 'Failed to export USFM'; + +// The export endpoint streams a ZIP on success and returns a JSON error body +// otherwise. Surface the server's message (supports both the `{ message }` +// envelope and the USFM route's `{ error }` shape) so the dialog can show +// something actionable instead of a generic failure. +const parseExportError = async (response: Response): Promise => { + try { + const data = (await response.json()) as { message?: string; error?: string }; + return data.message ?? data.error ?? DEFAULT_EXPORT_ERROR; + } catch { + return DEFAULT_EXPORT_ERROR; + } +}; + const exportUsfmRequest = async (payload: ExportUsfmPayload): Promise => { const { projectUnitId, bookIds } = payload; const response = await fetch(`${config.api.url}/project-units/${projectUnitId}/usfm`, { method: 'POST', + credentials: 'include', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ bookIds }), }); - if (!response.ok) throw new Error('Failed to export USFM'); + if (!response.ok) throw new Error(await parseExportError(response)); return response.blob(); };