diff --git a/packages/twenty-front/src/modules/settings/roles/components/SettingsRolesContainer.tsx b/packages/twenty-front/src/modules/settings/roles/components/SettingsRolesContainer.tsx index ba2e139694..e4765822ed 100644 --- a/packages/twenty-front/src/modules/settings/roles/components/SettingsRolesContainer.tsx +++ b/packages/twenty-front/src/modules/settings/roles/components/SettingsRolesContainer.tsx @@ -6,51 +6,18 @@ import { SettingsPageContainer } from '@/settings/components/SettingsPageContain import { SettingsRoleDefaultRole } from '@/settings/roles/components/SettingsRolesDefaultRole'; import { SettingsRolesList } from '@/settings/roles/components/SettingsRolesList'; -import { ROLES_LIST_TABS } from '@/settings/roles/constants/RolesListTabs'; import { settingsAllRolesSelector } from '@/settings/roles/states/settingsAllRolesSelector'; import { settingsRolesIsLoadingState } from '@/settings/roles/states/settingsRolesIsLoadingState'; import { SubMenuTopBarContainer } from '@/ui/layout/page/components/SubMenuTopBarContainer'; -import { activeTabIdComponentState } from '@/ui/layout/tab-list/states/activeTabIdComponentState'; -import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValue'; import { Trans, useLingui } from '@lingui/react/macro'; import { useRecoilValue } from 'recoil'; -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'; +import { H3Title } from 'twenty-ui/display'; export const SettingsRolesContainer = () => { const { t } = useLingui(); - const activeTabId = useRecoilComponentValue( - activeTabIdComponentState, - ROLES_LIST_TABS.COMPONENT_INSTANCE_ID, - ); 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; @@ -68,15 +35,8 @@ export const SettingsRolesContainer = () => { ]} > - - {activeTabId === ROLES_LIST_TABS.TABS_IDS.USER_ROLES && ( - - )} + ); diff --git a/packages/twenty-front/src/modules/settings/roles/components/SettingsRolesList.tsx b/packages/twenty-front/src/modules/settings/roles/components/SettingsRolesList.tsx index 3c494b3ff2..48f6ed6721 100644 --- a/packages/twenty-front/src/modules/settings/roles/components/SettingsRolesList.tsx +++ b/packages/twenty-front/src/modules/settings/roles/components/SettingsRolesList.tsx @@ -1,23 +1,31 @@ +import { Dropdown } from '@/ui/layout/dropdown/components/Dropdown'; +import { DropdownContent } from '@/ui/layout/dropdown/components/DropdownContent'; +import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer'; import { Table } from '@/ui/layout/table/components/Table'; import styled from '@emotion/styled'; import { t } from '@lingui/core/macro'; import { SettingsRolesTableHeader } from '@/settings/roles/components/SettingsRolesTableHeader'; import { SettingsRolesTableRow } from '@/settings/roles/components/SettingsRolesTableRow'; -import { ROLES_LIST_TABS } from '@/settings/roles/constants/RolesListTabs'; import { settingsAllRolesSelector } from '@/settings/roles/states/settingsAllRolesSelector'; -import { activeTabIdComponentState } from '@/ui/layout/tab-list/states/activeTabIdComponentState'; +import { SettingsTextInput } from '@/ui/input/components/SettingsTextInput'; import { TableCell } from '@/ui/layout/table/components/TableCell'; -import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValue'; +import { useState } from 'react'; import { useRecoilValue } from 'recoil'; import { SettingsPath } from 'twenty-shared/types'; -import { H2Title, IconPlus, IconSearch } from 'twenty-ui/display'; +import { + H2Title, + IconFilter, + IconKey, + IconPlus, + IconRobot, + IconSearch, +} from 'twenty-ui/display'; import { Button } from 'twenty-ui/input'; import { Section } from 'twenty-ui/layout'; +import { MenuItemToggle } from 'twenty-ui/navigation'; 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}; @@ -36,19 +44,22 @@ const StyledNoRoles = styled(TableCell)` color: ${({ theme }) => theme.font.color.tertiary}; `; -const StyledSearchInput = styled(SettingsTextInput)` +const StyledSearchAndFilterContainer = styled.div` + display: flex; + gap: ${({ theme }) => theme.spacing(2)}; margin-bottom: ${({ theme }) => theme.spacing(2)}; width: 100%; `; +const StyledSearchInput = styled(SettingsTextInput)` + flex: 1; +`; + export const SettingsRolesList = () => { const navigateSettings = useNavigateSettings(); - const activeTabId = useRecoilComponentValue( - activeTabIdComponentState, - ROLES_LIST_TABS.COMPONENT_INSTANCE_ID, - ); - const [searchTerm, setSearchTerm] = useState(''); + const [showAgentRoles, setShowAgentRoles] = useState(false); + const [showApiKeyRoles, setShowApiKeyRoles] = useState(false); const settingsAllRoles = useRecoilValue(settingsAllRolesSelector); @@ -57,52 +68,69 @@ export const SettingsRolesList = () => { ); const filteredRoles = sortedSettingsAllRoles.filter((role) => { - let matchesTab = false; + const matchesType = + role.canBeAssignedToUsers || + (showAgentRoles && role.canBeAssignedToAgents) || + (showApiKeyRoles && role.canBeAssignedToApiKeys); - switch (activeTabId) { - case ROLES_LIST_TABS.TABS_IDS.USER_ROLES: - matchesTab = role.canBeAssignedToUsers; - break; - case ROLES_LIST_TABS.TABS_IDS.AGENT_ROLES: - matchesTab = role.canBeAssignedToAgents; - break; - case ROLES_LIST_TABS.TABS_IDS.API_KEY_ROLES: - matchesTab = role.canBeAssignedToApiKeys; - break; - default: - matchesTab = role.canBeAssignedToUsers; - } + const matchesSearch = role.label + ?.toLowerCase() + .includes(searchTerm.toLowerCase()); - return ( - matchesTab && role.label?.toLowerCase().includes(searchTerm.toLowerCase()) - ); + return matchesType && matchesSearch; }); - const tabDescriptions: Record = { - [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 = - (activeTabId && tabDescriptions[activeTabId]) ?? - t`Assign roles to specify each member's access permissions`; - return (
- - - + + + + } + dropdownComponents={ + + + setShowAgentRoles(!showAgentRoles)} + toggled={showAgentRoles} + text={t`Agent roles`} + toggleSize="small" + /> + setShowApiKeyRoles(!showApiKeyRoles)} + toggled={showApiKeyRoles} + text={t`API key roles`} + toggleSize="small" + /> + + + } + /> + + diff --git a/packages/twenty-front/src/modules/settings/roles/role-permissions/components/SettingsRolePermissions.tsx b/packages/twenty-front/src/modules/settings/roles/role-permissions/components/SettingsRolePermissions.tsx index bf5a323e5f..97612c3baa 100644 --- a/packages/twenty-front/src/modules/settings/roles/role-permissions/components/SettingsRolePermissions.tsx +++ b/packages/twenty-front/src/modules/settings/roles/role-permissions/components/SettingsRolePermissions.tsx @@ -13,11 +13,13 @@ const StyledRolePermissionsContainer = styled.div` type SettingsRolePermissionsProps = { roleId: string; isEditable: boolean; + fromAgentId?: string; }; export const SettingsRolePermissions = ({ roleId, isEditable, + fromAgentId, }: SettingsRolePermissionsProps) => { return ( @@ -27,7 +29,7 @@ export const SettingsRolePermissions = ({ /> { const theme = useTheme(); const navigate = useNavigateSettings(); + const [searchParams] = useSearchParams(); + const fromAgentId = searchParams.get('fromAgent'); const [searchFilter, setSearchFilter] = useState(''); const { objectMetadataItemsThatCanHavePermission } = @@ -64,10 +67,11 @@ export const SettingsRolePermissionsObjectLevelObjectPicker = ({ }; const handleSelectObjectMetadata = (objectMetadataId: string) => { - navigate(SettingsPath.RoleObjectLevel, { - roleId, - objectMetadataId, - }); + navigate( + SettingsPath.RoleObjectLevel, + { roleId, objectMetadataId }, + fromAgentId ? { fromAgent: fromAgentId } : undefined, + ); }; const { filterObjectMetadataItemsWithPermissionOverride } = 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 d36717b028..c9450ce938 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 @@ -2,10 +2,12 @@ import { SettingsRolePermissionsObjectLevelTableHeader } from '@/settings/roles/ import { SettingsRolePermissionsObjectLevelTableRow } from '@/settings/roles/role-permissions/object-level-permissions/components/SettingsRolePermissionsObjectLevelTableRow'; import { useFilterObjectMetadataItemsWithPermissionOverride } from '@/settings/roles/role-permissions/object-level-permissions/hooks/useFilterObjectWithPermissionOverride'; import { useObjectMetadataItemsThatCanHavePermission } from '@/settings/roles/role-permissions/object-level-permissions/hooks/useObjectMetadataItemsThatCanHavePermission'; +import { settingsDraftRoleFamilyState } from '@/settings/roles/states/settingsDraftRoleFamilyState'; 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 { useRecoilValue } from 'recoil'; import { SettingsPath } from 'twenty-shared/types'; import { isDefined } from 'twenty-shared/utils'; import { IconPlus } from 'twenty-ui/display'; @@ -28,7 +30,7 @@ const StyledTableRows = styled.div` type SettingsRolePermissionsObjectLevelSectionProps = { roleId: string; - isEditable: boolean; + fromAgentId?: string; }; const StyledNoOverride = styled(TableCell)` @@ -37,10 +39,14 @@ const StyledNoOverride = styled(TableCell)` export const SettingsRolePermissionsObjectLevelSection = ({ roleId, - isEditable, + fromAgentId, }: SettingsRolePermissionsObjectLevelSectionProps) => { const navigateSettings = useNavigateSettings(); + const settingsDraftRole = useRecoilValue( + settingsDraftRoleFamilyState(roleId), + ); + const { objectMetadataItemsThatCanHavePermission } = useObjectMetadataItemsThatCanHavePermission(); @@ -59,9 +65,11 @@ export const SettingsRolePermissionsObjectLevelSection = ({ objectMetadataItemsThatCanHavePermission.length; const handleAddRule = () => { - navigateSettings(SettingsPath.RoleAddObjectLevel, { - roleId, - }); + navigateSettings( + SettingsPath.RoleAddObjectLevel, + { roleId }, + fromAgentId ? { fromAgent: fromAgentId } : undefined, + ); }; const hasObjectPermissions = @@ -82,6 +90,7 @@ export const SettingsRolePermissionsObjectLevelSection = ({ key={objectMetadataItem.id} objectMetadataItem={objectMetadataItem} roleId={roleId} + fromAgentId={fromAgentId} /> ), ) @@ -98,7 +107,9 @@ export const SettingsRolePermissionsObjectLevelSection = ({ title={t`Add rule`} variant="secondary" size="small" - disabled={!isEditable || allObjectsHaveSetPermission} + disabled={ + !settingsDraftRole.isEditable || allObjectsHaveSetPermission + } onClick={handleAddRule} /> diff --git a/packages/twenty-front/src/modules/settings/roles/role-permissions/object-level-permissions/components/SettingsRolePermissionsObjectLevelTableRow.tsx b/packages/twenty-front/src/modules/settings/roles/role-permissions/object-level-permissions/components/SettingsRolePermissionsObjectLevelTableRow.tsx index 1e914f6dba..a629a6860e 100644 --- a/packages/twenty-front/src/modules/settings/roles/role-permissions/object-level-permissions/components/SettingsRolePermissionsObjectLevelTableRow.tsx +++ b/packages/twenty-front/src/modules/settings/roles/role-permissions/object-level-permissions/components/SettingsRolePermissionsObjectLevelTableRow.tsx @@ -29,11 +29,13 @@ const StyledNameLabel = styled.div` type SettingsRolePermissionsObjectLevelTableRowProps = { objectMetadataItem: ObjectMetadataItem; roleId: string; + fromAgentId?: string; }; export const SettingsRolePermissionsObjectLevelTableRow = ({ objectMetadataItem, roleId, + fromAgentId, }: SettingsRolePermissionsObjectLevelTableRowProps) => { const { getIcon } = useIcons(); const theme = useTheme(); @@ -42,12 +44,18 @@ export const SettingsRolePermissionsObjectLevelTableRow = ({ const objectLabelPlural = objectMetadataItem.labelPlural; + const navigationPath = getSettingsPath(SettingsPath.RoleObjectLevel, { + roleId: roleId, + objectMetadataId: objectMetadataItem.id, + }); + + const navigationUrl = fromAgentId + ? `${navigationPath}?fromAgent=${fromAgentId}` + : navigationPath; + return ( 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 77e2ead965..36bd051c69 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 @@ -5,10 +5,12 @@ import { SettingsRolePermissionsObjectLevelObjectFormObjectLevel } from '@/setti import { settingsDraftRoleFamilyState } from '@/settings/roles/states/settingsDraftRoleFamilyState'; import { SubMenuTopBarContainer } from '@/ui/layout/page/components/SubMenuTopBarContainer'; import { t } from '@lingui/core/macro'; +import { useSearchParams } from 'react-router-dom'; import { useRecoilValue } from 'recoil'; import { SettingsPath } from 'twenty-shared/types'; -import { getSettingsPath } from 'twenty-shared/utils'; +import { getSettingsPath, isDefined } from 'twenty-shared/utils'; import { Button } from 'twenty-ui/input'; +import { useFindOneAgentQuery } from '~/generated-metadata/graphql'; type SettingsRolePermissionsObjectLevelObjectFormProps = { roleId: string; @@ -19,10 +21,18 @@ export const SettingsRolePermissionsObjectLevelObjectForm = ({ roleId, objectMetadataId, }: SettingsRolePermissionsObjectLevelObjectFormProps) => { + const [searchParams] = useSearchParams(); + const fromAgentId = searchParams.get('fromAgent'); + const settingsDraftRole = useRecoilValue( settingsDraftRoleFamilyState(roleId), ); + const { data: agentData } = useFindOneAgentQuery({ + variables: { id: fromAgentId || '' }, + skip: !fromAgentId, + }); + const objectMetadata = useObjectMetadataItemById({ objectId: objectMetadataId, }); @@ -32,37 +42,65 @@ export const SettingsRolePermissionsObjectLevelObjectForm = ({ const objectLabelSingular = objectMetadataItem.labelSingular; const objectLabelPlural = objectMetadataItem.labelPlural; + const agent = agentData?.findOneAgent; + + const breadcrumbLinks = + fromAgentId && isDefined(agent) + ? [ + { + children: t`Workspace`, + href: getSettingsPath(SettingsPath.Workspace), + }, + { + children: t`AI`, + href: getSettingsPath(SettingsPath.AI), + }, + { + children: agent.label, + href: getSettingsPath(SettingsPath.AIAgentDetail, { + agentId: agent.id, + }), + }, + { + children: t`Permissions · ${objectLabelSingular}`, + }, + ] + : [ + { + children: t`Workspace`, + href: getSettingsPath(SettingsPath.Workspace), + }, + { + children: t`Roles`, + href: getSettingsPath(SettingsPath.Roles), + }, + { + children: settingsDraftRole.label, + href: getSettingsPath(SettingsPath.RoleDetail, { + roleId, + }), + }, + { + children: t`Permissions · ${objectLabelSingular}`, + }, + ]; + + const finishButtonPath = + fromAgentId && isDefined(agent) + ? getSettingsPath(SettingsPath.AIAgentDetail, { agentId: agent.id }) + : getSettingsPath(SettingsPath.RoleDetail, { roleId }); + return ( } > diff --git a/packages/twenty-front/src/modules/settings/roles/role/components/SettingsRole.tsx b/packages/twenty-front/src/modules/settings/roles/role/components/SettingsRole.tsx index 047bef5a57..3eacc4bc5b 100644 --- a/packages/twenty-front/src/modules/settings/roles/role/components/SettingsRole.tsx +++ b/packages/twenty-front/src/modules/settings/roles/role/components/SettingsRole.tsx @@ -20,7 +20,7 @@ import { t } from '@lingui/core/macro'; import { useState } from 'react'; import { useRecoilState, useRecoilValue } from 'recoil'; import { SettingsPath } from 'twenty-shared/types'; -import { isDefined, getSettingsPath } from 'twenty-shared/utils'; +import { getSettingsPath, isDefined } from 'twenty-shared/utils'; import { IconLockOpen, IconSettings, IconUserPlus } from 'twenty-ui/display'; import { useNavigateSettings } from '~/hooks/useNavigateSettings'; @@ -60,6 +60,11 @@ export const SettingsRole = ({ roleId, isCreateMode }: SettingsRoleProps) => { const { saveDraftRoleToDB } = useSaveDraftRoleToDB({ isCreateMode, roleId, + onSuccess: async (savedRoleId) => { + if (isCreateMode) { + navigateSettings(SettingsPath.RoleDetail, { roleId: savedRoleId }); + } + }, }); const isRoleEditable = settingsDraftRole.isEditable; diff --git a/packages/twenty-front/src/modules/settings/roles/role/hooks/useSaveDraftRoleToDB.ts b/packages/twenty-front/src/modules/settings/roles/role/hooks/useSaveDraftRoleToDB.ts index 174a25767f..05d4655df5 100644 --- a/packages/twenty-front/src/modules/settings/roles/role/hooks/useSaveDraftRoleToDB.ts +++ b/packages/twenty-front/src/modules/settings/roles/role/hooks/useSaveDraftRoleToDB.ts @@ -8,7 +8,6 @@ import { settingsDraftRoleFamilyState } from '@/settings/roles/states/settingsDr import { settingsPersistedRoleFamilyState } from '@/settings/roles/states/settingsPersistedRoleFamilyState'; import { getOperationName } from '@apollo/client/utilities'; import { useRecoilValue } from 'recoil'; -import { SettingsPath } from 'twenty-shared/types'; import { isDefined, isNonEmptyArray } from 'twenty-shared/utils'; import { useCreateOneRoleMutation, @@ -18,7 +17,6 @@ import { useUpsertPermissionFlagsMutation, } from '~/generated-metadata/graphql'; import { type Role } from '~/generated/graphql'; -import { useNavigateSettings } from '~/hooks/useNavigateSettings'; import { getDirtyFields } from '~/utils/getDirtyFields'; const ROLE_BASIC_KEYS: Array = [ @@ -39,9 +37,11 @@ const ROLE_BASIC_KEYS: Array = [ export const useSaveDraftRoleToDB = ({ roleId, isCreateMode, + onSuccess, }: { roleId: string; isCreateMode: boolean; + onSuccess?: (savedRoleId: string) => void | Promise; }) => { const [createRole] = useCreateOneRoleMutation(); const [updateRole] = useUpdateOneRoleMutation(); @@ -51,7 +51,6 @@ export const useSaveDraftRoleToDB = ({ const { addWorkspaceMembersToRole } = useUpdateWorkspaceMemberRole(roleId); const { addAgentsToRole } = useUpdateAgentRole(roleId); const { addApiKeysToRole } = useUpdateApiKeyRole(roleId); - const navigateSettings = useNavigateSettings(); const settingsPersistedRole = useRecoilValue( settingsPersistedRoleFamilyState(roleId), @@ -100,7 +99,7 @@ export const useSaveDraftRoleToDB = ({ const { removeFieldPermissionInDraftRole } = useRemoveFieldPermissionInDraftRole(); - const saveDraftRoleToDB = async () => { + const removeUselessFieldPermissions = () => { if ( isNonEmptyArray( fieldPermissionsThatShouldntBeCreatedBecauseTheyAreUseless, @@ -113,215 +112,233 @@ export const useSaveDraftRoleToDB = ({ ); } } + }; - if (isCreateMode) { - const { data } = await createRole({ + const createNewRole = async () => { + const { data } = await createRole({ + variables: { + createRoleInput: { + id: roleId, + label: settingsDraftRole.label, + description: settingsDraftRole.description, + icon: settingsDraftRole.icon, + canUpdateAllSettings: settingsDraftRole.canUpdateAllSettings, + canAccessAllTools: settingsDraftRole.canAccessAllTools, + canReadAllObjectRecords: settingsDraftRole.canReadAllObjectRecords, + canUpdateAllObjectRecords: + settingsDraftRole.canUpdateAllObjectRecords, + canSoftDeleteAllObjectRecords: + settingsDraftRole.canSoftDeleteAllObjectRecords, + canDestroyAllObjectRecords: + settingsDraftRole.canDestroyAllObjectRecords, + canBeAssignedToUsers: settingsDraftRole.canBeAssignedToUsers, + canBeAssignedToAgents: settingsDraftRole.canBeAssignedToAgents, + canBeAssignedToApiKeys: settingsDraftRole.canBeAssignedToApiKeys, + } satisfies Partial, + }, + refetchQueries: [getOperationName(GET_ROLES) ?? ''], + }); + + if (!data) { + return; + } + + const createdRoleId = data.createOneRole.id; + + await upsertRolePermissions(createdRoleId); + await assignEntitiesToRole(createdRoleId); + + if (isDefined(onSuccess)) { + await onSuccess(createdRoleId); + } + }; + + const updateExistingRole = async () => { + if (isDefined(dirtyFields.permissionFlags)) { + await upsertPermissionFlags({ variables: { - createRoleInput: { - id: roleId, - label: settingsDraftRole.label, - description: settingsDraftRole.description, - icon: settingsDraftRole.icon, - canUpdateAllSettings: settingsDraftRole.canUpdateAllSettings, - canAccessAllTools: settingsDraftRole.canAccessAllTools, - canReadAllObjectRecords: settingsDraftRole.canReadAllObjectRecords, - canUpdateAllObjectRecords: - settingsDraftRole.canUpdateAllObjectRecords, - canSoftDeleteAllObjectRecords: - settingsDraftRole.canSoftDeleteAllObjectRecords, - canDestroyAllObjectRecords: - settingsDraftRole.canDestroyAllObjectRecords, - canBeAssignedToUsers: settingsDraftRole.canBeAssignedToUsers, - canBeAssignedToAgents: settingsDraftRole.canBeAssignedToAgents, - canBeAssignedToApiKeys: settingsDraftRole.canBeAssignedToApiKeys, - } satisfies Partial, + upsertPermissionFlagsInput: { + roleId: roleId, + permissionFlagKeys: + settingsDraftRole.permissionFlags?.map( + (permissionFlag) => permissionFlag.flag, + ) ?? [], + }, }, refetchQueries: [getOperationName(GET_ROLES) ?? ''], }); + } - if (!data) { - return; - } - - if (isDefined(dirtyFields.permissionFlags)) { - await upsertPermissionFlags({ - variables: { - upsertPermissionFlagsInput: { - roleId: data.createOneRole.id, - permissionFlagKeys: - settingsDraftRole.permissionFlags?.map( - (permissionFlag) => permissionFlag.flag, - ) ?? [], + if (ROLE_BASIC_KEYS.some((key) => key in dirtyFields)) { + await updateRole({ + variables: { + updateRoleInput: { + id: roleId, + update: { + label: settingsDraftRole.label, + description: settingsDraftRole.description, + icon: settingsDraftRole.icon, + canUpdateAllSettings: settingsDraftRole.canUpdateAllSettings, + canAccessAllTools: settingsDraftRole.canAccessAllTools, + canReadAllObjectRecords: + settingsDraftRole.canReadAllObjectRecords, + canUpdateAllObjectRecords: + settingsDraftRole.canUpdateAllObjectRecords, + canSoftDeleteAllObjectRecords: + settingsDraftRole.canSoftDeleteAllObjectRecords, + canDestroyAllObjectRecords: + settingsDraftRole.canDestroyAllObjectRecords, + canBeAssignedToUsers: settingsDraftRole.canBeAssignedToUsers, + canBeAssignedToAgents: settingsDraftRole.canBeAssignedToAgents, + canBeAssignedToApiKeys: settingsDraftRole.canBeAssignedToApiKeys, }, }, - refetchQueries: [getOperationName(GET_ROLES) ?? ''], - }); - } - - if (isDefined(dirtyFields.objectPermissions)) { - await upsertObjectPermissions({ - variables: { - upsertObjectPermissionsInput: { - roleId: data.createOneRole.id, - objectPermissions: - settingsDraftRole.objectPermissions?.map( - (objectPermission) => ({ - objectMetadataId: objectPermission.objectMetadataId, - canReadObjectRecords: objectPermission.canReadObjectRecords, - canUpdateObjectRecords: - objectPermission.canUpdateObjectRecords, - canSoftDeleteObjectRecords: - objectPermission.canSoftDeleteObjectRecords, - canDestroyObjectRecords: - objectPermission.canDestroyObjectRecords, - }), - ) ?? [], - }, - }, - refetchQueries: [getOperationName(GET_ROLES) ?? ''], - }); - } - - if (isNonEmptyArray(fieldPermissionsToUpsert) === true) { - await upsertFieldPermissions({ - variables: { - upsertFieldPermissionsInput: { - roleId: data.createOneRole.id, - fieldPermissions: - fieldPermissionsToUpsert.map((fieldPermission) => ({ - objectMetadataId: fieldPermission.objectMetadataId, - fieldMetadataId: fieldPermission.fieldMetadataId, - canReadFieldValue: fieldPermission.canReadFieldValue, - canUpdateFieldValue: fieldPermission.canUpdateFieldValue, - })) ?? [], - }, - }, - refetchQueries: [getOperationName(GET_ROLES) ?? ''], - }); - } - - if ( - isDefined(dirtyFields.workspaceMembers) && - settingsDraftRole.canBeAssignedToUsers - ) { - await addWorkspaceMembersToRole({ - roleId: data.createOneRole.id, - workspaceMemberIds: settingsDraftRole.workspaceMembers.map( - (member) => member.id, - ), - }); - } - - if ( - isDefined(dirtyFields.agents) && - settingsDraftRole.canBeAssignedToAgents - ) { - await addAgentsToRole({ - roleId: data.createOneRole.id, - agentIds: settingsDraftRole.agents.map((agent) => agent.id), - }); - } - - if ( - isDefined(dirtyFields.apiKeys) && - settingsDraftRole.canBeAssignedToApiKeys - ) { - await addApiKeysToRole({ - roleId: data.createOneRole.id, - apiKeyIds: settingsDraftRole.apiKeys.map((apiKey) => apiKey.id), - }); - } - - navigateSettings(SettingsPath.RoleDetail, { - roleId: data.createOneRole.id, + }, + refetchQueries: [getOperationName(GET_ROLES) ?? ''], }); + } + + if (isDefined(dirtyFields.objectPermissions)) { + await upsertObjectPermissions({ + variables: { + upsertObjectPermissionsInput: { + roleId: roleId, + objectPermissions: + settingsDraftRole.objectPermissions?.map((objectPermission) => ({ + objectMetadataId: objectPermission.objectMetadataId, + canReadObjectRecords: objectPermission.canReadObjectRecords, + canUpdateObjectRecords: objectPermission.canUpdateObjectRecords, + canSoftDeleteObjectRecords: + objectPermission.canSoftDeleteObjectRecords, + canDestroyObjectRecords: + objectPermission.canDestroyObjectRecords, + })) ?? [], + }, + }, + refetchQueries: [getOperationName(GET_ROLES) ?? ''], + }); + } + + if (isNonEmptyArray(fieldPermissionsToUpsert) === true) { + await upsertFieldPermissions({ + variables: { + upsertFieldPermissionsInput: { + roleId: roleId, + fieldPermissions: + fieldPermissionsToUpsert.map((fieldPermission) => ({ + objectMetadataId: fieldPermission.objectMetadataId, + fieldMetadataId: fieldPermission.fieldMetadataId, + canReadFieldValue: fieldPermission.canReadFieldValue, + canUpdateFieldValue: fieldPermission.canUpdateFieldValue, + })) ?? [], + }, + }, + refetchQueries: [getOperationName(GET_ROLES) ?? ''], + }); + } + }; + + const upsertRolePermissions = async (targetRoleId: string) => { + if (isDefined(dirtyFields.permissionFlags)) { + await upsertPermissionFlags({ + variables: { + upsertPermissionFlagsInput: { + roleId: targetRoleId, + permissionFlagKeys: + settingsDraftRole.permissionFlags?.map( + (permissionFlag) => permissionFlag.flag, + ) ?? [], + }, + }, + refetchQueries: [getOperationName(GET_ROLES) ?? ''], + }); + } + + if (isDefined(dirtyFields.objectPermissions)) { + await upsertObjectPermissions({ + variables: { + upsertObjectPermissionsInput: { + roleId: targetRoleId, + objectPermissions: + settingsDraftRole.objectPermissions?.map((objectPermission) => ({ + objectMetadataId: objectPermission.objectMetadataId, + canReadObjectRecords: objectPermission.canReadObjectRecords, + canUpdateObjectRecords: objectPermission.canUpdateObjectRecords, + canSoftDeleteObjectRecords: + objectPermission.canSoftDeleteObjectRecords, + canDestroyObjectRecords: + objectPermission.canDestroyObjectRecords, + })) ?? [], + }, + }, + refetchQueries: [getOperationName(GET_ROLES) ?? ''], + }); + } + + if (isNonEmptyArray(fieldPermissionsToUpsert) === true) { + await upsertFieldPermissions({ + variables: { + upsertFieldPermissionsInput: { + roleId: targetRoleId, + fieldPermissions: + fieldPermissionsToUpsert.map((fieldPermission) => ({ + objectMetadataId: fieldPermission.objectMetadataId, + fieldMetadataId: fieldPermission.fieldMetadataId, + canReadFieldValue: fieldPermission.canReadFieldValue, + canUpdateFieldValue: fieldPermission.canUpdateFieldValue, + })) ?? [], + }, + }, + refetchQueries: [getOperationName(GET_ROLES) ?? ''], + }); + } + }; + + const assignEntitiesToRole = async (targetRoleId: string) => { + if ( + isDefined(dirtyFields.workspaceMembers) && + settingsDraftRole.canBeAssignedToUsers + ) { + await addWorkspaceMembersToRole({ + roleId: targetRoleId, + workspaceMemberIds: settingsDraftRole.workspaceMembers.map( + (member) => member.id, + ), + }); + } + + if ( + isDefined(dirtyFields.agents) && + settingsDraftRole.canBeAssignedToAgents + ) { + await addAgentsToRole({ + roleId: targetRoleId, + agentIds: settingsDraftRole.agents.map((agent) => agent.id), + }); + } + + if ( + isDefined(dirtyFields.apiKeys) && + settingsDraftRole.canBeAssignedToApiKeys + ) { + await addApiKeysToRole({ + roleId: targetRoleId, + apiKeyIds: settingsDraftRole.apiKeys.map((apiKey) => apiKey.id), + }); + } + + if (isDefined(onSuccess)) { + await onSuccess(roleId); + } + }; + + const saveDraftRoleToDB = async () => { + removeUselessFieldPermissions(); + + if (isCreateMode) { + await createNewRole(); } else { - if (isDefined(dirtyFields.permissionFlags)) { - await upsertPermissionFlags({ - variables: { - upsertPermissionFlagsInput: { - roleId: roleId, - permissionFlagKeys: - settingsDraftRole.permissionFlags?.map( - (permissionFlag) => permissionFlag.flag, - ) ?? [], - }, - }, - refetchQueries: [getOperationName(GET_ROLES) ?? ''], - }); - } - - if (ROLE_BASIC_KEYS.some((key) => key in dirtyFields)) { - await updateRole({ - variables: { - updateRoleInput: { - id: roleId, - update: { - label: settingsDraftRole.label, - description: settingsDraftRole.description, - icon: settingsDraftRole.icon, - canUpdateAllSettings: settingsDraftRole.canUpdateAllSettings, - canAccessAllTools: settingsDraftRole.canAccessAllTools, - canReadAllObjectRecords: - settingsDraftRole.canReadAllObjectRecords, - canUpdateAllObjectRecords: - settingsDraftRole.canUpdateAllObjectRecords, - canSoftDeleteAllObjectRecords: - settingsDraftRole.canSoftDeleteAllObjectRecords, - canDestroyAllObjectRecords: - settingsDraftRole.canDestroyAllObjectRecords, - canBeAssignedToUsers: settingsDraftRole.canBeAssignedToUsers, - canBeAssignedToAgents: settingsDraftRole.canBeAssignedToAgents, - canBeAssignedToApiKeys: - settingsDraftRole.canBeAssignedToApiKeys, - }, - }, - }, - refetchQueries: [getOperationName(GET_ROLES) ?? ''], - }); - } - - if (isDefined(dirtyFields.objectPermissions)) { - await upsertObjectPermissions({ - variables: { - upsertObjectPermissionsInput: { - roleId: roleId, - objectPermissions: - settingsDraftRole.objectPermissions?.map( - (objectPermission) => ({ - objectMetadataId: objectPermission.objectMetadataId, - canReadObjectRecords: objectPermission.canReadObjectRecords, - canUpdateObjectRecords: - objectPermission.canUpdateObjectRecords, - canSoftDeleteObjectRecords: - objectPermission.canSoftDeleteObjectRecords, - canDestroyObjectRecords: - objectPermission.canDestroyObjectRecords, - }), - ) ?? [], - }, - }, - refetchQueries: [getOperationName(GET_ROLES) ?? ''], - }); - } - - if (isNonEmptyArray(fieldPermissionsToUpsert) === true) { - await upsertFieldPermissions({ - variables: { - upsertFieldPermissionsInput: { - roleId: roleId, - fieldPermissions: - fieldPermissionsToUpsert.map((fieldPermission) => ({ - objectMetadataId: fieldPermission.objectMetadataId, - fieldMetadataId: fieldPermission.fieldMetadataId, - canReadFieldValue: fieldPermission.canReadFieldValue, - canUpdateFieldValue: fieldPermission.canUpdateFieldValue, - })) ?? [], - }, - }, - refetchQueries: [getOperationName(GET_ROLES) ?? ''], - }); - } + await updateExistingRole(); } }; diff --git a/packages/twenty-front/src/pages/settings/ai/SettingsAgentForm.tsx b/packages/twenty-front/src/pages/settings/ai/SettingsAgentForm.tsx index 57f9de8882..57a3c1c655 100644 --- a/packages/twenty-front/src/pages/settings/ai/SettingsAgentForm.tsx +++ b/packages/twenty-front/src/pages/settings/ai/SettingsAgentForm.tsx @@ -4,6 +4,10 @@ import { useParams } from 'react-router-dom'; import { SaveAndCancelButtons } from '@/settings/components/SaveAndCancelButtons/SaveAndCancelButtons'; import { SettingsPageContainer } from '@/settings/components/SettingsPageContainer'; +import { SettingsRolesQueryEffect } from '@/settings/roles/components/SettingsRolesQueryEffect'; +import { useSaveDraftRoleToDB } from '@/settings/roles/role/hooks/useSaveDraftRoleToDB'; +import { settingsDraftRoleFamilyState } from '@/settings/roles/states/settingsDraftRoleFamilyState'; +import { settingsPersistedRoleFamilyState } from '@/settings/roles/states/settingsPersistedRoleFamilyState'; import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar'; import { SubMenuTopBarContainer } from '@/ui/layout/page/components/SubMenuTopBarContainer'; import { TabList } from '@/ui/layout/tab-list/components/TabList'; @@ -12,7 +16,7 @@ import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/ho import { t } from '@lingui/core/macro'; import { AppPath, SettingsPath } from 'twenty-shared/types'; import { getSettingsPath, isDefined } from 'twenty-shared/utils'; -import { H2Title, IconLock, IconSettings } from 'twenty-ui/display'; +import { IconLock, IconSettings } from 'twenty-ui/display'; import { Section } from 'twenty-ui/layout'; import { type CreateAgentInput, @@ -24,6 +28,8 @@ import { useNavigateApp } from '~/hooks/useNavigateApp'; import { useNavigateSettings } from '~/hooks/useNavigateSettings'; import { useState } from 'react'; +import { useRecoilState, useRecoilValue } from 'recoil'; +import { isDeeplyEqual } from '~/utils/isDeeplyEqual'; import { SettingsAgentDetailSkeletonLoader } from './components/SettingsAgentDetailSkeletonLoader'; import { SettingsAgentRoleTab } from './components/SettingsAgentRoleTab'; import { SettingsAgentSettingsTab } from './components/SettingsAgentSettingsTab'; @@ -107,6 +113,22 @@ export const SettingsAgentForm = ({ mode }: { mode: 'create' | 'edit' }) => { const agent = data?.findOneAgent; + const [settingsDraftRole, setSettingsDraftRole] = useRecoilState( + settingsDraftRoleFamilyState(formValues.role || ''), + ); + const settingsPersistedRole = useRecoilValue( + settingsPersistedRoleFamilyState(formValues.role || ''), + ); + + const { saveDraftRoleToDB } = useSaveDraftRoleToDB({ + roleId: formValues.role || '', + isCreateMode: false, + }); + + const isRoleDirty = + isDefined(formValues.role) && + !isDeeplyEqual(settingsDraftRole, settingsPersistedRole); + if (!isCreateMode && !loading && !agent) { return null; } @@ -138,6 +160,26 @@ export const SettingsAgentForm = ({ mode }: { mode: 'create' | 'edit' }) => { setIsSubmitting(true); try { + if (isRoleDirty && isDefined(formValues.role)) { + try { + await saveDraftRoleToDB(); + } catch (error) { + if (error instanceof ApolloError) { + enqueueErrorSnackBar({ + apolloError: error, + }); + } else { + const errorMessage = + error instanceof Error ? error.message : String(error); + enqueueErrorSnackBar({ + message: t`Failed to save role permissions: ${errorMessage}`, + }); + } + setIsSubmitting(false); + return; + } + } + if (isCreateMode) { const input: CreateAgentInput = { name: formValues.name, @@ -187,15 +229,21 @@ export const SettingsAgentForm = ({ mode }: { mode: 'create' | 'edit' }) => { } }; + const handleCancel = () => { + resetForm(); + + if (isRoleDirty && isDefined(settingsPersistedRole)) { + setSettingsDraftRole(settingsPersistedRole); + } + + navigate(SettingsPath.AI); + }; + const title = !isCreateMode ? loading ? t`Agent` : agent?.label : t`New Agent`; - const pageTitle = !isCreateMode ? t`Edit Agent` : t`New Agent`; - const pageDescription = !isCreateMode - ? t`Update agent information` - : t`Create a new AI agent`; const breadcrumbText = !isCreateMode ? loading ? t`Agent` @@ -210,6 +258,8 @@ export const SettingsAgentForm = ({ mode }: { mode: 'create' | 'edit' }) => { formValues={formValues} onFieldChange={handleFieldChange} disabled={isReadonlyMode || (isEditMode ? !agent?.isCustom : false)} + agentId={agentId} + agentLabel={formValues.label} /> ); @@ -229,13 +279,14 @@ export const SettingsAgentForm = ({ mode }: { mode: 'create' | 'edit' }) => { return ( <> + navigate(SettingsPath.AI)} + onCancel={handleCancel} isSaveDisabled={!canSave} isLoading={isSubmitting} isCancelDisabled={isSubmitting} @@ -253,7 +304,6 @@ export const SettingsAgentForm = ({ mode }: { mode: 'create' | 'edit' }) => { >
- {isEditMode && loading ? ( ) : ( diff --git a/packages/twenty-front/src/pages/settings/ai/components/SettingsAgentRoleTab.tsx b/packages/twenty-front/src/pages/settings/ai/components/SettingsAgentRoleTab.tsx index 3f2130dca5..d8d6abba95 100644 --- a/packages/twenty-front/src/pages/settings/ai/components/SettingsAgentRoleTab.tsx +++ b/packages/twenty-front/src/pages/settings/ai/components/SettingsAgentRoleTab.tsx @@ -1,33 +1,29 @@ +import { getOperationName } from '@apollo/client/utilities'; import styled from '@emotion/styled'; import { useLingui } from '@lingui/react/macro'; +import { useState } from 'react'; +import { useSetRecoilState } from 'recoil'; +import { v4 } from 'uuid'; +import { GET_ROLES } from '@/settings/roles/graphql/queries/getRolesQuery'; import { SettingsRolePermissions } from '@/settings/roles/role-permissions/components/SettingsRolePermissions'; -import { Select } from '@/ui/input/components/Select'; -import { SettingsPath } from 'twenty-shared/types'; +import { settingsDraftRoleFamilyState } from '@/settings/roles/states/settingsDraftRoleFamilyState'; +import { isNonEmptyString } from '@sniptt/guards'; import { isDefined } from 'twenty-shared/utils'; -import { - H1Title, - H1TitleFontColor, - H2Title, - IconArrowUpRight, - IconUser, - useIcons, -} from 'twenty-ui/display'; +import { H2Title, IconPlus } from 'twenty-ui/display'; import { Button } from 'twenty-ui/input'; import { Section } from 'twenty-ui/layout'; -import { useGetRolesQuery } from '~/generated-metadata/graphql'; -import { useNavigateSettings } from '~/hooks/useNavigateSettings'; +import { + useAssignRoleToAgentMutation, + useCreateOneRoleMutation, + useGetRolesQuery, +} from '~/generated-metadata/graphql'; import { type SettingsAIAgentFormValues } from '../hooks/useSettingsAgentFormState'; -const StyledRoleContainer = styled.div` - align-items: flex-end; - display: flex; - gap: ${({ theme }) => theme.spacing(2)}; - margin-bottom: ${({ theme }) => theme.spacing(8)}; -`; - -const StyledRoleSelector = styled.div` - flex: 1; +const StyledWarningText = styled.div` + color: ${({ theme }) => theme.font.color.tertiary}; + font-size: ${({ theme }) => theme.font.size.sm}; + margin-bottom: ${({ theme }) => theme.spacing(4)}; `; type SettingsAgentRoleTabProps = { @@ -37,81 +33,146 @@ type SettingsAgentRoleTabProps = { value: SettingsAIAgentFormValues[keyof SettingsAIAgentFormValues], ) => void; disabled: boolean; + agentId?: string; + agentLabel: string; }; export const SettingsAgentRoleTab = ({ formValues, onFieldChange, disabled, + agentId, + agentLabel, }: SettingsAgentRoleTabProps) => { const { t } = useLingui(); - const { getIcon } = useIcons(); - const navigateSettings = useNavigateSettings(); + const [isCreatingRole, setIsCreatingRole] = useState(false); const { data: rolesData } = useGetRolesQuery(); - - const rolesOptions = [ - { - label: t`None`, - value: null, - Icon: IconUser, - }, - ...(rolesData?.getRoles - ?.filter((role) => role.canBeAssignedToAgents) - .map((role) => ({ - label: role.label, - value: role.id, - Icon: getIcon(role.icon) ?? IconUser, - })) || []), - ]; + const [createRole] = useCreateOneRoleMutation(); + const [assignRoleToAgent] = useAssignRoleToAgentMutation(); + const setSettingsDraftRole = useSetRecoilState( + settingsDraftRoleFamilyState(formValues.role || ''), + ); const selectedRole = rolesData?.getRoles?.find( (role) => role.id === formValues.role, ); - const handleOpenRole = () => { - if (isDefined(selectedRole)) { - navigateSettings(SettingsPath.RoleDetail, { roleId: selectedRole.id }); + const hasValidAgentId = isNonEmptyString(agentId); + + const isRoleShared = selectedRole + ? (selectedRole.workspaceMembers?.length || 0) + + (selectedRole.agents?.length || 0) + + (selectedRole.apiKeys?.length || 0) > + 1 + : false; + + // Role is only editable if it's not shared and either: + // 1. Assigned exclusively to this agent (edit mode) + // 2. Not yet assigned to anyone (create mode) + const isRoleExclusiveToThisAgent = + !isRoleShared && + selectedRole && + (selectedRole.workspaceMembers?.length || 0) === 0 && + (selectedRole.apiKeys?.length || 0) === 0 && + (hasValidAgentId + ? selectedRole.agents?.length === 1 && + selectedRole.agents[0].id === agentId + : (selectedRole.agents?.length || 0) === 0); + + const handleCreateRole = async () => { + setIsCreatingRole(true); + try { + const roleId = v4(); + const roleName = `${agentLabel} Agent Role`; + + const { data } = await createRole({ + variables: { + createRoleInput: { + id: roleId, + label: roleName, + description: t`Role for ${agentLabel} agent`, + icon: 'IconLock', + canUpdateAllSettings: false, + canAccessAllTools: false, + canReadAllObjectRecords: false, + canUpdateAllObjectRecords: false, + canSoftDeleteAllObjectRecords: false, + canDestroyAllObjectRecords: false, + canBeAssignedToUsers: false, + canBeAssignedToAgents: true, + canBeAssignedToApiKeys: false, + }, + }, + refetchQueries: [getOperationName(GET_ROLES) ?? ''], + }); + + if (isDefined(data?.createOneRole)) { + onFieldChange('role', data.createOneRole.id); + + if (hasValidAgentId) { + await assignRoleToAgent({ + variables: { + agentId, + roleId: data.createOneRole.id, + }, + refetchQueries: ['GetRoles'], + }); + } + + setSettingsDraftRole({ + ...data.createOneRole, + workspaceMembers: [], + agents: [], + apiKeys: [], + objectPermissions: [], + fieldPermissions: [], + permissionFlags: [], + }); + } + } finally { + setIsCreatingRole(false); } }; + const isRoleEditable = + Boolean(selectedRole?.isEditable) && + !disabled && + Boolean(isRoleExclusiveToThisAgent); + return (
- - - -
{tableMetadata.fields.map((item) => ( @@ -198,33 +211,20 @@ export const SettingsObjectFieldTable = ({ ))} - {isNonEmptyArray(filteredActiveItems) && ( - - {filteredActiveItems.map((objectSettingsDetailItem) => ( - - ))} - - )} - {isNonEmptyArray(filteredDisabledItems) && ( - - {filteredDisabledItems.map((objectSettingsDetailItem) => ( - - ))} - - )} + {filteredItems.map((objectSettingsDetailItem) => { + const status = objectSettingsDetailItem.fieldMetadataItem.isActive + ? 'active' + : 'disabled'; + + return ( + + ); + })}
); diff --git a/packages/twenty-front/src/pages/settings/roles/SettingsRoleAddObjectLevel.tsx b/packages/twenty-front/src/pages/settings/roles/SettingsRoleAddObjectLevel.tsx index 76e40a64a1..5166e8f270 100644 --- a/packages/twenty-front/src/pages/settings/roles/SettingsRoleAddObjectLevel.tsx +++ b/packages/twenty-front/src/pages/settings/roles/SettingsRoleAddObjectLevel.tsx @@ -4,27 +4,54 @@ import { SettingsRolePermissionsObjectLevelObjectPicker } from '@/settings/roles import { settingsDraftRoleFamilyState } from '@/settings/roles/states/settingsDraftRoleFamilyState'; import { SubMenuTopBarContainer } from '@/ui/layout/page/components/SubMenuTopBarContainer'; import { t } from '@lingui/core/macro'; -import { Navigate, useParams } from 'react-router-dom'; +import { Navigate, useParams, useSearchParams } from 'react-router-dom'; import { useRecoilValue } from 'recoil'; import { SettingsPath } from 'twenty-shared/types'; -import { getSettingsPath } from 'twenty-shared/utils'; +import { getSettingsPath, isDefined } from 'twenty-shared/utils'; +import { useFindOneAgentQuery } from '~/generated-metadata/graphql'; export const SettingsRoleAddObjectLevel = () => { const { roleId } = useParams(); + const [searchParams] = useSearchParams(); + const fromAgentId = searchParams.get('fromAgent'); + const settingsDraftRole = useRecoilValue( settingsDraftRoleFamilyState(roleId ?? ''), ); + const { data: agentData } = useFindOneAgentQuery({ + variables: { id: fromAgentId || '' }, + skip: !fromAgentId, + }); + + const agent = agentData?.findOneAgent; + if (!roleId) { return ; } - return ( - <> - - { children: t`Add object permission`, href: getSettingsPath(SettingsPath.RoleAddObjectLevel, { roleId }), }, - ]} + ]; + + return ( + <> + +