Add role edit page container (#10037)

## Context

This PR adds a new SettingsRoleEdit page, the existing roles page now
redirects to the role edition page when clicking on it.
For now, we can't edit anything. Next step is to allow role assignment
in the corresponding tab.

<img width="941" alt="Screenshot 2025-02-05 at 17 16 14"
src="https://github.com/user-attachments/assets/ca46de15-6237-4de6-88e1-2384a09d4a27"
/>
This commit is contained in:
Weiko
2025-02-06 11:27:56 +01:00
committed by GitHub
parent 8a660d5d3f
commit e849378726
6 changed files with 179 additions and 1 deletions

View File

@ -265,6 +265,12 @@ const SettingsRoles = lazy(() =>
})),
);
const SettingsRoleEdit = lazy(() =>
import('~/pages/settings/roles/SettingsRoleEdit').then((module) => ({
default: module.SettingsRoleEdit,
})),
);
type SettingsRoutesProps = {
isBillingEnabled?: boolean;
isFunctionSettingsEnabled?: boolean;
@ -311,6 +317,7 @@ export const SettingsRoutes = ({
/>
<Route path={SettingsPath.NewObject} element={<SettingsNewObject />} />
<Route path={SettingsPath.Roles} element={<SettingsRoles />} />
<Route path={SettingsPath.RoleDetail} element={<SettingsRoleEdit />} />
<Route path={SettingsPath.Developers} element={<SettingsDevelopers />} />
<Route
path={SettingsPath.DevelopersNewApiKey}

View File

@ -37,4 +37,5 @@ export enum SettingsPath {
FeatureFlags = 'admin-panel/feature-flags',
Lab = 'lab',
Roles = 'roles',
RoleDetail = 'roles/:roleId',
}

View File

@ -0,0 +1,123 @@
import styled from '@emotion/styled';
import { t } from '@lingui/core/macro';
import { useEffect } from 'react';
import { useParams } from 'react-router-dom';
import { H3Title, IconLockOpen, IconUser, IconUserPlus } from 'twenty-ui';
import { SettingsPageContainer } from '@/settings/components/SettingsPageContainer';
import { SettingsPath } from '@/types/SettingsPath';
import { SubMenuTopBarContainer } from '@/ui/layout/page/components/SubMenuTopBarContainer';
import { TabList } from '@/ui/layout/tab/components/TabList';
import { useTabList } from '@/ui/layout/tab/hooks/useTabList';
import { useGetRolesQuery } from '~/generated/graphql';
import { useNavigateSettings } from '~/hooks/useNavigateSettings';
import { RolePermissions } from '~/pages/settings/roles/components/RolePermissions';
import { getSettingsPath } from '~/utils/navigation/getSettingsPath';
import { RoleAssignment } from './components/RoleAssignment';
const StyledContentContainer = styled.div`
flex: 1;
width: 100%;
padding-left: 0;
`;
const StyledTitleContainer = styled.div`
display: flex;
align-items: center;
gap: ${({ theme }) => theme.spacing(2)};
`;
const StyledIconUser = styled(IconUser)`
color: ${({ theme }) => theme.font.color.primary};
`;
export const SETTINGS_ROLE_DETAIL_TABS = {
COMPONENT_INSTANCE_ID: 'settings-role-detail-tabs',
TABS_IDS: {
ASSIGNMENT: 'assignment',
PERMISSIONS: 'permissions',
},
} as const;
export const SettingsRoleEdit = () => {
const { roleId = '' } = useParams();
const { data: rolesData, loading: rolesLoading } = useGetRolesQuery();
const navigateSettings = useNavigateSettings();
const role = rolesData?.getRoles.find((r) => r.id === roleId);
const { activeTabId } = useTabList(
SETTINGS_ROLE_DETAIL_TABS.COMPONENT_INSTANCE_ID,
);
useEffect(() => {
if (!rolesLoading && !role) {
navigateSettings(SettingsPath.Roles);
}
}, [role, navigateSettings, rolesLoading]);
if (!role) return null;
const tabs = [
{
id: SETTINGS_ROLE_DETAIL_TABS.TABS_IDS.ASSIGNMENT,
title: t`Assignment`,
Icon: IconUserPlus,
hide: false,
},
{
id: SETTINGS_ROLE_DETAIL_TABS.TABS_IDS.PERMISSIONS,
title: t`Permissions`,
Icon: IconLockOpen,
hide: false,
},
];
const renderActiveTabContent = () => {
switch (activeTabId) {
case SETTINGS_ROLE_DETAIL_TABS.TABS_IDS.ASSIGNMENT:
return <RoleAssignment role={role} />;
case SETTINGS_ROLE_DETAIL_TABS.TABS_IDS.PERMISSIONS:
return <RolePermissions role={role} />;
default:
return null;
}
};
return (
<>
<SubMenuTopBarContainer
title={
<StyledTitleContainer>
<StyledIconUser size={16} />
<H3Title title={role.label} />
</StyledTitleContainer>
}
links={[
{
children: 'Workspace',
href: getSettingsPath(SettingsPath.Workspace),
},
{
children: 'Roles',
href: getSettingsPath(SettingsPath.Roles),
},
{
children: role.label,
},
]}
>
<SettingsPageContainer>
<TabList
tabListInstanceId={SETTINGS_ROLE_DETAIL_TABS.COMPONENT_INSTANCE_ID}
tabs={tabs}
className="tab-list"
/>
<StyledContentContainer>
{renderActiveTabContent()}
</StyledContentContainer>
</SettingsPageContainer>
</SubMenuTopBarContainer>
</>
);
};

View File

@ -23,6 +23,7 @@ import { TableRow } from '@/ui/layout/table/components/TableRow';
import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled';
import { useTheme } from '@emotion/react';
import { FeatureFlagKey, useGetRolesQuery } from '~/generated/graphql';
import { useNavigateSettings } from '~/hooks/useNavigateSettings';
import { getSettingsPath } from '~/utils/navigation/getSettingsPath';
const StyledTable = styled(Table)`
@ -89,6 +90,7 @@ export const SettingsRoles = () => {
FeatureFlagKey.IsPermissionsEnabled,
);
const theme = useTheme();
const navigateSettings = useNavigateSettings();
const { data: rolesData, loading: isRolesLoading } = useGetRolesQuery();
@ -96,6 +98,10 @@ export const SettingsRoles = () => {
return null;
}
const handleRoleClick = (roleId: string) => {
navigateSettings(SettingsPath.RoleDetail, { roleId });
};
return (
<SubMenuTopBarContainer
title={t`Roles`}
@ -127,7 +133,10 @@ export const SettingsRoles = () => {
</StyledTableHeaderRow>
{!isRolesLoading &&
rolesData?.getRoles.map((role) => (
<StyledTableRow key={role.id}>
<StyledTableRow
key={role.id}
onClick={() => handleRoleClick(role.id)}
>
<TableCell>
<StyledNameCell>
<IconUser size={theme.icon.size.md} />

View File

@ -0,0 +1,19 @@
import { t } from '@lingui/core/macro';
import { H2Title, Section } from 'twenty-ui';
import { Role } from '~/generated-metadata/graphql';
type RoleAssignmentProps = {
role: Pick<Role, 'id' | 'label' | 'canUpdateAllSettings'>;
};
// eslint-disable-next-line unused-imports/no-unused-vars
export const RoleAssignment = ({ role }: RoleAssignmentProps) => {
return (
<Section>
<H2Title
title={t`Assigned members`}
description={t`This Role is assigned to these workspace member.`}
/>
</Section>
);
};

View File

@ -0,0 +1,19 @@
import { t } from '@lingui/core/macro';
import { H2Title, Section } from 'twenty-ui';
import { Role } from '~/generated-metadata/graphql';
type RolePermissionsProps = {
role: Pick<Role, 'id' | 'label' | 'canUpdateAllSettings'>;
};
// eslint-disable-next-line unused-imports/no-unused-vars
export const RolePermissions = ({ role }: RolePermissionsProps) => {
return (
<Section>
<H2Title
title={t`Permissions`}
description={t`This Role has the following permissions.`}
/>
</Section>
);
};