431f6ae98f
## What Replaces `SubMenuTopBarContainer` with a settings-specific `SettingsPageLayout` that puts the whole page chrome — breadcrumb, centered title, actions, an optional secondary bar (tabs or wizard step), and the 760px body — inside **one rounded card**, with `SidePanelForDesktop` as a sibling. Title, tabs and body content share one centered vertical axis at every card width. Supersedes #21122. One PR, no feature flag. ## New components (`@/settings/components/layout/`) - **SettingsPageLayout** — owns the rounded card + side-panel sibling, `useCommandMenuHotKeys`, mobile command menu - **SettingsPageHeader** — breadcrumb · centered title · actions in a symmetric `1fr auto 1fr` grid (symmetric padding throughout) - **SettingsSecondaryBar** — the secondary row, bracketed by top + bottom borders - **SettingsTabBar** — centered tabs reusing `activeTabIdComponentState` + `TabListFromUrlOptionalEffect` for URL-hash sync (does not touch the shared `TabList`) - **SettingsWizardStepBar** — back arrow · "N. Label" · optional trailing slot ## Migrations - Bulk rename across ~80 call sites (`SubMenuTopBarContainer` → `SettingsPageLayout`); old component deleted. - 5 tab pages (AI, APIs & Webhooks, Applications, Members, Role) + the Data Model object-detail page render their tabs in `secondaryBar` (object-detail keeps "See records" / "New Field" in the header actions). - The 2 role object-level steps render the wizard step bar with working back navigation. - Accounts consolidated into **General / Emails / Calendars** tabs; standalone `SettingsAccountsEmails` / `SettingsAccountsCalendars` pages + routes + stories removed. `SettingsPath.AccountsEmails` / `AccountsCalendars` now resolve to `accounts#emails` / `accounts#calendars`, so existing `getSettingsPath()` links deep-link to the right tab via the existing hash sync — no call-site changes. ## Verification - `nx typecheck twenty-front` and `nx lint twenty-front` both clean. - Browser (logged-in workspace): title / tab / body / card centers align on a single axis at multiple widths — width-invariant, so alignment holds when the AI side panel (a sibling) shrinks the card. Rounded card with even gaps on all four sides; tab row bracketed by two 1px lines; no-tab pages render header → body with no lines; wizard back navigation works; `…/accounts#emails` opens the Emails tab. The shared `PageHeader` and `TabList` are untouched. The settings side panel itself isn't wired to open yet — that's a follow-up PR.
120 lines
3.6 KiB
TypeScript
120 lines
3.6 KiB
TypeScript
import { isDDLLockedState } from '@/client-config/states/isDDLLockedState';
|
|
import { useFilteredObjectMetadataItems } from '@/object-metadata/hooks/useFilteredObjectMetadataItems';
|
|
import { SettingsDiscoveryHeroCard } from '@/settings/components/SettingsDiscoveryHeroCard';
|
|
import { SettingsPageContainer } from '@/settings/components/SettingsPageContainer';
|
|
import { useAtomStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomStateValue';
|
|
import { SettingsPageLayout } from '@/settings/components/layout/SettingsPageLayout';
|
|
import { useLingui } from '@lingui/react/macro';
|
|
import { SettingsPath } from 'twenty-shared/types';
|
|
import { getSettingsPath } from 'twenty-shared/utils';
|
|
import {
|
|
H2Title,
|
|
IconEye,
|
|
IconHierarchy2,
|
|
IconLink,
|
|
IconList,
|
|
IconPlus,
|
|
} from 'twenty-ui/display';
|
|
import { Button } from 'twenty-ui/input';
|
|
import { Section } from 'twenty-ui/layout';
|
|
import { UndecoratedLink } from 'twenty-ui/navigation';
|
|
import DarkCoverImage from '@/settings/data-model/assets/cover-dark.png';
|
|
import LightCoverImage from '@/settings/data-model/assets/cover-light.png';
|
|
import { SettingsObjectTable } from '~/pages/settings/data-model/SettingsObjectTable';
|
|
|
|
const SETTINGS_DATA_MODEL_HERO_INSTANCE_ID_PREFIX = 'settings-data-model-hero';
|
|
|
|
export const SettingsObjects = () => {
|
|
const { t } = useLingui();
|
|
|
|
const { objectMetadataItems } = useFilteredObjectMetadataItems();
|
|
const isDDLLocked = useAtomStateValue(isDDLLockedState);
|
|
|
|
const heroTabs = [
|
|
{
|
|
id: 'objects',
|
|
title: t`Objects`,
|
|
Icon: IconHierarchy2,
|
|
vimeoId: '926288174',
|
|
},
|
|
{
|
|
id: 'fields',
|
|
title: t`Fields`,
|
|
Icon: IconList,
|
|
vimeoId: '927628219',
|
|
},
|
|
{
|
|
id: 'relations',
|
|
title: t`Relations`,
|
|
Icon: IconLink,
|
|
vimeoId: '1185511827',
|
|
},
|
|
];
|
|
|
|
return (
|
|
<SettingsPageLayout
|
|
title={t`Data model`}
|
|
actionButton={
|
|
isDDLLocked ? (
|
|
<Button
|
|
Icon={IconPlus}
|
|
title={t`Add object`}
|
|
accent="blue"
|
|
size="small"
|
|
disabled
|
|
/>
|
|
) : (
|
|
<UndecoratedLink to={getSettingsPath(SettingsPath.NewObject)}>
|
|
<Button
|
|
Icon={IconPlus}
|
|
title={t`Add object`}
|
|
accent="blue"
|
|
size="small"
|
|
/>
|
|
</UndecoratedLink>
|
|
)
|
|
}
|
|
links={[
|
|
{
|
|
children: t`Workspace`,
|
|
href: getSettingsPath(SettingsPath.General),
|
|
},
|
|
{ children: t`Objects` },
|
|
]}
|
|
>
|
|
<SettingsPageContainer>
|
|
<Section>
|
|
<SettingsDiscoveryHeroCard
|
|
lightSrc={LightCoverImage}
|
|
darkSrc={DarkCoverImage}
|
|
instanceIdPrefix={SETTINGS_DATA_MODEL_HERO_INSTANCE_ID_PREFIX}
|
|
tabs={heroTabs}
|
|
playButtonAriaLabel={t`Watch data model demo`}
|
|
/>
|
|
</Section>
|
|
<Section>
|
|
<H2Title
|
|
title={t`Existing objects`}
|
|
description={t`Manage objects, fields and relationships`}
|
|
/>
|
|
<SettingsObjectTable objectMetadataItems={objectMetadataItems} />
|
|
</Section>
|
|
<Section>
|
|
<H2Title
|
|
title={t`Visualize data model`}
|
|
description={t`See your data structure as an interactive diagram`}
|
|
/>
|
|
<UndecoratedLink to={getSettingsPath(SettingsPath.ObjectOverview)}>
|
|
<Button
|
|
title={t`Visualize`}
|
|
variant="secondary"
|
|
size="small"
|
|
Icon={IconEye}
|
|
/>
|
|
</UndecoratedLink>
|
|
</Section>
|
|
</SettingsPageContainer>
|
|
</SettingsPageLayout>
|
|
);
|
|
};
|