diff --git a/packages/twenty-front/src/modules/page-layout/hooks/usePageLayoutShouldUseWhiteBackground.ts b/packages/twenty-front/src/modules/page-layout/hooks/usePageLayoutShouldUseWhiteBackground.ts
deleted file mode 100644
index 968a596ef6..0000000000
--- a/packages/twenty-front/src/modules/page-layout/hooks/usePageLayoutShouldUseWhiteBackground.ts
+++ /dev/null
@@ -1,13 +0,0 @@
-import { useLayoutRenderingContext } from '@/ui/layout/contexts/LayoutRenderingContext';
-import { useIsMobile } from '@/ui/utilities/responsive/hooks/useIsMobile';
-import { PageLayoutType } from '~/generated/graphql';
-
-export const usePageLayoutShouldUseWhiteBackground = () => {
- const isMobile = useIsMobile();
- const { isInRightDrawer, layoutType } = useLayoutRenderingContext();
-
- const shouldUseWhiteBackground =
- layoutType === PageLayoutType.RECORD_PAGE && (isMobile || isInRightDrawer);
-
- return { shouldUseWhiteBackground };
-};
diff --git a/packages/twenty-front/src/modules/page-layout/hooks/useRecordPageLayoutId.ts b/packages/twenty-front/src/modules/page-layout/hooks/useRecordPageLayoutId.ts
index 3f4891affa..6c3f41faf4 100644
--- a/packages/twenty-front/src/modules/page-layout/hooks/useRecordPageLayoutId.ts
+++ b/packages/twenty-front/src/modules/page-layout/hooks/useRecordPageLayoutId.ts
@@ -1,30 +1,7 @@
-import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
import { useFindOneRecord } from '@/object-record/hooks/useFindOneRecord';
import { type ObjectRecord } from '@/object-record/types/ObjectRecord';
-import { DEFAULT_COMPANY_RECORD_PAGE_LAYOUT_ID } from '@/page-layout/constants/DefaultCompanyRecordPageLayoutId';
-import { DEFAULT_NOTE_RECORD_PAGE_LAYOUT_ID } from '@/page-layout/constants/DefaultNoteRecordPageLayoutId';
-import { DEFAULT_OPPORTUNITY_RECORD_PAGE_LAYOUT_ID } from '@/page-layout/constants/DefaultOpportunityRecordPageLayoutId';
-import { DEFAULT_PERSON_RECORD_PAGE_LAYOUT_ID } from '@/page-layout/constants/DefaultPersonRecordPageLayoutId';
-import { DEFAULT_RECORD_PAGE_LAYOUT_ID } from '@/page-layout/constants/DefaultRecordPageLayoutId';
-import { DEFAULT_TASK_RECORD_PAGE_LAYOUT_ID } from '@/page-layout/constants/DefaultTaskRecordPageLayoutId';
-import { DEFAULT_WORKFLOW_PAGE_LAYOUT_ID } from '@/page-layout/constants/DefaultWorkflowPageLayoutId';
-import { DEFAULT_WORKFLOW_RUN_PAGE_LAYOUT_ID } from '@/page-layout/constants/DefaultWorkflowRunPageLayoutId';
-import { DEFAULT_WORKFLOW_VERSION_PAGE_LAYOUT_ID } from '@/page-layout/constants/DefaultWorkflowVersionPageLayoutId';
+import { getRecordPageLayoutId } from '@/page-layout/utils/getRecordPageLayoutId';
import { type TargetRecordIdentifier } from '@/ui/layout/contexts/TargetRecordIdentifier';
-import { isDefined } from 'twenty-shared/utils';
-
-const OBJECT_NAME_TO_DEFAULT_LAYOUT_ID: Record
= {
- [CoreObjectNameSingular.Company]: DEFAULT_COMPANY_RECORD_PAGE_LAYOUT_ID,
- [CoreObjectNameSingular.Person]: DEFAULT_PERSON_RECORD_PAGE_LAYOUT_ID,
- [CoreObjectNameSingular.Opportunity]:
- DEFAULT_OPPORTUNITY_RECORD_PAGE_LAYOUT_ID,
- [CoreObjectNameSingular.Note]: DEFAULT_NOTE_RECORD_PAGE_LAYOUT_ID,
- [CoreObjectNameSingular.Task]: DEFAULT_TASK_RECORD_PAGE_LAYOUT_ID,
- [CoreObjectNameSingular.Workflow]: DEFAULT_WORKFLOW_PAGE_LAYOUT_ID,
- [CoreObjectNameSingular.WorkflowVersion]:
- DEFAULT_WORKFLOW_VERSION_PAGE_LAYOUT_ID,
- [CoreObjectNameSingular.WorkflowRun]: DEFAULT_WORKFLOW_RUN_PAGE_LAYOUT_ID,
-};
export const useRecordPageLayoutId = ({
id,
@@ -37,23 +14,12 @@ export const useRecordPageLayoutId = ({
},
);
- if (!isDefined(record)) {
- return {
- pageLayoutId: null,
- };
- }
-
- if (isDefined(record.pageLayoutId)) {
- return {
- pageLayoutId: record.pageLayoutId,
- };
- }
-
- const defaultLayoutId =
- OBJECT_NAME_TO_DEFAULT_LAYOUT_ID[targetObjectNameSingular] ??
- DEFAULT_RECORD_PAGE_LAYOUT_ID;
+ const pageLayoutId = getRecordPageLayoutId({
+ record,
+ targetObjectNameSingular,
+ });
return {
- pageLayoutId: defaultLayoutId,
+ pageLayoutId,
};
};
diff --git a/packages/twenty-front/src/modules/page-layout/hooks/useRecordPageLayoutIdFromRecordStoreOrThrow.ts b/packages/twenty-front/src/modules/page-layout/hooks/useRecordPageLayoutIdFromRecordStoreOrThrow.ts
new file mode 100644
index 0000000000..373a6f3e33
--- /dev/null
+++ b/packages/twenty-front/src/modules/page-layout/hooks/useRecordPageLayoutIdFromRecordStoreOrThrow.ts
@@ -0,0 +1,29 @@
+import { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState';
+import { getRecordPageLayoutId } from '@/page-layout/utils/getRecordPageLayoutId';
+import { type TargetRecordIdentifier } from '@/ui/layout/contexts/TargetRecordIdentifier';
+import { useRecoilValue } from 'recoil';
+import { isDefined } from 'twenty-shared/utils';
+
+export const useRecordPageLayoutIdFromRecordStoreOrThrow = ({
+ id,
+ targetObjectNameSingular,
+}: TargetRecordIdentifier) => {
+ const record = useRecoilValue(recordStoreFamilyState(id));
+
+ if (!isDefined(record)) {
+ throw new Error(`Record with id ${id} not found in record store`);
+ }
+
+ const pageLayoutId = getRecordPageLayoutId({
+ record,
+ targetObjectNameSingular,
+ });
+
+ if (!isDefined(pageLayoutId)) {
+ throw new Error(`Page layout id not found for record with id ${id}`);
+ }
+
+ return {
+ pageLayoutId,
+ };
+};
diff --git a/packages/twenty-front/src/modules/page-layout/utils/getRecordPageLayoutId.ts b/packages/twenty-front/src/modules/page-layout/utils/getRecordPageLayoutId.ts
new file mode 100644
index 0000000000..3305bd32d6
--- /dev/null
+++ b/packages/twenty-front/src/modules/page-layout/utils/getRecordPageLayoutId.ts
@@ -0,0 +1,47 @@
+import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
+import { type ObjectRecord } from '@/object-record/types/ObjectRecord';
+import { DEFAULT_COMPANY_RECORD_PAGE_LAYOUT_ID } from '@/page-layout/constants/DefaultCompanyRecordPageLayoutId';
+import { DEFAULT_NOTE_RECORD_PAGE_LAYOUT_ID } from '@/page-layout/constants/DefaultNoteRecordPageLayoutId';
+import { DEFAULT_OPPORTUNITY_RECORD_PAGE_LAYOUT_ID } from '@/page-layout/constants/DefaultOpportunityRecordPageLayoutId';
+import { DEFAULT_PERSON_RECORD_PAGE_LAYOUT_ID } from '@/page-layout/constants/DefaultPersonRecordPageLayoutId';
+import { DEFAULT_RECORD_PAGE_LAYOUT_ID } from '@/page-layout/constants/DefaultRecordPageLayoutId';
+import { DEFAULT_TASK_RECORD_PAGE_LAYOUT_ID } from '@/page-layout/constants/DefaultTaskRecordPageLayoutId';
+import { DEFAULT_WORKFLOW_PAGE_LAYOUT_ID } from '@/page-layout/constants/DefaultWorkflowPageLayoutId';
+import { DEFAULT_WORKFLOW_RUN_PAGE_LAYOUT_ID } from '@/page-layout/constants/DefaultWorkflowRunPageLayoutId';
+import { DEFAULT_WORKFLOW_VERSION_PAGE_LAYOUT_ID } from '@/page-layout/constants/DefaultWorkflowVersionPageLayoutId';
+import { isDefined } from 'twenty-shared/utils';
+
+const OBJECT_NAME_TO_DEFAULT_LAYOUT_ID: Record = {
+ [CoreObjectNameSingular.Company]: DEFAULT_COMPANY_RECORD_PAGE_LAYOUT_ID,
+ [CoreObjectNameSingular.Person]: DEFAULT_PERSON_RECORD_PAGE_LAYOUT_ID,
+ [CoreObjectNameSingular.Opportunity]:
+ DEFAULT_OPPORTUNITY_RECORD_PAGE_LAYOUT_ID,
+ [CoreObjectNameSingular.Note]: DEFAULT_NOTE_RECORD_PAGE_LAYOUT_ID,
+ [CoreObjectNameSingular.Task]: DEFAULT_TASK_RECORD_PAGE_LAYOUT_ID,
+ [CoreObjectNameSingular.Workflow]: DEFAULT_WORKFLOW_PAGE_LAYOUT_ID,
+ [CoreObjectNameSingular.WorkflowVersion]:
+ DEFAULT_WORKFLOW_VERSION_PAGE_LAYOUT_ID,
+ [CoreObjectNameSingular.WorkflowRun]: DEFAULT_WORKFLOW_RUN_PAGE_LAYOUT_ID,
+};
+
+export const getRecordPageLayoutId = ({
+ record,
+ targetObjectNameSingular,
+}: {
+ record: ObjectRecord | null | undefined;
+ targetObjectNameSingular: string;
+}): string | null => {
+ if (!isDefined(record)) {
+ return null;
+ }
+
+ if (isDefined(record.pageLayoutId)) {
+ return record.pageLayoutId;
+ }
+
+ const defaultLayoutId =
+ OBJECT_NAME_TO_DEFAULT_LAYOUT_ID[targetObjectNameSingular] ??
+ DEFAULT_RECORD_PAGE_LAYOUT_ID;
+
+ return defaultLayoutId;
+};
diff --git a/packages/twenty-front/src/modules/page-layout/widgets/components/WidgetRenderer.tsx b/packages/twenty-front/src/modules/page-layout/widgets/components/WidgetRenderer.tsx
index 394933f9c1..d1dc206ce3 100644
--- a/packages/twenty-front/src/modules/page-layout/widgets/components/WidgetRenderer.tsx
+++ b/packages/twenty-front/src/modules/page-layout/widgets/components/WidgetRenderer.tsx
@@ -29,7 +29,7 @@ import styled from '@emotion/styled';
import { type MouseEvent } from 'react';
import { ErrorBoundary } from 'react-error-boundary';
import { IconLock } from 'twenty-ui/display';
-import { WidgetType } from '~/generated/graphql';
+import { PageLayoutType, WidgetType } from '~/generated/graphql';
const StyledNoAccessContainer = styled.div`
align-items: center;
@@ -79,6 +79,9 @@ export const WidgetRenderer = ({ widget }: WidgetRendererProps) => {
const isLastWidget = useIsCurrentWidgetLastOfTab(widget.id);
+ const isReorderEnabled =
+ currentPageLayout.type !== PageLayoutType.RECORD_PAGE;
+
// TODO: when we have more widgets without headers, we should use a more generic approach to hide the header
// each widget type could have metadata (e.g., hasHeader: boolean or headerMode: 'always' | 'editOnly' | 'never')
const isRichTextWidget = widget.type === WidgetType.STANDALONE_RICH_TEXT;
@@ -144,6 +147,7 @@ export const WidgetRenderer = ({ widget }: WidgetRendererProps) => {
variant={variant}
isInEditMode={isPageLayoutInEditMode}
isResizing={isResizing}
+ isReorderEnabled={isReorderEnabled}
title={widget.title}
onRemove={handleRemove}
actions={actions}
@@ -158,7 +162,11 @@ export const WidgetRenderer = ({ widget }: WidgetRendererProps) => {
/>
)}
-
+
{hasAccess ? (
`
box-sizing: border-box;
display: grid;
@@ -20,14 +21,17 @@ const StyledWidgetCardContent = styled.div<{
}
`}
- ${({ theme, variant }) => {
+ ${({ theme, variant, isEditable }) => {
if (variant === 'dashboard') {
return css`
padding: ${theme.spacing(2)};
`;
}
- if (variant === 'record-page') {
+ if (
+ variant === 'record-page' ||
+ (variant === 'side-column' && isEditable)
+ ) {
return css`
border: 1px solid ${theme.border.color.medium};
border-radius: ${theme.border.radius.md};
diff --git a/packages/twenty-front/src/modules/page-layout/widgets/widget-card/components/WidgetCardHeader.tsx b/packages/twenty-front/src/modules/page-layout/widgets/widget-card/components/WidgetCardHeader.tsx
index 19f8059ab7..8310a9df88 100644
--- a/packages/twenty-front/src/modules/page-layout/widgets/widget-card/components/WidgetCardHeader.tsx
+++ b/packages/twenty-front/src/modules/page-layout/widgets/widget-card/components/WidgetCardHeader.tsx
@@ -25,6 +25,7 @@ export type WidgetCardHeaderProps = {
actions?: WidgetAction[];
className?: string;
isResizing?: boolean;
+ isReorderEnabled?: boolean;
};
const StyledWidgetCardHeader = styled.div`
@@ -78,6 +79,7 @@ export const WidgetCardHeader = ({
isEmpty = false,
isInEditMode = false,
isResizing = false,
+ isReorderEnabled = true,
title,
onRemove,
forbiddenDisplay,
@@ -94,7 +96,7 @@ export const WidgetCardHeader = ({
return (
- {!isEmpty && isInEditMode && (
+ {!isEmpty && isInEditMode && isReorderEnabled && (
e.stopPropagation()}
diff --git a/packages/twenty-server/src/engine/core-modules/feature-flag/enums/feature-flag-key.enum.ts b/packages/twenty-server/src/engine/core-modules/feature-flag/enums/feature-flag-key.enum.ts
index 6f6120d24d..823687ddbe 100644
--- a/packages/twenty-server/src/engine/core-modules/feature-flag/enums/feature-flag-key.enum.ts
+++ b/packages/twenty-server/src/engine/core-modules/feature-flag/enums/feature-flag-key.enum.ts
@@ -5,6 +5,7 @@ export enum FeatureFlagKey {
IS_APPLICATION_ENABLED = 'IS_APPLICATION_ENABLED',
IS_APPLICATION_INSTALLATION_FROM_TARBALL_ENABLED = 'IS_APPLICATION_INSTALLATION_FROM_TARBALL_ENABLED',
IS_RECORD_PAGE_LAYOUT_ENABLED = 'IS_RECORD_PAGE_LAYOUT_ENABLED',
+ IS_RECORD_PAGE_LAYOUT_EDITING_ENABLED = 'IS_RECORD_PAGE_LAYOUT_EDITING_ENABLED',
IS_PUBLIC_DOMAIN_ENABLED = 'IS_PUBLIC_DOMAIN_ENABLED',
IS_EMAILING_DOMAIN_ENABLED = 'IS_EMAILING_DOMAIN_ENABLED',
IS_DASHBOARD_V2_ENABLED = 'IS_DASHBOARD_V2_ENABLED',
diff --git a/packages/twenty-server/src/engine/twenty-orm/entity-manager/workspace-entity-manager.spec.ts b/packages/twenty-server/src/engine/twenty-orm/entity-manager/workspace-entity-manager.spec.ts
index 1ce5d393ab..743d3b383a 100644
--- a/packages/twenty-server/src/engine/twenty-orm/entity-manager/workspace-entity-manager.spec.ts
+++ b/packages/twenty-server/src/engine/twenty-orm/entity-manager/workspace-entity-manager.spec.ts
@@ -226,6 +226,7 @@ describe('WorkspaceEntityManager', () => {
IS_NAVIGATION_MENU_ITEM_ENABLED: false,
IS_FILES_FIELD_ENABLED: false,
IS_APPLICATION_INSTALLATION_FROM_TARBALL_ENABLED: false,
+ IS_RECORD_PAGE_LAYOUT_EDITING_ENABLED: false,
},
userWorkspaceRoleMap: {},
eventEmitterService: {