role settings various fixes + update role object level permission design (#12664)

<img width="944" alt="Screenshot 2025-06-17 at 12 10 07"
src="https://github.com/user-attachments/assets/abfda0c2-3266-465c-b98e-7bf78660a057"
/>
<img width="943" alt="Screenshot 2025-06-17 at 12 10 00"
src="https://github.com/user-attachments/assets/8fd28479-1f55-4f3a-815c-1195154d3305"
/>
<img width="667" alt="Screenshot 2025-06-17 at 12 09 49"
src="https://github.com/user-attachments/assets/8d444523-4e43-4b59-95bb-45dc5fac5520"
/>
<img width="632" alt="Screenshot 2025-06-17 at 12 09 42"
src="https://github.com/user-attachments/assets/8a1e45bb-7fde-42a6-9f2d-79cbec8121cd"
/>
<img width="643" alt="Screenshot 2025-06-17 at 12 09 36"
src="https://github.com/user-attachments/assets/43f80a92-16e2-4a0e-8a07-2f3e7278ff4a"
/>
This commit is contained in:
Weiko
2025-06-17 16:00:31 +02:00
committed by GitHub
parent f10abec505
commit 8f07f681d2
12 changed files with 350 additions and 162 deletions

View File

@ -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={<SettingsRoleObjectLevel />}
/>
<Route
path={SettingsPath.RoleAddObjectLevel}
element={<SettingsRoleAddObjectLevel />}
/>
</Route>
<Route
element={

View File

@ -47,10 +47,6 @@ const StyledSearchContainer = styled.div`
padding-bottom: ${({ theme }) => 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)};

View File

@ -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 (
<StyledTypeSelectContainer>
<Section>
<StyledSearchContainer>
<StyledSearchInput
value={searchFilter}
onChange={handleSearchChange}
placeholder={t`Search an object`}
fullWidth
LeftIcon={IconSearch}
sizeVariant="lg"
/>
</StyledSearchContainer>
</Section>
{basicObjects.length > 0 && (
<Section>
<H2Title title={t`Basics`} description={t`All the basic objects`} />
<StyledContainer>
{basicObjects.map((objectMetadataItem) => {
const Icon = getIcon(objectMetadataItem.icon);
return (
<StyledCardContainer
key={objectMetadataItem.id}
onClick={() =>
handleSelectObjectMetadata(objectMetadataItem.id)
}
>
<SettingsCard
Icon={
<Icon
size={theme.icon.size.xl}
stroke={theme.icon.stroke.sm}
/>
}
title={objectMetadataItem.labelPlural}
/>
</StyledCardContainer>
);
})}
</StyledContainer>
</Section>
)}
{customObjects.length > 0 && (
<Section>
<H2Title title={t`Custom`} description={t`All your custom objects`} />
<StyledContainer>
{customObjects.map((objectMetadataItem) => {
const Icon = getIcon(objectMetadataItem.icon);
return (
<StyledCardContainer
key={objectMetadataItem.id}
onClick={() =>
handleSelectObjectMetadata(objectMetadataItem.id)
}
>
<SettingsCard
key={objectMetadataItem.id}
Icon={
<Icon
size={theme.icon.size.xl}
stroke={theme.icon.stroke.sm}
/>
}
title={objectMetadataItem.labelPlural}
/>
</StyledCardContainer>
);
})}
</StyledContainer>
</Section>
)}
</StyledTypeSelectContainer>
);
};

View File

@ -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<HTMLInputElement>) => {
setSearchFilter(event.target.value);
};
const filteredObjectMetadataItems = objectMetadataItems.filter(
(objectMetadataItem) =>
objectMetadataItem.labelSingular
.toLowerCase()
.includes(searchFilter.toLowerCase()) &&
!excludedObjectMetadataIds.includes(objectMetadataItem.id),
);
return (
<DropdownContent>
<DropdownMenuSearchInput
value={searchFilter}
onChange={handleSearchFilterChange}
placeholder={t`Search`}
/>
<DropdownMenuSeparator />
<DropdownMenuItemsContainer hasMaxHeight>
{filteredObjectMetadataItems.map((objectMetadataItem) => (
<MenuItem
key={objectMetadataItem.id}
text={objectMetadataItem.labelSingular}
LeftIcon={getIcon(objectMetadataItem.icon)}
onClick={() => onSelect(objectMetadataItem.id)}
/>
))}
</DropdownMenuItemsContainer>
</DropdownContent>
);
};

View File

@ -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 = ({
</StyledTableRows>
</Table>
<StyledCreateObjectOverrideSection>
<Dropdown
dropdownId="role-object-select"
dropdownHotkeyScope={{ scope: 'roleObject' }}
clickableComponent={
<Button
Icon={IconPlus}
title={t`Add rule`}
variant="secondary"
size="small"
disabled={!isEditable}
/>
}
dropdownOffset={{ x: 0, y: 4 }}
dropdownComponents={
<SettingsRolePermissionsObjectLevelObjectPickerDropdownContent
excludedObjectMetadataIds={
filteredObjectPermissions?.map(
(objectPermission) => objectPermission.objectMetadataId,
) ?? []
}
onSelect={handleSelectObjectMetadata}
/>
}
<Button
Icon={IconPlus}
title={t`Add rule`}
variant="secondary"
size="small"
disabled={!isEditable || allObjectsHaveSetPermission}
onClick={handleAddRule}
/>
</StyledCreateObjectOverrideSection>
</Section>

View File

@ -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 (
<SubMenuTopBarContainer
title={
<StyledTitleContainer>
<H3Title title={objectMetadataItem.labelPlural} />
<StyledObjectTypeTag objectTypeLabel={objectTypeLabel} />
</StyledTitleContainer>
}
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={
<Button
title={t`Back`}
variant="primary"
title={t`Finish`}
variant="secondary"
size="small"
accent="blue"
to={getSettingsPath(SettingsPath.RoleDetail, {

View File

@ -0,0 +1,31 @@
import { isDefined } from 'twenty-shared/utils';
import { ObjectPermission, Role } from '~/generated-metadata/graphql';
export const hasPermissionOverride = (
objectPermission: ObjectPermission,
settingsDraftRole: Role,
) => {
const permissionChecks = [
{
permission: objectPermission.canReadObjectRecords,
globalPermission: settingsDraftRole.canReadAllObjectRecords,
},
{
permission: objectPermission.canUpdateObjectRecords,
globalPermission: settingsDraftRole.canUpdateAllObjectRecords,
},
{
permission: objectPermission.canSoftDeleteObjectRecords,
globalPermission: settingsDraftRole.canSoftDeleteAllObjectRecords,
},
{
permission: objectPermission.canDestroyObjectRecords,
globalPermission: settingsDraftRole.canDestroyAllObjectRecords,
},
];
return permissionChecks.some(
({ permission, globalPermission }) =>
isDefined(permission) && permission !== globalPermission,
);
};

View File

@ -20,7 +20,7 @@ import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/
import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled';
import { getOperationName } from '@apollo/client/utilities';
import { t } from '@lingui/core/macro';
import { useRecoilValue } from 'recoil';
import { useRecoilState, useRecoilValue } from 'recoil';
import { isDefined } from 'twenty-shared/utils';
import { IconLockOpen, IconSettings, IconUserPlus } from 'twenty-ui/display';
import { v4 } from 'uuid';
@ -74,7 +74,7 @@ export const SettingsRole = ({ roleId, isCreateMode }: SettingsRoleProps) => {
const settingsRolesIsLoading = useRecoilValue(settingsRolesIsLoadingState);
const settingsDraftRole = useRecoilValue(
const [settingsDraftRole, setSettingsDraftRole] = useRecoilState(
settingsDraftRoleFamilyState(roleId),
);
@ -110,6 +110,12 @@ export const SettingsRole = ({ roleId, isCreateMode }: SettingsRoleProps) => {
const isDirty = !isDeeplyEqual(settingsDraftRole, settingsPersistedRole);
const handleCancel = () => {
if (isDefined(settingsPersistedRole)) {
setSettingsDraftRole(settingsPersistedRole);
}
};
const handleSave = async () => {
const dirtyFields = getDirtyFields(
settingsDraftRole,
@ -281,11 +287,10 @@ export const SettingsRole = ({ roleId, isCreateMode }: SettingsRoleProps) => {
},
]}
actionButton={
<SaveAndCancelButtons
onSave={handleSave}
onCancel={() => navigateSettings(SettingsPath.Roles)}
isSaveDisabled={!isRoleEditable || !isDirty}
/>
isRoleEditable &&
isDirty && (
<SaveAndCancelButtons onSave={handleSave} onCancel={handleCancel} />
)
}
>
<SettingsPageContainer>

View File

@ -45,4 +45,5 @@ export enum SettingsPath {
RoleCreate = 'roles/create',
RoleDetail = 'roles/:roleId',
RoleObjectLevel = 'roles/:roleId/object/:objectMetadataId',
RoleAddObjectLevel = 'roles/:roleId/add-object-permission',
}

View File

@ -0,0 +1,45 @@
import { SettingsPageContainer } from '@/settings/components/SettingsPageContainer';
import { SettingsRolesQueryEffect } from '@/settings/roles/components/SettingsRolesQueryEffect';
import { SettingsRolePermissionsObjectLevelObjectPicker } from '@/settings/roles/role-permissions/object-level-permissions/components/SettingsRolePermissionsObjectLevelObjectPicker';
import { settingsDraftRoleFamilyState } from '@/settings/roles/states/settingsDraftRoleFamilyState';
import { SettingsPath } from '@/types/SettingsPath';
import { SubMenuTopBarContainer } from '@/ui/layout/page/components/SubMenuTopBarContainer';
import { t } from '@lingui/core/macro';
import { Navigate, useParams } from 'react-router-dom';
import { useRecoilValue } from 'recoil';
import { getSettingsPath } from '~/utils/navigation/getSettingsPath';
export const SettingsRoleAddObjectLevel = () => {
const { roleId } = useParams();
const settingsDraftRole = useRecoilValue(
settingsDraftRoleFamilyState(roleId ?? ''),
);
if (!roleId) {
return <Navigate to={getSettingsPath(SettingsPath.Roles)} />;
}
return (
<>
<SettingsRolesQueryEffect />
<SubMenuTopBarContainer
title={t`1. Select an object`}
links={[
{ children: t`Roles`, href: '/settings/roles' },
{
children: settingsDraftRole.label ?? '',
href: `/settings/roles/${roleId}`,
},
{
children: t`Add object permission`,
href: `/settings/roles/${roleId}/add-object-permission`,
},
]}
>
<SettingsPageContainer>
<SettingsRolePermissionsObjectLevelObjectPicker roleId={roleId} />
</SettingsPageContainer>
</SubMenuTopBarContainer>
</>
);
};

View File

@ -1,14 +1,16 @@
import { SettingsRolesQueryEffect } from '@/settings/roles/components/SettingsRolesQueryEffect';
import { SettingsRole } from '@/settings/roles/role/components/SettingsRole';
import { SettingsRoleEditEffect } from '@/settings/roles/role/components/SettingsRoleEditEffect';
import { SettingsPath } from '@/types/SettingsPath';
import { Navigate, useParams } from 'react-router-dom';
import { isDefined } from 'twenty-shared/utils';
import { getSettingsPath } from '~/utils/navigation/getSettingsPath';
export const SettingsRoleEdit = () => {
const { roleId } = useParams();
if (!isDefined(roleId)) {
return <Navigate to="/settings/roles" />;
return <Navigate to={getSettingsPath(SettingsPath.Roles)} />;
}
return (

View File

@ -1,18 +1,21 @@
import { Navigate, useParams } from 'react-router-dom';
import { SettingsRolesQueryEffect } from '@/settings/roles/components/SettingsRolesQueryEffect';
import { SettingsRolePermissionsObjectLevelObjectForm } from '@/settings/roles/role-permissions/object-level-permissions/object-form/components/SettingsRolePermissionsObjectLevelObjectForm';
import { SettingsPath } from '@/types/SettingsPath';
import { Navigate, useParams } from 'react-router-dom';
import { isDefined } from 'twenty-shared/utils';
import { getSettingsPath } from '~/utils/navigation/getSettingsPath';
export const SettingsRoleObjectLevel = () => {
const { roleId, objectMetadataId } = useParams();
if (!isDefined(roleId)) {
return <Navigate to="/settings/roles" />;
return <Navigate to={getSettingsPath(SettingsPath.Roles)} />;
}
if (!isDefined(objectMetadataId)) {
return <Navigate to={`/settings/roles/${roleId}`} />;
return (
<Navigate to={getSettingsPath(SettingsPath.RoleDetail, { roleId })} />
);
}
return (