Add all object level read-only behavior (#10356)

Fixes https://github.com/twentyhq/core-team-issues/issues/427

---------

Co-authored-by: Marie Stoppa <marie.stoppa@essec.edu>
This commit is contained in:
Weiko
2025-02-21 09:59:47 +01:00
committed by GitHub
parent c46f7848b7
commit e301c7856b
27 changed files with 252 additions and 83 deletions

View File

@ -11,6 +11,7 @@ import { useLazyFetchAllRecords } from '@/object-record/hooks/useLazyFetchAllRec
import { useCheckIsSoftDeleteFilter } from '@/object-record/record-filter/hooks/useCheckIsSoftDeleteFilter'; import { useCheckIsSoftDeleteFilter } from '@/object-record/record-filter/hooks/useCheckIsSoftDeleteFilter';
import { useFilterValueDependencies } from '@/object-record/record-filter/hooks/useFilterValueDependencies'; import { useFilterValueDependencies } from '@/object-record/record-filter/hooks/useFilterValueDependencies';
import { useRecordTable } from '@/object-record/record-table/hooks/useRecordTable'; 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 { ConfirmationModal } from '@/ui/layout/modal/components/ConfirmationModal';
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2'; import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
import { useCallback, useState } from 'react'; import { useCallback, useState } from 'react';
@ -21,6 +22,8 @@ export const useDeleteMultipleRecordsAction: ActionHookWithObjectMetadataItem =
const [isDeleteRecordsModalOpen, setIsDeleteRecordsModalOpen] = const [isDeleteRecordsModalOpen, setIsDeleteRecordsModalOpen] =
useState(false); useState(false);
const hasObjectReadOnlyPermission = useHasObjectReadOnlyPermission();
const { resetTableRowSelection } = useRecordTable({ const { resetTableRowSelection } = useRecordTable({
recordTableId: objectMetadataItem.namePlural, recordTableId: objectMetadataItem.namePlural,
}); });
@ -77,6 +80,7 @@ export const useDeleteMultipleRecordsAction: ActionHookWithObjectMetadataItem =
const isRemoteObject = objectMetadataItem.isRemote; const isRemoteObject = objectMetadataItem.isRemote;
const shouldBeRegistered = const shouldBeRegistered =
!hasObjectReadOnlyPermission &&
!isRemoteObject && !isRemoteObject &&
!isDeletedFilterActive && !isDeletedFilterActive &&
isDefined(contextStoreNumberOfSelectedRecords) && isDefined(contextStoreNumberOfSelectedRecords) &&

View File

@ -12,6 +12,7 @@ import { useLazyFetchAllRecords } from '@/object-record/hooks/useLazyFetchAllRec
import { useFilterValueDependencies } from '@/object-record/record-filter/hooks/useFilterValueDependencies'; import { useFilterValueDependencies } from '@/object-record/record-filter/hooks/useFilterValueDependencies';
import { RecordFilterOperand } from '@/object-record/record-filter/types/RecordFilterOperand'; import { RecordFilterOperand } from '@/object-record/record-filter/types/RecordFilterOperand';
import { useRecordTable } from '@/object-record/record-table/hooks/useRecordTable'; 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 { ConfirmationModal } from '@/ui/layout/modal/components/ConfirmationModal';
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2'; import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
import { useCallback, useState } from 'react'; import { useCallback, useState } from 'react';
@ -26,6 +27,8 @@ export const useDestroyMultipleRecordsAction: ActionHookWithObjectMetadataItem =
recordTableId: objectMetadataItem.namePlural, recordTableId: objectMetadataItem.namePlural,
}); });
const hasObjectReadOnlyPermission = useHasObjectReadOnlyPermission();
const { destroyManyRecords } = useDestroyManyRecords({ const { destroyManyRecords } = useDestroyManyRecords({
objectNameSingular: objectMetadataItem.nameSingular, objectNameSingular: objectMetadataItem.nameSingular,
}); });
@ -86,6 +89,7 @@ export const useDestroyMultipleRecordsAction: ActionHookWithObjectMetadataItem =
const isRemoteObject = objectMetadataItem.isRemote; const isRemoteObject = objectMetadataItem.isRemote;
const shouldBeRegistered = const shouldBeRegistered =
!hasObjectReadOnlyPermission &&
!isRemoteObject && !isRemoteObject &&
isDeletedFilterActive && isDeletedFilterActive &&
isDefined(contextStoreNumberOfSelectedRecords) && isDefined(contextStoreNumberOfSelectedRecords) &&

View File

@ -1,6 +1,7 @@
import { ActionHookWithObjectMetadataItem } from '@/action-menu/actions/types/ActionHook'; import { ActionHookWithObjectMetadataItem } from '@/action-menu/actions/types/ActionHook';
import { useCreateNewTableRecord } from '@/object-record/record-table/hooks/useCreateNewTableRecords'; import { useCreateNewTableRecord } from '@/object-record/record-table/hooks/useCreateNewTableRecords';
import { getRecordIndexIdFromObjectNamePlural } from '@/object-record/utils/getRecordIndexIdFromObjectNamePlural'; import { getRecordIndexIdFromObjectNamePlural } from '@/object-record/utils/getRecordIndexIdFromObjectNamePlural';
import { useHasObjectReadOnlyPermission } from '@/settings/roles/hooks/useHasObjectReadOnlyPermission';
export const useCreateNewTableRecordNoSelectionRecordAction: ActionHookWithObjectMetadataItem = export const useCreateNewTableRecordNoSelectionRecordAction: ActionHookWithObjectMetadataItem =
({ objectMetadataItem }) => { ({ objectMetadataItem }) => {
@ -8,6 +9,8 @@ export const useCreateNewTableRecordNoSelectionRecordAction: ActionHookWithObjec
objectMetadataItem.namePlural, objectMetadataItem.namePlural,
); );
const hasObjectReadOnlyPermission = useHasObjectReadOnlyPermission();
const { createNewTableRecord } = useCreateNewTableRecord({ const { createNewTableRecord } = useCreateNewTableRecord({
objectMetadataItem, objectMetadataItem,
recordTableId, recordTableId,
@ -18,7 +21,7 @@ export const useCreateNewTableRecordNoSelectionRecordAction: ActionHookWithObjec
}; };
return { return {
shouldBeRegistered: true, shouldBeRegistered: !hasObjectReadOnlyPermission,
onClick, onClick,
}; };
}; };

View File

@ -6,6 +6,7 @@ import { useFavorites } from '@/favorites/hooks/useFavorites';
import { useDeleteOneRecord } from '@/object-record/hooks/useDeleteOneRecord'; import { useDeleteOneRecord } from '@/object-record/hooks/useDeleteOneRecord';
import { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState'; import { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState';
import { useRecordTable } from '@/object-record/record-table/hooks/useRecordTable'; 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 { ConfirmationModal } from '@/ui/layout/modal/components/ConfirmationModal';
import { useRightDrawer } from '@/ui/layout/right-drawer/hooks/useRightDrawer'; import { useRightDrawer } from '@/ui/layout/right-drawer/hooks/useRightDrawer';
import { isNull } from '@sniptt/guards'; import { isNull } from '@sniptt/guards';
@ -21,6 +22,8 @@ export const useDeleteSingleRecordAction: ActionHookWithObjectMetadataItem = ({
const [isDeleteRecordsModalOpen, setIsDeleteRecordsModalOpen] = const [isDeleteRecordsModalOpen, setIsDeleteRecordsModalOpen] =
useState(false); useState(false);
const hasObjectReadOnlyPermission = useHasObjectReadOnlyPermission();
const { resetTableRowSelection } = useRecordTable({ const { resetTableRowSelection } = useRecordTable({
recordTableId: objectMetadataItem.namePlural, recordTableId: objectMetadataItem.namePlural,
}); });
@ -61,7 +64,9 @@ export const useDeleteSingleRecordAction: ActionHookWithObjectMetadataItem = ({
const { isInRightDrawer } = useContext(ActionMenuContext); const { isInRightDrawer } = useContext(ActionMenuContext);
const shouldBeRegistered = const shouldBeRegistered =
!isRemoteObject && isNull(selectedRecord?.deletedAt); !isRemoteObject &&
isNull(selectedRecord?.deletedAt) &&
!hasObjectReadOnlyPermission;
const onClick = () => { const onClick = () => {
if (!shouldBeRegistered) { if (!shouldBeRegistered) {

View File

@ -4,6 +4,7 @@ import { ActionMenuContext } from '@/action-menu/contexts/ActionMenuContext';
import { useDestroyOneRecord } from '@/object-record/hooks/useDestroyOneRecord'; import { useDestroyOneRecord } from '@/object-record/hooks/useDestroyOneRecord';
import { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState'; import { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState';
import { useRecordTable } from '@/object-record/record-table/hooks/useRecordTable'; import { useRecordTable } from '@/object-record/record-table/hooks/useRecordTable';
import { useHasObjectReadOnlyPermission } from '@/settings/roles/hooks/useHasObjectReadOnlyPermission';
import { AppPath } from '@/types/AppPath'; import { AppPath } from '@/types/AppPath';
import { ConfirmationModal } from '@/ui/layout/modal/components/ConfirmationModal'; import { ConfirmationModal } from '@/ui/layout/modal/components/ConfirmationModal';
import { useRightDrawer } from '@/ui/layout/right-drawer/hooks/useRightDrawer'; import { useRightDrawer } from '@/ui/layout/right-drawer/hooks/useRightDrawer';
@ -20,6 +21,8 @@ export const useDestroySingleRecordAction: ActionHookWithObjectMetadataItem = ({
const [isDestroyRecordsModalOpen, setIsDestroyRecordsModalOpen] = const [isDestroyRecordsModalOpen, setIsDestroyRecordsModalOpen] =
useState(false); useState(false);
const hasObjectReadOnlyPermission = useHasObjectReadOnlyPermission();
const navigateApp = useNavigateApp(); const navigateApp = useNavigateApp();
const { resetTableRowSelection } = useRecordTable({ const { resetTableRowSelection } = useRecordTable({
@ -54,7 +57,9 @@ export const useDestroySingleRecordAction: ActionHookWithObjectMetadataItem = ({
const { isInRightDrawer } = useContext(ActionMenuContext); const { isInRightDrawer } = useContext(ActionMenuContext);
const shouldBeRegistered = const shouldBeRegistered =
!isRemoteObject && isDefined(selectedRecord?.deletedAt); !hasObjectReadOnlyPermission &&
!isRemoteObject &&
isDefined(selectedRecord?.deletedAt);
const onClick = () => { const onClick = () => {
if (!shouldBeRegistered) { if (!shouldBeRegistered) {

View File

@ -17,6 +17,7 @@ import { DropZone } from '@/activities/files/components/DropZone';
import { useAttachments } from '@/activities/files/hooks/useAttachments'; import { useAttachments } from '@/activities/files/hooks/useAttachments';
import { useUploadAttachmentFile } from '@/activities/files/hooks/useUploadAttachmentFile'; import { useUploadAttachmentFile } from '@/activities/files/hooks/useUploadAttachmentFile';
import { ActivityTargetableObject } from '@/activities/types/ActivityTargetableEntity'; import { ActivityTargetableObject } from '@/activities/types/ActivityTargetableEntity';
import { useHasObjectReadOnlyPermission } from '@/settings/roles/hooks/useHasObjectReadOnlyPermission';
import { isDefined } from 'twenty-shared'; import { isDefined } from 'twenty-shared';
const StyledAttachmentsContainer = styled.div` const StyledAttachmentsContainer = styled.div`
@ -46,6 +47,8 @@ export const Attachments = ({
const [isDraggingFile, setIsDraggingFile] = useState(false); const [isDraggingFile, setIsDraggingFile] = useState(false);
const hasObjectReadOnlyPermission = useHasObjectReadOnlyPermission();
const handleFileChange = (e: ChangeEvent<HTMLInputElement>) => { const handleFileChange = (e: ChangeEvent<HTMLInputElement>) => {
if (isDefined(e.target.files)) onUploadFile?.(e.target.files[0]); if (isDefined(e.target.files)) onUploadFile?.(e.target.files[0]);
}; };
@ -91,12 +94,14 @@ export const Attachments = ({
onChange={handleFileChange} onChange={handleFileChange}
type="file" type="file"
/> />
<Button {!hasObjectReadOnlyPermission && (
Icon={IconPlus} <Button
title="Add file" Icon={IconPlus}
variant="secondary" title="Add file"
onClick={handleUploadFileClick} variant="secondary"
/> onClick={handleUploadFileClick}
/>
)}
</AnimatedPlaceholderEmptyContainer> </AnimatedPlaceholderEmptyContainer>
)} )}
</StyledDropZoneContainer> </StyledDropZoneContainer>
@ -115,13 +120,15 @@ export const Attachments = ({
title="All" title="All"
attachments={attachments ?? []} attachments={attachments ?? []}
button={ button={
<Button !hasObjectReadOnlyPermission && (
Icon={IconPlus} <Button
size="small" Icon={IconPlus}
variant="secondary" size="small"
title="Add file" variant="secondary"
onClick={handleUploadFileClick} title="Add file"
></Button> onClick={handleUploadFileClick}
></Button>
)
} }
/> />
</StyledAttachmentsContainer> </StyledAttachmentsContainer>

View File

@ -4,6 +4,7 @@ import { NoteList } from '@/activities/notes/components/NoteList';
import { useNotes } from '@/activities/notes/hooks/useNotes'; import { useNotes } from '@/activities/notes/hooks/useNotes';
import { ActivityTargetableObject } from '@/activities/types/ActivityTargetableEntity'; import { ActivityTargetableObject } from '@/activities/types/ActivityTargetableEntity';
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular'; import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
import { useHasObjectReadOnlyPermission } from '@/settings/roles/hooks/useHasObjectReadOnlyPermission';
import styled from '@emotion/styled'; import styled from '@emotion/styled';
import { import {
AnimatedPlaceholder, AnimatedPlaceholder,
@ -31,6 +32,8 @@ export const Notes = ({
}) => { }) => {
const { notes, loading } = useNotes(targetableObject); const { notes, loading } = useNotes(targetableObject);
const hasObjectReadOnlyPermission = useHasObjectReadOnlyPermission();
const openCreateActivity = useOpenCreateActivityDrawer({ const openCreateActivity = useOpenCreateActivityDrawer({
activityObjectNameSingular: CoreObjectNameSingular.Note, activityObjectNameSingular: CoreObjectNameSingular.Note,
}); });
@ -56,16 +59,18 @@ export const Notes = ({
There are no associated notes with this record. There are no associated notes with this record.
</AnimatedPlaceholderEmptySubTitle> </AnimatedPlaceholderEmptySubTitle>
</AnimatedPlaceholderEmptyTextContainer> </AnimatedPlaceholderEmptyTextContainer>
<Button {!hasObjectReadOnlyPermission && (
Icon={IconPlus} <Button
title="New note" Icon={IconPlus}
variant="secondary" title="New note"
onClick={() => variant="secondary"
openCreateActivity({ onClick={() =>
targetableObjects: [targetableObject], openCreateActivity({
}) targetableObjects: [targetableObject],
} })
/> }
/>
)}
</AnimatedPlaceholderEmptyContainer> </AnimatedPlaceholderEmptyContainer>
); );
} }

View File

@ -17,6 +17,7 @@ import { useTasks } from '@/activities/tasks/hooks/useTasks';
import { ActivityTargetableObject } from '@/activities/types/ActivityTargetableEntity'; import { ActivityTargetableObject } from '@/activities/types/ActivityTargetableEntity';
import { Task } from '@/activities/types/Task'; import { Task } from '@/activities/types/Task';
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular'; import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
import { useHasObjectReadOnlyPermission } from '@/settings/roles/hooks/useHasObjectReadOnlyPermission';
import { useTabList } from '@/ui/layout/tab/hooks/useTabList'; import { useTabList } from '@/ui/layout/tab/hooks/useTabList';
import groupBy from 'lodash.groupby'; import groupBy from 'lodash.groupby';
import { AddTaskButton } from './AddTaskButton'; import { AddTaskButton } from './AddTaskButton';
@ -38,6 +39,8 @@ export const TaskGroups = ({ targetableObjects }: TaskGroupsProps) => {
targetableObjects: targetableObjects ?? [], targetableObjects: targetableObjects ?? [],
}); });
const hasObjectReadOnlyPermission = useHasObjectReadOnlyPermission();
const openCreateActivity = useOpenCreateActivityDrawer({ const openCreateActivity = useOpenCreateActivityDrawer({
activityObjectNameSingular: CoreObjectNameSingular.Task, activityObjectNameSingular: CoreObjectNameSingular.Task,
}); });
@ -71,16 +74,18 @@ export const TaskGroups = ({ targetableObjects }: TaskGroupsProps) => {
All tasks addressed. Maintain the momentum. All tasks addressed. Maintain the momentum.
</AnimatedPlaceholderEmptySubTitle> </AnimatedPlaceholderEmptySubTitle>
</AnimatedPlaceholderEmptyTextContainer> </AnimatedPlaceholderEmptyTextContainer>
<Button {!hasObjectReadOnlyPermission && (
Icon={IconPlus} <Button
title="New task" Icon={IconPlus}
variant={'secondary'} title="New task"
onClick={() => variant={'secondary'}
openCreateActivity({ onClick={() =>
targetableObjects: targetableObjects ?? [], openCreateActivity({
}) targetableObjects: targetableObjects ?? [],
} })
/> }
/>
)}
</AnimatedPlaceholderEmptyContainer> </AnimatedPlaceholderEmptyContainer>
); );
} }

View File

@ -1,7 +1,10 @@
import { createState } from '@ui/utilities/state/utils/createState'; import { createState } from '@ui/utilities/state/utils/createState';
import { UserWorkspace } from '~/generated/graphql'; import { UserWorkspace } from '~/generated/graphql';
export type CurrentUserWorkspace = Pick<UserWorkspace, 'settingsPermissions'>; export type CurrentUserWorkspace = Pick<
UserWorkspace,
'settingsPermissions' | 'objectRecordsPermissions'
>;
export const currentUserWorkspaceState = export const currentUserWorkspaceState =
createState<CurrentUserWorkspace | null>({ createState<CurrentUserWorkspace | null>({

View File

@ -11,6 +11,7 @@ import { useColumnNewCardActions } from '@/object-record/record-board/record-boa
import { useIsOpportunitiesCompanyFieldDisabled } from '@/object-record/record-board/record-board-column/hooks/useIsOpportunitiesCompanyFieldDisabled'; import { useIsOpportunitiesCompanyFieldDisabled } from '@/object-record/record-board/record-board-column/hooks/useIsOpportunitiesCompanyFieldDisabled';
import { RecordBoardColumnHotkeyScope } from '@/object-record/record-board/types/BoardColumnHotkeyScope'; import { RecordBoardColumnHotkeyScope } from '@/object-record/record-board/types/BoardColumnHotkeyScope';
import { RecordGroupDefinitionType } from '@/object-record/record-group/types/RecordGroupDefinition'; import { RecordGroupDefinitionType } from '@/object-record/record-group/types/RecordGroupDefinition';
import { useHasObjectReadOnlyPermission } from '@/settings/roles/hooks/useHasObjectReadOnlyPermission';
import { usePreviousHotkeyScope } from '@/ui/utilities/hotkey/hooks/usePreviousHotkeyScope'; import { usePreviousHotkeyScope } from '@/ui/utilities/hotkey/hooks/usePreviousHotkeyScope';
import { IconDotsVertical, IconPlus, LightIconButton, Tag } from 'twenty-ui'; import { IconDotsVertical, IconPlus, LightIconButton, Tag } from 'twenty-ui';
@ -97,6 +98,8 @@ export const RecordBoardColumnHeader = () => {
columnDefinition.id ?? '', columnDefinition.id ?? '',
); );
const hasObjectReadOnlyPermission = useHasObjectReadOnlyPermission();
const { isOpportunitiesCompanyFieldDisabled } = const { isOpportunitiesCompanyFieldDisabled } =
useIsOpportunitiesCompanyFieldDisabled(); useIsOpportunitiesCompanyFieldDisabled();
@ -146,12 +149,13 @@ export const RecordBoardColumnHeader = () => {
Icon={IconDotsVertical} Icon={IconDotsVertical}
onClick={handleBoardColumnMenuOpen} onClick={handleBoardColumnMenuOpen}
/> />
{!hasObjectReadOnlyPermission && (
<LightIconButton <LightIconButton
accent="tertiary" accent="tertiary"
Icon={IconPlus} Icon={IconPlus}
onClick={() => handleNewButtonClick('first', isOpportunity)} onClick={() => handleNewButtonClick('first', isOpportunity)}
/> />
)}
</StyledHeaderActions> </StyledHeaderActions>
)} )}
</StyledRightContainer> </StyledRightContainer>

View File

@ -1,6 +1,7 @@
import { RecordBoardCard } from '@/object-record/record-board/record-board-card/components/RecordBoardCard'; import { RecordBoardCard } from '@/object-record/record-board/record-board-card/components/RecordBoardCard';
import { useAddNewCard } from '@/object-record/record-board/record-board-column/hooks/useAddNewCard'; import { useAddNewCard } from '@/object-record/record-board/record-board-column/hooks/useAddNewCard';
import { recordBoardNewRecordByColumnIdSelector } from '@/object-record/record-board/states/selectors/recordBoardNewRecordByColumnIdSelector'; import { recordBoardNewRecordByColumnIdSelector } from '@/object-record/record-board/states/selectors/recordBoardNewRecordByColumnIdSelector';
import { useHasObjectReadOnlyPermission } from '@/settings/roles/hooks/useHasObjectReadOnlyPermission';
import { useRecoilValue } from 'recoil'; import { useRecoilValue } from 'recoil';
export const RecordBoardColumnNewRecord = ({ export const RecordBoardColumnNewRecord = ({
@ -16,8 +17,15 @@ export const RecordBoardColumnNewRecord = ({
scopeId: columnId, scopeId: columnId,
}), }),
); );
const { handleCreateSuccess } = useAddNewCard(); const { handleCreateSuccess } = useAddNewCard();
const hasObjectReadOnlyPermission = useHasObjectReadOnlyPermission();
if (hasObjectReadOnlyPermission) {
return null;
}
return ( return (
<> <>
{newRecord.isCreating && newRecord.position === position && ( {newRecord.isCreating && newRecord.position === position && (

View File

@ -1,4 +1,5 @@
import { useColumnNewCardActions } from '@/object-record/record-board/record-board-column/hooks/useColumnNewCardActions'; import { useColumnNewCardActions } from '@/object-record/record-board/record-board-column/hooks/useColumnNewCardActions';
import { useHasObjectReadOnlyPermission } from '@/settings/roles/hooks/useHasObjectReadOnlyPermission';
import { useTheme } from '@emotion/react'; import { useTheme } from '@emotion/react';
import styled from '@emotion/styled'; import styled from '@emotion/styled';
import { IconPlus } from 'twenty-ui'; import { IconPlus } from 'twenty-ui';
@ -29,6 +30,12 @@ export const RecordBoardColumnNewRecordButton = ({
const { handleNewButtonClick } = useColumnNewCardActions(columnId); const { handleNewButtonClick } = useColumnNewCardActions(columnId);
const hasObjectReadOnlyPermission = useHasObjectReadOnlyPermission();
if (hasObjectReadOnlyPermission) {
return null;
}
return ( return (
<StyledNewButton onClick={() => handleNewButtonClick('last', false)}> <StyledNewButton onClick={() => handleNewButtonClick('last', false)}>
<IconPlus size={theme.icon.size.md} /> <IconPlus size={theme.icon.size.md} />

View File

@ -3,6 +3,7 @@ import { useContext } from 'react';
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem'; import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
import { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState'; import { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState';
import { ObjectRecord } from '@/object-record/types/ObjectRecord'; import { ObjectRecord } from '@/object-record/types/ObjectRecord';
import { useHasObjectReadOnlyPermission } from '@/settings/roles/hooks/useHasObjectReadOnlyPermission';
import { useRecoilValue } from 'recoil'; import { useRecoilValue } from 'recoil';
import { FieldContext } from '../contexts/FieldContext'; import { FieldContext } from '../contexts/FieldContext';
import { isFieldValueReadOnly } from '../utils/isFieldValueReadOnly'; import { isFieldValueReadOnly } from '../utils/isFieldValueReadOnly';
@ -20,11 +21,14 @@ export const useIsFieldValueReadOnly = () => {
objectNameSingular: metadata.objectMetadataNameSingular ?? '', objectNameSingular: metadata.objectMetadataNameSingular ?? '',
}); });
const hasObjectReadOnlyPermission = useHasObjectReadOnlyPermission();
return isFieldValueReadOnly({ return isFieldValueReadOnly({
objectNameSingular: metadata.objectMetadataNameSingular, objectNameSingular: metadata.objectMetadataNameSingular,
fieldName: metadata.fieldName, fieldName: metadata.fieldName,
fieldType: type, fieldType: type,
isObjectRemote: objectMetadataItem.isRemote, isObjectRemote: objectMetadataItem.isRemote,
isRecordDeleted: recordFromStore?.deletedAt, isRecordDeleted: recordFromStore?.deletedAt,
hasObjectReadOnlyPermission,
}); });
}; };

View File

@ -12,6 +12,7 @@ type isFieldValueReadOnlyParams = {
fieldType?: FieldMetadataType; fieldType?: FieldMetadataType;
isObjectRemote?: boolean; isObjectRemote?: boolean;
isRecordDeleted?: boolean; isRecordDeleted?: boolean;
hasObjectReadOnlyPermission?: boolean;
}; };
export const isFieldValueReadOnly = ({ export const isFieldValueReadOnly = ({
@ -20,6 +21,7 @@ export const isFieldValueReadOnly = ({
fieldType, fieldType,
isObjectRemote = false, isObjectRemote = false,
isRecordDeleted = false, isRecordDeleted = false,
hasObjectReadOnlyPermission = false,
}: isFieldValueReadOnlyParams) => { }: isFieldValueReadOnlyParams) => {
if (fieldName === 'noteTargets' || fieldName === 'taskTargets') { if (fieldName === 'noteTargets' || fieldName === 'taskTargets') {
return true; return true;
@ -33,6 +35,10 @@ export const isFieldValueReadOnly = ({
return true; return true;
} }
if (hasObjectReadOnlyPermission) {
return true;
}
if (isWorkflowSubObjectMetadata(objectNameSingular)) { if (isWorkflowSubObjectMetadata(objectNameSingular)) {
return true; return true;
} }

View File

@ -4,14 +4,15 @@ import { useRecordGroupVisibility } from '@/object-record/record-group/hooks/use
import { recordGroupFieldMetadataComponentState } from '@/object-record/record-group/states/recordGroupFieldMetadataComponentState'; import { recordGroupFieldMetadataComponentState } from '@/object-record/record-group/states/recordGroupFieldMetadataComponentState';
import { RecordGroupAction } from '@/object-record/record-group/types/RecordGroupActions'; import { RecordGroupAction } from '@/object-record/record-group/types/RecordGroupActions';
import { useRecordIndexContextOrThrow } from '@/object-record/record-index/contexts/RecordIndexContext'; import { useRecordIndexContextOrThrow } from '@/object-record/record-index/contexts/RecordIndexContext';
import { useHasSettingsPermission } from '@/settings/roles/hooks/useHasSettingsPermission';
import { SettingsPath } from '@/types/SettingsPath'; import { SettingsPath } from '@/types/SettingsPath';
import { navigationMemorizedUrlState } from '@/ui/navigation/states/navigationMemorizedUrlState'; import { navigationMemorizedUrlState } from '@/ui/navigation/states/navigationMemorizedUrlState';
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2'; import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
import { ViewType } from '@/views/types/ViewType'; import { ViewType } from '@/views/types/ViewType';
import { useCallback, useContext, useMemo } from 'react'; import { useCallback, useContext } from 'react';
import { useLocation } from 'react-router-dom'; import { useLocation } from 'react-router-dom';
import { useSetRecoilState } from 'recoil'; import { useSetRecoilState } from 'recoil';
import { isDefined } from 'twenty-shared'; import { isDefined, SettingsFeatures } from 'twenty-shared';
import { IconEyeOff, IconSettings } from 'twenty-ui'; import { IconEyeOff, IconSettings } from 'twenty-ui';
import { useNavigateSettings } from '~/hooks/useNavigateSettings'; import { useNavigateSettings } from '~/hooks/useNavigateSettings';
@ -69,37 +70,36 @@ export const useRecordGroupActions = ({
recordGroupFieldMetadata, recordGroupFieldMetadata,
]); ]);
const recordGroupActions: RecordGroupAction[] = useMemo( const hasAccessToDataModelSettings = useHasSettingsPermission(
() => SettingsFeatures.DATA_MODEL,
[
{
id: 'edit',
label: 'Edit',
icon: IconSettings,
position: 0,
callback: () => {
navigateToSelectSettings();
},
},
{
id: 'hide',
label: 'Hide',
icon: IconEyeOff,
position: 1,
callback: () => {
handleRecordGroupVisibilityChange({
...recordGroupDefinition,
isVisible: false,
});
},
},
].filter(isDefined),
[
handleRecordGroupVisibilityChange,
navigateToSelectSettings,
recordGroupDefinition,
],
); );
const recordGroupActions: RecordGroupAction[] = [];
if (hasAccessToDataModelSettings) {
recordGroupActions.push({
id: 'edit',
label: 'Edit',
icon: IconSettings,
position: 0,
callback: () => {
navigateToSelectSettings();
},
});
}
recordGroupActions.push({
id: 'hide',
label: 'Hide',
icon: IconEyeOff,
position: 1,
callback: () => {
handleRecordGroupVisibilityChange({
...recordGroupDefinition,
isVisible: false,
});
},
});
return recordGroupActions; return recordGroupActions;
}; };

View File

@ -4,9 +4,11 @@ import { useRecordTableContextOrThrow } from '@/object-record/record-table/conte
import { RecordTableEmptyStateByGroupNoRecordAtAll } from '@/object-record/record-table/empty-state/components/RecordTableEmptyStateByGroupNoRecordAtAll'; import { RecordTableEmptyStateByGroupNoRecordAtAll } from '@/object-record/record-table/empty-state/components/RecordTableEmptyStateByGroupNoRecordAtAll';
import { RecordTableEmptyStateNoGroupNoRecordAtAll } from '@/object-record/record-table/empty-state/components/RecordTableEmptyStateNoGroupNoRecordAtAll'; import { RecordTableEmptyStateNoGroupNoRecordAtAll } from '@/object-record/record-table/empty-state/components/RecordTableEmptyStateNoGroupNoRecordAtAll';
import { RecordTableEmptyStateNoRecordFoundForFilter } from '@/object-record/record-table/empty-state/components/RecordTableEmptyStateNoRecordFoundForFilter'; import { RecordTableEmptyStateNoRecordFoundForFilter } from '@/object-record/record-table/empty-state/components/RecordTableEmptyStateNoRecordFoundForFilter';
import { RecordTableEmptyStateReadOnly } from '@/object-record/record-table/empty-state/components/RecordTableEmptyStateReadOnly';
import { RecordTableEmptyStateRemote } from '@/object-record/record-table/empty-state/components/RecordTableEmptyStateRemote'; import { RecordTableEmptyStateRemote } from '@/object-record/record-table/empty-state/components/RecordTableEmptyStateRemote';
import { RecordTableEmptyStateSoftDelete } from '@/object-record/record-table/empty-state/components/RecordTableEmptyStateSoftDelete'; import { RecordTableEmptyStateSoftDelete } from '@/object-record/record-table/empty-state/components/RecordTableEmptyStateSoftDelete';
import { isSoftDeleteFilterActiveComponentState } from '@/object-record/record-table/states/isSoftDeleteFilterActiveComponentState'; import { isSoftDeleteFilterActiveComponentState } from '@/object-record/record-table/states/isSoftDeleteFilterActiveComponentState';
import { useHasObjectReadOnlyPermission } from '@/settings/roles/hooks/useHasObjectReadOnlyPermission';
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2'; import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
export const RecordTableEmptyState = () => { export const RecordTableEmptyState = () => {
@ -17,6 +19,8 @@ export const RecordTableEmptyState = () => {
hasRecordGroupsComponentSelector, hasRecordGroupsComponentSelector,
); );
const hasObjectReadOnlyPermission = useHasObjectReadOnlyPermission();
const { totalCount } = useFindManyRecords({ objectNameSingular, limit: 1 }); const { totalCount } = useFindManyRecords({ objectNameSingular, limit: 1 });
const noRecordAtAll = totalCount === 0; const noRecordAtAll = totalCount === 0;
@ -27,6 +31,10 @@ export const RecordTableEmptyState = () => {
recordTableId, recordTableId,
); );
if (hasObjectReadOnlyPermission) {
return <RecordTableEmptyStateReadOnly />;
}
if (isRemote) { if (isRemote) {
return <RecordTableEmptyStateRemote />; return <RecordTableEmptyStateRemote />;
} else if (isSoftDeleteActive === true) { } else if (isSoftDeleteActive === true) {

View File

@ -19,6 +19,7 @@ type RecordTableEmptyStateDisplayButtonProps = {
ButtonIcon: IconComponent; ButtonIcon: IconComponent;
buttonTitle: string; buttonTitle: string;
onClick: () => void; onClick: () => void;
buttonIsDisabled?: boolean;
}; };
type RecordTableEmptyStateDisplayProps = { type RecordTableEmptyStateDisplayProps = {
@ -54,6 +55,7 @@ export const RecordTableEmptyStateDisplay = (
title={props.buttonTitle} title={props.buttonTitle}
variant={'secondary'} variant={'secondary'}
onClick={props.onClick} onClick={props.onClick}
disabled={props.buttonIsDisabled}
/> />
)} )}
</AnimatedPlaceholderEmptyContainer> </AnimatedPlaceholderEmptyContainer>

View File

@ -0,0 +1,24 @@
import { useObjectLabel } from '@/object-metadata/hooks/useObjectLabel';
import { useRecordTableContextOrThrow } from '@/object-record/record-table/contexts/RecordTableContext';
import { RecordTableEmptyStateDisplay } from '@/object-record/record-table/empty-state/components/RecordTableEmptyStateDisplay';
import { t } from '@lingui/core/macro';
import { IconPlus } from 'twenty-ui';
export const RecordTableEmptyStateReadOnly = () => {
const { objectMetadataItem } = useRecordTableContextOrThrow();
const objectLabel = useObjectLabel(objectMetadataItem);
const buttonTitle = `Add a ${objectLabel}`;
return (
<RecordTableEmptyStateDisplay
title={t`No records found`}
subTitle={t`You are not allowed to create records in this object`}
animatedPlaceholderType="noRecord"
buttonTitle={buttonTitle}
ButtonIcon={IconPlus}
buttonIsDisabled={true}
/>
);
};

View File

@ -13,6 +13,7 @@ import { isRecordTableScrolledLeftComponentState } from '@/object-record/record-
import { resizeFieldOffsetComponentState } from '@/object-record/record-table/states/resizeFieldOffsetComponentState'; import { resizeFieldOffsetComponentState } from '@/object-record/record-table/states/resizeFieldOffsetComponentState';
import { tableColumnsComponentState } from '@/object-record/record-table/states/tableColumnsComponentState'; import { tableColumnsComponentState } from '@/object-record/record-table/states/tableColumnsComponentState';
import { ColumnDefinition } from '@/object-record/record-table/types/ColumnDefinition'; import { ColumnDefinition } from '@/object-record/record-table/types/ColumnDefinition';
import { useHasObjectReadOnlyPermission } from '@/settings/roles/hooks/useHasObjectReadOnlyPermission';
import { useTrackPointer } from '@/ui/utilities/pointer-event/hooks/useTrackPointer'; import { useTrackPointer } from '@/ui/utilities/pointer-event/hooks/useTrackPointer';
import { getSnapshotValue } from '@/ui/utilities/recoil-scope/utils/getSnapshotValue'; import { getSnapshotValue } from '@/ui/utilities/recoil-scope/utils/getSnapshotValue';
import { useIsMobile } from '@/ui/utilities/responsive/hooks/useIsMobile'; import { useIsMobile } from '@/ui/utilities/responsive/hooks/useIsMobile';
@ -212,6 +213,8 @@ export const RecordTableHeaderCell = ({
const isReadOnly = isObjectMetadataReadOnly(objectMetadataItem); const isReadOnly = isObjectMetadataReadOnly(objectMetadataItem);
const hasObjectReadOnlyPermission = useHasObjectReadOnlyPermission();
return ( return (
<StyledColumnHeaderCell <StyledColumnHeaderCell
key={column.fieldMetadataId} key={column.fieldMetadataId}
@ -229,7 +232,8 @@ export const RecordTableHeaderCell = ({
<RecordTableColumnHeadWithDropdown column={column} /> <RecordTableColumnHeadWithDropdown column={column} />
{(useIsMobile() || iconVisibility) && {(useIsMobile() || iconVisibility) &&
!!column.isLabelIdentifier && !!column.isLabelIdentifier &&
!isReadOnly && ( !isReadOnly &&
!hasObjectReadOnlyPermission && (
<StyledHeaderIcon> <StyledHeaderIcon>
<LightIconButton <LightIconButton
Icon={IconPlus} Icon={IconPlus}

View File

@ -3,6 +3,7 @@ import { recordIndexAllRecordIdsComponentSelector } from '@/object-record/record
import { useRecordTableContextOrThrow } from '@/object-record/record-table/contexts/RecordTableContext'; import { useRecordTableContextOrThrow } from '@/object-record/record-table/contexts/RecordTableContext';
import { useCreateNewTableRecord } from '@/object-record/record-table/hooks/useCreateNewTableRecords'; import { useCreateNewTableRecord } from '@/object-record/record-table/hooks/useCreateNewTableRecords';
import { RecordTableActionRow } from '@/object-record/record-table/record-table-row/components/RecordTableActionRow'; import { RecordTableActionRow } from '@/object-record/record-table/record-table-row/components/RecordTableActionRow';
import { useHasObjectReadOnlyPermission } from '@/settings/roles/hooks/useHasObjectReadOnlyPermission';
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2'; import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
import { IconPlus } from 'twenty-ui'; import { IconPlus } from 'twenty-ui';
@ -15,6 +16,8 @@ export const RecordTableRecordGroupSectionAddNew = () => {
recordIndexAllRecordIdsComponentSelector, recordIndexAllRecordIdsComponentSelector,
); );
const hasObjectReadOnlyPermission = useHasObjectReadOnlyPermission();
const { createNewTableRecordInGroup } = useCreateNewTableRecord({ const { createNewTableRecordInGroup } = useCreateNewTableRecord({
objectMetadataItem, objectMetadataItem,
recordTableId, recordTableId,
@ -24,6 +27,10 @@ export const RecordTableRecordGroupSectionAddNew = () => {
createNewTableRecordInGroup(currentRecordGroupId); createNewTableRecordInGroup(currentRecordGroupId);
}; };
if (hasObjectReadOnlyPermission) {
return null;
}
return ( return (
<RecordTableActionRow <RecordTableActionRow
draggableId={`add-new-record-${currentRecordGroupId}`} draggableId={`add-new-record-${currentRecordGroupId}`}

View File

@ -4,6 +4,7 @@ import { MultipleObjectRecordSelectItem } from '@/object-record/relation-picker/
import { MULTI_OBJECT_RECORD_SELECT_SELECTABLE_LIST_ID } from '@/object-record/relation-picker/constants/MultiObjectRecordSelectSelectableListId'; import { MULTI_OBJECT_RECORD_SELECT_SELECTABLE_LIST_ID } from '@/object-record/relation-picker/constants/MultiObjectRecordSelectSelectableListId';
import { RecordPickerComponentInstanceContext } from '@/object-record/relation-picker/states/contexts/RecordPickerComponentInstanceContext'; import { RecordPickerComponentInstanceContext } from '@/object-record/relation-picker/states/contexts/RecordPickerComponentInstanceContext';
import { recordPickerSearchFilterComponentState } from '@/object-record/relation-picker/states/recordPickerSearchFilterComponentState'; import { recordPickerSearchFilterComponentState } from '@/object-record/relation-picker/states/recordPickerSearchFilterComponentState';
import { useHasObjectReadOnlyPermission } from '@/settings/roles/hooks/useHasObjectReadOnlyPermission';
import { CreateNewButton } from '@/ui/input/relation-picker/components/CreateNewButton'; import { CreateNewButton } from '@/ui/input/relation-picker/components/CreateNewButton';
import { DropdownMenuSkeletonItem } from '@/ui/input/relation-picker/components/skeletons/DropdownMenuSkeletonItem'; import { DropdownMenuSkeletonItem } from '@/ui/input/relation-picker/components/skeletons/DropdownMenuSkeletonItem';
import { DropdownMenu } from '@/ui/layout/dropdown/components/DropdownMenu'; import { DropdownMenu } from '@/ui/layout/dropdown/components/DropdownMenu';
@ -75,6 +76,8 @@ export const MultiRecordSelect = ({
instanceId, instanceId,
); );
const hasObjectReadOnlyPermission = useHasObjectReadOnlyPermission();
useEffect(() => { useEffect(() => {
setHotkeyScope(instanceId); setHotkeyScope(instanceId);
}, [setHotkeyScope, instanceId]); }, [setHotkeyScope, instanceId]);
@ -144,7 +147,7 @@ export const MultiRecordSelect = ({
<DropdownMenu ref={containerRef} data-select-disable width={200}> <DropdownMenu ref={containerRef} data-select-disable width={200}>
{dropdownPlacement?.includes('end') && ( {dropdownPlacement?.includes('end') && (
<> <>
{isDefined(onCreate) && ( {isDefined(onCreate) && !hasObjectReadOnlyPermission && (
<DropdownMenuItemsContainer scrollable={false}> <DropdownMenuItemsContainer scrollable={false}>
{createNewButton} {createNewButton}
</DropdownMenuItemsContainer> </DropdownMenuItemsContainer>

View File

@ -5,6 +5,7 @@ import {
import { useRecordPickerRecordsOptions } from '@/object-record/relation-picker/hooks/useRecordPickerRecordsOptions'; import { useRecordPickerRecordsOptions } from '@/object-record/relation-picker/hooks/useRecordPickerRecordsOptions';
import { useRecordSelectSearch } from '@/object-record/relation-picker/hooks/useRecordSelectSearch'; import { useRecordSelectSearch } from '@/object-record/relation-picker/hooks/useRecordSelectSearch';
import { RecordPickerComponentInstanceContext } from '@/object-record/relation-picker/states/contexts/RecordPickerComponentInstanceContext'; import { RecordPickerComponentInstanceContext } from '@/object-record/relation-picker/states/contexts/RecordPickerComponentInstanceContext';
import { useHasObjectReadOnlyPermission } from '@/settings/roles/hooks/useHasObjectReadOnlyPermission';
import { CreateNewButton } from '@/ui/input/relation-picker/components/CreateNewButton'; import { CreateNewButton } from '@/ui/input/relation-picker/components/CreateNewButton';
import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer'; import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer';
import { DropdownMenuSearchInput } from '@/ui/layout/dropdown/components/DropdownMenuSearchInput'; import { DropdownMenuSearchInput } from '@/ui/layout/dropdown/components/DropdownMenuSearchInput';
@ -48,6 +49,8 @@ export const SingleRecordSelectMenuItemsWithSearch = ({
RecordPickerComponentInstanceContext, RecordPickerComponentInstanceContext,
); );
const hasObjectReadOnlyPermission = useHasObjectReadOnlyPermission();
const { records, recordPickerSearchFilter } = useRecordPickerRecordsOptions({ const { records, recordPickerSearchFilter } = useRecordPickerRecordsOptions({
objectNameSingular, objectNameSingular,
selectedRecordIds, selectedRecordIds,
@ -69,9 +72,11 @@ export const SingleRecordSelectMenuItemsWithSearch = ({
<> <>
{dropdownPlacement?.includes('end') && ( {dropdownPlacement?.includes('end') && (
<> <>
<DropdownMenuItemsContainer scrollable={false}> {isDefined(onCreate) && !hasObjectReadOnlyPermission && (
{createNewButton} <DropdownMenuItemsContainer scrollable={false}>
</DropdownMenuItemsContainer> {createNewButton}
</DropdownMenuItemsContainer>
)}
{records.recordsToSelect.length > 0 && <DropdownMenuSeparator />} {records.recordsToSelect.length > 0 && <DropdownMenuSeparator />}
{shouldDisplayDropdownMenuItems && ( {shouldDisplayDropdownMenuItems && (
<SingleRecordSelectMenuItems <SingleRecordSelectMenuItems
@ -120,7 +125,7 @@ export const SingleRecordSelectMenuItemsWithSearch = ({
{records.recordsToSelect.length > 0 && isDefined(onCreate) && ( {records.recordsToSelect.length > 0 && isDefined(onCreate) && (
<DropdownMenuSeparator /> <DropdownMenuSeparator />
)} )}
{isDefined(onCreate) && ( {isDefined(onCreate) && !hasObjectReadOnlyPermission && (
<DropdownMenuItemsContainer scrollable={false}> <DropdownMenuItemsContainer scrollable={false}>
{createNewButton} {createNewButton}
</DropdownMenuItemsContainer> </DropdownMenuItemsContainer>

View File

@ -50,7 +50,7 @@ export type SettingsNavigationItem = {
soon?: boolean; soon?: boolean;
}; };
export const useSettingsNavigationItems = (): SettingsNavigationSection[] => { const useSettingsNavigationItems = (): SettingsNavigationSection[] => {
const billing = useRecoilValue(billingState); const billing = useRecoilValue(billingState);
const isFunctionSettingsEnabled = false; const isFunctionSettingsEnabled = false;
@ -195,3 +195,5 @@ export const useSettingsNavigationItems = (): SettingsNavigationSection[] => {
}, },
]; ];
}; };
export { useSettingsNavigationItems };

View File

@ -0,0 +1,27 @@
import { currentUserWorkspaceState } from '@/auth/states/currentUserWorkspaceState';
import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled';
import { useRecoilValue } from 'recoil';
import { isDefined, PermissionsOnAllObjectRecords } from 'twenty-shared';
import { FeatureFlagKey } from '~/generated-metadata/graphql';
export const useHasObjectReadOnlyPermission = () => {
const currentUserWorkspace = useRecoilValue(currentUserWorkspaceState);
const isPermissionEnabled = useIsFeatureEnabled(
FeatureFlagKey.IsPermissionsEnabled,
);
if (!isPermissionEnabled) {
return false;
}
if (!isDefined(currentUserWorkspace?.objectRecordsPermissions)) {
return true;
}
return (
currentUserWorkspace?.objectRecordsPermissions.length === 1 &&
currentUserWorkspace?.objectRecordsPermissions.includes(
PermissionsOnAllObjectRecords.READ_ALL_OBJECT_RECORDS,
)
);
};

View File

@ -1,3 +1,4 @@
import { useHasObjectReadOnlyPermission } from '@/settings/roles/hooks/useHasObjectReadOnlyPermission';
import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled'; import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled';
import { useLingui } from '@lingui/react/macro'; import { useLingui } from '@lingui/react/macro';
import { Button, IconButton, IconPlus, useIsMobile } from 'twenty-ui'; import { Button, IconButton, IconPlus, useIsMobile } from 'twenty-ui';
@ -11,10 +12,17 @@ export const PageAddButton = ({ onClick }: PageAddButtonProps) => {
const isCommandMenuV2Enabled = useIsFeatureEnabled( const isCommandMenuV2Enabled = useIsFeatureEnabled(
FeatureFlagKey.IsCommandMenuV2Enabled, FeatureFlagKey.IsCommandMenuV2Enabled,
); );
const hasObjectReadOnlyPermission = useHasObjectReadOnlyPermission();
const isMobile = useIsMobile(); const isMobile = useIsMobile();
const { t } = useLingui(); const { t } = useLingui();
if (hasObjectReadOnlyPermission) {
return null;
}
return ( return (
<> <>
{isCommandMenuV2Enabled ? ( {isCommandMenuV2Enabled ? (

View File

@ -17,6 +17,7 @@ import { SHOW_PAGE_ADD_BUTTON_DROPDOWN_ID } from '@/ui/layout/show-page/constant
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular'; import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
import { isWorkflowSubObjectMetadata } from '@/object-metadata/utils/isWorkflowSubObjectMetadata'; import { isWorkflowSubObjectMetadata } from '@/object-metadata/utils/isWorkflowSubObjectMetadata';
import { useHasObjectReadOnlyPermission } from '@/settings/roles/hooks/useHasObjectReadOnlyPermission';
import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled'; import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled';
import { FeatureFlagKey } from '~/generated/graphql'; import { FeatureFlagKey } from '~/generated/graphql';
import { Dropdown } from '../../dropdown/components/Dropdown'; import { Dropdown } from '../../dropdown/components/Dropdown';
@ -39,6 +40,8 @@ export const ShowPageAddButton = ({
activityObjectNameSingular: CoreObjectNameSingular.Task, activityObjectNameSingular: CoreObjectNameSingular.Task,
}); });
const hasObjectReadOnlyPermission = useHasObjectReadOnlyPermission();
const handleSelect = (objectNameSingular: CoreObjectNameSingular) => { const handleSelect = (objectNameSingular: CoreObjectNameSingular) => {
if (objectNameSingular === CoreObjectNameSingular.Note) { if (objectNameSingular === CoreObjectNameSingular.Note) {
openNote({ openNote({
@ -67,6 +70,10 @@ export const ShowPageAddButton = ({
return; return;
} }
if (hasObjectReadOnlyPermission) {
return null;
}
return ( return (
<StyledContainer> <StyledContainer>
<Dropdown <Dropdown

View File

@ -13,7 +13,9 @@ import tsconfigPaths from 'vite-tsconfig-paths';
type Checkers = Parameters<typeof checker>[0]; type Checkers = Parameters<typeof checker>[0];
export default defineConfig(({ command, mode }) => { export default defineConfig(({ command, mode }) => {
const env = loadEnv(mode, process.cwd(), ''); const env = loadEnv(mode, __dirname, '');
console.log(__dirname);
const { const {
REACT_APP_SERVER_BASE_URL, REACT_APP_SERVER_BASE_URL,