diff --git a/packages/twenty-front/src/modules/settings/roles/role-assignment/components/stories/SettingsRoleAssignment.stories.tsx b/packages/twenty-front/src/modules/settings/roles/role-assignment/components/stories/SettingsRoleAssignment.stories.tsx index a31b0e429..a6b79b052 100644 --- a/packages/twenty-front/src/modules/settings/roles/role-assignment/components/stories/SettingsRoleAssignment.stories.tsx +++ b/packages/twenty-front/src/modules/settings/roles/role-assignment/components/stories/SettingsRoleAssignment.stories.tsx @@ -3,10 +3,9 @@ import { settingsDraftRoleFamilyState } from '@/settings/roles/states/settingsDr import { Meta, StoryObj } from '@storybook/react'; import { useSetRecoilState } from 'recoil'; import { isDefined } from 'twenty-shared/utils'; -import { PENDING_ROLE_ID } from '~/pages/settings/roles/SettingsRoleCreate'; +import { ComponentDecorator, RouterDecorator } from 'twenty-ui/testing'; import { I18nFrontDecorator } from '~/testing/decorators/I18nFrontDecorator'; import { getRolesMock } from '~/testing/mock-data/roles'; -import { ComponentDecorator, RouterDecorator } from 'twenty-ui/testing'; const SettingsRoleAssignmentWrapper = ( args: React.ComponentProps, @@ -41,6 +40,6 @@ export const Default: Story = { export const PendingRole: Story = { args: { - roleId: PENDING_ROLE_ID, + roleId: 'newRoleId', }, }; 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 d2ccddc5b..cff402c54 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 @@ -12,13 +12,11 @@ const StyledRolePermissionsContainer = styled.div` type SettingsRolePermissionsProps = { roleId: string; isEditable: boolean; - isCreateMode: boolean; }; export const SettingsRolePermissions = ({ roleId, isEditable, - isCreateMode, }: SettingsRolePermissionsProps) => { return ( @@ -26,12 +24,10 @@ export const SettingsRolePermissions = ({ roleId={roleId} isEditable={isEditable} /> - {!isCreateMode && ( - - )} + { const [upsertSettingPermissions] = useUpsertSettingPermissionsMutation(); const [upsertObjectPermissions] = useUpsertObjectPermissionsMutation(); + const [isSaving, setIsSaving] = useState(false); + const { addWorkspaceMembersToRole } = useUpdateWorkspaceMemberRole(roleId); const settingsRolesIsLoading = useRecoilValue(settingsRolesIsLoadingState); @@ -114,6 +116,8 @@ export const SettingsRole = ({ roleId, isCreateMode }: SettingsRoleProps) => { }; const handleSave = async () => { + setIsSaving(true); + const dirtyFields = getDirtyFields( settingsDraftRole, settingsPersistedRole, @@ -126,151 +130,156 @@ export const SettingsRole = ({ roleId, isCreateMode }: SettingsRoleProps) => { return; } - if (isCreateMode) { - const roleId = v4(); - - const { data } = await createRole({ - variables: { - createRoleInput: { - id: roleId, - label: settingsDraftRole.label, - description: settingsDraftRole.description, - icon: settingsDraftRole.icon, - canUpdateAllSettings: settingsDraftRole.canUpdateAllSettings, - canReadAllObjectRecords: settingsDraftRole.canReadAllObjectRecords, - canUpdateAllObjectRecords: - settingsDraftRole.canUpdateAllObjectRecords, - canSoftDeleteAllObjectRecords: - settingsDraftRole.canSoftDeleteAllObjectRecords, - canDestroyAllObjectRecords: - settingsDraftRole.canDestroyAllObjectRecords, - }, - }, - refetchQueries: [getOperationName(GET_ROLES) ?? ''], - }); - - if (!data) { - return; - } - - if (isDefined(dirtyFields.workspaceMembers)) { - await addWorkspaceMembersToRole({ - roleId: data.createOneRole.id, - workspaceMemberIds: settingsDraftRole.workspaceMembers.map( - (member) => member.id, - ), - }); - } - - if (isDefined(dirtyFields.settingPermissions)) { - await upsertSettingPermissions({ + try { + if (isCreateMode) { + const { data } = await createRole({ variables: { - upsertSettingPermissionsInput: { - roleId: data.createOneRole.id, - settingPermissionKeys: - settingsDraftRole.settingPermissions?.map( - (settingPermission) => settingPermission.setting, - ) ?? [], - }, - }, - 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) ?? ''], - }); - } - - navigateSettings(SettingsPath.RoleDetail, { - roleId: data.createOneRole.id, - }); - } else { - if (isDefined(dirtyFields.settingPermissions)) { - await upsertSettingPermissions({ - variables: { - upsertSettingPermissionsInput: { - roleId: roleId, - settingPermissionKeys: - settingsDraftRole.settingPermissions?.map( - (settingPermission) => settingPermission.setting, - ) ?? [], - }, - }, - refetchQueries: [getOperationName(GET_ROLES) ?? ''], - }); - } - - if (ROLE_BASIC_KEYS.some((key) => key in dirtyFields)) { - await updateRole({ - variables: { - updateRoleInput: { + createRoleInput: { id: roleId, - update: { - label: settingsDraftRole.label, - description: settingsDraftRole.description, - icon: settingsDraftRole.icon, - canUpdateAllSettings: settingsDraftRole.canUpdateAllSettings, - canReadAllObjectRecords: - settingsDraftRole.canReadAllObjectRecords, - canUpdateAllObjectRecords: - settingsDraftRole.canUpdateAllObjectRecords, - canSoftDeleteAllObjectRecords: - settingsDraftRole.canSoftDeleteAllObjectRecords, - canDestroyAllObjectRecords: - settingsDraftRole.canDestroyAllObjectRecords, + label: settingsDraftRole.label, + description: settingsDraftRole.description, + icon: settingsDraftRole.icon, + canUpdateAllSettings: settingsDraftRole.canUpdateAllSettings, + canReadAllObjectRecords: + settingsDraftRole.canReadAllObjectRecords, + canUpdateAllObjectRecords: + settingsDraftRole.canUpdateAllObjectRecords, + canSoftDeleteAllObjectRecords: + settingsDraftRole.canSoftDeleteAllObjectRecords, + canDestroyAllObjectRecords: + settingsDraftRole.canDestroyAllObjectRecords, + }, + }, + refetchQueries: [getOperationName(GET_ROLES) ?? ''], + }); + + if (!data) { + return; + } + + if (isDefined(dirtyFields.settingPermissions)) { + await upsertSettingPermissions({ + variables: { + upsertSettingPermissionsInput: { + roleId: data.createOneRole.id, + settingPermissionKeys: + settingsDraftRole.settingPermissions?.map( + (settingPermission) => settingPermission.setting, + ) ?? [], }, }, - }, - refetchQueries: [getOperationName(GET_ROLES) ?? ''], - }); - } + 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, - }), - ) ?? [], + 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) ?? ''], - }); - } - } + refetchQueries: [getOperationName(GET_ROLES) ?? ''], + }); + } - await loadCurrentUser(); + if (isDefined(dirtyFields.workspaceMembers)) { + await addWorkspaceMembersToRole({ + roleId: data.createOneRole.id, + workspaceMemberIds: settingsDraftRole.workspaceMembers.map( + (member) => member.id, + ), + }); + } + + navigateSettings(SettingsPath.RoleDetail, { + roleId: data.createOneRole.id, + }); + } else { + if (isDefined(dirtyFields.settingPermissions)) { + await upsertSettingPermissions({ + variables: { + upsertSettingPermissionsInput: { + roleId: roleId, + settingPermissionKeys: + settingsDraftRole.settingPermissions?.map( + (settingPermission) => settingPermission.setting, + ) ?? [], + }, + }, + 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, + canReadAllObjectRecords: + settingsDraftRole.canReadAllObjectRecords, + canUpdateAllObjectRecords: + settingsDraftRole.canUpdateAllObjectRecords, + canSoftDeleteAllObjectRecords: + settingsDraftRole.canSoftDeleteAllObjectRecords, + canDestroyAllObjectRecords: + settingsDraftRole.canDestroyAllObjectRecords, + }, + }, + }, + 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) ?? ''], + }); + } + } + + await loadCurrentUser(); + } finally { + setIsSaving(false); + } }; return ( @@ -292,7 +301,11 @@ export const SettingsRole = ({ roleId, isCreateMode }: SettingsRoleProps) => { actionButton={ isRoleEditable && isDirty && ( - + ) } > @@ -311,7 +324,6 @@ export const SettingsRole = ({ roleId, isCreateMode }: SettingsRoleProps) => { )} {activeTabId === SETTINGS_ROLE_DETAIL_TABS.TABS_IDS.SETTINGS && ( diff --git a/packages/twenty-front/src/modules/settings/roles/role/components/SettingsRoleCreateEffect.tsx b/packages/twenty-front/src/modules/settings/roles/role/components/SettingsRoleCreateEffect.tsx index 7b52e62b6..0e5b689eb 100644 --- a/packages/twenty-front/src/modules/settings/roles/role/components/SettingsRoleCreateEffect.tsx +++ b/packages/twenty-front/src/modules/settings/roles/role/components/SettingsRoleCreateEffect.tsx @@ -1,5 +1,6 @@ import { SETTINGS_ROLE_DETAIL_TABS } from '@/settings/roles/role/constants/SettingsRoleDetailTabs'; import { settingsDraftRoleFamilyState } from '@/settings/roles/states/settingsDraftRoleFamilyState'; +import { settingsPersistedRoleFamilyState } from '@/settings/roles/states/settingsPersistedRoleFamilyState'; import { activeTabIdComponentState } from '@/ui/layout/tab-list/states/activeTabIdComponentState'; import { useSetRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentStateV2'; import { t } from '@lingui/core/macro'; @@ -16,6 +17,11 @@ export const SettingsRoleCreateEffect = ({ const setSettingsDraftRole = useSetRecoilState( settingsDraftRoleFamilyState(roleId), ); + + const setSettingsPersistedRole = useSetRecoilState( + settingsPersistedRoleFamilyState(roleId), + ); + const setActiveTabId = useSetRecoilComponentStateV2( activeTabIdComponentState, SETTINGS_ROLE_DETAIL_TABS.COMPONENT_INSTANCE_ID + '-' + roleId, @@ -44,9 +50,16 @@ export const SettingsRoleCreateEffect = ({ workspaceMembers: [], }; + setSettingsPersistedRole(undefined); setSettingsDraftRole(newRole); setIsInitialized(true); - }, [isInitialized, roleId, setActiveTabId, setSettingsDraftRole]); + }, [ + isInitialized, + roleId, + setActiveTabId, + setSettingsDraftRole, + setSettingsPersistedRole, + ]); return null; }; diff --git a/packages/twenty-front/src/pages/settings/roles/SettingsRoleCreate.tsx b/packages/twenty-front/src/pages/settings/roles/SettingsRoleCreate.tsx index 79ce6db93..65c5bd2c6 100644 --- a/packages/twenty-front/src/pages/settings/roles/SettingsRoleCreate.tsx +++ b/packages/twenty-front/src/pages/settings/roles/SettingsRoleCreate.tsx @@ -1,15 +1,16 @@ 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'; +import { v4 } from 'uuid'; export const SettingsRoleCreate = () => { + const newRoleId = v4(); + return ( <> - - + + ); }; diff --git a/packages/twenty-front/src/pages/settings/roles/SettingsRoleEdit.tsx b/packages/twenty-front/src/pages/settings/roles/SettingsRoleEdit.tsx index 928d238fe..0e5d36f05 100644 --- a/packages/twenty-front/src/pages/settings/roles/SettingsRoleEdit.tsx +++ b/packages/twenty-front/src/pages/settings/roles/SettingsRoleEdit.tsx @@ -3,21 +3,29 @@ 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 { useRecoilValue } from 'recoil'; import { isDefined } from 'twenty-shared/utils'; +import { settingsPersistedRoleFamilyState } from '~/modules/settings/roles/states/settingsPersistedRoleFamilyState'; import { getSettingsPath } from '~/utils/navigation/getSettingsPath'; export const SettingsRoleEdit = () => { const { roleId } = useParams(); + const persistedRole = useRecoilValue( + settingsPersistedRoleFamilyState(roleId ?? ''), + ); + if (!isDefined(roleId)) { return ; } + const isCreateMode = !isDefined(persistedRole?.id); + return ( <> - + ); }; diff --git a/packages/twenty-server/src/engine/metadata-modules/role/role.service.ts b/packages/twenty-server/src/engine/metadata-modules/role/role.service.ts index 117aff634..e2b95e44b 100644 --- a/packages/twenty-server/src/engine/metadata-modules/role/role.service.ts +++ b/packages/twenty-server/src/engine/metadata-modules/role/role.service.ts @@ -67,6 +67,7 @@ export class RoleService { await this.validateRoleInputOrThrow({ input, workspaceId }); const role = await this.roleRepository.save({ + id: input.id, label: input.label, description: input.description, icon: input.icon,