Feat/14410 move roles tab out of all roles (#14415)

# Move the roles tabs out of All Roles section #14410

**Description:**  
This is a **draft PR** opened for early feedback before final polishing.

fixes(#14410)

**Changes included:**  
- Moved the roles tabs out of the “All Roles” section as per the Figma
design.
- Added **search functionality** to the roles sections, following the
Figma specification.
- Updated layout and components to reflect the Figma design: [Figma
link](https://www.figma.com/design/xt8O9mFeLl46C5InWwoMrN/Twenty?node-id=67064-202200&t=Z27CkXdNfG1vJMzG-11)

**Notes for reviewers:**  
Please provide any **recommendations, feedback, or changes** before this
is ready for final review.

---

A **screen recording** demonstrating the functionality ->

[Screencast from 2025-09-11
17-39-35.webm](https://github.com/user-attachments/assets/b1a0376e-8d12-414f-899e-83f35dd14842)
 

This is my **first contribution**, so I opened a draft PR before
polishing. Please recommend any changes and review if needed.

---------

Co-authored-by: Abdul Rahman <ar5438376@gmail.com>
This commit is contained in:
Saurav Jain
2025-09-15 00:43:52 +05:30
committed by GitHub
co-authored by Abdul Rahman
parent e13db84f48
commit 469e7165fd
2 changed files with 72 additions and 44 deletions
@@ -14,7 +14,10 @@ import { activeTabIdComponentState } from '@/ui/layout/tab-list/states/activeTab
import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValue';
import { Trans, useLingui } from '@lingui/react/macro';
import { useRecoilValue } from 'recoil';
import { H3Title } from 'twenty-ui/display';
import { H3Title, IconUser, IconRobot, IconKey } from 'twenty-ui/display';
import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled';
import { FeatureFlagKey } from '~/generated/graphql';
import { TabList } from '@/ui/layout/tab-list/components/TabList';
export const SettingsRolesContainer = () => {
const { t } = useLingui();
@@ -25,6 +28,29 @@ export const SettingsRolesContainer = () => {
);
const settingsAllRoles = useRecoilValue(settingsAllRolesSelector);
const settingsRolesIsLoading = useRecoilValue(settingsRolesIsLoadingState);
const isAiEnabled = useIsFeatureEnabled(FeatureFlagKey.IS_AI_ENABLED);
const tabs = [
{
id: ROLES_LIST_TABS.TABS_IDS.USER_ROLES,
title: t`User Roles`,
Icon: IconUser,
},
...(isAiEnabled
? [
{
id: ROLES_LIST_TABS.TABS_IDS.AGENT_ROLES,
title: t`Agent Roles`,
Icon: IconRobot,
},
]
: []),
{
id: ROLES_LIST_TABS.TABS_IDS.API_KEY_ROLES,
title: t`API Key Roles`,
Icon: IconKey,
},
];
if (settingsRolesIsLoading && !settingsAllRoles) {
return null;
@@ -42,6 +68,11 @@ export const SettingsRolesContainer = () => {
]}
>
<SettingsPageContainer>
<TabList
tabs={tabs}
className="tab-list"
componentInstanceId={ROLES_LIST_TABS.COMPONENT_INSTANCE_ID}
/>
<SettingsRolesList />
{activeTabId === ROLES_LIST_TABS.TABS_IDS.USER_ROLES && (
<SettingsRoleDefaultRole roles={settingsAllRoles} />
@@ -6,25 +6,18 @@ import { SettingsRolesTableHeader } from '@/settings/roles/components/SettingsRo
import { SettingsRolesTableRow } from '@/settings/roles/components/SettingsRolesTableRow';
import { ROLES_LIST_TABS } from '@/settings/roles/constants/RolesListTabs';
import { settingsAllRolesSelector } from '@/settings/roles/states/settingsAllRolesSelector';
import { TabList } from '@/ui/layout/tab-list/components/TabList';
import { activeTabIdComponentState } from '@/ui/layout/tab-list/states/activeTabIdComponentState';
import { TableCell } from '@/ui/layout/table/components/TableCell';
import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValue';
import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled';
import { useRecoilValue } from 'recoil';
import { SettingsPath } from 'twenty-shared/types';
import {
H2Title,
IconKey,
IconPlus,
IconRobot,
IconUser,
} from 'twenty-ui/display';
import { H2Title, IconPlus, IconSearch } from 'twenty-ui/display';
import { Button } from 'twenty-ui/input';
import { Section } from 'twenty-ui/layout';
import { FeatureFlagKey } from '~/generated/graphql';
import { useNavigateSettings } from '~/hooks/useNavigateSettings';
import { sortByAscString } from '~/utils/array/sortByAscString';
import { SettingsTextInput } from '@/ui/input/components/SettingsTextInput';
import { useState } from 'react';
const StyledCreateRoleSection = styled(Section)`
border-top: 1px solid ${({ theme }) => theme.border.color.light};
@@ -43,6 +36,11 @@ const StyledNoRoles = styled(TableCell)`
color: ${({ theme }) => theme.font.color.tertiary};
`;
const StyledSearchInput = styled(SettingsTextInput)`
margin-bottom: ${({ theme }) => theme.spacing(2)};
width: 100%;
`;
export const SettingsRolesList = () => {
const navigateSettings = useNavigateSettings();
const activeTabId = useRecoilComponentValue(
@@ -50,60 +48,59 @@ export const SettingsRolesList = () => {
ROLES_LIST_TABS.COMPONENT_INSTANCE_ID,
);
const [searchTerm, setSearchTerm] = useState('');
const settingsAllRoles = useRecoilValue(settingsAllRolesSelector);
const isAiEnabled = useIsFeatureEnabled(FeatureFlagKey.IS_AI_ENABLED);
const sortedSettingsAllRoles = [...settingsAllRoles].sort((a, b) =>
sortByAscString(a.label, b.label),
);
const filteredRoles = sortedSettingsAllRoles.filter((role) => {
let matchesTab = false;
switch (activeTabId) {
case ROLES_LIST_TABS.TABS_IDS.USER_ROLES:
return role.canBeAssignedToUsers;
matchesTab = role.canBeAssignedToUsers;
break;
case ROLES_LIST_TABS.TABS_IDS.AGENT_ROLES:
return role.canBeAssignedToAgents;
matchesTab = role.canBeAssignedToAgents;
break;
case ROLES_LIST_TABS.TABS_IDS.API_KEY_ROLES:
return role.canBeAssignedToApiKeys;
matchesTab = role.canBeAssignedToApiKeys;
break;
default:
return role.canBeAssignedToUsers;
matchesTab = role.canBeAssignedToUsers;
}
return (
matchesTab && role.label?.toLowerCase().includes(searchTerm.toLowerCase())
);
});
const tabs = [
{
id: ROLES_LIST_TABS.TABS_IDS.USER_ROLES,
title: t`User Roles`,
Icon: IconUser,
},
...(isAiEnabled
? [
{
id: ROLES_LIST_TABS.TABS_IDS.AGENT_ROLES,
title: t`Agent Roles`,
Icon: IconRobot,
},
]
: []),
{
id: ROLES_LIST_TABS.TABS_IDS.API_KEY_ROLES,
title: t`API Key Roles`,
Icon: IconKey,
},
];
const tabDescriptions: Record<string, string> = {
[ROLES_LIST_TABS.TABS_IDS.USER_ROLES]:
t`Assign roles to specify each member's access permissions`,
[ROLES_LIST_TABS.TABS_IDS.AGENT_ROLES]:
t`Assign roles to specify each agent's access permissions`,
[ROLES_LIST_TABS.TABS_IDS.API_KEY_ROLES]:
t`Assign roles to specify each API key's access permissions`,
};
const description = isAiEnabled
? t`Manage roles and permissions for team members, agents, and API keys`
: t`Manage roles and permissions for team members and API keys`;
const description =
(activeTabId && tabDescriptions[activeTabId]) ??
t`Assign roles to specify each member's access permissions`;
return (
<Section>
<H2Title title={t`All roles`} description={description} />
<TabList
tabs={tabs}
className="tab-list"
componentInstanceId={ROLES_LIST_TABS.COMPONENT_INSTANCE_ID}
<StyledSearchInput
instanceId="settings-objects-search"
LeftIcon={IconSearch}
placeholder={t`Search a role...`}
value={searchTerm}
onChange={setSearchTerm}
/>
<Table>