Role page various fixes (#12324)

Various fixes from fast follows

- Sort roles by alphabetical order
- Change some tooltips
- During role creation, role should have all permissions enabled by
default
- Changed Permission icons design and refactored duplicating logic in a
dedicated component
- Changed "Revoked by" design
- Display role icon in default role picker
- Workspace member avatar was missing in role list and member picker
- Set "seeded" member role as editable for new workspaces
- Various css fixes
This commit is contained in:
Weiko
2025-05-27 17:58:55 +02:00
committed by GitHub
parent 8051646567
commit f210d274bf
16 changed files with 214 additions and 137 deletions

View File

@ -1,34 +1,12 @@
import { PermissionIcon } from '@/settings/roles/role-permissions/objects-permissions/components/PermissionIcon';
import { SETTINGS_ROLE_OBJECT_LEVEL_PERMISSION_TO_ROLE_OBJECT_PERMISSION_MAPPING } from '@/settings/roles/role-permissions/objects-permissions/constants/settingsRoleObjectLevelPermissionToRoleObjectPermissionMapping';
import { SETTINGS_ROLE_OBJECT_PERMISSION_ICON_CONFIG } from '@/settings/roles/role-permissions/objects-permissions/constants/settingsRoleObjectPermissionIconConfig';
import { SettingsRoleObjectPermissionKey } from '@/settings/roles/role-permissions/objects-permissions/constants/settingsRoleObjectPermissionIconConfig';
import { settingsDraftRoleFamilyState } from '@/settings/roles/states/settingsDraftRoleFamilyState';
import { useTheme } from '@emotion/react';
import styled from '@emotion/styled';
import { useRecoilValue } from 'recoil';
import { isDefined } from 'twenty-shared/utils';
import { ObjectPermission } from '~/generated/graphql';
const StyledIconWrapper = styled.div<{ isForbidden?: boolean }>`
align-items: center;
background: ${({ theme, isForbidden }) =>
isForbidden ? theme.adaptiveColors.orange1 : theme.adaptiveColors.blue1};
border: 1px solid
${({ theme, isForbidden }) =>
isForbidden ? theme.adaptiveColors.orange3 : 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<{ isForbidden?: boolean }>`
align-items: center;
display: flex;
color: ${({ theme, isForbidden }) =>
isForbidden ? theme.color.orange : theme.color.blue};
justify-content: center;
`;
const StyledSettingsRolePermissionsObjectLevelOverrideCell = styled.div`
display: flex;
gap: ${({ theme }) => theme.spacing(1)};
@ -43,8 +21,6 @@ export const SettingsRolePermissionsObjectLevelOverrideCell = ({
objectPermission,
roleId,
}: SettingsRolePermissionsObjectLevelOverrideCellProps) => {
const theme = useTheme();
const settingsDraftRole = useRecoilValue(
settingsDraftRoleFamilyState(roleId),
);
@ -52,44 +28,33 @@ export const SettingsRolePermissionsObjectLevelOverrideCell = ({
const permissionMappings =
SETTINGS_ROLE_OBJECT_LEVEL_PERMISSION_TO_ROLE_OBJECT_PERMISSION_MAPPING;
type ObjectPermissionKey = keyof typeof permissionMappings;
const isOverridden = (permission: ObjectPermissionKey) => {
const isOverridden = (permission: SettingsRoleObjectPermissionKey) => {
const rolePermission = permissionMappings[permission];
return (
isDefined(objectPermission[permission]) &&
!!settingsDraftRole[rolePermission as keyof typeof settingsDraftRole] !==
!!objectPermission[permission]
!!settingsDraftRole[rolePermission] !== !!objectPermission[permission]
);
};
return (
<StyledSettingsRolePermissionsObjectLevelOverrideCell>
{(Object.keys(permissionMappings) as ObjectPermissionKey[]).map(
(permission) => {
const { Icon, IconForbidden: IconOverride } =
SETTINGS_ROLE_OBJECT_PERMISSION_ICON_CONFIG[permission];
const permissionValue = objectPermission[permission];
{(
Object.keys(permissionMappings) as SettingsRoleObjectPermissionKey[]
).map((permission) => {
const permissionValue = objectPermission[permission];
if (!isOverridden(permission)) {
return null;
}
if (!isOverridden(permission)) {
return null;
}
return (
<StyledIconWrapper
key={permission}
isForbidden={permissionValue === false}
>
<StyledIcon isForbidden={permissionValue === false}>
{permissionValue === false && (
<IconOverride size={theme.icon.size.sm} />
)}
{permissionValue === true && <Icon size={theme.icon.size.sm} />}
</StyledIcon>
</StyledIconWrapper>
);
},
)}
return (
<PermissionIcon
key={permission}
permission={permission}
state={permissionValue === false ? 'revoked' : 'granted'}
/>
);
})}
</StyledSettingsRolePermissionsObjectLevelOverrideCell>
);
};

View File

@ -130,6 +130,7 @@ export const SettingsRolePermissionsObjectLevelSection = ({
disabled={!isEditable}
/>
}
dropdownOffset={{ x: 0, y: 4 }}
dropdownComponents={
<SettingsRolePermissionsObjectLevelObjectPickerDropdownContent
excludedObjectMetadataIds={

View File

@ -1,11 +1,11 @@
import { OverridableCheckbox } from '@/settings/roles/role-permissions/object-level-permissions/object-form/components/OverridableCheckbox';
import { PermissionIcon } from '@/settings/roles/role-permissions/objects-permissions/components/PermissionIcon';
import { SETTINGS_ROLE_OBJECT_LEVEL_PERMISSION_TO_ROLE_OBJECT_PERMISSION_MAPPING } from '@/settings/roles/role-permissions/objects-permissions/constants/settingsRoleObjectLevelPermissionToRoleObjectPermissionMapping';
import { SETTINGS_ROLE_OBJECT_PERMISSION_ICON_CONFIG } from '@/settings/roles/role-permissions/objects-permissions/constants/settingsRoleObjectPermissionIconConfig';
import { SettingsRoleObjectPermissionKey } from '@/settings/roles/role-permissions/objects-permissions/constants/settingsRoleObjectPermissionIconConfig';
import { SettingsRolePermissionsObjectLevelPermission } from '@/settings/roles/role-permissions/objects-permissions/types/SettingsRolePermissionsObjectPermission';
import { settingsDraftRoleFamilyState } from '@/settings/roles/states/settingsDraftRoleFamilyState';
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 { t } from '@lingui/core/macro';
import { useRecoilValue } from 'recoil';
@ -13,25 +13,36 @@ import { isDefined } from 'twenty-shared/utils';
import { ObjectPermission } from '~/generated-metadata/graphql';
import type { Role } from '~/generated/graphql';
const StyledLabel = styled.span`
color: ${({ theme }) => theme.font.color.primary};
`;
const StyledOverrideInfo = styled.span`
background: ${({ theme }) => theme.adaptiveColors.orange1};
border-radius: ${({ theme }) => theme.spacing(1)};
color: ${({ theme }) => theme.color.orange};
padding: ${({ theme }) => theme.spacing(1)};
const StyledTableRow = styled(TableRow)`
align-items: center;
display: flex;
`;
const StyledPermissionCell = styled(TableCell)`
align-items: center;
display: flex;
flex: 1;
gap: ${({ theme }) => theme.spacing(2)};
gap: ${({ theme }) => theme.spacing(1)};
padding-left: ${({ theme }) => theme.spacing(2)};
`;
const StyledPermissionContent = styled.div`
align-items: center;
display: flex;
gap: ${({ theme }) => theme.spacing(2)};
`;
const StyledPermissionLabel = styled.span`
color: ${({ theme }) => theme.font.color.primary};
`;
const StyledOverrideInfo = styled.div`
align-items: center;
color: ${({ theme }) => theme.font.color.tertiary};
display: flex;
gap: ${({ theme }) => theme.spacing(1)};
`;
const StyledCheckboxCell = styled(TableCell)`
align-items: center;
display: flex;
@ -39,11 +50,6 @@ const StyledCheckboxCell = styled(TableCell)`
padding-right: ${({ theme }) => theme.spacing(4)};
`;
const StyledTableRow = styled(TableRow)`
align-items: center;
display: flex;
`;
type OverridableCheckboxType = 'no_cta' | 'default' | 'override';
type SettingsRolePermissionsObjectLevelObjectFormObjectLevelTableRowProps = {
@ -60,17 +66,12 @@ export const SettingsRolePermissionsObjectLevelObjectFormObjectLevelTableRow =
settingsDraftRoleObjectPermissions,
roleId,
}: SettingsRolePermissionsObjectLevelObjectFormObjectLevelTableRowProps) => {
const theme = useTheme();
const settingsDraftRole = useRecoilValue(
settingsDraftRoleFamilyState(roleId),
);
const label = permission.label;
const { Icon } =
SETTINGS_ROLE_OBJECT_PERMISSION_ICON_CONFIG[permission.key];
const permissionMappings =
SETTINGS_ROLE_OBJECT_LEVEL_PERMISSION_TO_ROLE_OBJECT_PERMISSION_MAPPING;
@ -120,13 +121,21 @@ export const SettingsRolePermissionsObjectLevelObjectFormObjectLevelTableRow =
return (
<StyledTableRow>
<StyledPermissionCell>
<Icon size={theme.icon.size.sm} />
<StyledLabel>{label}</StyledLabel>
{isRevoked ? (
<StyledOverrideInfo>
{t`Revoked for this object`}
</StyledOverrideInfo>
) : null}
<StyledPermissionContent>
<PermissionIcon
permission={permission.key as SettingsRoleObjectPermissionKey}
state={isRevoked ? 'revoked' : 'granted'}
/>
<StyledPermissionLabel>{label}</StyledPermissionLabel>
</StyledPermissionContent>
<StyledOverrideInfo>
{isRevoked ? (
<>
{' · '}
{t`Revoked for this object`}
</>
) : null}
</StyledOverrideInfo>
</StyledPermissionCell>
<StyledCheckboxCell>
<OverridableCheckbox

View File

@ -0,0 +1,51 @@
import {
SETTINGS_ROLE_OBJECT_PERMISSION_ICON_CONFIG,
SettingsRoleObjectPermissionKey,
} from '@/settings/roles/role-permissions/objects-permissions/constants/settingsRoleObjectPermissionIconConfig';
import { useTheme } from '@emotion/react';
import styled from '@emotion/styled';
type PermissionIconProps = {
permission: SettingsRoleObjectPermissionKey;
state: 'granted' | 'revoked';
};
const StyledIconWrapper = styled.div<{ isRevoked?: boolean }>`
align-items: center;
background: ${({ theme, isRevoked }) =>
isRevoked ? theme.adaptiveColors.orange1 : theme.adaptiveColors.blue1};
border: 1px solid
${({ theme, isRevoked }) =>
isRevoked ? theme.adaptiveColors.orange3 : 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<{ isRevoked?: boolean }>`
align-items: center;
display: flex;
color: ${({ theme, isRevoked }) =>
isRevoked ? theme.color.orange : theme.color.blue};
justify-content: center;
`;
export const PermissionIcon = ({ permission, state }: PermissionIconProps) => {
const theme = useTheme();
const { Icon, IconForbidden } =
SETTINGS_ROLE_OBJECT_PERMISSION_ICON_CONFIG[permission];
const isRevoked = state === 'revoked';
return (
<StyledIconWrapper isRevoked={isRevoked}>
<StyledIcon isRevoked={isRevoked}>
{isRevoked && <IconForbidden size={theme.icon.size.sm} />}
{!isRevoked && <Icon size={theme.icon.size.sm} />}
</StyledIcon>
</StyledIconWrapper>
);
};

View File

@ -1,32 +1,37 @@
import { SETTINGS_ROLE_OBJECT_PERMISSION_ICON_CONFIG } from '@/settings/roles/role-permissions/objects-permissions/constants/settingsRoleObjectPermissionIconConfig';
import { PermissionIcon } from '@/settings/roles/role-permissions/objects-permissions/components/PermissionIcon';
import { SettingsRoleObjectPermissionKey } from '@/settings/roles/role-permissions/objects-permissions/constants/settingsRoleObjectPermissionIconConfig';
import { SettingsRolePermissionsObjectPermission } from '@/settings/roles/role-permissions/objects-permissions/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 { t } from '@lingui/core/macro';
import pluralize from 'pluralize';
import { Checkbox, CheckboxAccent } from 'twenty-ui/input';
const StyledLabel = styled.span`
color: ${({ theme }) => theme.font.color.primary};
`;
const StyledOverrideInfo = styled.span`
background: ${({ theme }) => theme.adaptiveColors.orange1};
border-radius: ${({ theme }) => theme.spacing(1)};
color: ${({ theme }) => theme.color.orange};
padding: ${({ theme }) => theme.spacing(1)};
`;
const StyledPermissionCell = styled(TableCell)`
align-items: center;
display: flex;
flex: 1;
gap: ${({ theme }) => theme.spacing(2)};
gap: ${({ theme }) => theme.spacing(1)};
padding-left: ${({ theme }) => theme.spacing(2)};
`;
const StyledPermissionContent = styled.div`
align-items: center;
display: flex;
gap: ${({ theme }) => theme.spacing(2)};
`;
const StyledPermissionLabel = styled.span`
color: ${({ theme }) => theme.font.color.primary};
`;
const StyledOverrideInfo = styled.div`
align-items: center;
color: ${({ theme }) => theme.font.color.tertiary};
display: flex;
gap: ${({ theme }) => theme.spacing(1)};
`;
const StyledCheckboxCell = styled(TableCell)`
align-items: center;
display: flex;
@ -48,25 +53,30 @@ export const SettingsRolePermissionsObjectsTableRow = ({
permission,
isEditable,
}: SettingsRolePermissionsObjectsTableRowProps) => {
const theme = useTheme();
const revokedBy = permission.revokedBy;
const isRevoked = revokedBy && revokedBy > 0;
const isRevoked =
revokedBy !== undefined && revokedBy !== null && revokedBy > 0;
const label = permission.label;
const pluralizedObject = pluralize('object', revokedBy);
const { Icon } = SETTINGS_ROLE_OBJECT_PERMISSION_ICON_CONFIG[permission.key];
return (
<StyledTableRow>
<StyledPermissionCell>
<Icon size={theme.icon.size.sm} />
<StyledLabel>{label}</StyledLabel>
{isRevoked ? (
<StyledOverrideInfo>
{t`Revoked on ${revokedBy} ${pluralizedObject}`}
</StyledOverrideInfo>
) : null}
<StyledPermissionContent>
<PermissionIcon
permission={permission.key as SettingsRoleObjectPermissionKey}
state={isRevoked ? 'revoked' : 'granted'}
/>
<StyledPermissionLabel>{label}</StyledPermissionLabel>
</StyledPermissionContent>
<StyledOverrideInfo>
{isRevoked ? (
<>
{' · '}
{t`Revoked on ${revokedBy} ${pluralizedObject}`}
</>
) : null}
</StyledOverrideInfo>
</StyledPermissionCell>
<StyledCheckboxCell>
<Checkbox

View File

@ -14,8 +14,14 @@ type SettingsRoleObjectPermissionIconConfig = {
IconForbidden: IconComponent;
};
export type SettingsRoleObjectPermissionKey =
| 'canReadObjectRecords'
| 'canUpdateObjectRecords'
| 'canSoftDeleteObjectRecords'
| 'canDestroyObjectRecords';
export const SETTINGS_ROLE_OBJECT_PERMISSION_ICON_CONFIG: Record<
string,
SettingsRoleObjectPermissionKey,
SettingsRoleObjectPermissionIconConfig
> = {
canReadObjectRecords: {