add role update (#11217)

## Context
This PR introduces the new Create and Edit role components, behind the
PERMISSIONS_ENABLED_V2 feature flag.
This commit is contained in:
Weiko
2025-03-31 17:57:14 +02:00
committed by GitHub
parent 3c9bf2294f
commit 06ff16e086
58 changed files with 1527 additions and 624 deletions

View File

@ -0,0 +1,15 @@
import { SettingsRolesQueryEffect } from '@/settings/roles/components/SettingsRolesQueryEffect';
import { SettingsRole } from '@/settings/roles/role/components/SettingsRole';
import { SettingsRoleCreateEffect } from '@/settings/roles/role/components/SettingsRoleCreateEffect';
export const PENDING_ROLE_ID = 'pending-role-id';
export const SettingsRoleCreate = () => {
return (
<>
<SettingsRolesQueryEffect />
<SettingsRoleCreateEffect roleId={PENDING_ROLE_ID} />
<SettingsRole roleId={PENDING_ROLE_ID} isCreateMode={true} />
</>
);
};

View File

@ -1,119 +1,21 @@
import { t } from '@lingui/core/macro';
import { useEffect } from 'react';
import { useParams } from 'react-router-dom';
import { H3Title, IconLockOpen, IconSettings, IconUserPlus } from 'twenty-ui';
import { SettingsPageContainer } from '@/settings/components/SettingsPageContainer';
import { RoleAssignment } from '@/settings/roles/role-assignment/components/RoleAssignment';
import { RolePermissions } from '@/settings/roles/role-permissions/components/RolePermissions';
import { RoleSettings } from '@/settings/roles/role-settings/components/RoleSettings';
import { SettingsPath } from '@/types/SettingsPath';
import { SubMenuTopBarContainer } from '@/ui/layout/page/components/SubMenuTopBarContainer';
import { TabList } from '@/ui/layout/tab/components/TabList';
import { activeTabIdComponentState } from '@/ui/layout/tab/states/activeTabIdComponentState';
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
import { useGetRolesQuery } from '~/generated/graphql';
import { useNavigateSettings } from '~/hooks/useNavigateSettings';
import { getSettingsPath } from '~/utils/navigation/getSettingsPath';
export const SETTINGS_ROLE_DETAIL_TABS = {
COMPONENT_INSTANCE_ID: 'settings-role-detail-tabs',
TABS_IDS: {
ASSIGNMENT: 'assignment',
PERMISSIONS: 'permissions',
SETTINGS: 'settings',
},
} as const;
import { SettingsRolesQueryEffect } from '@/settings/roles/components/SettingsRolesQueryEffect';
import { SettingsRole } from '@/settings/roles/role/components/SettingsRole';
import { SettingsRoleEditEffect } from '@/settings/roles/role/components/SettingsRoleEditEffect';
import { Navigate, useParams } from 'react-router-dom';
import { isDefined } from 'twenty-shared/utils';
export const SettingsRoleEdit = () => {
const { roleId = '' } = useParams();
const navigateSettings = useNavigateSettings();
const { data: rolesData, loading: rolesLoading } = useGetRolesQuery({
fetchPolicy: 'network-only',
});
const { roleId } = useParams();
const role = rolesData?.getRoles.find((r) => r.id === roleId);
const activeTabId = useRecoilComponentValueV2(
activeTabIdComponentState,
SETTINGS_ROLE_DETAIL_TABS.COMPONENT_INSTANCE_ID,
);
useEffect(() => {
if (!rolesLoading && !role) {
navigateSettings(SettingsPath.Roles);
}
}, [role, navigateSettings, rolesLoading]);
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,
},
{
id: SETTINGS_ROLE_DETAIL_TABS.TABS_IDS.SETTINGS,
title: t`Settings`,
Icon: IconSettings,
hide: false,
},
];
const renderActiveTabContent = () => {
if (!role) {
return null;
}
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} />;
case SETTINGS_ROLE_DETAIL_TABS.TABS_IDS.SETTINGS:
return <RoleSettings role={role} />;
default:
return null;
}
};
if (!isDefined(roleId)) {
return <Navigate to="/settings/roles" />;
}
return (
<SubMenuTopBarContainer
title={role && <H3Title title={role.label} />}
links={[
{
children: 'Workspace',
href: getSettingsPath(SettingsPath.Workspace),
},
{
children: 'Roles',
href: getSettingsPath(SettingsPath.Roles),
},
{
children: role?.label,
},
]}
>
{!rolesLoading && role ? (
<SettingsPageContainer>
<TabList
tabs={tabs}
className="tab-list"
componentInstanceId={
SETTINGS_ROLE_DETAIL_TABS.COMPONENT_INSTANCE_ID
}
/>
{renderActiveTabContent()}
</SettingsPageContainer>
) : (
<></>
)}
</SubMenuTopBarContainer>
<>
<SettingsRolesQueryEffect />
<SettingsRoleEditEffect roleId={roleId} />
<SettingsRole roleId={roleId} isCreateMode={false} />
</>
);
};

View File

@ -1,39 +1,11 @@
import { Trans, useLingui } from '@lingui/react/macro';
import { SettingsPageContainer } from '@/settings/components/SettingsPageContainer';
import { Roles } from '@/settings/roles/components/Roles';
import { RolesDefaultRole } from '@/settings/roles/components/RolesDefaultRole';
import { SettingsPath } from '@/types/SettingsPath';
import { SubMenuTopBarContainer } from '@/ui/layout/page/components/SubMenuTopBarContainer';
import { H3Title } from 'twenty-ui';
import { useGetRolesQuery } from '~/generated/graphql';
import { getSettingsPath } from '~/utils/navigation/getSettingsPath';
import { SettingsRolesContainer } from '@/settings/roles/components/SettingsRolesContainer';
import { SettingsRolesQueryEffect } from '@/settings/roles/components/SettingsRolesQueryEffect';
export const SettingsRoles = () => {
const { t } = useLingui();
const { data: rolesData, loading: rolesLoading } = useGetRolesQuery({
fetchPolicy: 'network-only',
});
return (
<SubMenuTopBarContainer
title={rolesData && <H3Title title={t`Roles`} />}
links={[
{
children: <Trans>Workspace</Trans>,
href: getSettingsPath(SettingsPath.Workspace),
},
{ children: <Trans>Roles</Trans> },
]}
>
<SettingsPageContainer>
{!rolesLoading && rolesData && (
<>
<Roles roles={rolesData.getRoles ?? []} />
<RolesDefaultRole roles={rolesData.getRoles ?? []} />
</>
)}
</SettingsPageContainer>
</SubMenuTopBarContainer>
<>
<SettingsRolesQueryEffect />
<SettingsRolesContainer />
</>
);
};

View File

@ -0,0 +1,27 @@
import { Meta, StoryObj } from '@storybook/react';
import {
PageDecorator,
PageDecoratorArgs,
} from '~/testing/decorators/PageDecorator';
import { graphqlMocks } from '~/testing/graphqlMocks';
import { SettingsRoleCreate } from '../SettingsRoleCreate';
const meta: Meta<PageDecoratorArgs> = {
title: 'Pages/Settings/Roles/SettingsRoleCreate',
component: SettingsRoleCreate,
decorators: [PageDecorator],
args: {
routePath: '/settings/roles/create',
},
parameters: {
msw: graphqlMocks,
},
};
export default meta;
export type Story = StoryObj<typeof SettingsRoleCreate>;
export const Default: Story = {};

View File

@ -6,13 +6,18 @@ import {
} from '~/testing/decorators/PageDecorator';
import { graphqlMocks } from '~/testing/graphqlMocks';
import { GET_ROLES } from '@/settings/roles/graphql/queries/getRolesQuery';
import { getOperationName } from '@apollo/client/utilities';
import { graphql, HttpResponse } from 'msw';
import { SettingsRoles } from '../SettingsRoles';
const meta: Meta<PageDecoratorArgs> = {
title: 'Pages/Settings/Roles/SettingsRoles',
component: SettingsRoles,
decorators: [PageDecorator],
args: { routePath: '/settings/roles' },
args: {
routePath: '/settings/roles',
},
parameters: {
msw: graphqlMocks,
},
@ -23,3 +28,19 @@ export default meta;
export type Story = StoryObj<typeof SettingsRoles>;
export const Default: Story = {};
export const NoRoles: Story = {
parameters: {
msw: {
handlers: [
graphql.query(getOperationName(GET_ROLES) ?? '', () => {
return HttpResponse.json({
data: {
getRoles: [],
},
});
}),
],
},
},
};