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:
@ -7,7 +7,7 @@ import { Select } from '@/ui/input/components/Select';
|
|||||||
import { t } from '@lingui/core/macro';
|
import { t } from '@lingui/core/macro';
|
||||||
import { useRecoilState } from 'recoil';
|
import { useRecoilState } from 'recoil';
|
||||||
import { isDefined } from 'twenty-shared/utils';
|
import { isDefined } from 'twenty-shared/utils';
|
||||||
import { H2Title, IconUserPin } from 'twenty-ui/display';
|
import { H2Title, IconUserPin, useIcons } from 'twenty-ui/display';
|
||||||
import { Card, Section } from 'twenty-ui/layout';
|
import { Card, Section } from 'twenty-ui/layout';
|
||||||
import {
|
import {
|
||||||
Role,
|
Role,
|
||||||
@ -51,10 +51,18 @@ export const SettingsRoleDefaultRole = ({
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const { getIcon } = useIcons();
|
||||||
|
|
||||||
if (!currentWorkspace || !defaultRole) {
|
if (!currentWorkspace || !defaultRole) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const options = roles.map((role) => ({
|
||||||
|
label: role.label,
|
||||||
|
value: role.id,
|
||||||
|
Icon: getIcon(role.icon),
|
||||||
|
}));
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Section>
|
<Section>
|
||||||
<H2Title
|
<H2Title
|
||||||
@ -71,10 +79,7 @@ export const SettingsRoleDefaultRole = ({
|
|||||||
selectSizeVariant="small"
|
selectSizeVariant="small"
|
||||||
withSearchInput
|
withSearchInput
|
||||||
dropdownId="default-role-select"
|
dropdownId="default-role-select"
|
||||||
options={roles.map((role) => ({
|
options={options}
|
||||||
label: role.label,
|
|
||||||
value: role.id,
|
|
||||||
}))}
|
|
||||||
value={defaultRole?.id ?? ''}
|
value={defaultRole?.id ?? ''}
|
||||||
onChange={(value) =>
|
onChange={(value) =>
|
||||||
updateDefaultRole(value as string, currentWorkspace)
|
updateDefaultRole(value as string, currentWorkspace)
|
||||||
|
|||||||
@ -9,11 +9,12 @@ import { SettingsPath } from '@/types/SettingsPath';
|
|||||||
import { TableCell } from '@/ui/layout/table/components/TableCell';
|
import { TableCell } from '@/ui/layout/table/components/TableCell';
|
||||||
import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled';
|
import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled';
|
||||||
import { useRecoilValue } from 'recoil';
|
import { useRecoilValue } from 'recoil';
|
||||||
|
import { H2Title, IconPlus } from 'twenty-ui/display';
|
||||||
|
import { Button } from 'twenty-ui/input';
|
||||||
|
import { Section } from 'twenty-ui/layout';
|
||||||
import { FeatureFlagKey } from '~/generated/graphql';
|
import { FeatureFlagKey } from '~/generated/graphql';
|
||||||
import { useNavigateSettings } from '~/hooks/useNavigateSettings';
|
import { useNavigateSettings } from '~/hooks/useNavigateSettings';
|
||||||
import { Button } from 'twenty-ui/input';
|
import { sortByAscString } from '~/utils/array/sortByAscString';
|
||||||
import { H2Title, IconPlus } from 'twenty-ui/display';
|
|
||||||
import { Section } from 'twenty-ui/layout';
|
|
||||||
|
|
||||||
const StyledCreateRoleSection = styled(Section)`
|
const StyledCreateRoleSection = styled(Section)`
|
||||||
border-top: 1px solid ${({ theme }) => theme.border.color.light};
|
border-top: 1px solid ${({ theme }) => theme.border.color.light};
|
||||||
@ -40,6 +41,10 @@ export const SettingsRolesList = () => {
|
|||||||
|
|
||||||
const settingsAllRoles = useRecoilValue(settingsAllRolesSelector);
|
const settingsAllRoles = useRecoilValue(settingsAllRolesSelector);
|
||||||
|
|
||||||
|
const sortedSettingsAllRoles = [...settingsAllRoles].sort((a, b) =>
|
||||||
|
sortByAscString(a.label, b.label),
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Section>
|
<Section>
|
||||||
<H2Title
|
<H2Title
|
||||||
@ -49,10 +54,10 @@ export const SettingsRolesList = () => {
|
|||||||
<Table>
|
<Table>
|
||||||
<SettingsRolesTableHeader />
|
<SettingsRolesTableHeader />
|
||||||
<StyledTableRows>
|
<StyledTableRows>
|
||||||
{settingsAllRoles.length === 0 ? (
|
{sortedSettingsAllRoles.length === 0 ? (
|
||||||
<StyledNoRoles>{t`No roles found`}</StyledNoRoles>
|
<StyledNoRoles>{t`No roles found`}</StyledNoRoles>
|
||||||
) : (
|
) : (
|
||||||
settingsAllRoles.map((role) => (
|
sortedSettingsAllRoles.map((role) => (
|
||||||
<SettingsRolesTableRow key={role.id} role={role} />
|
<SettingsRolesTableRow key={role.id} role={role} />
|
||||||
))
|
))
|
||||||
)}
|
)}
|
||||||
|
|||||||
@ -111,11 +111,8 @@ export const SettingsRolesTableRow = ({ role }: SettingsRolesTableRowProps) => {
|
|||||||
<TableCell align={'left'}>
|
<TableCell align={'left'}>
|
||||||
<StyledAssignedText>{role.workspaceMembers.length}</StyledAssignedText>
|
<StyledAssignedText>{role.workspaceMembers.length}</StyledAssignedText>
|
||||||
</TableCell>
|
</TableCell>
|
||||||
<TableCell align={'right'}>
|
<TableCell align={'right'} color={theme.font.color.tertiary}>
|
||||||
<IconChevronRight
|
<IconChevronRight size={theme.icon.size.md} />
|
||||||
size={theme.icon.size.md}
|
|
||||||
color={theme.font.color.tertiary}
|
|
||||||
/>
|
|
||||||
</TableCell>
|
</TableCell>
|
||||||
</StyledTableRow>
|
</StyledTableRow>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -243,6 +243,7 @@ export const SettingsRoleAssignment = ({
|
|||||||
<Dropdown
|
<Dropdown
|
||||||
dropdownId="role-member-select"
|
dropdownId="role-member-select"
|
||||||
dropdownHotkeyScope={{ scope: 'roleAssignment' }}
|
dropdownHotkeyScope={{ scope: 'roleAssignment' }}
|
||||||
|
dropdownOffset={{ x: 0, y: 4 }}
|
||||||
clickableComponent={
|
clickableComponent={
|
||||||
<>
|
<>
|
||||||
<div id="assign-member">
|
<div id="assign-member">
|
||||||
@ -256,7 +257,7 @@ export const SettingsRoleAssignment = ({
|
|||||||
</div>
|
</div>
|
||||||
<AppTooltip
|
<AppTooltip
|
||||||
anchorSelect="#assign-member"
|
anchorSelect="#assign-member"
|
||||||
content={t`No more members to assign`}
|
content={t`The workspace needs at least one Admin`}
|
||||||
delay={TooltipDelay.noDelay}
|
delay={TooltipDelay.noDelay}
|
||||||
hidden={!allWorkspaceMembersHaveThisRole}
|
hidden={!allWorkspaceMembersHaveThisRole}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@ -1,8 +1,8 @@
|
|||||||
import { TableCell } from '@/ui/layout/table/components/TableCell';
|
import { TableCell } from '@/ui/layout/table/components/TableCell';
|
||||||
import { TableRow } from '@/ui/layout/table/components/TableRow';
|
import { TableRow } from '@/ui/layout/table/components/TableRow';
|
||||||
import styled from '@emotion/styled';
|
import styled from '@emotion/styled';
|
||||||
import { WorkspaceMember } from '~/generated-metadata/graphql';
|
|
||||||
import { Avatar, OverflowingTextWithTooltip } from 'twenty-ui/display';
|
import { Avatar, OverflowingTextWithTooltip } from 'twenty-ui/display';
|
||||||
|
import { WorkspaceMember } from '~/generated-metadata/graphql';
|
||||||
|
|
||||||
const StyledIconWrapper = styled.div`
|
const StyledIconWrapper = styled.div`
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
import { t } from '@lingui/core/macro';
|
import { t } from '@lingui/core/macro';
|
||||||
import { SearchRecord } from '~/generated-metadata/graphql';
|
|
||||||
import { MenuItem, MenuItemAvatar } from 'twenty-ui/navigation';
|
import { MenuItem, MenuItemAvatar } from 'twenty-ui/navigation';
|
||||||
|
import { SearchRecord } from '~/generated-metadata/graphql';
|
||||||
|
|
||||||
type SettingsRoleAssignmentWorkspaceMemberPickerDropdownContentProps = {
|
type SettingsRoleAssignmentWorkspaceMemberPickerDropdownContentProps = {
|
||||||
loading: boolean;
|
loading: boolean;
|
||||||
@ -34,6 +34,7 @@ export const SettingsRoleAssignmentWorkspaceMemberPickerDropdownContent = ({
|
|||||||
size: 'md',
|
size: 'md',
|
||||||
placeholder: workspaceMember.label ?? '',
|
placeholder: workspaceMember.label ?? '',
|
||||||
placeholderColorSeed: workspaceMember.recordId,
|
placeholderColorSeed: workspaceMember.recordId,
|
||||||
|
avatarUrl: workspaceMember.imageUrl,
|
||||||
}}
|
}}
|
||||||
text={workspaceMember.label}
|
text={workspaceMember.label}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@ -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_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 { settingsDraftRoleFamilyState } from '@/settings/roles/states/settingsDraftRoleFamilyState';
|
||||||
import { useTheme } from '@emotion/react';
|
|
||||||
import styled from '@emotion/styled';
|
import styled from '@emotion/styled';
|
||||||
import { useRecoilValue } from 'recoil';
|
import { useRecoilValue } from 'recoil';
|
||||||
import { isDefined } from 'twenty-shared/utils';
|
import { isDefined } from 'twenty-shared/utils';
|
||||||
import { ObjectPermission } from '~/generated/graphql';
|
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`
|
const StyledSettingsRolePermissionsObjectLevelOverrideCell = styled.div`
|
||||||
display: flex;
|
display: flex;
|
||||||
gap: ${({ theme }) => theme.spacing(1)};
|
gap: ${({ theme }) => theme.spacing(1)};
|
||||||
@ -43,8 +21,6 @@ export const SettingsRolePermissionsObjectLevelOverrideCell = ({
|
|||||||
objectPermission,
|
objectPermission,
|
||||||
roleId,
|
roleId,
|
||||||
}: SettingsRolePermissionsObjectLevelOverrideCellProps) => {
|
}: SettingsRolePermissionsObjectLevelOverrideCellProps) => {
|
||||||
const theme = useTheme();
|
|
||||||
|
|
||||||
const settingsDraftRole = useRecoilValue(
|
const settingsDraftRole = useRecoilValue(
|
||||||
settingsDraftRoleFamilyState(roleId),
|
settingsDraftRoleFamilyState(roleId),
|
||||||
);
|
);
|
||||||
@ -52,44 +28,33 @@ export const SettingsRolePermissionsObjectLevelOverrideCell = ({
|
|||||||
const permissionMappings =
|
const permissionMappings =
|
||||||
SETTINGS_ROLE_OBJECT_LEVEL_PERMISSION_TO_ROLE_OBJECT_PERMISSION_MAPPING;
|
SETTINGS_ROLE_OBJECT_LEVEL_PERMISSION_TO_ROLE_OBJECT_PERMISSION_MAPPING;
|
||||||
|
|
||||||
type ObjectPermissionKey = keyof typeof permissionMappings;
|
const isOverridden = (permission: SettingsRoleObjectPermissionKey) => {
|
||||||
|
|
||||||
const isOverridden = (permission: ObjectPermissionKey) => {
|
|
||||||
const rolePermission = permissionMappings[permission];
|
const rolePermission = permissionMappings[permission];
|
||||||
return (
|
return (
|
||||||
isDefined(objectPermission[permission]) &&
|
isDefined(objectPermission[permission]) &&
|
||||||
!!settingsDraftRole[rolePermission as keyof typeof settingsDraftRole] !==
|
!!settingsDraftRole[rolePermission] !== !!objectPermission[permission]
|
||||||
!!objectPermission[permission]
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<StyledSettingsRolePermissionsObjectLevelOverrideCell>
|
<StyledSettingsRolePermissionsObjectLevelOverrideCell>
|
||||||
{(Object.keys(permissionMappings) as ObjectPermissionKey[]).map(
|
{(
|
||||||
(permission) => {
|
Object.keys(permissionMappings) as SettingsRoleObjectPermissionKey[]
|
||||||
const { Icon, IconForbidden: IconOverride } =
|
).map((permission) => {
|
||||||
SETTINGS_ROLE_OBJECT_PERMISSION_ICON_CONFIG[permission];
|
const permissionValue = objectPermission[permission];
|
||||||
const permissionValue = objectPermission[permission];
|
|
||||||
|
|
||||||
if (!isOverridden(permission)) {
|
if (!isOverridden(permission)) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<StyledIconWrapper
|
<PermissionIcon
|
||||||
key={permission}
|
key={permission}
|
||||||
isForbidden={permissionValue === false}
|
permission={permission}
|
||||||
>
|
state={permissionValue === false ? 'revoked' : 'granted'}
|
||||||
<StyledIcon isForbidden={permissionValue === false}>
|
/>
|
||||||
{permissionValue === false && (
|
);
|
||||||
<IconOverride size={theme.icon.size.sm} />
|
})}
|
||||||
)}
|
|
||||||
{permissionValue === true && <Icon size={theme.icon.size.sm} />}
|
|
||||||
</StyledIcon>
|
|
||||||
</StyledIconWrapper>
|
|
||||||
);
|
|
||||||
},
|
|
||||||
)}
|
|
||||||
</StyledSettingsRolePermissionsObjectLevelOverrideCell>
|
</StyledSettingsRolePermissionsObjectLevelOverrideCell>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@ -130,6 +130,7 @@ export const SettingsRolePermissionsObjectLevelSection = ({
|
|||||||
disabled={!isEditable}
|
disabled={!isEditable}
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
|
dropdownOffset={{ x: 0, y: 4 }}
|
||||||
dropdownComponents={
|
dropdownComponents={
|
||||||
<SettingsRolePermissionsObjectLevelObjectPickerDropdownContent
|
<SettingsRolePermissionsObjectLevelObjectPickerDropdownContent
|
||||||
excludedObjectMetadataIds={
|
excludedObjectMetadataIds={
|
||||||
|
|||||||
@ -1,11 +1,11 @@
|
|||||||
import { OverridableCheckbox } from '@/settings/roles/role-permissions/object-level-permissions/object-form/components/OverridableCheckbox';
|
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_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 { SettingsRolePermissionsObjectLevelPermission } from '@/settings/roles/role-permissions/objects-permissions/types/SettingsRolePermissionsObjectPermission';
|
||||||
import { settingsDraftRoleFamilyState } from '@/settings/roles/states/settingsDraftRoleFamilyState';
|
import { settingsDraftRoleFamilyState } from '@/settings/roles/states/settingsDraftRoleFamilyState';
|
||||||
import { TableCell } from '@/ui/layout/table/components/TableCell';
|
import { TableCell } from '@/ui/layout/table/components/TableCell';
|
||||||
import { TableRow } from '@/ui/layout/table/components/TableRow';
|
import { TableRow } from '@/ui/layout/table/components/TableRow';
|
||||||
import { useTheme } from '@emotion/react';
|
|
||||||
import styled from '@emotion/styled';
|
import styled from '@emotion/styled';
|
||||||
import { t } from '@lingui/core/macro';
|
import { t } from '@lingui/core/macro';
|
||||||
import { useRecoilValue } from 'recoil';
|
import { useRecoilValue } from 'recoil';
|
||||||
@ -13,25 +13,36 @@ import { isDefined } from 'twenty-shared/utils';
|
|||||||
import { ObjectPermission } from '~/generated-metadata/graphql';
|
import { ObjectPermission } from '~/generated-metadata/graphql';
|
||||||
import type { Role } from '~/generated/graphql';
|
import type { Role } from '~/generated/graphql';
|
||||||
|
|
||||||
const StyledLabel = styled.span`
|
const StyledTableRow = styled(TableRow)`
|
||||||
color: ${({ theme }) => theme.font.color.primary};
|
align-items: center;
|
||||||
`;
|
display: flex;
|
||||||
|
|
||||||
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)`
|
const StyledPermissionCell = styled(TableCell)`
|
||||||
align-items: center;
|
align-items: center;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex: 1;
|
flex: 1;
|
||||||
gap: ${({ theme }) => theme.spacing(2)};
|
gap: ${({ theme }) => theme.spacing(1)};
|
||||||
padding-left: ${({ theme }) => theme.spacing(2)};
|
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)`
|
const StyledCheckboxCell = styled(TableCell)`
|
||||||
align-items: center;
|
align-items: center;
|
||||||
display: flex;
|
display: flex;
|
||||||
@ -39,11 +50,6 @@ const StyledCheckboxCell = styled(TableCell)`
|
|||||||
padding-right: ${({ theme }) => theme.spacing(4)};
|
padding-right: ${({ theme }) => theme.spacing(4)};
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const StyledTableRow = styled(TableRow)`
|
|
||||||
align-items: center;
|
|
||||||
display: flex;
|
|
||||||
`;
|
|
||||||
|
|
||||||
type OverridableCheckboxType = 'no_cta' | 'default' | 'override';
|
type OverridableCheckboxType = 'no_cta' | 'default' | 'override';
|
||||||
|
|
||||||
type SettingsRolePermissionsObjectLevelObjectFormObjectLevelTableRowProps = {
|
type SettingsRolePermissionsObjectLevelObjectFormObjectLevelTableRowProps = {
|
||||||
@ -60,17 +66,12 @@ export const SettingsRolePermissionsObjectLevelObjectFormObjectLevelTableRow =
|
|||||||
settingsDraftRoleObjectPermissions,
|
settingsDraftRoleObjectPermissions,
|
||||||
roleId,
|
roleId,
|
||||||
}: SettingsRolePermissionsObjectLevelObjectFormObjectLevelTableRowProps) => {
|
}: SettingsRolePermissionsObjectLevelObjectFormObjectLevelTableRowProps) => {
|
||||||
const theme = useTheme();
|
|
||||||
|
|
||||||
const settingsDraftRole = useRecoilValue(
|
const settingsDraftRole = useRecoilValue(
|
||||||
settingsDraftRoleFamilyState(roleId),
|
settingsDraftRoleFamilyState(roleId),
|
||||||
);
|
);
|
||||||
|
|
||||||
const label = permission.label;
|
const label = permission.label;
|
||||||
|
|
||||||
const { Icon } =
|
|
||||||
SETTINGS_ROLE_OBJECT_PERMISSION_ICON_CONFIG[permission.key];
|
|
||||||
|
|
||||||
const permissionMappings =
|
const permissionMappings =
|
||||||
SETTINGS_ROLE_OBJECT_LEVEL_PERMISSION_TO_ROLE_OBJECT_PERMISSION_MAPPING;
|
SETTINGS_ROLE_OBJECT_LEVEL_PERMISSION_TO_ROLE_OBJECT_PERMISSION_MAPPING;
|
||||||
|
|
||||||
@ -120,13 +121,21 @@ export const SettingsRolePermissionsObjectLevelObjectFormObjectLevelTableRow =
|
|||||||
return (
|
return (
|
||||||
<StyledTableRow>
|
<StyledTableRow>
|
||||||
<StyledPermissionCell>
|
<StyledPermissionCell>
|
||||||
<Icon size={theme.icon.size.sm} />
|
<StyledPermissionContent>
|
||||||
<StyledLabel>{label}</StyledLabel>
|
<PermissionIcon
|
||||||
{isRevoked ? (
|
permission={permission.key as SettingsRoleObjectPermissionKey}
|
||||||
<StyledOverrideInfo>
|
state={isRevoked ? 'revoked' : 'granted'}
|
||||||
{t`Revoked for this object`}
|
/>
|
||||||
</StyledOverrideInfo>
|
<StyledPermissionLabel>{label}</StyledPermissionLabel>
|
||||||
) : null}
|
</StyledPermissionContent>
|
||||||
|
<StyledOverrideInfo>
|
||||||
|
{isRevoked ? (
|
||||||
|
<>
|
||||||
|
{' · '}
|
||||||
|
{t`Revoked for this object`}
|
||||||
|
</>
|
||||||
|
) : null}
|
||||||
|
</StyledOverrideInfo>
|
||||||
</StyledPermissionCell>
|
</StyledPermissionCell>
|
||||||
<StyledCheckboxCell>
|
<StyledCheckboxCell>
|
||||||
<OverridableCheckbox
|
<OverridableCheckbox
|
||||||
|
|||||||
@ -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>
|
||||||
|
);
|
||||||
|
};
|
||||||
@ -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 { SettingsRolePermissionsObjectPermission } from '@/settings/roles/role-permissions/objects-permissions/types/SettingsRolePermissionsObjectPermission';
|
||||||
import { TableCell } from '@/ui/layout/table/components/TableCell';
|
import { TableCell } from '@/ui/layout/table/components/TableCell';
|
||||||
import { TableRow } from '@/ui/layout/table/components/TableRow';
|
import { TableRow } from '@/ui/layout/table/components/TableRow';
|
||||||
import { useTheme } from '@emotion/react';
|
|
||||||
import styled from '@emotion/styled';
|
import styled from '@emotion/styled';
|
||||||
import { t } from '@lingui/core/macro';
|
import { t } from '@lingui/core/macro';
|
||||||
import pluralize from 'pluralize';
|
import pluralize from 'pluralize';
|
||||||
import { Checkbox, CheckboxAccent } from 'twenty-ui/input';
|
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)`
|
const StyledPermissionCell = styled(TableCell)`
|
||||||
align-items: center;
|
align-items: center;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex: 1;
|
flex: 1;
|
||||||
gap: ${({ theme }) => theme.spacing(2)};
|
gap: ${({ theme }) => theme.spacing(1)};
|
||||||
padding-left: ${({ theme }) => theme.spacing(2)};
|
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)`
|
const StyledCheckboxCell = styled(TableCell)`
|
||||||
align-items: center;
|
align-items: center;
|
||||||
display: flex;
|
display: flex;
|
||||||
@ -48,25 +53,30 @@ export const SettingsRolePermissionsObjectsTableRow = ({
|
|||||||
permission,
|
permission,
|
||||||
isEditable,
|
isEditable,
|
||||||
}: SettingsRolePermissionsObjectsTableRowProps) => {
|
}: SettingsRolePermissionsObjectsTableRowProps) => {
|
||||||
const theme = useTheme();
|
|
||||||
|
|
||||||
const revokedBy = permission.revokedBy;
|
const revokedBy = permission.revokedBy;
|
||||||
const isRevoked = revokedBy && revokedBy > 0;
|
const isRevoked =
|
||||||
|
revokedBy !== undefined && revokedBy !== null && revokedBy > 0;
|
||||||
const label = permission.label;
|
const label = permission.label;
|
||||||
const pluralizedObject = pluralize('object', revokedBy);
|
const pluralizedObject = pluralize('object', revokedBy);
|
||||||
|
|
||||||
const { Icon } = SETTINGS_ROLE_OBJECT_PERMISSION_ICON_CONFIG[permission.key];
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<StyledTableRow>
|
<StyledTableRow>
|
||||||
<StyledPermissionCell>
|
<StyledPermissionCell>
|
||||||
<Icon size={theme.icon.size.sm} />
|
<StyledPermissionContent>
|
||||||
<StyledLabel>{label}</StyledLabel>
|
<PermissionIcon
|
||||||
{isRevoked ? (
|
permission={permission.key as SettingsRoleObjectPermissionKey}
|
||||||
<StyledOverrideInfo>
|
state={isRevoked ? 'revoked' : 'granted'}
|
||||||
{t`Revoked on ${revokedBy} ${pluralizedObject}`}
|
/>
|
||||||
</StyledOverrideInfo>
|
<StyledPermissionLabel>{label}</StyledPermissionLabel>
|
||||||
) : null}
|
</StyledPermissionContent>
|
||||||
|
<StyledOverrideInfo>
|
||||||
|
{isRevoked ? (
|
||||||
|
<>
|
||||||
|
{' · '}
|
||||||
|
{t`Revoked on ${revokedBy} ${pluralizedObject}`}
|
||||||
|
</>
|
||||||
|
) : null}
|
||||||
|
</StyledOverrideInfo>
|
||||||
</StyledPermissionCell>
|
</StyledPermissionCell>
|
||||||
<StyledCheckboxCell>
|
<StyledCheckboxCell>
|
||||||
<Checkbox
|
<Checkbox
|
||||||
|
|||||||
@ -14,8 +14,14 @@ type SettingsRoleObjectPermissionIconConfig = {
|
|||||||
IconForbidden: IconComponent;
|
IconForbidden: IconComponent;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type SettingsRoleObjectPermissionKey =
|
||||||
|
| 'canReadObjectRecords'
|
||||||
|
| 'canUpdateObjectRecords'
|
||||||
|
| 'canSoftDeleteObjectRecords'
|
||||||
|
| 'canDestroyObjectRecords';
|
||||||
|
|
||||||
export const SETTINGS_ROLE_OBJECT_PERMISSION_ICON_CONFIG: Record<
|
export const SETTINGS_ROLE_OBJECT_PERMISSION_ICON_CONFIG: Record<
|
||||||
string,
|
SettingsRoleObjectPermissionKey,
|
||||||
SettingsRoleObjectPermissionIconConfig
|
SettingsRoleObjectPermissionIconConfig
|
||||||
> = {
|
> = {
|
||||||
canReadObjectRecords: {
|
canReadObjectRecords: {
|
||||||
|
|||||||
@ -34,11 +34,11 @@ export const SettingsRoleCreateEffect = ({
|
|||||||
label: '',
|
label: '',
|
||||||
description: '',
|
description: '',
|
||||||
icon: 'IconUser',
|
icon: 'IconUser',
|
||||||
canUpdateAllSettings: false,
|
canUpdateAllSettings: true,
|
||||||
canReadAllObjectRecords: false,
|
canReadAllObjectRecords: true,
|
||||||
canUpdateAllObjectRecords: false,
|
canUpdateAllObjectRecords: true,
|
||||||
canSoftDeleteAllObjectRecords: false,
|
canSoftDeleteAllObjectRecords: true,
|
||||||
canDestroyAllObjectRecords: false,
|
canDestroyAllObjectRecords: true,
|
||||||
isEditable: true,
|
isEditable: true,
|
||||||
workspaceMembers: [],
|
workspaceMembers: [],
|
||||||
};
|
};
|
||||||
|
|||||||
@ -2,6 +2,7 @@ import { Module } from '@nestjs/common';
|
|||||||
import { TypeOrmModule } from '@nestjs/typeorm';
|
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||||
|
|
||||||
import { FeatureFlagModule } from 'src/engine/core-modules/feature-flag/feature-flag.module';
|
import { FeatureFlagModule } from 'src/engine/core-modules/feature-flag/feature-flag.module';
|
||||||
|
import { FileModule } from 'src/engine/core-modules/file/file.module';
|
||||||
import { UserWorkspace } from 'src/engine/core-modules/user-workspace/user-workspace.entity';
|
import { UserWorkspace } from 'src/engine/core-modules/user-workspace/user-workspace.entity';
|
||||||
import { UserWorkspaceModule } from 'src/engine/core-modules/user-workspace/user-workspace.module';
|
import { UserWorkspaceModule } from 'src/engine/core-modules/user-workspace/user-workspace.module';
|
||||||
import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
|
import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
|
||||||
@ -26,6 +27,7 @@ import { WorkspacePermissionsCacheModule } from 'src/engine/metadata-modules/wor
|
|||||||
ObjectPermissionModule,
|
ObjectPermissionModule,
|
||||||
SettingPermissionModule,
|
SettingPermissionModule,
|
||||||
WorkspacePermissionsCacheModule,
|
WorkspacePermissionsCacheModule,
|
||||||
|
FileModule,
|
||||||
],
|
],
|
||||||
providers: [RoleService, RoleResolver],
|
providers: [RoleService, RoleResolver],
|
||||||
exports: [RoleService],
|
exports: [RoleService],
|
||||||
|
|||||||
@ -8,8 +8,12 @@ import {
|
|||||||
Resolver,
|
Resolver,
|
||||||
} from '@nestjs/graphql';
|
} from '@nestjs/graphql';
|
||||||
|
|
||||||
|
import { buildSignedPath } from 'twenty-shared/utils';
|
||||||
|
|
||||||
import { FeatureFlagKey } from 'src/engine/core-modules/feature-flag/enums/feature-flag-key.enum';
|
import { FeatureFlagKey } from 'src/engine/core-modules/feature-flag/enums/feature-flag-key.enum';
|
||||||
import { FeatureFlagService } from 'src/engine/core-modules/feature-flag/services/feature-flag.service';
|
import { FeatureFlagService } from 'src/engine/core-modules/feature-flag/services/feature-flag.service';
|
||||||
|
import { FileService } from 'src/engine/core-modules/file/services/file.service';
|
||||||
|
import { extractFilenameFromPath } from 'src/engine/core-modules/file/utils/extract-file-id-from-path.utils';
|
||||||
import { UserWorkspaceService } from 'src/engine/core-modules/user-workspace/user-workspace.service';
|
import { UserWorkspaceService } from 'src/engine/core-modules/user-workspace/user-workspace.service';
|
||||||
import { WorkspaceMember } from 'src/engine/core-modules/user/dtos/workspace-member.dto';
|
import { WorkspaceMember } from 'src/engine/core-modules/user/dtos/workspace-member.dto';
|
||||||
import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
|
import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
|
||||||
@ -47,6 +51,7 @@ export class RoleResolver {
|
|||||||
private readonly featureFlagService: FeatureFlagService,
|
private readonly featureFlagService: FeatureFlagService,
|
||||||
private readonly objectPermissionService: ObjectPermissionService,
|
private readonly objectPermissionService: ObjectPermissionService,
|
||||||
private readonly settingPermissionService: SettingPermissionService,
|
private readonly settingPermissionService: SettingPermissionService,
|
||||||
|
private readonly fileService: FileService,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
@Query(() => [RoleDTO])
|
@Query(() => [RoleDTO])
|
||||||
@ -180,10 +185,29 @@ export class RoleResolver {
|
|||||||
@Parent() role: RoleDTO,
|
@Parent() role: RoleDTO,
|
||||||
@AuthWorkspace() workspace: Workspace,
|
@AuthWorkspace() workspace: Workspace,
|
||||||
): Promise<WorkspaceMemberWorkspaceEntity[]> {
|
): Promise<WorkspaceMemberWorkspaceEntity[]> {
|
||||||
return this.userRoleService.getWorkspaceMembersAssignedToRole(
|
const workspaceMembers =
|
||||||
role.id,
|
await this.userRoleService.getWorkspaceMembersAssignedToRole(
|
||||||
workspace.id,
|
role.id,
|
||||||
|
workspace.id,
|
||||||
|
);
|
||||||
|
|
||||||
|
await Promise.all(
|
||||||
|
workspaceMembers.map(async (workspaceMember) => {
|
||||||
|
if (workspaceMember && workspaceMember.avatarUrl) {
|
||||||
|
const avatarUrlToken = this.fileService.encodeFileToken({
|
||||||
|
filename: extractFilenameFromPath(workspaceMember.avatarUrl),
|
||||||
|
workspaceId: workspace.id,
|
||||||
|
});
|
||||||
|
|
||||||
|
workspaceMember.avatarUrl = buildSignedPath({
|
||||||
|
path: workspaceMember.avatarUrl,
|
||||||
|
token: avatarUrlToken,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
return workspaceMembers;
|
||||||
}
|
}
|
||||||
|
|
||||||
private async validatePermissionsV2EnabledOrThrow(workspace: Workspace) {
|
private async validatePermissionsV2EnabledOrThrow(workspace: Workspace) {
|
||||||
|
|||||||
@ -218,7 +218,7 @@ export class RoleService {
|
|||||||
canUpdateAllObjectRecords: true,
|
canUpdateAllObjectRecords: true,
|
||||||
canSoftDeleteAllObjectRecords: true,
|
canSoftDeleteAllObjectRecords: true,
|
||||||
canDestroyAllObjectRecords: true,
|
canDestroyAllObjectRecords: true,
|
||||||
isEditable: false,
|
isEditable: true,
|
||||||
workspaceId,
|
workspaceId,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user