From 04273c3b3e81f7b9b2186fa44950e4833a4e060c Mon Sep 17 00:00:00 2001 From: Anthony LC Date: Thu, 24 Jul 2025 11:45:09 +0200 Subject: [PATCH] =?UTF-8?q?=F0=9F=90=9B(frontend)=20redirection=20401=20ov?= =?UTF-8?q?erridden?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit To capture a 401 we were using "onError" in the queryClient default mutation options. The problem is this way does not capture globally the onError, if a mutation uses as well is own "onError", it will override the default one, causing the 401 to not be captured anymore. We now use MutationCache, which allows us to capture globally the onError, even if a mutation has its own "onError" defined, this global one will still be called. --- CHANGELOG.md | 1 + .../__tests__/app-impress/doc-routing.spec.ts | 17 +++---- .../apps/impress/src/core/AppProvider.tsx | 47 +++++++++++-------- .../docs/doc-management/api/useUpdateDoc.tsx | 8 +++- 4 files changed, 45 insertions(+), 28 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2d7fec8b..2e992f2c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -23,6 +23,7 @@ and this project adheres to - 🐛(service-worker) Fix useOffline Maximum update depth exceeded #1196 - 🐛(helm) charts generate invalid YAML for collaboration API / WS #890 +- 🐛(frontend) 401 redirection overridden #1214 ## [3.4.2] - 2025-07-18 diff --git a/src/frontend/apps/e2e/__tests__/app-impress/doc-routing.spec.ts b/src/frontend/apps/e2e/__tests__/app-impress/doc-routing.spec.ts index 83845be0..6f8eb7c6 100644 --- a/src/frontend/apps/e2e/__tests__/app-impress/doc-routing.spec.ts +++ b/src/frontend/apps/e2e/__tests__/app-impress/doc-routing.spec.ts @@ -9,6 +9,7 @@ import { mockedDocument, verifyDocName, } from './utils-common'; +import { createRootSubPage } from './utils-sub-pages'; test.describe('Doc Routing', () => { test.beforeEach(async ({ page }) => { @@ -60,16 +61,20 @@ test.describe('Doc Routing', () => { }); test('checks 401 on docs/[id] page', async ({ page, browserName }) => { - const [docTitle] = await createDoc(page, '401-doc', browserName, 1); + const [docTitle] = await createDoc(page, '401-doc-parent', browserName, 1); await verifyDocName(page, docTitle); + await createRootSubPage(page, browserName, '401-doc-child'); + + await page.locator('.ProseMirror.bn-editor').fill('Hello World'); + const responsePromise = page.route( - /.*\/link-configuration\/$|users\/me\/$/, + /.*\/documents\/.*\/$|users\/me\/$/, async (route) => { const request = route.request(); if ( - request.method().includes('PUT') || + request.method().includes('PATCH') || request.method().includes('GET') ) { await route.fulfill({ @@ -84,11 +89,7 @@ test.describe('Doc Routing', () => { }, ); - await page.getByRole('button', { name: 'Share' }).click(); - - const selectVisibility = page.getByLabel('Visibility', { exact: true }); - await selectVisibility.click(); - await page.getByLabel('Connected').click(); + await page.getByRole('link', { name: '401-doc-parent' }).click(); await responsePromise; diff --git a/src/frontend/apps/impress/src/core/AppProvider.tsx b/src/frontend/apps/impress/src/core/AppProvider.tsx index 9f18e114..b52f8fd2 100644 --- a/src/frontend/apps/impress/src/core/AppProvider.tsx +++ b/src/frontend/apps/impress/src/core/AppProvider.tsx @@ -1,5 +1,9 @@ import { CunninghamProvider } from '@openfun/cunningham-react'; -import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; +import { + MutationCache, + QueryClient, + QueryClientProvider, +} from '@tanstack/react-query'; import { useRouter } from 'next/router'; import { useEffect } from 'react'; @@ -24,8 +28,24 @@ const defaultOptions = { retry: DEFAULT_QUERY_RETRY, }, }; + +let globalRouterReplace: ((url: string) => void) | null = null; + const queryClient = new QueryClient({ defaultOptions, + mutationCache: new MutationCache({ + onError: (error) => { + if (error instanceof Error && 'status' in error && error.status === 401) { + void queryClient.resetQueries({ + queryKey: [KEY_AUTH], + }); + setAuthUrl(); + if (globalRouterReplace) { + void globalRouterReplace('/401'); + } + } + }, + }), }); export function AppProvider({ children }: { children: React.ReactNode }) { @@ -40,25 +60,14 @@ export function AppProvider({ children }: { children: React.ReactNode }) { return initializeResizeListener(); }, [initializeResizeListener]); + /** + * Update the global router replace function + * This allows us to use the router replace function globally + */ useEffect(() => { - queryClient.setDefaultOptions({ - ...defaultOptions, - mutations: { - onError: (error) => { - if ( - error instanceof Error && - 'status' in error && - error.status === 401 - ) { - void queryClient.resetQueries({ - queryKey: [KEY_AUTH], - }); - setAuthUrl(); - void replace(`/401`); - } - }, - }, - }); + globalRouterReplace = (url: string) => { + void replace(url); + }; }, [replace]); return ( diff --git a/src/frontend/apps/impress/src/features/docs/doc-management/api/useUpdateDoc.tsx b/src/frontend/apps/impress/src/features/docs/doc-management/api/useUpdateDoc.tsx index 12a87367..d63283e2 100644 --- a/src/frontend/apps/impress/src/features/docs/doc-management/api/useUpdateDoc.tsx +++ b/src/frontend/apps/impress/src/features/docs/doc-management/api/useUpdateDoc.tsx @@ -52,10 +52,16 @@ export function useUpdateDoc(queryConfig?: UseUpdateDoc) { void queryConfig.onSuccess(data, variables, context); } }, - onError: () => { + onError: (error, variables, context) => { + // If error it means the user is probably not allowed to edit the doc + // so we invalidate the canEdit query to update the UI accordingly void queryClient.invalidateQueries({ queryKey: [KEY_CAN_EDIT], }); + + if (queryConfig?.onError) { + queryConfig.onError(error, variables, context); + } }, }); }