From 5b7fa9aabab9321ade408f65aa0271e16fb82348 Mon Sep 17 00:00:00 2001 From: Erik Michelson Date: Mon, 27 Jul 2026 15:58:51 +0200 Subject: [PATCH] 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 --- ...reate-non-existing-note-hint.spec.tsx.snap | 2 ++ .../create-non-existing-note-hint.spec.tsx | 29 ++++++++++++++++++- .../create-non-existing-note-hint.tsx | 7 ++++- 3 files changed, 36 insertions(+), 2 deletions(-) diff --git a/frontend/src/components/common/note-loading-boundary/__snapshots__/create-non-existing-note-hint.spec.tsx.snap b/frontend/src/components/common/note-loading-boundary/__snapshots__/create-non-existing-note-hint.spec.tsx.snap index 8ff28ee55..8f1590af0 100644 --- a/frontend/src/components/common/note-loading-boundary/__snapshots__/create-non-existing-note-hint.spec.tsx.snap +++ b/frontend/src/components/common/note-loading-boundary/__snapshots__/create-non-existing-note-hint.spec.tsx.snap @@ -38,6 +38,8 @@ exports[`create non existing note hint renders an button as initial state 1`] = `; +exports[`create non existing note hint renders nothing for a guest without note creation permission 1`] = `
`; + exports[`create non existing note hint renders nothing if no note id has been provided 1`] = `
`; exports[`create non existing note hint shows an error message if note couldn't be created 1`] = ` diff --git a/frontend/src/components/common/note-loading-boundary/create-non-existing-note-hint.spec.tsx b/frontend/src/components/common/note-loading-boundary/create-non-existing-note-hint.spec.tsx index adfe849ac..136932bba 100644 --- a/frontend/src/components/common/note-loading-boundary/create-non-existing-note-hint.spec.tsx +++ b/frontend/src/components/common/note-loading-boundary/create-non-existing-note-hint.spec.tsx @@ -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({ 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({ guestAccess: PermissionLevel.WRITE })) + const onNoteCreatedCallback = jest.fn() + const view = render( + + ) + await waitForOtherPromisesToFinish() + expect(onNoteCreatedCallback).not.toBeCalled() + expect(view.container).toMatchSnapshot() + }) + it('renders an button as initial state', async () => { mockCreateNoteWithPrimaryAlias() const onNoteCreatedCallback = jest.fn() diff --git a/frontend/src/components/common/note-loading-boundary/create-non-existing-note-hint.tsx b/frontend/src/components/common/note-loading-boundary/create-non-existing-note-hint.tsx index 340271110..05cbe5556 100644 --- a/frontend/src/components/common/note-loading-boundary/create-non-existing-note-hint.tsx +++ b/frontend/src/components/common/note-loading-boundary/create-non-existing-note-hint.tsx @@ -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 = ({ 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 } }, [onNoteCreated, returnState.value]) - if (noteId === undefined) { + if (noteId === undefined || (!isLoggedIn && guestAccessLevel !== PermissionLevel.FULL)) { return null } else if (returnState.value) { return (