From c37df722fdfaba4d1e960730da96b42f0fce35c8 Mon Sep 17 00:00:00 2001 From: Anthony LC Date: Tue, 6 Oct 2026 16:20:44 +0200 Subject: [PATCH] =?UTF-8?q?fixup!=20=F0=9F=A5=85(frontend)=20change=20ui?= =?UTF-8?q?=20when=20catch=20unexpected=20upload=20error?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../doc-editor/components/BlockNoteEditor.tsx | 8 ++ .../hook/__tests__/useUploadFile.test.tsx | 20 +++++ .../docs/doc-editor/hook/useUploadFile.tsx | 80 ++++++------------- 3 files changed, 53 insertions(+), 55 deletions(-) diff --git a/src/frontend/apps/impress/src/features/docs/doc-editor/components/BlockNoteEditor.tsx b/src/frontend/apps/impress/src/features/docs/doc-editor/components/BlockNoteEditor.tsx index 8425caec7..92308f65c 100644 --- a/src/frontend/apps/impress/src/features/docs/doc-editor/components/BlockNoteEditor.tsx +++ b/src/frontend/apps/impress/src/features/docs/doc-editor/components/BlockNoteEditor.tsx @@ -23,6 +23,10 @@ import { import { FloatingComposerController, FloatingThreadController, + ReactAudioBlock, + ReactFileBlock, + ReactImageBlock, + ReactVideoBlock, ThreadsSidebar, useCreateBlockNote, } from '@blocknote/react'; @@ -83,6 +87,10 @@ const baseBlockNoteSchema = withPageBreak( BlockNoteSchema.create({ blockSpecs: { ...defaultBlockSpecs, + audio: ReactAudioBlock(), + file: ReactFileBlock(), + image: ReactImageBlock(), + video: ReactVideoBlock(), callout: CalloutBlock(), codeBlock: createSafeCodeBlockSpec(), diagram: createReactDiagramBlockSpec(), diff --git a/src/frontend/apps/impress/src/features/docs/doc-editor/hook/__tests__/useUploadFile.test.tsx b/src/frontend/apps/impress/src/features/docs/doc-editor/hook/__tests__/useUploadFile.test.tsx index 366816054..b31099bcb 100644 --- a/src/frontend/apps/impress/src/features/docs/doc-editor/hook/__tests__/useUploadFile.test.tsx +++ b/src/frontend/apps/impress/src/features/docs/doc-editor/hook/__tests__/useUploadFile.test.tsx @@ -71,6 +71,26 @@ describe('useUploadFile', () => { 'error', ); }); + + it('toasts the causes of the API and rethrows when the upload fails', async () => { + fetchMock.hardReset(); + fetchMock.mockGlobal(); + fetchMock.post(uploadUrl, { + status: 400, + body: { file: ['File type not allowed.'] }, + }); + + const result = renderUseUploadFile(); + + await expect( + result.current.uploadFile(createFile(MAX_FILE_SIZE)), + ).rejects.toThrow('Failed to upload on the doc'); + + expect(mockToast).toHaveBeenCalledWith( + 'A problem occurred while uploading the file, cause: File type not allowed.', + 'error', + ); + }); }); describe('useUploadStatus', () => { diff --git a/src/frontend/apps/impress/src/features/docs/doc-editor/hook/useUploadFile.tsx b/src/frontend/apps/impress/src/features/docs/doc-editor/hook/useUploadFile.tsx index c3dee556d..cd6330ebd 100644 --- a/src/frontend/apps/impress/src/features/docs/doc-editor/hook/useUploadFile.tsx +++ b/src/frontend/apps/impress/src/features/docs/doc-editor/hook/useUploadFile.tsx @@ -1,10 +1,10 @@ import { Block } from '@blocknote/core'; import { VariantType } from '@gouvfr-lasuite/ui-components'; import { captureException } from '@sentry/nextjs'; -import { useCallback, useEffect, useRef } from 'react'; +import { useCallback, useEffect } from 'react'; import { useTranslation } from 'react-i18next'; -import { backendUrl } from '@/api'; +import { APIError, backendUrl } from '@/api'; import { useConfig } from '@/core'; import { useToast } from '@/hooks'; import { formatFileSize } from '@/utils'; @@ -12,7 +12,6 @@ import { isSafeUrl } from '@/utils/url'; import { useCreateDocAttachment } from '../api'; import { ANALYZE_URL } from '../conf'; -import { useEditorStore } from '../stores'; import { DocsBlockNoteEditor } from '../types'; const DEFAULT_MAX_FILE_SIZE = 10 * 1024 * 1024; // Default to 10MB @@ -45,20 +44,12 @@ export const useUploadFile = (docId: string) => { const { t } = useTranslation(); const { toast } = useToast(); const { data: config } = useConfig(); - const { - mutateAsync: createDocAttachment, - isError: isErrorAttachment, - error: errorAttachment, - } = useCreateDocAttachment(); - const editor = useEditorStore((state) => state.editor); - const uploadBlockIdRef = useRef(undefined); + const { mutateAsync: createDocAttachment } = useCreateDocAttachment(); const maxFileSize = config?.DOCUMENT_IMAGE_MAX_SIZE ?? DEFAULT_MAX_FILE_SIZE; const uploadFile = useCallback( - async (file: File, blockId?: string) => { - uploadBlockIdRef.current = blockId; - + async (file: File) => { // The server rejects an oversized file, but the proxy in front of it usually cuts the // request first and answers a bare 413 the editor cannot make sense of. Telling the // user before sending anything saves them the wait and the cryptic message. @@ -80,53 +71,32 @@ export const useUploadFile = (docId: string) => { const body = new FormData(); body.append('file', file); - const ret = await createDocAttachment({ - docId, - body, - }); + try { + const ret = await createDocAttachment({ + docId, + body, + }); - return `${backendUrl()}${ret.file}`; + return `${backendUrl()}${ret.file}`; + } catch (error) { + const causes = error instanceof APIError ? error.cause : undefined; + + toast( + causes?.length + ? `${t('A problem occurred while uploading the file, cause:')} ${causes.join(' ')}` + : t( + 'A problem occurred while uploading the file, please try again.', + ), + VariantType.ERROR, + ); + + throw error; + } }, [createDocAttachment, docId, maxFileSize, t, toast], ); - /** - * Handle the upload error by replacing the block with an - * uploadLoader block displaying a warning message. - */ - useEffect(() => { - const blockId = uploadBlockIdRef.current; - if (!errorAttachment || !blockId || !editor) { - return; - } - - try { - editor.replaceBlocks( - [blockId], - [ - { - type: 'uploadLoader', - props: { - information: t( - 'A problem occurred while uploading the file, please try again.', - ), - type: 'warning', - }, - }, - ], - ); - } catch { - /* The block may be gone already (removed or updated by a collaborator) */ - } - - uploadBlockIdRef.current = undefined; - }, [editor, errorAttachment, t]); - - return { - uploadFile, - isErrorAttachment, - errorAttachment, - }; + return { uploadFile }; }; /**