feat(notes): redirect note to its primary address

Signed-off-by: Erik Michelson <github@erik.michelson.eu>
This commit is contained in:
Erik Michelson 2023-03-24 16:43:50 +01:00
parent c0d4d233da
commit 4b82031464
No known key found for this signature in database
GPG key ID: DB99ADDDC5C0AF82
3 changed files with 53 additions and 3 deletions

View file

@ -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."

View file

@ -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 (
<Fragment>
@ -64,6 +74,11 @@ describe('Note loading boundary', () => {
</Fragment>
)
})
jest.spyOn(useUiNotificationsModule, 'useUiNotifications').mockReturnValue({
showErrorNotification: jest.fn(),
dismissNotification: jest.fn(),
dispatchUiNotification: jest.fn()
})
mockGetNoteIdQueryParameter()
})

View file

@ -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<PropsWithChildren> = ({ 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<PropsWithChildren> = ({ children }) =
return (
<CustomAsyncLoadingBoundary
loading={loading || !value}
loading={loading || !value || !primaryAddressChecked}
error={error}
errorComponent={errorComponent}
loadingComponent={<LoadingScreen />}>