diff --git a/packages/twenty-front/src/modules/app/components/SettingsRoutes.tsx b/packages/twenty-front/src/modules/app/components/SettingsRoutes.tsx
index 5c4cb936a..83a9b601b 100644
--- a/packages/twenty-front/src/modules/app/components/SettingsRoutes.tsx
+++ b/packages/twenty-front/src/modules/app/components/SettingsRoutes.tsx
@@ -327,6 +327,14 @@ const SettingsRoleObjectLevel = lazy(() =>
})),
);
+const SettingsRoleAddObjectLevel = lazy(() =>
+ import('~/pages/settings/roles/SettingsRoleAddObjectLevel').then(
+ (module) => ({
+ default: module.SettingsRoleAddObjectLevel,
+ }),
+ ),
+);
+
type SettingsRoutesProps = {
isFunctionSettingsEnabled?: boolean;
isAdminPageEnabled?: boolean;
@@ -420,6 +428,10 @@ export const SettingsRoutes = ({
path={SettingsPath.RoleObjectLevel}
element={}
/>
+ }
+ />
theme.spacing(2)};
`;
-const StyledTable = styled.div`
- border-bottom: 1px solid ${({ theme }) => theme.border.color.light};
-`;
-
const StyledSearchInput = styled(TextInput)`
input {
background: ${({ theme }) => theme.background.transparent.lighter};
@@ -58,6 +54,10 @@ const StyledSearchInput = styled(TextInput)`
}
`;
+const StyledTable = styled.div`
+ border-bottom: 1px solid ${({ theme }) => theme.border.color.light};
+`;
+
const StyledTableRows = styled.div`
gap: ${({ theme }) => theme.spacing(0.5)};
padding-bottom: ${({ theme }) => theme.spacing(2)};
diff --git a/packages/twenty-front/src/modules/settings/roles/role-permissions/object-level-permissions/components/SettingsRolePermissionsObjectLevelObjectPicker.tsx b/packages/twenty-front/src/modules/settings/roles/role-permissions/object-level-permissions/components/SettingsRolePermissionsObjectLevelObjectPicker.tsx
new file mode 100644
index 000000000..f2cbfd495
--- /dev/null
+++ b/packages/twenty-front/src/modules/settings/roles/role-permissions/object-level-permissions/components/SettingsRolePermissionsObjectLevelObjectPicker.tsx
@@ -0,0 +1,198 @@
+import { useFilteredObjectMetadataItems } from '@/object-metadata/hooks/useFilteredObjectMetadataItems';
+import { isWorkflowRelatedObjectMetadata } from '@/object-metadata/utils/isWorkflowRelatedObjectMetadata';
+import { SettingsCard } from '@/settings/components/SettingsCard';
+import { hasPermissionOverride } from '@/settings/roles/role-permissions/object-level-permissions/utils/hasPermissionOverride';
+import { settingsDraftRoleFamilyState } from '@/settings/roles/states/settingsDraftRoleFamilyState';
+import { SettingsPath } from '@/types/SettingsPath';
+import { TextInput } from '@/ui/input/components/TextInput';
+import { useTheme } from '@emotion/react';
+import styled from '@emotion/styled';
+import { t } from '@lingui/core/macro';
+import { useMemo, useState } from 'react';
+import { useRecoilState } from 'recoil';
+import { H2Title, IconSearch, useIcons } from 'twenty-ui/display';
+import { Section } from 'twenty-ui/layout';
+import { useNavigateSettings } from '~/hooks/useNavigateSettings';
+
+const StyledTypeSelectContainer = styled.div`
+ display: flex;
+ flex-direction: column;
+ gap: inherit;
+ width: 100%;
+`;
+
+const StyledContainer = styled.div`
+ display: flex;
+ gap: ${({ theme }) => theme.spacing(2)};
+ justify-content: flex-start;
+ flex-wrap: wrap;
+ width: 100%;
+`;
+
+const StyledCardContainer = styled.div`
+ cursor: pointer;
+ display: flex;
+ position: relative;
+ width: calc(50% - ${({ theme }) => theme.spacing(1)});
+`;
+
+const StyledSearchContainer = styled.div`
+ padding-bottom: ${({ theme }) => theme.spacing(2)};
+`;
+
+const StyledSearchInput = styled(TextInput)`
+ input {
+ background: ${({ theme }) => theme.background.transparent.lighter};
+ border: 1px solid ${({ theme }) => theme.border.color.medium};
+ }
+`;
+
+export const SettingsRolePermissionsObjectLevelObjectPicker = ({
+ roleId,
+}: {
+ roleId: string;
+}) => {
+ const theme = useTheme();
+ const navigate = useNavigateSettings();
+ const [searchFilter, setSearchFilter] = useState('');
+ const [settingsDraftRole, setSettingsDraftRole] = useRecoilState(
+ settingsDraftRoleFamilyState(roleId),
+ );
+
+ const { alphaSortedActiveNonSystemObjectMetadataItems: objectMetadataItems } =
+ useFilteredObjectMetadataItems();
+
+ const { getIcon } = useIcons();
+
+ const handleSearchChange = (text: string) => {
+ setSearchFilter(text);
+ };
+
+ const handleSelectObjectMetadata = (objectMetadataId: string) => {
+ setSettingsDraftRole((draftRole) => ({
+ ...draftRole,
+ objectPermissions: [
+ ...(draftRole.objectPermissions ?? []).filter(
+ (permission) => permission.objectMetadataId !== objectMetadataId,
+ ),
+ {
+ objectMetadataId,
+ canReadObjectRecords: null,
+ canUpdateObjectRecords: null,
+ canSoftDeleteObjectRecords: null,
+ canDestroyObjectRecords: null,
+ },
+ ],
+ }));
+ navigate(SettingsPath.RoleObjectLevel, {
+ roleId,
+ objectMetadataId,
+ });
+ };
+
+ const excludedObjectMetadataIds = useMemo(
+ () =>
+ settingsDraftRole.objectPermissions
+ ?.filter((objectPermission) =>
+ hasPermissionOverride(objectPermission, settingsDraftRole),
+ )
+ .map((p) => p.objectMetadataId) ?? [],
+ [settingsDraftRole],
+ );
+
+ const filteredObjectMetadataItems = useMemo(
+ () =>
+ objectMetadataItems.filter(
+ (objectMetadataItem) =>
+ objectMetadataItem.labelPlural
+ .toLowerCase()
+ .includes(searchFilter.toLowerCase()) &&
+ !excludedObjectMetadataIds.includes(objectMetadataItem.id) &&
+ !isWorkflowRelatedObjectMetadata(objectMetadataItem.nameSingular),
+ ),
+ [objectMetadataItems, searchFilter, excludedObjectMetadataIds],
+ );
+
+ const basicObjects = filteredObjectMetadataItems.filter(
+ (item) => !item.isCustom,
+ );
+ const customObjects = filteredObjectMetadataItems.filter(
+ (item) => item.isCustom,
+ );
+
+ return (
+
+
+
+ {basicObjects.length > 0 && (
+
+
+
+ {basicObjects.map((objectMetadataItem) => {
+ const Icon = getIcon(objectMetadataItem.icon);
+ return (
+
+ handleSelectObjectMetadata(objectMetadataItem.id)
+ }
+ >
+
+ }
+ title={objectMetadataItem.labelPlural}
+ />
+
+ );
+ })}
+
+
+ )}
+
+ {customObjects.length > 0 && (
+
+
+
+ {customObjects.map((objectMetadataItem) => {
+ const Icon = getIcon(objectMetadataItem.icon);
+ return (
+
+ handleSelectObjectMetadata(objectMetadataItem.id)
+ }
+ >
+
+ }
+ title={objectMetadataItem.labelPlural}
+ />
+
+ );
+ })}
+
+
+ )}
+
+ );
+};
diff --git a/packages/twenty-front/src/modules/settings/roles/role-permissions/object-level-permissions/components/SettingsRolePermissionsObjectLevelObjectPickerDropdownContent.tsx b/packages/twenty-front/src/modules/settings/roles/role-permissions/object-level-permissions/components/SettingsRolePermissionsObjectLevelObjectPickerDropdownContent.tsx
deleted file mode 100644
index 27d992992..000000000
--- a/packages/twenty-front/src/modules/settings/roles/role-permissions/object-level-permissions/components/SettingsRolePermissionsObjectLevelObjectPickerDropdownContent.tsx
+++ /dev/null
@@ -1,59 +0,0 @@
-import { useFilteredObjectMetadataItems } from '@/object-metadata/hooks/useFilteredObjectMetadataItems';
-import { DropdownContent } from '@/ui/layout/dropdown/components/DropdownContent';
-import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer';
-import { DropdownMenuSearchInput } from '@/ui/layout/dropdown/components/DropdownMenuSearchInput';
-import { DropdownMenuSeparator } from '@/ui/layout/dropdown/components/DropdownMenuSeparator';
-import { t } from '@lingui/core/macro';
-import { ChangeEvent, useState } from 'react';
-import { useIcons } from 'twenty-ui/display';
-import { MenuItem } from 'twenty-ui/navigation';
-
-type SettingsRolePermissionsObjectLevelObjectPickerDropdownContentProps = {
- excludedObjectMetadataIds: string[];
- onSelect: (objectMetadataId: string) => void;
-};
-
-export const SettingsRolePermissionsObjectLevelObjectPickerDropdownContent = ({
- excludedObjectMetadataIds,
- onSelect,
-}: SettingsRolePermissionsObjectLevelObjectPickerDropdownContentProps) => {
- const [searchFilter, setSearchFilter] = useState('');
-
- const { alphaSortedActiveNonSystemObjectMetadataItems: objectMetadataItems } =
- useFilteredObjectMetadataItems();
-
- const { getIcon } = useIcons();
-
- const handleSearchFilterChange = (event: ChangeEvent) => {
- setSearchFilter(event.target.value);
- };
-
- const filteredObjectMetadataItems = objectMetadataItems.filter(
- (objectMetadataItem) =>
- objectMetadataItem.labelSingular
- .toLowerCase()
- .includes(searchFilter.toLowerCase()) &&
- !excludedObjectMetadataIds.includes(objectMetadataItem.id),
- );
-
- return (
-
-
-
-
- {filteredObjectMetadataItems.map((objectMetadataItem) => (
-
-
- );
-};
diff --git a/packages/twenty-front/src/modules/settings/roles/role-permissions/object-level-permissions/components/SettingsRolePermissionsObjectLevelSection.tsx b/packages/twenty-front/src/modules/settings/roles/role-permissions/object-level-permissions/components/SettingsRolePermissionsObjectLevelSection.tsx
index e25586446..877a541e6 100644
--- a/packages/twenty-front/src/modules/settings/roles/role-permissions/object-level-permissions/components/SettingsRolePermissionsObjectLevelSection.tsx
+++ b/packages/twenty-front/src/modules/settings/roles/role-permissions/object-level-permissions/components/SettingsRolePermissionsObjectLevelSection.tsx
@@ -1,16 +1,16 @@
-import { useObjectMetadataItems } from '@/object-metadata/hooks/useObjectMetadataItems';
+import { useFilteredObjectMetadataItems } from '@/object-metadata/hooks/useFilteredObjectMetadataItems';
import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
-import { SettingsRolePermissionsObjectLevelObjectPickerDropdownContent } from '@/settings/roles/role-permissions/object-level-permissions/components/SettingsRolePermissionsObjectLevelObjectPickerDropdownContent';
+import { isWorkflowRelatedObjectMetadata } from '@/object-metadata/utils/isWorkflowRelatedObjectMetadata';
import { SettingsRolePermissionsObjectLevelTableHeader } from '@/settings/roles/role-permissions/object-level-permissions/components/SettingsRolePermissionsObjectLevelTableHeader';
import { SettingsRolePermissionsObjectLevelTableRow } from '@/settings/roles/role-permissions/object-level-permissions/components/SettingsRolePermissionsObjectLevelTableRow';
+import { hasPermissionOverride } from '@/settings/roles/role-permissions/object-level-permissions/utils/hasPermissionOverride';
import { settingsDraftRoleFamilyState } from '@/settings/roles/states/settingsDraftRoleFamilyState';
import { SettingsPath } from '@/types/SettingsPath';
-import { Dropdown } from '@/ui/layout/dropdown/components/Dropdown';
import { Table } from '@/ui/layout/table/components/Table';
import { TableCell } from '@/ui/layout/table/components/TableCell';
import styled from '@emotion/styled';
import { t } from '@lingui/core/macro';
-import { useRecoilState } from 'recoil';
+import { useRecoilValue } from 'recoil';
import { isDefined } from 'twenty-shared/utils';
import { H2Title, IconPlus } from 'twenty-ui/display';
import { Button } from 'twenty-ui/input';
@@ -43,15 +43,20 @@ export const SettingsRolePermissionsObjectLevelSection = ({
roleId,
isEditable,
}: SettingsRolePermissionsObjectLevelSectionProps) => {
- const [settingsDraftRole, setSettingsDraftRole] = useRecoilState(
+ const settingsDraftRole = useRecoilValue(
settingsDraftRoleFamilyState(roleId),
);
- const navigate = useNavigateSettings();
+ const navigateSettings = useNavigateSettings();
- const objectMetadataItems = useObjectMetadataItems();
+ const { alphaSortedActiveNonSystemObjectMetadataItems: objectMetadataItems } =
+ useFilteredObjectMetadataItems();
- const objectMetadataMap = objectMetadataItems.objectMetadataItems.reduce(
+ const filteredObjectMetadataItems = objectMetadataItems.filter(
+ (item) => !isWorkflowRelatedObjectMetadata(item.nameSingular),
+ );
+
+ const objectMetadataMap = filteredObjectMetadataItems.reduce(
(acc, item) => {
acc[item.id] = item;
return acc;
@@ -61,39 +66,18 @@ export const SettingsRolePermissionsObjectLevelSection = ({
const filteredObjectPermissions = settingsDraftRole.objectPermissions?.filter(
(objectPermission) =>
- (isDefined(objectPermission.canReadObjectRecords) &&
- objectPermission.canReadObjectRecords !==
- settingsDraftRole.canReadAllObjectRecords) ||
- (isDefined(objectPermission.canUpdateObjectRecords) &&
- objectPermission.canUpdateObjectRecords !==
- settingsDraftRole.canUpdateAllObjectRecords) ||
- (isDefined(objectPermission.canSoftDeleteObjectRecords) &&
- objectPermission.canSoftDeleteObjectRecords !==
- settingsDraftRole.canSoftDeleteAllObjectRecords) ||
- (isDefined(objectPermission.canDestroyObjectRecords) &&
- objectPermission.canDestroyObjectRecords !==
- settingsDraftRole.canDestroyAllObjectRecords),
+ hasPermissionOverride(objectPermission, settingsDraftRole) &&
+ !isWorkflowRelatedObjectMetadata(
+ objectMetadataMap[objectPermission.objectMetadataId]?.nameSingular,
+ ),
);
- const handleSelectObjectMetadata = (objectMetadataId: string) => {
- setSettingsDraftRole((draftRole) => ({
- ...draftRole,
- objectPermissions: [
- ...(draftRole.objectPermissions ?? []).filter(
- (permission) => permission.objectMetadataId !== objectMetadataId,
- ),
- {
- objectMetadataId,
- canReadObjectRecords: null,
- canUpdateObjectRecords: null,
- canSoftDeleteObjectRecords: null,
- canDestroyObjectRecords: null,
- },
- ],
- }));
- navigate(SettingsPath.RoleObjectLevel, {
+ const allObjectsHaveSetPermission =
+ filteredObjectPermissions?.length === filteredObjectMetadataItems.length;
+
+ const handleAddRule = () => {
+ navigateSettings(SettingsPath.RoleAddObjectLevel, {
roleId,
- objectMetadataId,
});
};
@@ -124,29 +108,13 @@ export const SettingsRolePermissionsObjectLevelSection = ({
-
- }
- dropdownOffset={{ x: 0, y: 4 }}
- dropdownComponents={
- objectPermission.objectMetadataId,
- ) ?? []
- }
- onSelect={handleSelectObjectMetadata}
- />
- }
+
diff --git a/packages/twenty-front/src/modules/settings/roles/role-permissions/object-level-permissions/object-form/components/SettingsRolePermissionsObjectLevelObjectForm.tsx b/packages/twenty-front/src/modules/settings/roles/role-permissions/object-level-permissions/object-form/components/SettingsRolePermissionsObjectLevelObjectForm.tsx
index ad9b9f8f4..5d5b8fa1e 100644
--- a/packages/twenty-front/src/modules/settings/roles/role-permissions/object-level-permissions/object-form/components/SettingsRolePermissionsObjectLevelObjectForm.tsx
+++ b/packages/twenty-front/src/modules/settings/roles/role-permissions/object-level-permissions/object-form/components/SettingsRolePermissionsObjectLevelObjectForm.tsx
@@ -1,28 +1,14 @@
import { useObjectMetadataItemById } from '@/object-metadata/hooks/useObjectMetadataItemById';
import { SettingsPageContainer } from '@/settings/components/SettingsPageContainer';
-import { SettingsDataModelObjectTypeTag } from '@/settings/data-model/objects/components/SettingsDataModelObjectTypeTag';
-import { getObjectTypeLabel } from '@/settings/data-model/utils/getObjectTypeLabel';
import { SettingsRolePermissionsObjectLevelObjectFormObjectLevel } from '@/settings/roles/role-permissions/object-level-permissions/object-form/components/SettingsRolePermissionsObjectLevelObjectFormObjectLevel';
import { settingsDraftRoleFamilyState } from '@/settings/roles/states/settingsDraftRoleFamilyState';
import { SettingsPath } from '@/types/SettingsPath';
import { SubMenuTopBarContainer } from '@/ui/layout/page/components/SubMenuTopBarContainer';
-import styled from '@emotion/styled';
import { t } from '@lingui/core/macro';
import { useRecoilValue } from 'recoil';
-import { H3Title } from 'twenty-ui/display';
import { Button } from 'twenty-ui/input';
import { getSettingsPath } from '~/utils/navigation/getSettingsPath';
-const StyledObjectTypeTag = styled(SettingsDataModelObjectTypeTag)`
- box-sizing: border-box;
- height: ${({ theme }) => theme.spacing(5)};
- margin-left: ${({ theme }) => theme.spacing(2)};
-`;
-
-const StyledTitleContainer = styled.div`
- display: flex;
-`;
-
type SettingsRolePermissionsObjectLevelObjectFormProps = {
roleId: string;
objectMetadataId: string;
@@ -42,23 +28,19 @@ export const SettingsRolePermissionsObjectLevelObjectForm = ({
const objectMetadataItem = objectMetadata.objectMetadataItem;
- const objectTypeLabel = getObjectTypeLabel(objectMetadataItem);
+ const objectLabelSingular = objectMetadataItem.labelSingular;
+ const objectLabelPlural = objectMetadataItem.labelPlural;
return (
-
-
-
- }
+ title={t`2. Set ${objectLabelPlural} permissions`}
links={[
{
- children: 'Workspace',
+ children: t`Workspace`,
href: getSettingsPath(SettingsPath.Workspace),
},
{
- children: 'Roles',
+ children: t`Roles`,
href: getSettingsPath(SettingsPath.Roles),
},
{
@@ -68,13 +50,13 @@ export const SettingsRolePermissionsObjectLevelObjectForm = ({
}),
},
{
- children: `Permissions · ${objectMetadataItem.labelSingular}`,
+ children: t`Permissions · ${objectLabelSingular}`,
},
]}
actionButton={