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(); };