fix(frontend): conditionally hide create note button on 404 page

The 404 page for notes renders a button to create a note with the
requested alias. This button was previously rendered unconditionally,
and therefore rendered even when the user (or guest) didn't have any
permission to create notes. The button is now hidden when no permission
to create notes is existing.

Signed-off-by: Erik Michelson <github@erik.michelson.eu>
This commit is contained in:
Erik Michelson
2026-07-27 15:58:51 +02:00
committed by Philip Molares
parent 02f5682643
commit 5b7fa9aaba
3 changed files with 36 additions and 2 deletions
@@ -38,6 +38,8 @@ exports[`create non existing note hint renders an button as initial state 1`] =
</div>
`;
exports[`create non existing note hint renders nothing for a guest without note creation permission 1`] = `<div />`;
exports[`create non existing note hint renders nothing if no note id has been provided 1`] = `<div />`;
exports[`create non existing note hint shows an error message if note couldn't be created 1`] = `
@@ -4,15 +4,19 @@
* SPDX-License-Identifier: AGPL-3.0-only
*/
import * as createNoteWithPrimaryAliasModule from '../../../api/notes'
import { AuthProviderType, PermissionLevel } from '@hedgedoc/commons'
import { mockAppState } from '../../../test-utils/mock-app-state'
import { mockI18n } from '../../../test-utils/mock-i18n'
import * as UseFrontendConfigMock from '../frontend-config-context/use-frontend-config'
import { CreateNonExistingNoteHint } from './create-non-existing-note-hint'
import type { NoteInterface, NoteMetadataInterface } from '@hedgedoc/commons'
import type { FrontendConfigInterface, NoteInterface, NoteMetadataInterface } from '@hedgedoc/commons'
import { waitForOtherPromisesToFinish } from '@hedgedoc/commons'
import { act, render, screen, waitFor } from '@testing-library/react'
import { Mock } from 'ts-mockery'
jest.mock('../../../api/notes')
jest.mock('../../../hooks/common/use-single-string-url-parameter')
jest.mock('../frontend-config-context/use-frontend-config')
describe('create non existing note hint', () => {
const mockedNoteId = 'mockedNoteId'
@@ -45,6 +49,13 @@ describe('create non existing note hint', () => {
await mockI18n()
})
beforeEach(() => {
mockAppState({ user: { authProvider: AuthProviderType.LOCAL } })
jest
.spyOn(UseFrontendConfigMock, 'useFrontendConfig')
.mockReturnValue(Mock.of<FrontendConfigInterface>({ guestAccess: PermissionLevel.FULL }))
})
afterEach(() => {
jest.resetAllMocks()
jest.resetModules()
@@ -60,6 +71,22 @@ describe('create non existing note hint', () => {
expect(view.container).toMatchSnapshot()
})
it('renders nothing for a guest without note creation permission', async () => {
mockAppState({ user: { authProvider: AuthProviderType.GUEST } })
jest
.spyOn(UseFrontendConfigMock, 'useFrontendConfig')
.mockReturnValue(Mock.of<FrontendConfigInterface>({ guestAccess: PermissionLevel.WRITE }))
const onNoteCreatedCallback = jest.fn()
const view = render(
<CreateNonExistingNoteHint
noteId={mockedNoteId}
onNoteCreated={onNoteCreatedCallback}></CreateNonExistingNoteHint>
)
await waitForOtherPromisesToFinish()
expect(onNoteCreatedCallback).not.toBeCalled()
expect(view.container).toMatchSnapshot()
})
it('renders an button as initial state', async () => {
mockCreateNoteWithPrimaryAlias()
const onNoteCreatedCallback = jest.fn()
@@ -4,8 +4,10 @@
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { createNoteWithPrimaryAlias } from '../../../api/notes'
import { useIsLoggedIn } from '../../../hooks/common/use-is-logged-in'
import { testId } from '../../../utils/test-id'
import { UiIcon } from '../icons/ui-icon'
import { useFrontendConfig } from '../frontend-config-context/use-frontend-config'
import React, { useCallback, useEffect } from 'react'
import { Alert, Button } from 'react-bootstrap'
import {
@@ -15,6 +17,7 @@ import {
} from 'react-bootstrap-icons'
import { Trans, useTranslation } from 'react-i18next'
import { useAsyncFn } from 'react-use'
import { PermissionLevel } from '@hedgedoc/commons'
export interface CreateNonExistingNoteHintProps {
onNoteCreated: () => void
@@ -30,6 +33,8 @@ export interface CreateNonExistingNoteHintProps {
*/
export const CreateNonExistingNoteHint: React.FC<CreateNonExistingNoteHintProps> = ({ onNoteCreated, noteId }) => {
useTranslation()
const guestAccessLevel = useFrontendConfig().guestAccess
const isLoggedIn = useIsLoggedIn()
const [returnState, createNote] = useAsyncFn(async () => {
if (noteId !== undefined) {
@@ -47,7 +52,7 @@ export const CreateNonExistingNoteHint: React.FC<CreateNonExistingNoteHintProps>
}
}, [onNoteCreated, returnState.value])
if (noteId === undefined) {
if (noteId === undefined || (!isLoggedIn && guestAccessLevel !== PermissionLevel.FULL)) {
return null
} else if (returnState.value) {
return (