diff --git a/frontend/locales/en.json b/frontend/locales/en.json index a80ac44ba..f3542a663 100644 --- a/frontend/locales/en.json +++ b/frontend/locales/en.json @@ -45,6 +45,7 @@ "success": "Note has been created." }, "error": { + "redirecting": "An error occurred while redirecting you to the primary address of this note", "notFound": { "title": "Note not found", "description": "The requested note doesn't exist." diff --git a/frontend/src/components/common/note-loading-boundary/note-loading-boundary.spec.tsx b/frontend/src/components/common/note-loading-boundary/note-loading-boundary.spec.tsx index 10c676bec..e297b38b9 100644 --- a/frontend/src/components/common/note-loading-boundary/note-loading-boundary.spec.tsx +++ b/frontend/src/components/common/note-loading-boundary/note-loading-boundary.spec.tsx @@ -7,11 +7,13 @@ import { ApiError } from '../../../api/common/api-error' import * as getNoteModule from '../../../api/notes' import type { Note } from '../../../api/notes/types' import * as LoadingScreenModule from '../../../components/application-loader/loading-screen/loading-screen' +import * as useApplicationStateModule from '../../../hooks/common/use-application-state' import * as useSingleStringUrlParameterModule from '../../../hooks/common/use-single-string-url-parameter' import * as setNoteDataFromServerModule from '../../../redux/note-details/methods' import { testId } from '../../../utils/test-id' import * as CommonErrorPageModule from '../../error-pages/common-error-page' import { mockI18n } from '../../markdown-renderer/test-utils/mock-i18n' +import * as useUiNotificationsModule from '../../notifications/ui-notification-boundary' import * as CreateNonExistingNoteHintModule from './create-non-existing-note-hint' import { NoteLoadingBoundary } from './note-loading-boundary' import { render, screen } from '@testing-library/react' @@ -19,13 +21,20 @@ import { Fragment } from 'react' import { Mock } from 'ts-mockery' jest.mock('../../../hooks/common/use-single-string-url-parameter') +jest.mock('../../../hooks/common/use-application-state') jest.mock('../../../api/notes') jest.mock('../../../redux/note-details/methods') jest.mock('../../error-pages/common-error-page', () => ({ CommonErrorPage: jest.fn() })) jest.mock('../../../components/application-loader/loading-screen/loading-screen') +jest.mock('../../notifications/ui-notification-boundary') jest.mock('./create-non-existing-note-hint') +jest.mock('next/router', () => ({ + useRouter: () => ({ + push: jest.fn() + }) +})) describe('Note loading boundary', () => { const mockedNoteId = 'mockedNoteId' @@ -37,6 +46,7 @@ describe('Note loading boundary', () => { beforeEach(async () => { await mockI18n() + jest.spyOn(useApplicationStateModule, 'useApplicationState').mockReturnValue(mockedNoteId) jest.spyOn(CreateNonExistingNoteHintModule, 'CreateNonExistingNoteHint').mockImplementation(() => { return ( @@ -64,6 +74,11 @@ describe('Note loading boundary', () => { ) }) + jest.spyOn(useUiNotificationsModule, 'useUiNotifications').mockReturnValue({ + showErrorNotification: jest.fn(), + dismissNotification: jest.fn(), + dispatchUiNotification: jest.fn() + }) mockGetNoteIdQueryParameter() }) diff --git a/frontend/src/components/common/note-loading-boundary/note-loading-boundary.tsx b/frontend/src/components/common/note-loading-boundary/note-loading-boundary.tsx index 30f17df89..4d1a6fe91 100644 --- a/frontend/src/components/common/note-loading-boundary/note-loading-boundary.tsx +++ b/frontend/src/components/common/note-loading-boundary/note-loading-boundary.tsx @@ -5,14 +5,18 @@ */ import { ApiError } from '../../../api/common/api-error' import { ErrorToI18nKeyMapper } from '../../../api/common/error-to-i18n-key-mapper' +import { useApplicationState } from '../../../hooks/common/use-application-state' +import { useSingleStringUrlParameter } from '../../../hooks/common/use-single-string-url-parameter' import { LoadingScreen } from '../../application-loader/loading-screen/loading-screen' import { CommonErrorPage } from '../../error-pages/common-error-page' +import { useUiNotifications } from '../../notifications/ui-notification-boundary' import { CustomAsyncLoadingBoundary } from '../async-loading-boundary/custom-async-loading-boundary' import { ShowIf } from '../show-if/show-if' import { CreateNonExistingNoteHint } from './create-non-existing-note-hint' import { useLoadNoteFromServer } from './hooks/use-load-note-from-server' +import { useRouter } from 'next/router' import type { PropsWithChildren } from 'react' -import React, { useEffect, useMemo } from 'react' +import React, { useEffect, useMemo, useState } from 'react' /** * Loads the note identified by the note-id in the URL. @@ -23,10 +27,40 @@ import React, { useEffect, useMemo } from 'react' */ export const NoteLoadingBoundary: React.FC = ({ children }) => { const [{ error, loading, value }, loadNoteFromServer] = useLoadNoteFromServer() + const noteId = useSingleStringUrlParameter('noteId', '') + const primaryNoteAddress = useApplicationState((state) => state.noteDetails.primaryAddress) + const router = useRouter() + const { showErrorNotification } = useUiNotifications() + const [primaryAddressChecked, setPrimaryAddressChecked] = useState(false) useEffect(() => { + if (primaryAddressChecked) { + return + } loadNoteFromServer() - }, [loadNoteFromServer]) + }, [loadNoteFromServer, primaryAddressChecked]) + + useEffect(() => { + if (!value || primaryAddressChecked) { + return + } + if (noteId !== primaryNoteAddress) { + router + .replace(`/n/${primaryNoteAddress}`, undefined, { shallow: true }) + .then(() => setPrimaryAddressChecked(true)) + .catch(showErrorNotification('noteLoadingBoundary.error.redirecting')) + } else { + setPrimaryAddressChecked(true) + } + }, [ + value, + primaryNoteAddress, + noteId, + router, + showErrorNotification, + primaryAddressChecked, + setPrimaryAddressChecked + ]) const errorComponent = useMemo(() => { if (error === undefined) { @@ -50,7 +84,7 @@ export const NoteLoadingBoundary: React.FC = ({ children }) = return ( }>