Compare commits

...

1 Commits

Author SHA1 Message Date
Sonarly Claude Code 3c4b20a77a Dashboard edit/save buttons shown to users who lack LAYOUTS permission
https://sonarly.com/issue/5361?type=bug

The front-end `DashboardActionsConfig.tsx` is missing `requiredPermissionFlag: PermissionFlagType.LAYOUTS` on Edit/Save/Cancel dashboard actions, allowing non-admin users to enter edit mode but fail on save with PERMISSION_DENIED from the backend guard.

Fix: Added `requiredPermissionFlag: PermissionFlagType.LAYOUTS` to all four dashboard action configurations in `DashboardActionsConfig.tsx`:

1. **EDIT_LAYOUT** — "Edit Dashboard" button on the show page
2. **SAVE_LAYOUT** — "Save Dashboard" button in edit mode
3. **CANCEL_LAYOUT_EDITION** — "Cancel Edition" button in edit mode
4. **DUPLICATE_DASHBOARD** — "Duplicate Dashboard" button

This aligns the front-end permission gating with the back-end `SettingsPermissionGuard(PermissionFlagType.LAYOUTS)` on the `updatePageLayoutWithTabsAndWidgets` mutation. The `useRegisteredActions` hook (lines 79-84) already filters out actions whose `requiredPermissionFlag` the current user lacks — so users without the LAYOUTS permission will no longer see the Edit/Save/Cancel/Duplicate dashboard buttons, preventing them from entering edit mode and hitting the server-side PERMISSION_DENIED error.

The fix follows the identical pattern already used in `DefaultRecordActionsConfig.tsx` for record page layout actions (added in commit `b15d092abd`).
2026-03-07 02:03:23 +00:00
@@ -13,6 +13,7 @@ import { ActionViewType } from 'twenty-shared/types';
import { PageLayoutSingleRecordActionKeys } from '@/page-layout/actions/PageLayoutSingleRecordActionKeys';
import { msg } from '@lingui/core/macro';
import { isDefined } from 'twenty-shared/utils';
import { PermissionFlagType } from '~/generated-metadata/graphql';
import {
IconCancel,
IconCopyPlus,
@@ -31,6 +32,7 @@ export const DASHBOARD_ACTIONS_CONFIG = inheritActionsFromDefaultConfig({
Icon: IconPencil,
type: ActionType.Standard,
scope: ActionScope.RecordSelection,
requiredPermissionFlag: PermissionFlagType.LAYOUTS,
shouldBeRegistered: ({ selectedRecord, objectPermissions }) =>
isDefined(selectedRecord) &&
!selectedRecord?.isRemote &&
@@ -50,6 +52,7 @@ export const DASHBOARD_ACTIONS_CONFIG = inheritActionsFromDefaultConfig({
Icon: IconDeviceFloppy,
type: ActionType.Standard,
scope: ActionScope.RecordSelection,
requiredPermissionFlag: PermissionFlagType.LAYOUTS,
shouldBeRegistered: ({ selectedRecord, objectPermissions }) =>
isDefined(selectedRecord) &&
!selectedRecord?.isRemote &&
@@ -68,6 +71,7 @@ export const DASHBOARD_ACTIONS_CONFIG = inheritActionsFromDefaultConfig({
Icon: IconCancel,
type: ActionType.Standard,
scope: ActionScope.RecordSelection,
requiredPermissionFlag: PermissionFlagType.LAYOUTS,
shouldBeRegistered: ({ selectedRecord, objectPermissions }) =>
isDefined(selectedRecord) &&
!selectedRecord?.isRemote &&
@@ -86,6 +90,7 @@ export const DASHBOARD_ACTIONS_CONFIG = inheritActionsFromDefaultConfig({
Icon: IconCopyPlus,
type: ActionType.Standard,
scope: ActionScope.RecordSelection,
requiredPermissionFlag: PermissionFlagType.LAYOUTS,
shouldBeRegistered: ({ selectedRecord, objectPermissions }) =>
isDefined(selectedRecord) &&
!selectedRecord?.isRemote &&