diff --git a/.env.example b/.env.example index 84d55c8cd..600ab761e 100644 --- a/.env.example +++ b/.env.example @@ -61,4 +61,6 @@ GA4_SECRET= GA4_MEASUREMENT_ID=G-XXXXXXXXXX OPEN_AI_API_KEY=sk-xxxx OPEN_AI_FREE_LIMIT=5 -OPEN_AI_PAID_LIMIT=1000 \ No newline at end of file +OPEN_AI_PAID_LIMIT=1000 +DISABLE_DATA_DELETION=true +DISABLE_REVEAL_NAMES=true diff --git a/backend/src/admin/router.ts b/backend/src/admin/router.ts index 1bc605bf3..21db5a46a 100644 --- a/backend/src/admin/router.ts +++ b/backend/src/admin/router.ts @@ -47,6 +47,8 @@ export default function getRouter(io: Server) { disableAnonymous: config.DISABLE_ANONYMOUS_LOGIN, disablePasswords: config.DISABLE_PASSWORD_LOGIN, disablePasswordRegistration: config.DISABLE_PASSWORD_REGISTRATION, + disableAccountDeletion: config.DISABLE_ACCOUNT_DELETION, + disableShowAuthor: config.DISABLE_SHOW_AUTHOR, ai: !!config.OPEN_AI_API_KEY, oAuth: { google: !!config.GOOGLE_KEY && !!config.GOOGLE_SECRET, diff --git a/backend/src/common/payloads.ts b/backend/src/common/payloads.ts index c0b8f4a88..f621f19b4 100644 --- a/backend/src/common/payloads.ts +++ b/backend/src/common/payloads.ts @@ -67,6 +67,8 @@ export interface BackendCapabilities { disableAnonymous: boolean; disablePasswords: boolean; disablePasswordRegistration: boolean; + disableAccountDeletion: boolean; + disableShowAuthor: boolean; ai: boolean; } diff --git a/backend/src/config.ts b/backend/src/config.ts index bb2311b89..6af47aeef 100644 --- a/backend/src/config.ts +++ b/backend/src/config.ts @@ -127,6 +127,8 @@ const config: BackendConfig = { OPEN_AI_API_KEY: defaults('OPEN_AI_API_KEY', ''), OPEN_AI_FREE_LIMIT: defaultsNumber('OPEN_AI_FREE_LIMIT', 5), OPEN_AI_PAID_LIMIT: defaultsNumber('OPEN_AI_PAID_LIMIT', 100), + DISABLE_ACCOUNT_DELETION: defaultsBool('DISABLE_ACCOUNT_DELETION', false), + DISABLE_SHOW_AUTHOR: defaultsBool('DISABLE_SHOW_AUTHOR', false), }; export default config; diff --git a/backend/src/types.ts b/backend/src/types.ts index 634191ff5..d5bec75d4 100644 --- a/backend/src/types.ts +++ b/backend/src/types.ts @@ -66,6 +66,8 @@ export interface BackendConfig { OPEN_AI_API_KEY: string; OPEN_AI_FREE_LIMIT: number; OPEN_AI_PAID_LIMIT: number; + DISABLE_ACCOUNT_DELETION: boolean; + DISABLE_SHOW_AUTHOR: boolean; } export type LicenceMetadata = { diff --git a/docs/docs/self-hosting/optionals.md b/docs/docs/self-hosting/optionals.md index 5edce427a..88891a58e 100644 --- a/docs/docs/self-hosting/optionals.md +++ b/docs/docs/self-hosting/optionals.md @@ -42,6 +42,8 @@ services: DISABLE_ANONYMOUS_LOGIN: 'false' # Set to true to disable anonymous accounts DISABLE_PASSWORD_LOGIN: 'false' # Set to true to disable password accounts (email accounts). DISABLE_PASSWORD_REGISTRATION: 'false' # Set to true to disable password accounts registration (but not login!) + DISABLE_ACCOUNT_DELETION: 'false' # Set to true to disable data deletion (GDPR) + DISABLE_SHOW_AUTHOR: 'false' # Set to true to disable the ability to reveal names, even for board owners # -- AI - Open AI: Set these to enable AI capabilities. This is optional. -- OPEN_AI_API_KEY: '' # To activate AI capabilities, you need to get an API key from https://platform.openai.com/account/api-keys diff --git a/docs/docs/self-hosting/quick-start/ComposeView.tsx b/docs/docs/self-hosting/quick-start/ComposeView.tsx index 485c1af06..133769601 100644 --- a/docs/docs/self-hosting/quick-start/ComposeView.tsx +++ b/docs/docs/self-hosting/quick-start/ComposeView.tsx @@ -16,6 +16,8 @@ type ComposeViewSettings = { disableAnon: boolean; disablePassword: boolean; disableRegistration: boolean; + disableAccountDeletion: boolean; + disableShowAuthor: boolean; useSendgrid: boolean; useSmtp: boolean; sendgridKey: string; @@ -60,6 +62,8 @@ export default function ComposeView({ disableAnon, disablePassword, disableRegistration, + disableAccountDeletion, + disableShowAuthor, useSendgrid, useSmtp, sendgridKey, @@ -81,6 +85,8 @@ export default function ComposeView({ p(disableAnon, 'DISABLE_ANONYMOUS_LOGIN', 'true'), p(disablePassword, 'DISABLE_PASSWORD_LOGIN', 'true'), p(disableRegistration, 'DISABLE_PASSWORD_REGISTRATION', 'true'), + p(disableAccountDeletion, 'DISABLE_ACCOUNT_DELETION', 'true'), + p(disableShowAuthor, 'DISABLE_SHOW_AUTHOR', 'true'), p(useSendgrid, 'SENDGRID_API_KEY', sendgridKey), p(useSendgrid, 'SENDGRID_SENDER', sendgridSender), p(useSmtp, 'MAIL_SMTP_HOST', smtpHost), diff --git a/docs/docs/self-hosting/quick-start/Editor.tsx b/docs/docs/self-hosting/quick-start/Editor.tsx index 8cf4351de..4c5befbd5 100644 --- a/docs/docs/self-hosting/quick-start/Editor.tsx +++ b/docs/docs/self-hosting/quick-start/Editor.tsx @@ -43,6 +43,14 @@ export default function Editor() { ); const [disableRegistration, setDisablePasswordRegistration] = usePersistedState('disable-password-reg', false); + const [disableAccountDeletion, setDisableAccountDeletion] = usePersistedState( + 'disable-delete-account', + false + ); + const [disableShowAuthor, setDisableShowAuthor] = usePersistedState( + 'disable-show-author', + false + ); const [useSendgrid, setUseSendgrid] = usePersistedState( 'use-sendgrid', false @@ -176,6 +184,22 @@ export default function Editor() { value={disableRegistration || disablePassword} onChange={setDisablePasswordRegistration} /> + + @@ -317,6 +341,8 @@ export default function Editor() { disableAnon, disablePassword, disableRegistration, + disableAccountDeletion, + disableShowAuthor, useSendgrid, useSmtp, sendgridKey, diff --git a/frontend/src/common/payloads.ts b/frontend/src/common/payloads.ts index c0b8f4a88..f621f19b4 100644 --- a/frontend/src/common/payloads.ts +++ b/frontend/src/common/payloads.ts @@ -67,6 +67,8 @@ export interface BackendCapabilities { disableAnonymous: boolean; disablePasswords: boolean; disablePasswordRegistration: boolean; + disableAccountDeletion: boolean; + disableShowAuthor: boolean; ai: boolean; } diff --git a/frontend/src/global/state.ts b/frontend/src/global/state.ts index c259b49c8..c0e62ed55 100644 --- a/frontend/src/global/state.ts +++ b/frontend/src/global/state.ts @@ -2,6 +2,27 @@ import { selector } from 'recoil'; import { BackendCapabilities } from 'common'; import { fetchBackendCapabilities } from 'api'; +const defaultBackendCapabilities: BackendCapabilities = { + adminEmail: '', + licenced: true, + selfHosted: false, + disableAnonymous: false, + disablePasswords: false, + disablePasswordRegistration: false, + disableAccountDeletion: false, + disableShowAuthor: false, + oAuth: { + google: false, + github: false, + twitter: false, + microsoft: false, + slack: false, + okta: false, + }, + emailAvailable: false, + ai: false, +}; + export const backendCapabilitiesState = selector({ key: 'BACKEND_CAPABILITIES', get: async () => { @@ -11,23 +32,6 @@ export const backendCapabilitiesState = selector({ return data; } - return { - adminEmail: '', - licenced: true, - selfHosted: false, - disableAnonymous: false, - disablePasswords: false, - disablePasswordRegistration: false, - oAuth: { - google: false, - github: false, - twitter: false, - microsoft: false, - slack: false, - okta: false, - }, - emailAvailable: false, - ai: false, - }; + return defaultBackendCapabilities; }, }); diff --git a/frontend/src/testing/index.tsx b/frontend/src/testing/index.tsx index 947832ec3..46c564eac 100644 --- a/frontend/src/testing/index.tsx +++ b/frontend/src/testing/index.tsx @@ -1,6 +1,7 @@ import { PropsWithChildren, useEffect } from 'react'; import { render, RenderOptions, RenderResult } from '@testing-library/react'; -import { FullUser, Session, defaultOptions } from 'common'; +import { vi } from 'vitest'; +import { BackendCapabilities, FullUser, Session, defaultOptions } from 'common'; import { DragDropContext, Droppable, @@ -57,10 +58,46 @@ export const initialSession: Session = { demo: false, }; +const capabilities: BackendCapabilities = { + adminEmail: 'admin@acme.com', + ai: false, + disableAccountDeletion: false, + disableAnonymous: false, + disablePasswordRegistration: false, + disablePasswords: false, + disableShowAuthor: false, + emailAvailable: true, + licenced: true, + oAuth: { + github: false, + google: false, + microsoft: false, + okta: false, + slack: false, + twitter: false, + }, + selfHosted: false, + slackClientId: 'xxx', +}; +// fetchBackendCapabilities +vi.mock('../api/index', () => { + return { + fetchBackendCapabilities: () => { + return Promise.resolve(capabilities); + }, + }; +}); + export function AllTheProviders({ children }: PropsWithChildren<{}>) { return ( - snap.set(userState, user)}> + { + snap.set(userState, user); + }} + > + {/* */} {children} + {/* */} ); } diff --git a/frontend/src/views/account/AccountPage.tsx b/frontend/src/views/account/AccountPage.tsx index ff91614a5..e9f2ad641 100644 --- a/frontend/src/views/account/AccountPage.tsx +++ b/frontend/src/views/account/AccountPage.tsx @@ -238,28 +238,30 @@ function AccountPage() { ) : null} -
- - {t('AccountPage.deleteAccount.warning')} - + {capabilities.disableAccountDeletion ? null : ( +
+ + {t('AccountPage.deleteAccount.warning')} + - + - -
+ +
+ )} ); diff --git a/frontend/src/views/game/board/__tests__/permissions-logic.test.ts b/frontend/src/views/game/board/__tests__/permissions-logic.test.ts index 9db0ea64f..9b5e8b6f5 100644 --- a/frontend/src/views/game/board/__tests__/permissions-logic.test.ts +++ b/frontend/src/views/game/board/__tests__/permissions-logic.test.ts @@ -10,6 +10,7 @@ import { VoteType, defaultOptions, VoteExtract, + BackendCapabilities, } from 'common'; import { v4 } from 'uuid'; @@ -31,6 +32,28 @@ const anotherUser: User = { name: 'Another User', }; +const capabilities: BackendCapabilities = { + adminEmail: 'admin@acme.com', + ai: false, + disableAccountDeletion: false, + disableAnonymous: false, + disablePasswordRegistration: false, + disablePasswords: false, + disableShowAuthor: false, + emailAvailable: true, + licenced: true, + oAuth: { + github: false, + google: false, + microsoft: false, + okta: false, + slack: false, + twitter: false, + }, + selfHosted: false, + slackClientId: 'xxx', +}; + function buildVotes(type: VoteType, users: User[], post: Post): VoteExtract[] { return users.map( (user) => @@ -161,7 +184,7 @@ describe('Posts Permission Logic', () => { it('When using default rules, a user on its own post', () => { const p = post(currentUser); const s = session(defaultOptions, p); - const result = postPermissionLogic(p, s, currentUser, false); + const result = postPermissionLogic(p, s, capabilities, currentUser, false); expect(result.canCreateAction).toBe(true); expect(result.canEdit).toBe(true); expect(result.canDelete).toBe(true); @@ -174,7 +197,7 @@ describe('Posts Permission Logic', () => { it('When using default rules, a user on its own post, but set to readonly', () => { const p = post(currentUser); const s = session(defaultOptions, p); - const result = postPermissionLogic(p, s, currentUser, true); + const result = postPermissionLogic(p, s, capabilities, currentUser, true); expect(result.canCreateAction).toBe(false); expect(result.canEdit).toBe(false); expect(result.canDelete).toBe(false); @@ -187,7 +210,7 @@ describe('Posts Permission Logic', () => { it('When using default rules, a non-logged in user', () => { const p = post(currentUser); const s = session(defaultOptions, p); - const result = postPermissionLogic(p, s, null, false); + const result = postPermissionLogic(p, s, capabilities, null, false); expect(result.canCreateAction).toBe(false); expect(result.canEdit).toBe(false); expect(result.canDelete).toBe(false); @@ -200,7 +223,7 @@ describe('Posts Permission Logic', () => { it('When using default rules, a user on another users post', () => { const p = post(anotherUser); const s = session(defaultOptions, p); - const result = postPermissionLogic(p, s, currentUser, false); + const result = postPermissionLogic(p, s, capabilities, currentUser, false); expect(result.canCreateAction).toBe(true); expect(result.canEdit).toBe(false); expect(result.canDelete).toBe(false); @@ -213,7 +236,7 @@ describe('Posts Permission Logic', () => { it('When using default rules, a user on another users post but already voted', () => { const p = post(anotherUser, [currentUser]); const s = session(defaultOptions, p); - const result = postPermissionLogic(p, s, currentUser, false); + const result = postPermissionLogic(p, s, capabilities, currentUser, false); expect(result.canCreateAction).toBe(true); expect(result.canEdit).toBe(false); expect(result.canDelete).toBe(false); @@ -234,7 +257,7 @@ describe('Posts Permission Logic', () => { p1, p2 ); - const result = postPermissionLogic(p2, s, currentUser, false); + const result = postPermissionLogic(p2, s, capabilities, currentUser, false); expect(result.canCreateAction).toBe(true); expect(result.canEdit).toBe(false); expect(result.canDelete).toBe(false); @@ -255,7 +278,7 @@ describe('Posts Permission Logic', () => { p2, p3 ); - const result = postPermissionLogic(p3, s, currentUser, false); + const result = postPermissionLogic(p3, s, capabilities, currentUser, false); expect(result.canCreateAction).toBe(true); expect(result.canEdit).toBe(false); expect(result.canDelete).toBe(false); @@ -274,7 +297,7 @@ describe('Posts Permission Logic', () => { }, p ); - const result = postPermissionLogic(p, s, currentUser, false); + const result = postPermissionLogic(p, s, capabilities, currentUser, false); expect(result.canCreateAction).toBe(false); expect(result.canEdit).toBe(false); expect(result.canDelete).toBe(false); @@ -293,7 +316,7 @@ describe('Posts Permission Logic', () => { }, p ); - const result = postPermissionLogic(p, s, currentUser, false); + const result = postPermissionLogic(p, s, capabilities, currentUser, false); expect(result.canCreateAction).toBe(false); expect(result.canEdit).toBe(false); expect(result.canDelete).toBe(false); @@ -312,7 +335,7 @@ describe('Posts Permission Logic', () => { }, p ); - const result = postPermissionLogic(p, s, currentUser, false); + const result = postPermissionLogic(p, s, capabilities, currentUser, false); expect(result.canCreateAction).toBe(true); expect(result.canEdit).toBe(true); expect(result.canDelete).toBe(true); @@ -331,7 +354,7 @@ describe('Posts Permission Logic', () => { }, p ); - const result = postPermissionLogic(p, s, currentUser, false); + const result = postPermissionLogic(p, s, capabilities, currentUser, false); expect(result.canCreateAction).toBe(true); expect(result.canEdit).toBe(false); expect(result.canDelete).toBe(false); @@ -351,7 +374,7 @@ describe('Posts Permission Logic', () => { }, p ); - const result = postPermissionLogic(p, s, currentUser, false); + const result = postPermissionLogic(p, s, capabilities, currentUser, false); expect(result.canCreateAction).toBe(true); expect(result.canEdit).toBe(false); expect(result.canDelete).toBe(false); @@ -371,7 +394,7 @@ describe('Posts Permission Logic', () => { }, p ); - const result = postPermissionLogic(p, s, currentUser, false); + const result = postPermissionLogic(p, s, capabilities, currentUser, false); expect(result.canCreateAction).toBe(true); expect(result.canEdit).toBe(false); expect(result.canDelete).toBe(false); @@ -391,7 +414,7 @@ describe('Posts Permission Logic', () => { }, p ); - const result = postPermissionLogic(p, s, currentUser, false); + const result = postPermissionLogic(p, s, capabilities, currentUser, false); expect(result.canCreateAction).toBe(true); expect(result.canEdit).toBe(false); expect(result.canDelete).toBe(false); @@ -410,7 +433,7 @@ describe('Posts Permission Logic', () => { }, p ); - const result = postPermissionLogic(p, s, currentUser, false); + const result = postPermissionLogic(p, s, capabilities, currentUser, false); expect(result.canUseGiphy).toBe(true); }); @@ -423,7 +446,7 @@ describe('Posts Permission Logic', () => { }, p ); - const result = postPermissionLogic(p, s, currentUser, false); + const result = postPermissionLogic(p, s, capabilities, currentUser, false); expect(result.canUseGiphy).toBe(false); }); @@ -436,7 +459,7 @@ describe('Posts Permission Logic', () => { }, p ); - const result = postPermissionLogic(p, s, currentUser, false); + const result = postPermissionLogic(p, s, capabilities, currentUser, false); expect(result.canReorder).toBe(true); }); @@ -449,7 +472,7 @@ describe('Posts Permission Logic', () => { }, p ); - const result = postPermissionLogic(p, s, currentUser, false); + const result = postPermissionLogic(p, s, capabilities, currentUser, false); expect(result.canReorder).toBe(false); }); @@ -462,7 +485,7 @@ describe('Posts Permission Logic', () => { }, p ); - const result = postPermissionLogic(p, s, currentUser, false); + const result = postPermissionLogic(p, s, capabilities, currentUser, false); expect(result.canCreateGroup).toBe(true); }); @@ -475,7 +498,7 @@ describe('Posts Permission Logic', () => { }, p ); - const result = postPermissionLogic(p, s, currentUser, false); + const result = postPermissionLogic(p, s, capabilities, currentUser, false); expect(result.canCreateGroup).toBe(false); }); @@ -488,7 +511,7 @@ describe('Posts Permission Logic', () => { }, p ); - const result = postPermissionLogic(p, s, currentUser, false); + const result = postPermissionLogic(p, s, capabilities, currentUser, false); expect(result.isBlurred).toBe(false); }); @@ -501,7 +524,7 @@ describe('Posts Permission Logic', () => { }, p ); - const result = postPermissionLogic(p, s, currentUser, false); + const result = postPermissionLogic(p, s, capabilities, currentUser, false); expect(result.isBlurred).toBe(true); }); @@ -514,7 +537,7 @@ describe('Posts Permission Logic', () => { }, p ); - const result = postPermissionLogic(p, s, currentUser, false); + const result = postPermissionLogic(p, s, capabilities, currentUser, false); expect(result.isBlurred).toBe(false); }); @@ -527,7 +550,7 @@ describe('Posts Permission Logic', () => { }, p ); - const result = postPermissionLogic(p, s, currentUser, false); + const result = postPermissionLogic(p, s, capabilities, currentUser, false); expect(result.canCancelVote).toBe(true); }); @@ -541,7 +564,7 @@ describe('Posts Permission Logic', () => { }, p ); - const result = postPermissionLogic(p, s, currentUser, false); + const result = postPermissionLogic(p, s, capabilities, currentUser, false); expect(result.canCancelVote).toBe(false); }); @@ -554,7 +577,7 @@ describe('Posts Permission Logic', () => { }, p ); - const result = postPermissionLogic(p, s, currentUser, false); + const result = postPermissionLogic(p, s, capabilities, currentUser, false); expect(result.canCancelVote).toBe(false); }); }); diff --git a/frontend/src/views/game/board/permissions-logic.ts b/frontend/src/views/game/board/permissions-logic.ts index e6b38e216..d659f314d 100644 --- a/frontend/src/views/game/board/permissions-logic.ts +++ b/frontend/src/views/game/board/permissions-logic.ts @@ -1,4 +1,4 @@ -import { Post, Session, User, VoteType } from 'common'; +import { BackendCapabilities, Post, Session, User, VoteType } from 'common'; import some from 'lodash/some'; export interface SessionUserPermissions { @@ -53,6 +53,7 @@ export interface PostUserPermissions { export function postPermissionLogic( post: Post, session: Session | null, + capabilities: BackendCapabilities, user: User | null, readonly: boolean ): PostUserPermissions { @@ -113,7 +114,7 @@ export function postPermissionLogic( const canDisplayDownVote = maxDownVotes !== null ? maxDownVotes > 0 : true; const canEdit = !readonly && isLoggedIn && isAuthor; const canDelete = !readonly && isLoggedIn && isAuthor; - const canShowAuthor = allowAuthorVisible; + const canShowAuthor = allowAuthorVisible && !capabilities.disableShowAuthor; const canUseGiphy = isLoggedIn && allowGiphy; const canReorder = !readonly && isLoggedIn && allowReordering; const canCreateGroup = !readonly && isLoggedIn && allowGrouping; diff --git a/frontend/src/views/game/board/post/__tests__/Post.test.tsx b/frontend/src/views/game/board/post/__tests__/Post.test.tsx index 00fa7d31a..da4185986 100644 --- a/frontend/src/views/game/board/post/__tests__/Post.test.tsx +++ b/frontend/src/views/game/board/post/__tests__/Post.test.tsx @@ -61,6 +61,23 @@ post.votes = [ ]; describe('Post', () => { + it('Initialise stuff', () => { + renderWithRouter( + + ); + }); it('Should properly display the post content', () => { const { getByLabelText } = renderWithRouter( { return { - columns: columns.map((c) => calculateColumn(c, user, session)), + columns: columns.map((c) => + calculateColumn(c, user, session, capabilities) + ), actions: buildActions(columns), }; - }, [columns, user, session]); + }, [columns, user, session, capabilities]); return results; } @@ -26,23 +30,31 @@ export function useSummary(columns: ColumnContent[]): Stats { function calculateColumn( column: ColumnContent, user: User | null, - session: Session | null + session: Session | null, + capabilities: BackendCapabilities ): ColumnStats { const posts: ColumnStatsItem[] = column.posts.map((p) => - postToItem(p, user, session) + postToItem(p, user, session, capabilities) ); const groups: ColumnStatsItem[] = column.groups .filter((g) => !!g.posts.length) - .map((g) => groupToItem(g, user, session)); + .map((g) => groupToItem(g, user, session, capabilities)); return { items: sortBy([...posts, ...groups], sortingFunction), column }; } function postToItem( post: Post, user: User | null, - session: Session | null + session: Session | null, + capabilities: BackendCapabilities ): ColumnStatsItem { - const permissions = postPermissionLogic(post, session, user, false); + const permissions = postPermissionLogic( + post, + session, + capabilities, + user, + false + ); return { id: post.id, content: post.content, @@ -58,7 +70,8 @@ function postToItem( function groupToItem( group: PostGroup, user: User | null, - session: Session | null + session: Session | null, + capabilities: BackendCapabilities ): ColumnStatsItem { return { id: group.id, @@ -66,7 +79,7 @@ function groupToItem( user: group.user, type: 'group', children: sortBy( - group.posts.map((p) => postToItem(p, user, session)), + group.posts.map((p) => postToItem(p, user, session, capabilities)), sortingFunction ), likes: countVotesForGroup(group, 'like'), diff --git a/frontend/src/views/session-editor/sections/posts/PostsSection.tsx b/frontend/src/views/session-editor/sections/posts/PostsSection.tsx index a4ad3390c..f214a1158 100644 --- a/frontend/src/views/session-editor/sections/posts/PostsSection.tsx +++ b/frontend/src/views/session-editor/sections/posts/PostsSection.tsx @@ -5,6 +5,7 @@ import { OptionItem } from '../OptionItem'; import { useTranslation } from 'react-i18next'; import BooleanOption from '../BooleanOption'; import MaxPostsSlider from './MaxPostsSlider'; +import useBackendCapabilities from 'global/useBackendCapabilities'; interface PostsSectionProps { options: SessionOptions; @@ -13,6 +14,7 @@ interface PostsSectionProps { function PostsSection({ options, onChange }: PostsSectionProps) { const { t } = useTranslation(); + const capabilities = useBackendCapabilities(); const setAllowAction = useCallback( (value: boolean) => { @@ -118,15 +120,17 @@ function PostsSection({ options, onChange }: PostsSectionProps) { > - - - + {capabilities.disableShowAuthor ? null : ( + + + + )}