From 8e710004baf043bcca4b11c981e3c7957236bbfe Mon Sep 17 00:00:00 2001 From: Weiko Date: Mon, 2 Jun 2025 20:24:53 +0200 Subject: [PATCH] Role page various fixes 2 (#12416) - Fix: AvatarURL signedPath for workspace members were not consistent when queried multiple times and it was causing the frontend to wrongly interpret this as a change in the deepEqual condition - Use SaveAndCancel button to be consistent with data model page - When applying all object permission changes, a "smarter" logic applies and removes all permissions if read is unchecked for example - Hide settings permissions when Settings All Access is toggled --- .../components/SettingsRolesQueryEffect.tsx | 12 +++++- .../components/SettingsRolesTableRow.tsx | 15 ++++++- .../components/SettingsRoleAssignment.tsx | 22 +++++------ ...oleAssignmentConfirmationModalSubtitle.tsx | 16 ++++++-- .../SettingsRoleAssignmentTableRow.tsx | 13 +++++-- ...ssignmentWorkspaceMemberPickerDropdown.tsx | 4 +- ...ntWorkspaceMemberPickerDropdownContent.tsx | 28 +++++++++---- ...onfirmationModalSelectedWorkspaceMember.ts | 1 - ...tingsRolePermissionsObjectLevelSection.tsx | 8 +++- .../SettingsRolePermissionsObjectsSection.tsx | 22 +++++++++++ ...SettingsRolePermissionsSettingsSection.tsx | 39 ++++++++++++------- .../roles/role/components/SettingsRole.tsx | 17 +++----- .../metadata-modules/role/role.resolver.ts | 11 ------ 13 files changed, 139 insertions(+), 69 deletions(-) diff --git a/packages/twenty-front/src/modules/settings/roles/components/SettingsRolesQueryEffect.tsx b/packages/twenty-front/src/modules/settings/roles/components/SettingsRolesQueryEffect.tsx index 133d7f63e..c519d1212 100644 --- a/packages/twenty-front/src/modules/settings/roles/components/SettingsRolesQueryEffect.tsx +++ b/packages/twenty-front/src/modules/settings/roles/components/SettingsRolesQueryEffect.tsx @@ -28,11 +28,21 @@ export const SettingsRolesQueryEffect = () => { snapshot, settingsPersistedRoleFamilyState(role.id), ); + + const currentDraftRole = getSnapshotValue( + snapshot, + settingsDraftRoleFamilyState(role.id), + ); + if (isDeeplyEqual(role, persistedRole)) { return; } - set(settingsDraftRoleFamilyState(role.id), role); + set(settingsPersistedRoleFamilyState(role.id), role); + + if (!isDeeplyEqual(currentDraftRole, role)) { + set(settingsDraftRoleFamilyState(role.id), role); + } }); }, [], diff --git a/packages/twenty-front/src/modules/settings/roles/components/SettingsRolesTableRow.tsx b/packages/twenty-front/src/modules/settings/roles/components/SettingsRolesTableRow.tsx index 10c0a4a18..674eb65d6 100644 --- a/packages/twenty-front/src/modules/settings/roles/components/SettingsRolesTableRow.tsx +++ b/packages/twenty-front/src/modules/settings/roles/components/SettingsRolesTableRow.tsx @@ -1,9 +1,12 @@ +import { currentWorkspaceMembersState } from '@/auth/states/currentWorkspaceMembersStates'; import { SettingsPath } from '@/types/SettingsPath'; import { TableCell } from '@/ui/layout/table/components/TableCell'; import { TableRow } from '@/ui/layout/table/components/TableRow'; import { useTheme } from '@emotion/react'; import styled from '@emotion/styled'; import React from 'react'; +import { useRecoilValue } from 'recoil'; +import { isDefined } from 'twenty-shared/utils'; import { AppTooltip, Avatar, @@ -62,6 +65,16 @@ export const SettingsRolesTableRow = ({ role }: SettingsRolesTableRowProps) => { const { getIcon } = useIcons(); const Icon = getIcon(role.icon ?? 'IconUser'); + const currentWorkspaceMembers = useRecoilValue(currentWorkspaceMembersState); + + const enrichedWorkspaceMembers = role.workspaceMembers + .map((workspaceMember) => + currentWorkspaceMembers.find( + (member) => member.id === workspaceMember.id, + ), + ) + .filter(isDefined); + return ( { - {role.workspaceMembers.slice(0, 5).map((workspaceMember) => ( + {enrichedWorkspaceMembers.slice(0, 5).map((workspaceMember) => (
{ - const existingRole = workspaceMemberRoleMap.get( - workspaceMemberSearchRecord.recordId, - ); + const existingRole = workspaceMemberRoleMap.get(workspaceMember.id); setSelectedWorkspaceMember({ - id: workspaceMemberSearchRecord.recordId, - name: `${workspaceMemberSearchRecord.label}`, + id: workspaceMember.id, + name: `${workspaceMember.name.firstName} ${workspaceMember.name.lastName}`, role: existingRole, - avatarUrl: workspaceMemberSearchRecord.imageUrl, }); openModal(ROLE_ASSIGNMENT_CONFIRMATION_MODAL_ID); closeDropdown(); diff --git a/packages/twenty-front/src/modules/settings/roles/role-assignment/components/SettingsRoleAssignmentConfirmationModalSubtitle.tsx b/packages/twenty-front/src/modules/settings/roles/role-assignment/components/SettingsRoleAssignmentConfirmationModalSubtitle.tsx index c826c74a8..04958ef4e 100644 --- a/packages/twenty-front/src/modules/settings/roles/role-assignment/components/SettingsRoleAssignmentConfirmationModalSubtitle.tsx +++ b/packages/twenty-front/src/modules/settings/roles/role-assignment/components/SettingsRoleAssignmentConfirmationModalSubtitle.tsx @@ -1,7 +1,9 @@ +import { currentWorkspaceMembersState } from '@/auth/states/currentWorkspaceMembersStates'; import { SettingsCard } from '@/settings/components/SettingsCard'; import { SettingsRoleAssignmentConfirmationModalSelectedWorkspaceMember } from '@/settings/roles/role-assignment/types/SettingsRoleAssignmentConfirmationModalSelectedWorkspaceMember'; import styled from '@emotion/styled'; import { t } from '@lingui/core/macro'; +import { useRecoilValue } from 'recoil'; import { Avatar } from 'twenty-ui/display'; const StyledSettingsCardContainer = styled.div` @@ -17,7 +19,13 @@ export const SettingsRoleAssignmentConfirmationModalSubtitle = ({ selectedWorkspaceMember, onRoleClick, }: SettingsRoleAssignmentConfirmationModalSubtitleProps) => { - const workspaceMemberName = selectedWorkspaceMember.name; + const currentWorkspaceMembers = useRecoilValue(currentWorkspaceMembersState); + + const enrichedSelectedWorkspaceMember = currentWorkspaceMembers.find( + (member) => member.id === selectedWorkspaceMember.id, + ); + + const workspaceMemberName = `${enrichedSelectedWorkspaceMember?.name.firstName} ${enrichedSelectedWorkspaceMember?.name.lastName}`; return ( <> @@ -27,9 +35,9 @@ export const SettingsRoleAssignmentConfirmationModalSubtitle = ({ title={selectedWorkspaceMember.role?.label || ''} Icon={ diff --git a/packages/twenty-front/src/modules/settings/roles/role-assignment/components/SettingsRoleAssignmentTableRow.tsx b/packages/twenty-front/src/modules/settings/roles/role-assignment/components/SettingsRoleAssignmentTableRow.tsx index bd07bea32..57ebbcd1a 100644 --- a/packages/twenty-front/src/modules/settings/roles/role-assignment/components/SettingsRoleAssignmentTableRow.tsx +++ b/packages/twenty-front/src/modules/settings/roles/role-assignment/components/SettingsRoleAssignmentTableRow.tsx @@ -1,6 +1,8 @@ +import { currentWorkspaceMembersState } from '@/auth/states/currentWorkspaceMembersStates'; import { TableCell } from '@/ui/layout/table/components/TableCell'; import { TableRow } from '@/ui/layout/table/components/TableRow'; import styled from '@emotion/styled'; +import { useRecoilValue } from 'recoil'; import { Avatar, OverflowingTextWithTooltip } from 'twenty-ui/display'; import { WorkspaceMember } from '~/generated-metadata/graphql'; @@ -35,15 +37,20 @@ type SettingsRoleAssignmentTableRowProps = { export const SettingsRoleAssignmentTableRow = ({ workspaceMember, }: SettingsRoleAssignmentTableRowProps) => { + const currentWorkspaceMembers = useRecoilValue(currentWorkspaceMembersState); + const enrichedWorkspaceMember = currentWorkspaceMembers.find( + (member) => member.id === workspaceMember.id, + ); + return ( diff --git a/packages/twenty-front/src/modules/settings/roles/role-assignment/components/SettingsRoleAssignmentWorkspaceMemberPickerDropdown.tsx b/packages/twenty-front/src/modules/settings/roles/role-assignment/components/SettingsRoleAssignmentWorkspaceMemberPickerDropdown.tsx index 817359309..c0c9e8be4 100644 --- a/packages/twenty-front/src/modules/settings/roles/role-assignment/components/SettingsRoleAssignmentWorkspaceMemberPickerDropdown.tsx +++ b/packages/twenty-front/src/modules/settings/roles/role-assignment/components/SettingsRoleAssignmentWorkspaceMemberPickerDropdown.tsx @@ -1,3 +1,4 @@ +import { CurrentWorkspaceMember } from '@/auth/states/currentWorkspaceMemberState'; import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular'; import { useObjectRecordSearchRecords } from '@/object-record/hooks/useObjectRecordSearchRecords'; import { SettingsRoleAssignmentWorkspaceMemberPickerDropdownContent } from '@/settings/roles/role-assignment/components/SettingsRoleAssignmentWorkspaceMemberPickerDropdownContent'; @@ -7,11 +8,10 @@ import { DropdownMenuSearchInput } from '@/ui/layout/dropdown/components/Dropdow import { DropdownMenuSeparator } from '@/ui/layout/dropdown/components/DropdownMenuSeparator'; import { useLingui } from '@lingui/react/macro'; import { ChangeEvent, useState } from 'react'; -import { SearchRecord } from '~/generated-metadata/graphql'; type SettingsRoleAssignmentWorkspaceMemberPickerDropdownProps = { excludedWorkspaceMemberIds: string[]; - onSelect: (workspaceMemberSearchRecord: SearchRecord) => void; + onSelect: (workspaceMember: CurrentWorkspaceMember) => void; }; export const SettingsRoleAssignmentWorkspaceMemberPickerDropdown = ({ diff --git a/packages/twenty-front/src/modules/settings/roles/role-assignment/components/SettingsRoleAssignmentWorkspaceMemberPickerDropdownContent.tsx b/packages/twenty-front/src/modules/settings/roles/role-assignment/components/SettingsRoleAssignmentWorkspaceMemberPickerDropdownContent.tsx index 24e5c0887..d9ea81578 100644 --- a/packages/twenty-front/src/modules/settings/roles/role-assignment/components/SettingsRoleAssignmentWorkspaceMemberPickerDropdownContent.tsx +++ b/packages/twenty-front/src/modules/settings/roles/role-assignment/components/SettingsRoleAssignmentWorkspaceMemberPickerDropdownContent.tsx @@ -1,4 +1,8 @@ +import { currentWorkspaceMembersState } from '@/auth/states/currentWorkspaceMembersStates'; +import { CurrentWorkspaceMember } from '@/auth/states/currentWorkspaceMemberState'; import { t } from '@lingui/core/macro'; +import { useRecoilValue } from 'recoil'; +import { isDefined } from 'twenty-shared/utils'; import { MenuItem, MenuItemAvatar } from 'twenty-ui/navigation'; import { SearchRecord } from '~/generated-metadata/graphql'; @@ -6,7 +10,7 @@ type SettingsRoleAssignmentWorkspaceMemberPickerDropdownContentProps = { loading: boolean; searchFilter: string; filteredWorkspaceMembers: SearchRecord[]; - onSelect: (workspaceMemberSearchRecord: SearchRecord) => void; + onSelect: (workspaceMember: CurrentWorkspaceMember) => void; }; export const SettingsRoleAssignmentWorkspaceMemberPickerDropdownContent = ({ @@ -15,6 +19,8 @@ export const SettingsRoleAssignmentWorkspaceMemberPickerDropdownContent = ({ filteredWorkspaceMembers, onSelect, }: SettingsRoleAssignmentWorkspaceMemberPickerDropdownContentProps) => { + const currentWorkspaceMembers = useRecoilValue(currentWorkspaceMembersState); + if (loading) { return null; } @@ -23,20 +29,28 @@ export const SettingsRoleAssignmentWorkspaceMemberPickerDropdownContent = ({ return ; } + const enrichedWorkspaceMembers = filteredWorkspaceMembers + .map((workspaceMember) => + currentWorkspaceMembers.find( + (member) => member.id === workspaceMember.recordId, + ), + ) + .filter(isDefined); + return ( <> - {filteredWorkspaceMembers.map((workspaceMember) => ( + {enrichedWorkspaceMembers.map((workspaceMember) => ( onSelect(workspaceMember)} avatar={{ type: 'rounded', size: 'md', - placeholder: workspaceMember.label ?? '', - placeholderColorSeed: workspaceMember.recordId, - avatarUrl: workspaceMember.imageUrl, + placeholder: workspaceMember?.name.firstName ?? '', + placeholderColorSeed: workspaceMember?.id, + avatarUrl: workspaceMember?.avatarUrl, }} - text={workspaceMember.label} + text={workspaceMember?.name.firstName ?? ''} /> ))} diff --git a/packages/twenty-front/src/modules/settings/roles/role-assignment/types/SettingsRoleAssignmentConfirmationModalSelectedWorkspaceMember.ts b/packages/twenty-front/src/modules/settings/roles/role-assignment/types/SettingsRoleAssignmentConfirmationModalSelectedWorkspaceMember.ts index 8e4e4dda7..903bd61e5 100644 --- a/packages/twenty-front/src/modules/settings/roles/role-assignment/types/SettingsRoleAssignmentConfirmationModalSelectedWorkspaceMember.ts +++ b/packages/twenty-front/src/modules/settings/roles/role-assignment/types/SettingsRoleAssignmentConfirmationModalSelectedWorkspaceMember.ts @@ -2,7 +2,6 @@ export type SettingsRoleAssignmentConfirmationModalSelectedWorkspaceMember = { id: string; name: string; role?: { id: string; label: string }; - avatarUrl?: string | null; colorScheme?: string; userEmail?: string; }; 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 6619bdcc8..e25586446 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 @@ -82,7 +82,13 @@ export const SettingsRolePermissionsObjectLevelSection = ({ ...(draftRole.objectPermissions ?? []).filter( (permission) => permission.objectMetadataId !== objectMetadataId, ), - { objectMetadataId, roleId }, + { + objectMetadataId, + canReadObjectRecords: null, + canUpdateObjectRecords: null, + canSoftDeleteObjectRecords: null, + canDestroyObjectRecords: null, + }, ], })); navigate(SettingsPath.RoleObjectLevel, { diff --git a/packages/twenty-front/src/modules/settings/roles/role-permissions/objects-permissions/components/SettingsRolePermissionsObjectsSection.tsx b/packages/twenty-front/src/modules/settings/roles/role-permissions/objects-permissions/components/SettingsRolePermissionsObjectsSection.tsx index b7edb366d..13b1d5495 100644 --- a/packages/twenty-front/src/modules/settings/roles/role-permissions/objects-permissions/components/SettingsRolePermissionsObjectsSection.tsx +++ b/packages/twenty-front/src/modules/settings/roles/role-permissions/objects-permissions/components/SettingsRolePermissionsObjectsSection.tsx @@ -47,6 +47,13 @@ export const SettingsRolePermissionsObjectsSection = ({ setSettingsDraftRole({ ...settingsDraftRole, canReadAllObjectRecords: value, + ...(value === false + ? { + canUpdateAllObjectRecords: value, + canSoftDeleteAllObjectRecords: value, + canDestroyAllObjectRecords: value, + } + : {}), }); }, }, @@ -64,6 +71,11 @@ export const SettingsRolePermissionsObjectsSection = ({ setSettingsDraftRole({ ...settingsDraftRole, canUpdateAllObjectRecords: value, + ...(value === true + ? { + canReadAllObjectRecords: value, + } + : {}), }); }, }, @@ -81,6 +93,11 @@ export const SettingsRolePermissionsObjectsSection = ({ setSettingsDraftRole({ ...settingsDraftRole, canSoftDeleteAllObjectRecords: value, + ...(value === true + ? { + canReadAllObjectRecords: value, + } + : {}), }); }, }, @@ -98,6 +115,11 @@ export const SettingsRolePermissionsObjectsSection = ({ setSettingsDraftRole({ ...settingsDraftRole, canDestroyAllObjectRecords: value, + ...(value === true + ? { + canReadAllObjectRecords: value, + } + : {}), }); }, }, diff --git a/packages/twenty-front/src/modules/settings/roles/role-permissions/settings-permissions/components/SettingsRolePermissionsSettingsSection.tsx b/packages/twenty-front/src/modules/settings/roles/role-permissions/settings-permissions/components/SettingsRolePermissionsSettingsSection.tsx index b5b9bea8d..df69cad5c 100644 --- a/packages/twenty-front/src/modules/settings/roles/role-permissions/settings-permissions/components/SettingsRolePermissionsSettingsSection.tsx +++ b/packages/twenty-front/src/modules/settings/roles/role-permissions/settings-permissions/components/SettingsRolePermissionsSettingsSection.tsx @@ -16,7 +16,7 @@ import { IconSettings, IconUsers, } from 'twenty-ui/display'; -import { Card, Section } from 'twenty-ui/layout'; +import { AnimatedExpandableContainer, Card, Section } from 'twenty-ui/layout'; import { FeatureFlagKey, SettingPermissionType, @@ -112,19 +112,30 @@ export const SettingsRolePermissionsSettingsSection = ({ /> )} - - - - {settingsPermissionsConfig.map((permission) => ( - - ))} - - + + + + + {settingsPermissionsConfig.map((permission) => ( + + ))} + + + ); }; 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 62d7a8569..c95e18328 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 @@ -1,3 +1,4 @@ +import { SaveAndCancelButtons } from '@/settings/components/SaveAndCancelButtons/SaveAndCancelButtons'; import { SettingsPageContainer } from '@/settings/components/SettingsPageContainer'; import { GET_ROLES } from '@/settings/roles/graphql/queries/getRolesQuery'; import { useUpdateWorkspaceMemberRole } from '@/settings/roles/hooks/useUpdateWorkspaceMemberRole'; @@ -22,7 +23,6 @@ import { t } from '@lingui/core/macro'; import { useRecoilValue } from 'recoil'; import { isDefined } from 'twenty-shared/utils'; import { IconLockOpen, IconSettings, IconUserPlus } from 'twenty-ui/display'; -import { Button } from 'twenty-ui/input'; import { v4 } from 'uuid'; import { FeatureFlagKey, @@ -281,16 +281,11 @@ export const SettingsRole = ({ roleId, isCreateMode }: SettingsRoleProps) => { }, ]} actionButton={ - isDirty && ( -