Add object level permission permissions to role page (ReadOnly) (#11568)
## Context This PR adds the display of object-level permissions. A following PR will add the ability to update those permissions. The PR contains the SettingsRoleObjectLevel page but it's not fully implemented yet (save won't trigger the corresponding mutation) <img width="616" alt="Screenshot 2025-04-14 at 18 02 40" src="https://github.com/user-attachments/assets/f8c58193-31f3-468a-a96d-f06a9f2e1423" />
This commit is contained in:
@ -1,34 +1,9 @@
|
||||
import { SettingsOptionCardContentToggle } from '@/settings/components/SettingsOptions/SettingsOptionCardContentToggle';
|
||||
import { SettingsRolePermissionsObjectsTableHeader } from '@/settings/roles/role-permissions/components/SettingsRolePermissionsObjectsTableHeader';
|
||||
import { SettingsRolePermissionsObjectsTableRow } from '@/settings/roles/role-permissions/components/SettingsRolePermissionsObjectsTableRow';
|
||||
import { SettingsRolePermissionsSettingsTableHeader } from '@/settings/roles/role-permissions/components/SettingsRolePermissionsSettingsTableHeader';
|
||||
import { SettingsRolePermissionsSettingsTableRow } from '@/settings/roles/role-permissions/components/SettingsRolePermissionsSettingsTableRow';
|
||||
import { settingsDraftRoleFamilyState } from '@/settings/roles/states/settingsDraftRoleFamilyState';
|
||||
import { SettingsRolePermissionsObjectPermission } from '@/settings/roles/types/SettingsRolePermissionsObjectPermission';
|
||||
import { SettingsRolePermissionsSettingPermission } from '@/settings/roles/types/SettingsRolePermissionsSettingPermission';
|
||||
import { SettingsRolePermissionsObjectLevelSection } from '@/settings/roles/role-permissions/object-level-permissions/components/SettingsRolePermissionsObjectLevelSection';
|
||||
import { SettingsRolePermissionsObjectsSection } from '@/settings/roles/role-permissions/objects-permissions/components/SettingsRolePermissionsObjectsSection';
|
||||
import { SettingsRolePermissionsSettingsSection } from '@/settings/roles/role-permissions/settings-permissions/components/SettingsRolePermissionsSettingsSection';
|
||||
import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled';
|
||||
import styled from '@emotion/styled';
|
||||
import { t } from '@lingui/core/macro';
|
||||
import { useRecoilState } from 'recoil';
|
||||
import {
|
||||
H2Title,
|
||||
IconCode,
|
||||
IconEye,
|
||||
IconHierarchy,
|
||||
IconKey,
|
||||
IconLockOpen,
|
||||
IconPencil,
|
||||
IconServer,
|
||||
IconSettings,
|
||||
IconTrash,
|
||||
IconTrashX,
|
||||
IconUsers,
|
||||
} from 'twenty-ui/display';
|
||||
import { Card, Section } from 'twenty-ui/layout';
|
||||
import {
|
||||
FeatureFlagKey,
|
||||
SettingPermissionType,
|
||||
} from '~/generated-metadata/graphql';
|
||||
import { FeatureFlagKey } from '~/generated-metadata/graphql';
|
||||
|
||||
const StyledRolePermissionsContainer = styled.div`
|
||||
display: flex;
|
||||
@ -36,19 +11,6 @@ const StyledRolePermissionsContainer = styled.div`
|
||||
gap: ${({ theme }) => theme.spacing(8)};
|
||||
`;
|
||||
|
||||
const StyledTable = styled.div`
|
||||
border-bottom: 1px solid ${({ theme }) => theme.border.color.light};
|
||||
`;
|
||||
|
||||
const StyledTableRows = styled.div`
|
||||
padding-bottom: ${({ theme }) => theme.spacing(2)};
|
||||
padding-top: ${({ theme }) => theme.spacing(2)};
|
||||
`;
|
||||
|
||||
const StyledCard = styled(Card)`
|
||||
margin-bottom: ${({ theme }) => theme.spacing(4)};
|
||||
`;
|
||||
|
||||
type SettingsRolePermissionsProps = {
|
||||
roleId: string;
|
||||
isEditable: boolean;
|
||||
@ -58,168 +20,26 @@ export const SettingsRolePermissions = ({
|
||||
roleId,
|
||||
isEditable,
|
||||
}: SettingsRolePermissionsProps) => {
|
||||
const [settingsDraftRole, setSettingsDraftRole] = useRecoilState(
|
||||
settingsDraftRoleFamilyState(roleId),
|
||||
);
|
||||
|
||||
const objectPermissionsConfig: SettingsRolePermissionsObjectPermission[] = [
|
||||
{
|
||||
key: 'seeRecords',
|
||||
label: t`See Records on All Objects`,
|
||||
Icon: IconEye,
|
||||
value: settingsDraftRole.canReadAllObjectRecords,
|
||||
setValue: (value: boolean) => {
|
||||
setSettingsDraftRole({
|
||||
...settingsDraftRole,
|
||||
canReadAllObjectRecords: value,
|
||||
});
|
||||
},
|
||||
},
|
||||
{
|
||||
key: 'editRecords',
|
||||
label: t`Edit Records on All Objects`,
|
||||
Icon: IconPencil,
|
||||
value: settingsDraftRole.canUpdateAllObjectRecords,
|
||||
setValue: (value: boolean) => {
|
||||
setSettingsDraftRole({
|
||||
...settingsDraftRole,
|
||||
canUpdateAllObjectRecords: value,
|
||||
});
|
||||
},
|
||||
},
|
||||
{
|
||||
key: 'deleteRecords',
|
||||
label: t`Delete Records on All Objects`,
|
||||
Icon: IconTrash,
|
||||
value: settingsDraftRole.canSoftDeleteAllObjectRecords,
|
||||
setValue: (value: boolean) => {
|
||||
setSettingsDraftRole({
|
||||
...settingsDraftRole,
|
||||
canSoftDeleteAllObjectRecords: value,
|
||||
});
|
||||
},
|
||||
},
|
||||
{
|
||||
key: 'destroyRecords',
|
||||
label: t`Destroy Records on All Objects`,
|
||||
Icon: IconTrashX,
|
||||
value: settingsDraftRole.canDestroyAllObjectRecords,
|
||||
setValue: (value: boolean) => {
|
||||
setSettingsDraftRole({
|
||||
...settingsDraftRole,
|
||||
canDestroyAllObjectRecords: value,
|
||||
});
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
const settingsPermissionsConfig: SettingsRolePermissionsSettingPermission[] =
|
||||
[
|
||||
{
|
||||
key: SettingPermissionType.API_KEYS_AND_WEBHOOKS,
|
||||
name: t`API Keys & Webhooks`,
|
||||
description: t`Manage API keys and webhooks`,
|
||||
Icon: IconCode,
|
||||
},
|
||||
{
|
||||
key: SettingPermissionType.WORKSPACE,
|
||||
name: t`Workspace`,
|
||||
description: t`Set global workspace preferences`,
|
||||
Icon: IconSettings,
|
||||
},
|
||||
{
|
||||
key: SettingPermissionType.WORKSPACE_MEMBERS,
|
||||
name: t`Users`,
|
||||
description: t`Add or remove users`,
|
||||
Icon: IconUsers,
|
||||
},
|
||||
{
|
||||
key: SettingPermissionType.ROLES,
|
||||
name: t`Roles`,
|
||||
description: t`Define user roles and access levels`,
|
||||
Icon: IconLockOpen,
|
||||
},
|
||||
{
|
||||
key: SettingPermissionType.DATA_MODEL,
|
||||
name: t`Data Model`,
|
||||
description: t`Edit CRM data structure and fields`,
|
||||
Icon: IconHierarchy,
|
||||
},
|
||||
{
|
||||
key: SettingPermissionType.ADMIN_PANEL,
|
||||
name: t`Admin Panel`,
|
||||
description: t`Admin settings and system tools`,
|
||||
Icon: IconServer,
|
||||
},
|
||||
{
|
||||
key: SettingPermissionType.SECURITY,
|
||||
name: t`Security`,
|
||||
description: t`Manage security policies`,
|
||||
Icon: IconKey,
|
||||
},
|
||||
];
|
||||
|
||||
const isPermissionsV2Enabled = useIsFeatureEnabled(
|
||||
FeatureFlagKey.IsPermissionsV2Enabled,
|
||||
);
|
||||
|
||||
return (
|
||||
<StyledRolePermissionsContainer>
|
||||
<Section>
|
||||
<H2Title
|
||||
title={t`Objects`}
|
||||
description={t`Ability to interact with each object`}
|
||||
<SettingsRolePermissionsObjectsSection
|
||||
roleId={roleId}
|
||||
isEditable={isEditable}
|
||||
/>
|
||||
{isPermissionsV2Enabled && (
|
||||
<SettingsRolePermissionsObjectLevelSection
|
||||
roleId={roleId}
|
||||
isEditable={isEditable}
|
||||
/>
|
||||
<StyledTable>
|
||||
<SettingsRolePermissionsObjectsTableHeader
|
||||
roleId={roleId}
|
||||
objectPermissionsConfig={objectPermissionsConfig}
|
||||
isEditable={isEditable}
|
||||
/>
|
||||
<StyledTableRows>
|
||||
{objectPermissionsConfig.map((permission) => (
|
||||
<SettingsRolePermissionsObjectsTableRow
|
||||
key={permission.key}
|
||||
permission={permission}
|
||||
isEditable={isEditable}
|
||||
/>
|
||||
))}
|
||||
</StyledTableRows>
|
||||
</StyledTable>
|
||||
</Section>
|
||||
<Section>
|
||||
<H2Title title={t`Settings`} description={t`Settings permissions`} />
|
||||
{isPermissionsV2Enabled && (
|
||||
<StyledCard rounded>
|
||||
<SettingsOptionCardContentToggle
|
||||
Icon={IconSettings}
|
||||
title={t`Settings All Access`}
|
||||
description={t`Ability to edit all settings`}
|
||||
checked={settingsDraftRole.canUpdateAllSettings}
|
||||
disabled={!isEditable}
|
||||
onChange={() => {
|
||||
setSettingsDraftRole({
|
||||
...settingsDraftRole,
|
||||
canUpdateAllSettings: !settingsDraftRole.canUpdateAllSettings,
|
||||
});
|
||||
}}
|
||||
/>
|
||||
</StyledCard>
|
||||
)}
|
||||
<StyledTable>
|
||||
<SettingsRolePermissionsSettingsTableHeader />
|
||||
<StyledTableRows>
|
||||
{settingsPermissionsConfig.map((permission) => (
|
||||
<SettingsRolePermissionsSettingsTableRow
|
||||
key={permission.key}
|
||||
roleId={roleId}
|
||||
permission={permission}
|
||||
isEditable={isEditable}
|
||||
/>
|
||||
))}
|
||||
</StyledTableRows>
|
||||
</StyledTable>
|
||||
</Section>
|
||||
)}
|
||||
<SettingsRolePermissionsSettingsSection
|
||||
roleId={roleId}
|
||||
isEditable={isEditable}
|
||||
/>
|
||||
</StyledRolePermissionsContainer>
|
||||
);
|
||||
};
|
||||
|
||||
@ -1,68 +0,0 @@
|
||||
import { settingsDraftRoleFamilyState } from '@/settings/roles/states/settingsDraftRoleFamilyState';
|
||||
import { SettingsRolePermissionsObjectPermission } from '@/settings/roles/types/SettingsRolePermissionsObjectPermission';
|
||||
import { TableHeader } from '@/ui/layout/table/components/TableHeader';
|
||||
import { TableRow } from '@/ui/layout/table/components/TableRow';
|
||||
import styled from '@emotion/styled';
|
||||
import { t } from '@lingui/core/macro';
|
||||
import { useRecoilState } from 'recoil';
|
||||
import { Checkbox } from 'twenty-ui/input';
|
||||
|
||||
const StyledNameHeader = styled(TableHeader)`
|
||||
flex: 1;
|
||||
`;
|
||||
|
||||
const StyledActionsHeader = styled(TableHeader)`
|
||||
align-items: center;
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
padding-right: ${({ theme }) => theme.spacing(4)};
|
||||
`;
|
||||
|
||||
type SettingsRolePermissionsObjectsTableHeaderProps = {
|
||||
roleId: string;
|
||||
objectPermissionsConfig: SettingsRolePermissionsObjectPermission[];
|
||||
isEditable: boolean;
|
||||
};
|
||||
|
||||
export const SettingsRolePermissionsObjectsTableHeader = ({
|
||||
roleId,
|
||||
objectPermissionsConfig,
|
||||
isEditable,
|
||||
}: SettingsRolePermissionsObjectsTableHeaderProps) => {
|
||||
const [settingsDraftRole, setSettingsDraftRole] = useRecoilState(
|
||||
settingsDraftRoleFamilyState(roleId),
|
||||
);
|
||||
|
||||
const allPermissionsEnabled = objectPermissionsConfig.every(
|
||||
(permission) => permission.value,
|
||||
);
|
||||
|
||||
const somePermissionsEnabled = objectPermissionsConfig.some(
|
||||
(permission) => permission.value,
|
||||
);
|
||||
|
||||
return (
|
||||
<TableRow>
|
||||
<StyledNameHeader>{t`Name`}</StyledNameHeader>
|
||||
<StyledActionsHeader aria-label={t`Actions`}>
|
||||
<Checkbox
|
||||
checked={allPermissionsEnabled}
|
||||
indeterminate={somePermissionsEnabled && !allPermissionsEnabled}
|
||||
disabled={!isEditable}
|
||||
aria-label={t`Toggle all object permissions`}
|
||||
onChange={() => {
|
||||
const newValue = !allPermissionsEnabled;
|
||||
|
||||
setSettingsDraftRole({
|
||||
...settingsDraftRole,
|
||||
canReadAllObjectRecords: newValue,
|
||||
canUpdateAllObjectRecords: newValue,
|
||||
canSoftDeleteAllObjectRecords: newValue,
|
||||
canDestroyAllObjectRecords: newValue,
|
||||
});
|
||||
}}
|
||||
/>
|
||||
</StyledActionsHeader>
|
||||
</TableRow>
|
||||
);
|
||||
};
|
||||
@ -1,80 +0,0 @@
|
||||
import { SettingsRolePermissionsObjectPermission } from '@/settings/roles/types/SettingsRolePermissionsObjectPermission';
|
||||
import { TableCell } from '@/ui/layout/table/components/TableCell';
|
||||
import { TableRow } from '@/ui/layout/table/components/TableRow';
|
||||
import { useTheme } from '@emotion/react';
|
||||
import styled from '@emotion/styled';
|
||||
import { Checkbox } from 'twenty-ui/input';
|
||||
|
||||
const StyledIconWrapper = styled.div`
|
||||
align-items: center;
|
||||
background: ${({ theme }) => theme.adaptiveColors.blue1};
|
||||
border: 1px solid ${({ theme }) => theme.adaptiveColors.blue3};
|
||||
border-radius: ${({ theme }) => theme.border.radius.sm};
|
||||
display: flex;
|
||||
height: ${({ theme }) => theme.spacing(4)};
|
||||
justify-content: center;
|
||||
width: ${({ theme }) => theme.spacing(4)};
|
||||
`;
|
||||
|
||||
const StyledIcon = styled.div`
|
||||
align-items: center;
|
||||
display: flex;
|
||||
color: ${({ theme }) => theme.color.blue};
|
||||
justify-content: center;
|
||||
`;
|
||||
|
||||
const StyledLabel = styled.span`
|
||||
color: ${({ theme }) => theme.font.color.primary};
|
||||
`;
|
||||
|
||||
const StyledPermissionCell = styled(TableCell)`
|
||||
align-items: center;
|
||||
display: flex;
|
||||
flex: 1;
|
||||
gap: ${({ theme }) => theme.spacing(2)};
|
||||
padding-left: ${({ theme }) => theme.spacing(2)};
|
||||
`;
|
||||
|
||||
const StyledCheckboxCell = styled(TableCell)`
|
||||
align-items: center;
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
padding-right: ${({ theme }) => theme.spacing(4)};
|
||||
`;
|
||||
|
||||
const StyledTableRow = styled(TableRow)`
|
||||
align-items: center;
|
||||
display: flex;
|
||||
`;
|
||||
|
||||
type SettingsRolePermissionsObjectsTableRowProps = {
|
||||
permission: SettingsRolePermissionsObjectPermission;
|
||||
isEditable: boolean;
|
||||
};
|
||||
|
||||
export const SettingsRolePermissionsObjectsTableRow = ({
|
||||
permission,
|
||||
isEditable,
|
||||
}: SettingsRolePermissionsObjectsTableRowProps) => {
|
||||
const theme = useTheme();
|
||||
|
||||
return (
|
||||
<StyledTableRow>
|
||||
<StyledPermissionCell>
|
||||
<StyledIconWrapper>
|
||||
<StyledIcon>
|
||||
<permission.Icon size={theme.icon.size.sm} />
|
||||
</StyledIcon>
|
||||
</StyledIconWrapper>
|
||||
<StyledLabel>{permission.label}</StyledLabel>
|
||||
</StyledPermissionCell>
|
||||
<StyledCheckboxCell>
|
||||
<Checkbox
|
||||
checked={permission.value}
|
||||
onChange={() => permission.setValue(!permission.value)}
|
||||
disabled={!isEditable}
|
||||
/>
|
||||
</StyledCheckboxCell>
|
||||
</StyledTableRow>
|
||||
);
|
||||
};
|
||||
@ -1,11 +0,0 @@
|
||||
import { TableHeader } from '@/ui/layout/table/components/TableHeader';
|
||||
import { TableRow } from '@/ui/layout/table/components/TableRow';
|
||||
import { t } from '@lingui/core/macro';
|
||||
|
||||
export const SettingsRolePermissionsSettingsTableHeader = () => (
|
||||
<TableRow gridAutoColumns="3fr 4fr 24px">
|
||||
<TableHeader>{t`Name`}</TableHeader>
|
||||
<TableHeader>{t`Description`}</TableHeader>
|
||||
<TableHeader></TableHeader>
|
||||
</TableRow>
|
||||
);
|
||||
@ -1,117 +0,0 @@
|
||||
import { settingsDraftRoleFamilyState } from '@/settings/roles/states/settingsDraftRoleFamilyState';
|
||||
import { SettingsRolePermissionsSettingPermission } from '@/settings/roles/types/SettingsRolePermissionsSettingPermission';
|
||||
import { TableCell } from '@/ui/layout/table/components/TableCell';
|
||||
import { TableRow } from '@/ui/layout/table/components/TableRow';
|
||||
import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled';
|
||||
import { useTheme } from '@emotion/react';
|
||||
import styled from '@emotion/styled';
|
||||
import { useRecoilState } from 'recoil';
|
||||
import { Checkbox } from 'twenty-ui/input';
|
||||
import { v4 } from 'uuid';
|
||||
import { FeatureFlagKey } from '~/generated-metadata/graphql';
|
||||
|
||||
const StyledName = styled.span`
|
||||
color: ${({ theme }) => theme.font.color.primary};
|
||||
`;
|
||||
|
||||
const StyledDescription = styled(StyledName)`
|
||||
color: ${({ theme }) => theme.font.color.secondary};
|
||||
`;
|
||||
|
||||
const StyledPermissionCell = styled(TableCell)`
|
||||
align-items: center;
|
||||
display: flex;
|
||||
flex: 1;
|
||||
gap: ${({ theme }) => theme.spacing(2)};
|
||||
`;
|
||||
|
||||
const StyledCheckboxCell = styled(TableCell)`
|
||||
align-items: center;
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
padding-right: ${({ theme }) => theme.spacing(4)};
|
||||
`;
|
||||
|
||||
const StyledIconContainer = styled.div`
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
`;
|
||||
|
||||
type SettingsRolePermissionsSettingsTableRowProps = {
|
||||
roleId: string;
|
||||
permission: SettingsRolePermissionsSettingPermission;
|
||||
isEditable: boolean;
|
||||
};
|
||||
|
||||
export const SettingsRolePermissionsSettingsTableRow = ({
|
||||
roleId,
|
||||
permission,
|
||||
isEditable,
|
||||
}: SettingsRolePermissionsSettingsTableRowProps) => {
|
||||
const theme = useTheme();
|
||||
const [settingsDraftRole, setSettingsDraftRole] = useRecoilState(
|
||||
settingsDraftRoleFamilyState(roleId),
|
||||
);
|
||||
const isPermissionsV2Enabled = useIsFeatureEnabled(
|
||||
FeatureFlagKey.IsPermissionsV2Enabled,
|
||||
);
|
||||
const canUpdateAllSettings = settingsDraftRole.canUpdateAllSettings;
|
||||
|
||||
const isSettingPermissionEnabled =
|
||||
settingsDraftRole.settingPermissions?.some(
|
||||
(settingPermission) => settingPermission.setting === permission.key,
|
||||
) ?? false;
|
||||
|
||||
const handleChange = (value: boolean) => {
|
||||
const currentPermissions = settingsDraftRole.settingPermissions ?? [];
|
||||
|
||||
if (value === true) {
|
||||
setSettingsDraftRole({
|
||||
...settingsDraftRole,
|
||||
settingPermissions: [
|
||||
...currentPermissions,
|
||||
{
|
||||
id: v4(),
|
||||
setting: permission.key,
|
||||
roleId,
|
||||
},
|
||||
],
|
||||
});
|
||||
} else {
|
||||
setSettingsDraftRole({
|
||||
...settingsDraftRole,
|
||||
settingPermissions: currentPermissions.filter(
|
||||
(p) => p.setting !== permission.key,
|
||||
),
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<TableRow key={permission.key} gridAutoColumns="3fr 4fr 24px">
|
||||
<StyledPermissionCell>
|
||||
<StyledIconContainer>
|
||||
<permission.Icon
|
||||
size={theme.icon.size.md}
|
||||
color={theme.font.color.primary}
|
||||
stroke={theme.icon.stroke.sm}
|
||||
/>
|
||||
</StyledIconContainer>
|
||||
<StyledName>{permission.name}</StyledName>
|
||||
</StyledPermissionCell>
|
||||
<StyledPermissionCell>
|
||||
<StyledDescription>{permission.description}</StyledDescription>
|
||||
</StyledPermissionCell>
|
||||
<StyledCheckboxCell>
|
||||
<Checkbox
|
||||
checked={isSettingPermissionEnabled || canUpdateAllSettings}
|
||||
disabled={
|
||||
!isEditable || canUpdateAllSettings || !isPermissionsV2Enabled
|
||||
}
|
||||
onChange={(event) => handleChange(event.target.checked)}
|
||||
/>
|
||||
</StyledCheckboxCell>
|
||||
</TableRow>
|
||||
);
|
||||
};
|
||||
Reference in New Issue
Block a user