diff --git a/packages/twenty-front/src/modules/object-record/record-merge/components/MergePreviewTab.tsx b/packages/twenty-front/src/modules/object-record/record-merge/components/MergePreviewTab.tsx
index 46dab38975..33ada923ac 100644
--- a/packages/twenty-front/src/modules/object-record/record-merge/components/MergePreviewTab.tsx
+++ b/packages/twenty-front/src/modules/object-record/record-merge/components/MergePreviewTab.tsx
@@ -1,10 +1,8 @@
import { usePerformMergePreview } from '@/object-record/record-merge/hooks/usePerformMergePreview';
-import { SummaryCard } from '@/object-record/record-show/components/SummaryCard';
-import { CardType } from '@/object-record/record-show/types/CardType';
-import { getCardComponent } from '@/object-record/record-show/utils/getCardComponent';
+import { PageLayoutSingleTabRenderer } from '@/page-layout/components/PageLayoutSingleTabRenderer';
+import { usePageLayoutIdForRecord } from '@/page-layout/hooks/usePageLayoutIdForRecord';
import { LayoutRenderingProvider } from '@/ui/layout/contexts/LayoutRenderingContext';
import { isDefined } from 'twenty-shared/utils';
-import { Section } from 'twenty-ui/layout';
import { PageLayoutType } from '~/generated-metadata/graphql';
type MergePreviewTabProps = {
@@ -18,7 +16,16 @@ export const MergePreviewTab = ({
objectNameSingular,
});
- if (!isDefined(mergePreviewRecord) || isGeneratingPreview) {
+ const { pageLayoutId } = usePageLayoutIdForRecord({
+ id: mergePreviewRecord?.id ?? '',
+ targetObjectNameSingular: objectNameSingular,
+ });
+
+ if (
+ !isDefined(mergePreviewRecord) ||
+ isGeneratingPreview ||
+ !isDefined(pageLayoutId)
+ ) {
return null;
}
@@ -35,17 +42,7 @@ export const MergePreviewTab = ({
isInSidePanel: true,
}}
>
-
-
-
- {getCardComponent(CardType.FieldCard, {
- showDuplicatesSection: false,
- })}
-
+
);
};
diff --git a/packages/twenty-front/src/modules/object-record/record-merge/components/MergeRecordTab.tsx b/packages/twenty-front/src/modules/object-record/record-merge/components/MergeRecordTab.tsx
index 8ba8ce3d82..0bcea251a9 100644
--- a/packages/twenty-front/src/modules/object-record/record-merge/components/MergeRecordTab.tsx
+++ b/packages/twenty-front/src/modules/object-record/record-merge/components/MergeRecordTab.tsx
@@ -1,12 +1,11 @@
-import { SummaryCard } from '@/object-record/record-show/components/SummaryCard';
-import { CardType } from '@/object-record/record-show/types/CardType';
-import { getCardComponent } from '@/object-record/record-show/utils/getCardComponent';
+import { RecordShowEffect } from '@/object-record/record-show/components/RecordShowEffect';
+import { PageLayoutSingleTabRenderer } from '@/page-layout/components/PageLayoutSingleTabRenderer';
+import { usePageLayoutIdForRecord } from '@/page-layout/hooks/usePageLayoutIdForRecord';
import { LayoutRenderingProvider } from '@/ui/layout/contexts/LayoutRenderingContext';
-import { Section } from 'twenty-ui/layout';
+import { isDefined } from 'twenty-shared/utils';
import { PageLayoutType } from '~/generated-metadata/graphql';
type MergeRecordTabProps = {
- isInSidePanel?: boolean;
objectNameSingular: string;
recordId: string;
};
@@ -15,6 +14,11 @@ export const MergeRecordTab = ({
objectNameSingular,
recordId,
}: MergeRecordTabProps) => {
+ const { pageLayoutId } = usePageLayoutIdForRecord({
+ id: recordId,
+ targetObjectNameSingular: objectNameSingular,
+ });
+
return (
-
-
-
- {getCardComponent(CardType.FieldCard, {
- showDuplicatesSection: false,
- })}
-
+
+ {isDefined(pageLayoutId) && (
+
+ )}
);
};
diff --git a/packages/twenty-front/src/modules/object-record/record-merge/components/MergeRecordsContainer.tsx b/packages/twenty-front/src/modules/object-record/record-merge/components/MergeRecordsContainer.tsx
index b5c0a8a6c8..748214db2b 100644
--- a/packages/twenty-front/src/modules/object-record/record-merge/components/MergeRecordsContainer.tsx
+++ b/packages/twenty-front/src/modules/object-record/record-merge/components/MergeRecordsContainer.tsx
@@ -8,11 +8,11 @@ import { activeTabIdComponentState } from '@/ui/layout/tab-list/states/activeTab
import { TabListComponentInstanceContext } from '@/ui/layout/tab-list/states/contexts/TabListComponentInstanceContext';
import { useAtomComponentStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomComponentStateValue';
-import { SidePanelPageComponentInstanceContext } from '@/side-panel/states/contexts/SidePanelPageComponentInstanceContext';
+import { useMergeRecordsContainerTabs } from '@/object-record/record-merge/hooks/useMergeRecordsContainerTabs';
import { useMergeRecordsSelectedRecords } from '@/object-record/record-merge/hooks/useMergeRecordsSelectedRecords';
import { MergeRecordsTabId } from '@/object-record/record-merge/types/MergeRecordsTabId';
+import { SidePanelPageComponentInstanceContext } from '@/side-panel/states/contexts/SidePanelPageComponentInstanceContext';
import { useAvailableComponentInstanceIdOrThrow } from '@/ui/utilities/state/component-state/hooks/useAvailableComponentInstanceIdOrThrow';
-import { useMergeRecordsContainerTabs } from '@/object-record/record-merge/hooks/useMergeRecordsContainerTabs';
import { MergePreviewTab } from './MergePreviewTab';
import { MergeRecordTab } from './MergeRecordTab';
import { MergeRecordsFooter } from './MergeRecordsFooter';
@@ -36,7 +36,6 @@ const StyledContentContainer = styled.div`
background: ${themeCssVariables.background.primary};
flex: 1;
overflow-y: auto;
- padding-bottom: ${themeCssVariables.spacing[16]};
`;
type MergeRecordsContainerProps = {
diff --git a/packages/twenty-front/src/modules/object-record/record-merge/components/MergeSettingsTab.tsx b/packages/twenty-front/src/modules/object-record/record-merge/components/MergeSettingsTab.tsx
index ac9f271296..745317ed09 100644
--- a/packages/twenty-front/src/modules/object-record/record-merge/components/MergeSettingsTab.tsx
+++ b/packages/twenty-front/src/modules/object-record/record-merge/components/MergeSettingsTab.tsx
@@ -1,15 +1,15 @@
-import { t } from '@lingui/core/macro';
import { useMergeRecordsSelectedRecords } from '@/object-record/record-merge/hooks/useMergeRecordsSelectedRecords';
import { useMergeRecordsSettings } from '@/object-record/record-merge/hooks/useMergeRecordsSettings';
-import { Select } from '@/ui/input/components/Select';
-import { styled } from '@linaria/react';
-import { Section } from 'twenty-ui/layout';
import { getPositionNumberIcon } from '@/object-record/record-merge/utils/getPositionNumberIcon';
import { getPositionWordLabel } from '@/object-record/record-merge/utils/getPositionWordLabel';
+import { Select } from '@/ui/input/components/Select';
+import { styled } from '@linaria/react';
+import { t } from '@lingui/core/macro';
+import { Section } from 'twenty-ui/layout';
import { themeCssVariables } from 'twenty-ui/theme-constants';
const StyledSectionContainer = styled.div`
- margin: ${themeCssVariables.spacing[4]};
+ padding: ${themeCssVariables.spacing[2]};
width: auto;
`;
diff --git a/packages/twenty-front/src/modules/object-record/record-show/components/FieldsCard.tsx b/packages/twenty-front/src/modules/object-record/record-show/components/FieldsCard.tsx
deleted file mode 100644
index 1257799bcc..0000000000
--- a/packages/twenty-front/src/modules/object-record/record-show/components/FieldsCard.tsx
+++ /dev/null
@@ -1,27 +0,0 @@
-import { RecordFieldList } from '@/object-record/record-field-list/components/RecordFieldList';
-import { RecordFieldsScopeContextProvider } from '@/object-record/record-field-list/contexts/RecordFieldsScopeContext';
-import { type FieldCardConfiguration } from '@/object-record/record-show/types/CardConfiguration';
-import { useLayoutRenderingContext } from '@/ui/layout/contexts/LayoutRenderingContext';
-import { useTargetRecord } from '@/ui/layout/contexts/useTargetRecord';
-
-type FieldsCardProps = {
- configuration?: FieldCardConfiguration;
-};
-
-export const FieldsCard = ({ configuration }: FieldsCardProps) => {
- const targetRecord = useTargetRecord();
- const { isInSidePanel } = useLayoutRenderingContext();
-
- const instanceId = `fields-card-${targetRecord.id}${isInSidePanel ? '-side-panel' : ''}`;
-
- return (
-
-
-
- );
-};
diff --git a/packages/twenty-front/src/modules/object-record/record-show/types/CardConfiguration.ts b/packages/twenty-front/src/modules/object-record/record-show/types/CardConfiguration.ts
deleted file mode 100644
index ad5655f789..0000000000
--- a/packages/twenty-front/src/modules/object-record/record-show/types/CardConfiguration.ts
+++ /dev/null
@@ -1,29 +0,0 @@
-import { type CardType } from '@/object-record/record-show/types/CardType';
-
-// Card configuration types - each card type can define its own configuration
-export type FieldCardConfiguration = {
- showDuplicatesSection?: boolean;
-};
-
-// For cards that don't need configuration, use undefined
-export type EmptyCardConfiguration = undefined;
-
-// Type mapping from CardType to its specific configuration type
-// This creates precise typing: each CardType is linked to exactly one configuration type
-export type CardTypeToConfiguration = {
- [CardType.FieldCard]: FieldCardConfiguration;
- [CardType.TimelineCard]: EmptyCardConfiguration;
- [CardType.TaskCard]: EmptyCardConfiguration;
- [CardType.NoteCard]: EmptyCardConfiguration;
- [CardType.FileCard]: EmptyCardConfiguration;
- [CardType.EmailCard]: EmptyCardConfiguration;
- [CardType.CalendarCard]: EmptyCardConfiguration;
- [CardType.FieldRichTextCard]: EmptyCardConfiguration;
- [CardType.WorkflowCard]: EmptyCardConfiguration;
- [CardType.WorkflowVersionCard]: EmptyCardConfiguration;
- [CardType.WorkflowRunCard]: EmptyCardConfiguration;
- [CardType.DashboardCard]: EmptyCardConfiguration;
-};
-
-// Union type for all card configurations (for general use)
-export type CardConfiguration = FieldCardConfiguration | EmptyCardConfiguration;
diff --git a/packages/twenty-front/src/modules/object-record/record-show/types/CardType.ts b/packages/twenty-front/src/modules/object-record/record-show/types/CardType.ts
deleted file mode 100644
index a80aa4497d..0000000000
--- a/packages/twenty-front/src/modules/object-record/record-show/types/CardType.ts
+++ /dev/null
@@ -1,14 +0,0 @@
-export enum CardType {
- FieldCard = 'FieldCard',
- TimelineCard = 'TimelineCard',
- TaskCard = 'TaskCard',
- NoteCard = 'NoteCard',
- FileCard = 'FileCard',
- EmailCard = 'EmailCard',
- CalendarCard = 'CalendarCard',
- WorkflowCard = 'WorkflowCard',
- WorkflowVersionCard = 'WorkflowVersionCard',
- WorkflowRunCard = 'WorkflowRunCard',
- FieldRichTextCard = 'FieldRichTextCard',
- DashboardCard = 'DashboardCard',
-}
diff --git a/packages/twenty-front/src/modules/object-record/record-show/utils/getCardComponent.tsx b/packages/twenty-front/src/modules/object-record/record-show/utils/getCardComponent.tsx
deleted file mode 100644
index f7ec0f487d..0000000000
--- a/packages/twenty-front/src/modules/object-record/record-show/utils/getCardComponent.tsx
+++ /dev/null
@@ -1,94 +0,0 @@
-import { CalendarEventsCard } from '@/activities/calendar/components/CalendarEventsCard';
-import { EmailsCard } from '@/activities/emails/components/EmailsCard';
-import { FilesCard } from '@/activities/files/components/FilesCard';
-import { NotesCard } from '@/activities/notes/components/NotesCard';
-import { TasksCard } from '@/activities/tasks/components/TasksCard';
-import { TimelineCard } from '@/activities/timeline-activities/components/TimelineCard';
-import { FieldsCard } from '@/object-record/record-show/components/FieldsCard';
-import {
- type CardConfiguration,
- type CardTypeToConfiguration,
- type FieldCardConfiguration,
-} from '@/object-record/record-show/types/CardConfiguration';
-import { CardType } from '@/object-record/record-show/types/CardType';
-import { useLayoutRenderingContext } from '@/ui/layout/contexts/LayoutRenderingContext';
-import { FieldRichTextCard } from '@/ui/layout/show-page/components/FieldRichTextCard';
-import { WorkflowCard } from '@/workflow/workflow-diagram/components/WorkflowCard';
-import { WorkflowRunCard } from '@/workflow/workflow-diagram/components/WorkflowRunCard';
-import { WorkflowVersionCard } from '@/workflow/workflow-diagram/components/WorkflowVersionCard';
-import { assertUnreachable } from 'twenty-shared/utils';
-
-const CardRenderer = ({
- Component,
- configuration,
-}: {
- Component: React.ComponentType<{ configuration?: T }> | React.ComponentType;
- configuration?: T;
-}) => {
- const { targetRecordIdentifier } = useLayoutRenderingContext();
-
- if (!targetRecordIdentifier) {
- return null;
- }
-
- // TypeScript can't infer if Component accepts configuration prop or not
- // So we cast to the more permissive type and let the component ignore unused props
- const ComponentWithConfig = Component as React.ComponentType<{
- configuration?: T;
- }>;
-
- return ;
-};
-
-// Generic function with precise type mapping from CardType to Configuration
-// TypeScript will enforce that the correct configuration type is passed for each card type
-export const getCardComponent = (
- cardType: T,
- configuration?: CardTypeToConfiguration[T],
-): JSX.Element | null => {
- switch (cardType) {
- case CardType.TimelineCard:
- return ;
-
- case CardType.FieldCard:
- return (
-
- );
-
- case CardType.FieldRichTextCard:
- return ;
-
- case CardType.TaskCard:
- return ;
-
- case CardType.NoteCard:
- return ;
-
- case CardType.FileCard:
- return ;
-
- case CardType.EmailCard:
- return ;
-
- case CardType.CalendarCard:
- return ;
-
- case CardType.WorkflowCard:
- return ;
-
- case CardType.WorkflowVersionCard:
- return ;
-
- case CardType.WorkflowRunCard:
- return ;
-
- case CardType.DashboardCard:
- throw new Error('Dashboard are handled separately currently');
-
- default:
- assertUnreachable(cardType);
- }
-};
diff --git a/packages/twenty-front/src/modules/page-layout/components/PageLayoutSingleTabRenderer.tsx b/packages/twenty-front/src/modules/page-layout/components/PageLayoutSingleTabRenderer.tsx
new file mode 100644
index 0000000000..412a94d31c
--- /dev/null
+++ b/packages/twenty-front/src/modules/page-layout/components/PageLayoutSingleTabRenderer.tsx
@@ -0,0 +1,115 @@
+import { SummaryCard } from '@/object-record/record-show/components/SummaryCard';
+import { PageLayoutContent } from '@/page-layout/components/PageLayoutContent';
+import { PageLayoutEditModeProvider } from '@/page-layout/components/PageLayoutEditModeProvider';
+import { PageLayoutInitializationQueryEffect } from '@/page-layout/components/PageLayoutInitializationQueryEffect';
+import { PageLayoutRecordPageCustomizationSessionRegistrationEffect } from '@/page-layout/components/PageLayoutRecordPageCustomizationSessionRegistrationEffect';
+import { PageLayoutRelationWidgetsSyncEffect } from '@/page-layout/components/PageLayoutRelationWidgetsSyncEffect';
+import { PageLayoutContentProvider } from '@/page-layout/contexts/PageLayoutContentContext';
+import { useCurrentPageLayoutOrThrow } from '@/page-layout/hooks/useCurrentPageLayoutOrThrow';
+import { usePageLayoutTabWithVisibleWidgetsOrThrow } from '@/page-layout/hooks/usePageLayoutTabWithVisibleWidgetsOrThrow';
+import { PageLayoutComponentInstanceContext } from '@/page-layout/states/contexts/PageLayoutComponentInstanceContext';
+import { pageLayoutIsInitializedComponentState } from '@/page-layout/states/pageLayoutIsInitializedComponentState';
+import { getTabLayoutMode } from '@/page-layout/utils/getTabLayoutMode';
+import { getTabListInstanceIdFromPageLayoutAndRecord } from '@/page-layout/utils/getTabListInstanceIdFromPageLayoutAndRecord';
+import { sortTabsByPosition } from '@/page-layout/utils/sortTabsByPosition';
+import { useLayoutRenderingContext } from '@/ui/layout/contexts/LayoutRenderingContext';
+import { useTargetRecord } from '@/ui/layout/contexts/useTargetRecord';
+import { TabListComponentInstanceContext } from '@/ui/layout/tab-list/states/contexts/TabListComponentInstanceContext';
+import { useAtomComponentStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomComponentStateValue';
+import { useFeatureFlagsMap } from '@/workspace/hooks/useFeatureFlagsMap';
+import { FeatureFlagKey } from '~/generated-metadata/graphql';
+
+type PageLayoutSingleTabRendererProps = {
+ pageLayoutId: string;
+};
+
+const PageLayoutSingleTabRendererContent = () => {
+ const pageLayoutIsInitialized = useAtomComponentStateValue(
+ pageLayoutIsInitializedComponentState,
+ );
+
+ if (!pageLayoutIsInitialized) {
+ return null;
+ }
+
+ return ;
+};
+
+const PageLayoutSingleTabRendererInner = () => {
+ const { currentPageLayout } = useCurrentPageLayoutOrThrow();
+ const targetRecordIdentifier = useTargetRecord();
+ const { isInSidePanel } = useLayoutRenderingContext();
+
+ const sortedTabs = sortTabsByPosition(currentPageLayout.tabs);
+ const firstTab = sortedTabs[0];
+
+ const firstTabWithVisibleWidgets = usePageLayoutTabWithVisibleWidgetsOrThrow(
+ firstTab.id,
+ );
+
+ const layoutMode = getTabLayoutMode({
+ tab: firstTabWithVisibleWidgets,
+ pageLayoutType: currentPageLayout.type,
+ });
+
+ return (
+ <>
+
+
+
+
+
+ >
+ );
+};
+
+export const PageLayoutSingleTabRenderer = ({
+ pageLayoutId,
+}: PageLayoutSingleTabRendererProps) => {
+ const { targetRecordIdentifier, layoutType } = useLayoutRenderingContext();
+
+ const featureFlags = useFeatureFlagsMap();
+ const isRecordPageLayoutEditingEnabled =
+ featureFlags[FeatureFlagKey.IS_RECORD_PAGE_LAYOUT_EDITING_ENABLED];
+
+ const tabListInstanceId = getTabListInstanceIdFromPageLayoutAndRecord({
+ pageLayoutId,
+ layoutType,
+ targetRecordIdentifier,
+ });
+
+ return (
+
+
+
+
+
+ {!isRecordPageLayoutEditingEnabled && (
+
+ )}
+
+
+
+
+ );
+};
diff --git a/packages/twenty-front/src/modules/ui/layout/tab-list/types/LayoutCard.ts b/packages/twenty-front/src/modules/ui/layout/tab-list/types/LayoutCard.ts
deleted file mode 100644
index ffda093ce9..0000000000
--- a/packages/twenty-front/src/modules/ui/layout/tab-list/types/LayoutCard.ts
+++ /dev/null
@@ -1,7 +0,0 @@
-import { type CardConfiguration } from '@/object-record/record-show/types/CardConfiguration';
-import { type CardType } from '@/object-record/record-show/types/CardType';
-
-export type LayoutCard = {
- type: CardType;
- configuration?: CardConfiguration;
-};
diff --git a/packages/twenty-front/src/modules/ui/layout/tab-list/types/SingleTabProps.ts b/packages/twenty-front/src/modules/ui/layout/tab-list/types/SingleTabProps.ts
index 13e2873189..416e53ae67 100644
--- a/packages/twenty-front/src/modules/ui/layout/tab-list/types/SingleTabProps.ts
+++ b/packages/twenty-front/src/modules/ui/layout/tab-list/types/SingleTabProps.ts
@@ -1,4 +1,3 @@
-import { type LayoutCard } from '@/ui/layout/tab-list/types/LayoutCard';
import { type IconComponent } from 'twenty-ui/display';
export type SingleTabProps = {
@@ -8,6 +7,5 @@ export type SingleTabProps = {
hide?: boolean;
disabled?: boolean;
pill?: string | React.ReactElement;
- cards?: LayoutCard[];
logo?: string;
};