From e301c7856b7e67758972e5dd4f177d6a6f7910f8 Mon Sep 17 00:00:00 2001 From: Weiko Date: Fri, 21 Feb 2025 09:59:47 +0100 Subject: [PATCH] Add all object level read-only behavior (#10356) Fixes https://github.com/twentyhq/core-team-issues/issues/427 --------- Co-authored-by: Marie Stoppa --- .../hooks/useDeleteMultipleRecordsAction.tsx | 4 ++ .../hooks/useDestroyMultipleRecordsAction.tsx | 4 ++ ...teNewTableRecordNoSelectionRecordAction.ts | 5 +- .../hooks/useDeleteSingleRecordAction.tsx | 7 +- .../hooks/useDestroySingleRecordAction.tsx | 7 +- .../files/components/Attachments.tsx | 33 ++++++---- .../activities/notes/components/Notes.tsx | 25 +++++--- .../tasks/components/TaskGroups.tsx | 25 +++++--- .../auth/states/currentUserWorkspaceState.ts | 5 +- .../components/RecordBoardColumnHeader.tsx | 16 +++-- .../components/RecordBoardColumnNewRecord.tsx | 8 +++ .../RecordBoardColumnNewRecordButton.tsx | 7 ++ .../hooks/useIsFieldValueReadOnly.ts | 4 ++ .../utils/isFieldValueReadOnly.ts | 6 ++ .../hooks/useRecordGroupActions.ts | 64 +++++++++---------- .../components/RecordTableEmptyState.tsx | 8 +++ .../RecordTableEmptyStateDisplay.tsx | 2 + .../RecordTableEmptyStateReadOnly.tsx | 24 +++++++ .../components/RecordTableHeaderCell.tsx | 6 +- .../RecordTableRecordGroupSectionAddNew.tsx | 7 ++ .../components/MultiRecordSelect.tsx | 5 +- .../SingleRecordSelectMenuItemsWithSearch.tsx | 13 ++-- .../hooks/useSettingsNavigationItems.tsx | 4 +- .../hooks/useHasObjectReadOnlyPermission.ts | 27 ++++++++ .../layout/page/components/PageAddButton.tsx | 8 +++ .../components/ShowPageAddButton.tsx | 7 ++ packages/twenty-front/vite.config.ts | 4 +- 27 files changed, 252 insertions(+), 83 deletions(-) create mode 100644 packages/twenty-front/src/modules/object-record/record-table/empty-state/components/RecordTableEmptyStateReadOnly.tsx create mode 100644 packages/twenty-front/src/modules/settings/roles/hooks/useHasObjectReadOnlyPermission.ts diff --git a/packages/twenty-front/src/modules/action-menu/actions/record-actions/multiple-records/hooks/useDeleteMultipleRecordsAction.tsx b/packages/twenty-front/src/modules/action-menu/actions/record-actions/multiple-records/hooks/useDeleteMultipleRecordsAction.tsx index 49daa3f73..94f7b6e49 100644 --- a/packages/twenty-front/src/modules/action-menu/actions/record-actions/multiple-records/hooks/useDeleteMultipleRecordsAction.tsx +++ b/packages/twenty-front/src/modules/action-menu/actions/record-actions/multiple-records/hooks/useDeleteMultipleRecordsAction.tsx @@ -11,6 +11,7 @@ import { useLazyFetchAllRecords } from '@/object-record/hooks/useLazyFetchAllRec import { useCheckIsSoftDeleteFilter } from '@/object-record/record-filter/hooks/useCheckIsSoftDeleteFilter'; import { useFilterValueDependencies } from '@/object-record/record-filter/hooks/useFilterValueDependencies'; import { useRecordTable } from '@/object-record/record-table/hooks/useRecordTable'; +import { useHasObjectReadOnlyPermission } from '@/settings/roles/hooks/useHasObjectReadOnlyPermission'; import { ConfirmationModal } from '@/ui/layout/modal/components/ConfirmationModal'; import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2'; import { useCallback, useState } from 'react'; @@ -21,6 +22,8 @@ export const useDeleteMultipleRecordsAction: ActionHookWithObjectMetadataItem = const [isDeleteRecordsModalOpen, setIsDeleteRecordsModalOpen] = useState(false); + const hasObjectReadOnlyPermission = useHasObjectReadOnlyPermission(); + const { resetTableRowSelection } = useRecordTable({ recordTableId: objectMetadataItem.namePlural, }); @@ -77,6 +80,7 @@ export const useDeleteMultipleRecordsAction: ActionHookWithObjectMetadataItem = const isRemoteObject = objectMetadataItem.isRemote; const shouldBeRegistered = + !hasObjectReadOnlyPermission && !isRemoteObject && !isDeletedFilterActive && isDefined(contextStoreNumberOfSelectedRecords) && diff --git a/packages/twenty-front/src/modules/action-menu/actions/record-actions/multiple-records/hooks/useDestroyMultipleRecordsAction.tsx b/packages/twenty-front/src/modules/action-menu/actions/record-actions/multiple-records/hooks/useDestroyMultipleRecordsAction.tsx index 68bbc1c5f..9ab4aced3 100644 --- a/packages/twenty-front/src/modules/action-menu/actions/record-actions/multiple-records/hooks/useDestroyMultipleRecordsAction.tsx +++ b/packages/twenty-front/src/modules/action-menu/actions/record-actions/multiple-records/hooks/useDestroyMultipleRecordsAction.tsx @@ -12,6 +12,7 @@ import { useLazyFetchAllRecords } from '@/object-record/hooks/useLazyFetchAllRec import { useFilterValueDependencies } from '@/object-record/record-filter/hooks/useFilterValueDependencies'; import { RecordFilterOperand } from '@/object-record/record-filter/types/RecordFilterOperand'; import { useRecordTable } from '@/object-record/record-table/hooks/useRecordTable'; +import { useHasObjectReadOnlyPermission } from '@/settings/roles/hooks/useHasObjectReadOnlyPermission'; import { ConfirmationModal } from '@/ui/layout/modal/components/ConfirmationModal'; import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2'; import { useCallback, useState } from 'react'; @@ -26,6 +27,8 @@ export const useDestroyMultipleRecordsAction: ActionHookWithObjectMetadataItem = recordTableId: objectMetadataItem.namePlural, }); + const hasObjectReadOnlyPermission = useHasObjectReadOnlyPermission(); + const { destroyManyRecords } = useDestroyManyRecords({ objectNameSingular: objectMetadataItem.nameSingular, }); @@ -86,6 +89,7 @@ export const useDestroyMultipleRecordsAction: ActionHookWithObjectMetadataItem = const isRemoteObject = objectMetadataItem.isRemote; const shouldBeRegistered = + !hasObjectReadOnlyPermission && !isRemoteObject && isDeletedFilterActive && isDefined(contextStoreNumberOfSelectedRecords) && diff --git a/packages/twenty-front/src/modules/action-menu/actions/record-actions/no-selection/hooks/useCreateNewTableRecordNoSelectionRecordAction.ts b/packages/twenty-front/src/modules/action-menu/actions/record-actions/no-selection/hooks/useCreateNewTableRecordNoSelectionRecordAction.ts index f640ff6a7..a1a2ca7f1 100644 --- a/packages/twenty-front/src/modules/action-menu/actions/record-actions/no-selection/hooks/useCreateNewTableRecordNoSelectionRecordAction.ts +++ b/packages/twenty-front/src/modules/action-menu/actions/record-actions/no-selection/hooks/useCreateNewTableRecordNoSelectionRecordAction.ts @@ -1,6 +1,7 @@ import { ActionHookWithObjectMetadataItem } from '@/action-menu/actions/types/ActionHook'; import { useCreateNewTableRecord } from '@/object-record/record-table/hooks/useCreateNewTableRecords'; import { getRecordIndexIdFromObjectNamePlural } from '@/object-record/utils/getRecordIndexIdFromObjectNamePlural'; +import { useHasObjectReadOnlyPermission } from '@/settings/roles/hooks/useHasObjectReadOnlyPermission'; export const useCreateNewTableRecordNoSelectionRecordAction: ActionHookWithObjectMetadataItem = ({ objectMetadataItem }) => { @@ -8,6 +9,8 @@ export const useCreateNewTableRecordNoSelectionRecordAction: ActionHookWithObjec objectMetadataItem.namePlural, ); + const hasObjectReadOnlyPermission = useHasObjectReadOnlyPermission(); + const { createNewTableRecord } = useCreateNewTableRecord({ objectMetadataItem, recordTableId, @@ -18,7 +21,7 @@ export const useCreateNewTableRecordNoSelectionRecordAction: ActionHookWithObjec }; return { - shouldBeRegistered: true, + shouldBeRegistered: !hasObjectReadOnlyPermission, onClick, }; }; diff --git a/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/hooks/useDeleteSingleRecordAction.tsx b/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/hooks/useDeleteSingleRecordAction.tsx index 5ee3a8377..ced7c172d 100644 --- a/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/hooks/useDeleteSingleRecordAction.tsx +++ b/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/hooks/useDeleteSingleRecordAction.tsx @@ -6,6 +6,7 @@ import { useFavorites } from '@/favorites/hooks/useFavorites'; import { useDeleteOneRecord } from '@/object-record/hooks/useDeleteOneRecord'; import { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState'; import { useRecordTable } from '@/object-record/record-table/hooks/useRecordTable'; +import { useHasObjectReadOnlyPermission } from '@/settings/roles/hooks/useHasObjectReadOnlyPermission'; import { ConfirmationModal } from '@/ui/layout/modal/components/ConfirmationModal'; import { useRightDrawer } from '@/ui/layout/right-drawer/hooks/useRightDrawer'; import { isNull } from '@sniptt/guards'; @@ -21,6 +22,8 @@ export const useDeleteSingleRecordAction: ActionHookWithObjectMetadataItem = ({ const [isDeleteRecordsModalOpen, setIsDeleteRecordsModalOpen] = useState(false); + const hasObjectReadOnlyPermission = useHasObjectReadOnlyPermission(); + const { resetTableRowSelection } = useRecordTable({ recordTableId: objectMetadataItem.namePlural, }); @@ -61,7 +64,9 @@ export const useDeleteSingleRecordAction: ActionHookWithObjectMetadataItem = ({ const { isInRightDrawer } = useContext(ActionMenuContext); const shouldBeRegistered = - !isRemoteObject && isNull(selectedRecord?.deletedAt); + !isRemoteObject && + isNull(selectedRecord?.deletedAt) && + !hasObjectReadOnlyPermission; const onClick = () => { if (!shouldBeRegistered) { diff --git a/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/hooks/useDestroySingleRecordAction.tsx b/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/hooks/useDestroySingleRecordAction.tsx index 5278bffea..0cd03ef53 100644 --- a/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/hooks/useDestroySingleRecordAction.tsx +++ b/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/hooks/useDestroySingleRecordAction.tsx @@ -4,6 +4,7 @@ import { ActionMenuContext } from '@/action-menu/contexts/ActionMenuContext'; import { useDestroyOneRecord } from '@/object-record/hooks/useDestroyOneRecord'; import { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState'; import { useRecordTable } from '@/object-record/record-table/hooks/useRecordTable'; +import { useHasObjectReadOnlyPermission } from '@/settings/roles/hooks/useHasObjectReadOnlyPermission'; import { AppPath } from '@/types/AppPath'; import { ConfirmationModal } from '@/ui/layout/modal/components/ConfirmationModal'; import { useRightDrawer } from '@/ui/layout/right-drawer/hooks/useRightDrawer'; @@ -20,6 +21,8 @@ export const useDestroySingleRecordAction: ActionHookWithObjectMetadataItem = ({ const [isDestroyRecordsModalOpen, setIsDestroyRecordsModalOpen] = useState(false); + const hasObjectReadOnlyPermission = useHasObjectReadOnlyPermission(); + const navigateApp = useNavigateApp(); const { resetTableRowSelection } = useRecordTable({ @@ -54,7 +57,9 @@ export const useDestroySingleRecordAction: ActionHookWithObjectMetadataItem = ({ const { isInRightDrawer } = useContext(ActionMenuContext); const shouldBeRegistered = - !isRemoteObject && isDefined(selectedRecord?.deletedAt); + !hasObjectReadOnlyPermission && + !isRemoteObject && + isDefined(selectedRecord?.deletedAt); const onClick = () => { if (!shouldBeRegistered) { diff --git a/packages/twenty-front/src/modules/activities/files/components/Attachments.tsx b/packages/twenty-front/src/modules/activities/files/components/Attachments.tsx index 047bc21ba..5862c55ac 100644 --- a/packages/twenty-front/src/modules/activities/files/components/Attachments.tsx +++ b/packages/twenty-front/src/modules/activities/files/components/Attachments.tsx @@ -17,6 +17,7 @@ import { DropZone } from '@/activities/files/components/DropZone'; import { useAttachments } from '@/activities/files/hooks/useAttachments'; import { useUploadAttachmentFile } from '@/activities/files/hooks/useUploadAttachmentFile'; import { ActivityTargetableObject } from '@/activities/types/ActivityTargetableEntity'; +import { useHasObjectReadOnlyPermission } from '@/settings/roles/hooks/useHasObjectReadOnlyPermission'; import { isDefined } from 'twenty-shared'; const StyledAttachmentsContainer = styled.div` @@ -46,6 +47,8 @@ export const Attachments = ({ const [isDraggingFile, setIsDraggingFile] = useState(false); + const hasObjectReadOnlyPermission = useHasObjectReadOnlyPermission(); + const handleFileChange = (e: ChangeEvent) => { if (isDefined(e.target.files)) onUploadFile?.(e.target.files[0]); }; @@ -91,12 +94,14 @@ export const Attachments = ({ onChange={handleFileChange} type="file" /> - + !hasObjectReadOnlyPermission && ( + + ) } /> diff --git a/packages/twenty-front/src/modules/activities/notes/components/Notes.tsx b/packages/twenty-front/src/modules/activities/notes/components/Notes.tsx index c99a8e57c..f77602260 100644 --- a/packages/twenty-front/src/modules/activities/notes/components/Notes.tsx +++ b/packages/twenty-front/src/modules/activities/notes/components/Notes.tsx @@ -4,6 +4,7 @@ import { NoteList } from '@/activities/notes/components/NoteList'; import { useNotes } from '@/activities/notes/hooks/useNotes'; import { ActivityTargetableObject } from '@/activities/types/ActivityTargetableEntity'; import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular'; +import { useHasObjectReadOnlyPermission } from '@/settings/roles/hooks/useHasObjectReadOnlyPermission'; import styled from '@emotion/styled'; import { AnimatedPlaceholder, @@ -31,6 +32,8 @@ export const Notes = ({ }) => { const { notes, loading } = useNotes(targetableObject); + const hasObjectReadOnlyPermission = useHasObjectReadOnlyPermission(); + const openCreateActivity = useOpenCreateActivityDrawer({ activityObjectNameSingular: CoreObjectNameSingular.Note, }); @@ -56,16 +59,18 @@ export const Notes = ({ There are no associated notes with this record. -