From 431f6ae98f3d3c042b8e72ea59d9a980f9614240 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A9lix=20Malfait?= Date: Tue, 2 Jun 2026 14:22:57 +0200 Subject: [PATCH] feat(settings): move settings chrome into a single rounded card (#21131) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## 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. --- .../modules/app/components/SettingsRoutes.tsx | 96 +++--- ...tingsAccountsCalendarChannelsContainer.tsx | 60 +--- ...gsAccountsEditImapSmtpCaldavConnection.tsx | 6 +- ...ttingsAccountsMessageChannelsContainer.tsx | 80 +---- .../SettingsAccountsNewEmailGroupChannel.tsx | 8 +- ...ngsAccountsNewImapSmtpCaldavConnection.tsx | 6 +- ...gsAccountsSelectedMessageChannelEffect.tsx | 19 +- .../components/SettingsPageContainer.tsx | 4 + .../components/layout/SettingsPageHeader.tsx | 87 ++++++ .../components/layout/SettingsPageLayout.tsx | 95 ++++++ .../layout/SettingsSecondaryBar.tsx | 29 ++ .../components/layout/SettingsTabBar.tsx | 64 ++++ .../layout/SettingsWizardStepBar.tsx | 63 ++++ .../layout/useSettingsActiveTabId.ts | 29 ++ .../SettingsDevelopersWebhookForm.tsx | 9 +- .../components/SettingPublicDomain.tsx | 8 +- .../components/SettingsCustomDomain.tsx | 12 +- .../domains/components/SettingsSubdomain.tsx | 12 +- .../hooks/useSettingsNavigationItems.tsx | 19 +- ...gsRolePermissionsObjectLevelObjectForm.tsx | 45 ++- .../roles/role/components/SettingsRole.tsx | 25 +- .../components/SettingsSecuritySettings.tsx} | 288 +++++++++--------- .../components/SubMenuTopBarContainer.tsx | 84 ----- .../__stories__/NavigationDrawer.stories.tsx | 2 +- .../src/pages/settings/SettingsWorkspace.tsx | 58 ---- .../__stories__/SettingsBilling.stories.tsx | 2 +- ...tories.tsx => SettingsGeneral.stories.tsx} | 8 +- .../__stories__/SettingsProfile.stories.tsx | 2 +- .../settings/accounts/SettingsAccounts.tsx | 10 +- .../accounts/SettingsAccountsCalendars.tsx | 72 ++++- ...tingsAccountsConfigurationStepCalendar.tsx | 6 +- ...SettingsAccountsConfigurationStepEmail.tsx | 6 +- .../accounts/SettingsAccountsEmails.tsx | 70 ++++- .../settings/accounts/SettingsNewAccount.tsx | 6 +- .../settings/admin-panel/SettingsAdmin.tsx | 6 +- .../SettingsAdminAiProviderDetail.tsx | 6 +- ...cationRegistrationConfigVariableDetail.tsx | 6 +- ...ingsAdminApplicationRegistrationDetail.tsx | 6 +- .../SettingsAdminConfigVariableDetails.tsx | 6 +- .../SettingsAdminIndicatorHealthStatus.tsx | 6 +- .../SettingsAdminInferredVersion.tsx | 6 +- .../SettingsAdminInstanceStatus.tsx | 6 +- .../admin-panel/SettingsAdminNewAiModel.tsx | 6 +- .../SettingsAdminNewAiProvider.tsx | 6 +- .../admin-panel/SettingsAdminQueueDetail.tsx | 6 +- .../admin-panel/SettingsAdminUserDetail.tsx | 6 +- .../SettingsAdminWorkspaceChatThread.tsx | 6 +- .../SettingsAdminWorkspaceDetail.tsx | 6 +- .../SettingsAdminWorkspacesStatus.tsx | 6 +- .../src/pages/settings/ai/SettingsAI.tsx | 34 +-- .../pages/settings/ai/SettingsAgentForm.tsx | 8 +- .../settings/ai/SettingsAgentTurnDetail.tsx | 20 +- .../pages/settings/ai/SettingsAiPrompts.tsx | 8 +- .../settings/ai/SettingsAiUsageUserDetail.tsx | 12 +- .../pages/settings/ai/SettingsSkillForm.tsx | 8 +- .../pages/settings/ai/SettingsToolDetail.tsx | 8 +- .../SettingsApiWebhooks.tsx | 39 ++- .../constants/SettingsApiWebhooksTabs.ts | 0 ...ttingsApplicationCommandMenuItemDetail.tsx | 8 +- .../SettingsApplicationConnectionDetail.tsx | 8 +- .../SettingsApplicationDetails.tsx | 8 +- ...ettingsApplicationFrontComponentDetail.tsx | 8 +- ...SettingsApplicationRegistrationDetails.tsx | 8 +- .../applications/SettingsApplications.tsx | 30 +- .../SettingsAvailableApplicationDetails.tsx | 8 +- ...ttingsApplicationConnectionDetail.test.tsx | 4 +- ...cationRegistrationConfigVariableDetail.tsx | 8 +- .../{ => billing}/SettingsBilling.tsx | 8 +- .../settings/{ => billing}/SettingsUsage.tsx | 8 +- .../{ => billing}/SettingsUsageUserDetail.tsx | 15 +- .../settings/community/SettingsCommunity.tsx | 94 ++++++ .../settings/data-model/SettingsNewObject.tsx | 8 +- .../data-model/SettingsObjectDetailPage.tsx | 121 ++++---- .../data-model/SettingsObjectFieldEdit.tsx | 8 +- .../data-model/SettingsObjectOverview.tsx | 8 +- .../settings/data-model/SettingsObjects.tsx | 8 +- .../SettingsObjectNewFieldConfigure.tsx | 8 +- .../SettingsObjectNewFieldSelect.tsx | 8 +- .../new-index/SettingsObjectNewIndex.tsx | 8 +- .../SettingsDevelopersApiKeyDetail.tsx | 8 +- .../api-keys/SettingsDevelopersApiKeysNew.tsx | 8 +- .../playground/SettingsGraphQLPlayground.tsx | 2 +- .../playground/SettingsRestPlayground.tsx | 2 +- .../webhooks/components/SettingsWebhooks.tsx | 8 +- .../{ => email}/SettingsWorkspaceEmail.tsx | 8 +- ...ttingsWorkspaceEmailGroupChannelDetail.tsx | 8 +- .../SettingsEmailingDomainDetail.tsx | 8 +- .../SettingsNewEmailingDomain.tsx | 8 +- .../enterprise/SettingsEnterprise.tsx | 8 +- .../settings/general/SettingsGeneral.tsx | 94 ++++++ .../pages/settings/layout/SettingsLayout.tsx | 8 +- .../SettingsLayoutDetailScaffold.tsx | 8 +- .../SettingsLogicFunctionDetail.tsx | 10 +- .../members/SettingsWorkspaceMember.tsx | 8 +- .../members/SettingsWorkspaceMembers.tsx | 27 +- .../roles/SettingsRoleAddObjectLevel.tsx | 19 +- .../{ => profile}/SettingsProfile.tsx | 6 +- .../SettingsTwoFactorAuthenticationMethod.tsx | 6 +- .../components/SettingsExperience.tsx | 6 +- .../SettingsSecurityApprovedAccessDomain.tsx | 8 +- .../SettingsSecuritySSOIdentifyProvider.tsx | 8 +- .../security/event-logs/SettingsEventLogs.tsx | 2 +- .../settings/updates/SettingsUpdates.tsx | 63 ---- .../twenty-front/src/utils/title-utils.ts | 5 +- .../standard-command-menu-item.constant.ts | 8 +- .../twenty-shared/src/types/SettingsPath.ts | 8 +- .../__tests__/getSettingsPath.test.ts | 4 +- 107 files changed, 1367 insertions(+), 1008 deletions(-) create mode 100644 packages/twenty-front/src/modules/settings/components/layout/SettingsPageHeader.tsx create mode 100644 packages/twenty-front/src/modules/settings/components/layout/SettingsPageLayout.tsx create mode 100644 packages/twenty-front/src/modules/settings/components/layout/SettingsSecondaryBar.tsx create mode 100644 packages/twenty-front/src/modules/settings/components/layout/SettingsTabBar.tsx create mode 100644 packages/twenty-front/src/modules/settings/components/layout/SettingsWizardStepBar.tsx create mode 100644 packages/twenty-front/src/modules/settings/components/layout/useSettingsActiveTabId.ts rename packages/twenty-front/src/{pages/settings/security/SettingsSecurity.tsx => modules/settings/security/components/SettingsSecuritySettings.tsx} (56%) delete mode 100644 packages/twenty-front/src/modules/ui/layout/page/components/SubMenuTopBarContainer.tsx delete mode 100644 packages/twenty-front/src/pages/settings/SettingsWorkspace.tsx rename packages/twenty-front/src/pages/settings/__stories__/{SettingsWorkspace.stories.tsx => SettingsGeneral.stories.tsx} (68%) rename packages/twenty-front/src/pages/settings/{workspace => api-webhooks}/SettingsApiWebhooks.tsx (87%) rename packages/twenty-front/src/pages/settings/{workspace => api-webhooks}/constants/SettingsApiWebhooksTabs.ts (100%) rename packages/twenty-front/src/pages/settings/{ => billing}/SettingsBilling.tsx (81%) rename packages/twenty-front/src/pages/settings/{ => billing}/SettingsUsage.tsx (79%) rename packages/twenty-front/src/pages/settings/{ => billing}/SettingsUsageUserDetail.tsx (92%) create mode 100644 packages/twenty-front/src/pages/settings/community/SettingsCommunity.tsx rename packages/twenty-front/src/pages/settings/{ => email}/SettingsWorkspaceEmail.tsx (87%) rename packages/twenty-front/src/pages/settings/{workspace => email}/SettingsWorkspaceEmailGroupChannelDetail.tsx (96%) create mode 100644 packages/twenty-front/src/pages/settings/general/SettingsGeneral.tsx rename packages/twenty-front/src/pages/settings/{ => profile}/SettingsProfile.tsx (96%) rename packages/twenty-front/src/pages/settings/{ => profile}/SettingsTwoFactorAuthenticationMethod.tsx (97%) delete mode 100644 packages/twenty-front/src/pages/settings/updates/SettingsUpdates.tsx diff --git a/packages/twenty-front/src/modules/app/components/SettingsRoutes.tsx b/packages/twenty-front/src/modules/app/components/SettingsRoutes.tsx index 5e250fbd180..3bde2e353a2 100644 --- a/packages/twenty-front/src/modules/app/components/SettingsRoutes.tsx +++ b/packages/twenty-front/src/modules/app/components/SettingsRoutes.tsx @@ -24,20 +24,6 @@ const SettingsRestPlayground = lazy(() => ), ); -const SettingsAccountsCalendars = lazy(() => - import('~/pages/settings/accounts/SettingsAccountsCalendars').then( - (module) => ({ - default: module.SettingsAccountsCalendars, - }), - ), -); - -const SettingsAccountsEmails = lazy(() => - import('~/pages/settings/accounts/SettingsAccountsEmails').then((module) => ({ - default: module.SettingsAccountsEmails, - })), -); - const SettingsAccountsConfiguration = lazy(() => import('~/pages/settings/accounts/SettingsAccountsConfiguration').then( (module) => ({ @@ -122,20 +108,20 @@ const SettingsLogicFunctionDetail = lazy(() => ), ); -const SettingsWorkspace = lazy(() => - import('~/pages/settings/SettingsWorkspace').then((module) => ({ - default: module.SettingsWorkspace, +const SettingsGeneral = lazy(() => + import('~/pages/settings/general/SettingsGeneral').then((module) => ({ + default: module.SettingsGeneral, })), ); const SettingsWorkspaceEmail = lazy(() => - import('~/pages/settings/SettingsWorkspaceEmail').then((module) => ({ + import('~/pages/settings/email/SettingsWorkspaceEmail').then((module) => ({ default: module.SettingsWorkspaceEmail, })), ); const SettingsWorkspaceEmailGroupChannelDetail = lazy(() => - import('~/pages/settings/workspace/SettingsWorkspaceEmailGroupChannelDetail').then( + import('~/pages/settings/email/SettingsWorkspaceEmailGroupChannelDetail').then( (module) => ({ default: module.SettingsWorkspaceEmailGroupChannelDetail, }), @@ -157,9 +143,11 @@ const SettingsCustomDomainPage = lazy(() => ); const SettingsApiWebhooks = lazy(() => - import('~/pages/settings/workspace/SettingsApiWebhooks').then((module) => ({ - default: module.SettingsApiWebhooks, - })), + import('~/pages/settings/api-webhooks/SettingsApiWebhooks').then( + (module) => ({ + default: module.SettingsApiWebhooks, + }), + ), ); const SettingsAI = lazy(() => @@ -319,13 +307,13 @@ const SettingsWorkspaceMember = lazy(() => ); const SettingsProfile = lazy(() => - import('~/pages/settings/SettingsProfile').then((module) => ({ + import('~/pages/settings/profile/SettingsProfile').then((module) => ({ default: module.SettingsProfile, })), ); const SettingsTwoFactorAuthenticationMethod = lazy(() => - import('~/pages/settings/SettingsTwoFactorAuthenticationMethod').then( + import('~/pages/settings/profile/SettingsTwoFactorAuthenticationMethod').then( (module) => ({ default: module.SettingsTwoFactorAuthenticationMethod, }), @@ -346,20 +334,34 @@ const SettingsAccounts = lazy(() => })), ); +const SettingsAccountsEmails = lazy(() => + import('~/pages/settings/accounts/SettingsAccountsEmails').then((module) => ({ + default: module.SettingsAccountsEmails, + })), +); + +const SettingsAccountsCalendars = lazy(() => + import('~/pages/settings/accounts/SettingsAccountsCalendars').then( + (module) => ({ + default: module.SettingsAccountsCalendars, + }), + ), +); + const SettingsBilling = lazy(() => - import('~/pages/settings/SettingsBilling').then((module) => ({ + import('~/pages/settings/billing/SettingsBilling').then((module) => ({ default: module.SettingsBilling, })), ); const SettingsUsage = lazy(() => - import('~/pages/settings/SettingsUsage').then((module) => ({ + import('~/pages/settings/billing/SettingsUsage').then((module) => ({ default: module.SettingsUsage, })), ); const SettingsUsageUserDetail = lazy(() => - import('~/pages/settings/SettingsUsageUserDetail').then((module) => ({ + import('~/pages/settings/billing/SettingsUsageUserDetail').then((module) => ({ default: module.SettingsUsageUserDetail, })), ); @@ -417,12 +419,6 @@ const SettingsObjectFieldEdit = lazy(() => ), ); -const SettingsSecurity = lazy(() => - import('~/pages/settings/security/SettingsSecurity').then((module) => ({ - default: module.SettingsSecurity, - })), -); - const SettingsSecuritySSOIdentifyProvider = lazy(() => import('~/pages/settings/security/SettingsSecuritySSOIdentifyProvider').then( (module) => ({ @@ -565,9 +561,9 @@ const SettingsAdminWorkspaceChatThread = lazy(() => ), ); -const SettingsUpdates = lazy(() => - import('~/pages/settings/updates/SettingsUpdates').then((module) => ({ - default: module.SettingsUpdates, +const SettingsCommunity = lazy(() => + import('~/pages/settings/community/SettingsCommunity').then((module) => ({ + default: module.SettingsCommunity, })), ); @@ -624,20 +620,20 @@ export const SettingsRoutes = ({ isAdminPageEnabled }: SettingsRoutesProps) => ( > } /> } - /> - } + path={SettingsPath.AccountsEmails} + element={} /> } /> } + path={SettingsPath.NewAccount} + element={} + /> + } /> ( /> } > - } /> + } /> } @@ -916,6 +912,13 @@ export const SettingsRoutes = ({ isAdminPageEnabled }: SettingsRoutesProps) => ( /> + + } + /> + ( /> } > - } /> } @@ -1018,7 +1020,7 @@ export const SettingsRoutes = ({ isAdminPageEnabled }: SettingsRoutesProps) => ( /> } > - } /> + } /> diff --git a/packages/twenty-front/src/modules/settings/accounts/components/SettingsAccountsCalendarChannelsContainer.tsx b/packages/twenty-front/src/modules/settings/accounts/components/SettingsAccountsCalendarChannelsContainer.tsx index 9b0f1ebaa48..c15e4720d26 100644 --- a/packages/twenty-front/src/modules/settings/accounts/components/SettingsAccountsCalendarChannelsContainer.tsx +++ b/packages/twenty-front/src/modules/settings/accounts/components/SettingsAccountsCalendarChannelsContainer.tsx @@ -1,70 +1,32 @@ -import { styled } from '@linaria/react'; - +import { type CalendarChannel } from '@/accounts/types/CalendarChannel'; import { SettingsAccountsCalendarChannelDetails } from '@/settings/accounts/components/SettingsAccountsCalendarChannelDetails'; -import { SettingsNewAccountSection } from '@/settings/accounts/components/SettingsNewAccountSection'; import { SETTINGS_ACCOUNT_CALENDAR_CHANNELS_TAB_LIST_COMPONENT_ID } from '@/settings/accounts/constants/SettingsAccountCalendarChannelsTabListComponentId'; -import { useMyCalendarChannels } from '@/settings/accounts/hooks/useMyCalendarChannels'; -import { TabList } from '@/ui/layout/tab-list/components/TabList'; -import { activeTabIdComponentState } from '@/ui/layout/tab-list/states/activeTabIdComponentState'; -import { useAtomComponentStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomComponentStateValue'; +import { useSettingsActiveTabId } from '@/settings/components/layout/useSettingsActiveTabId'; import React from 'react'; -import { CalendarChannelSyncStage } from 'twenty-shared/types'; -import { themeCssVariables } from 'twenty-ui/theme-constants'; -const StyledCalenderContainer = styled.div` - padding-bottom: ${themeCssVariables.spacing[6]}; -`; +type SettingsAccountsCalendarChannelsContainerProps = { + calendarChannels: CalendarChannel[]; +}; -export const SettingsAccountsCalendarChannelsContainer = () => { - const activeTabId = useAtomComponentStateValue( - activeTabIdComponentState, +export const SettingsAccountsCalendarChannelsContainer = ({ + calendarChannels, +}: SettingsAccountsCalendarChannelsContainerProps) => { + const activeTabId = useSettingsActiveTabId( SETTINGS_ACCOUNT_CALENDAR_CHANNELS_TAB_LIST_COMPONENT_ID, + calendarChannels.map((channel) => channel.id), ); - const { channels: allCalendarChannels } = useMyCalendarChannels(); - - const calendarChannels = allCalendarChannels.filter( - (channel) => - channel.syncStage !== CalendarChannelSyncStage.PENDING_CONFIGURATION, - ); - - const tabs = [ - ...calendarChannels.map((calendarChannel) => ({ - id: calendarChannel.id, - title: calendarChannel.handle, - })), - ]; - - if (!calendarChannels.length) { - return ; - } - return ( <> - {tabs.length > 1 && ( - - - - )} {calendarChannels.map((calendarChannel) => ( - {(calendarChannels.length === 1 || - calendarChannel.id === activeTabId) && ( + {calendarChannel.id === activeTabId && ( )} ))} - {/* TODO: remove or keep? */} - {/* {activeTabId === 'general' && ( - - )} */} ); }; diff --git a/packages/twenty-front/src/modules/settings/accounts/components/SettingsAccountsEditImapSmtpCaldavConnection.tsx b/packages/twenty-front/src/modules/settings/accounts/components/SettingsAccountsEditImapSmtpCaldavConnection.tsx index 08072ca8dbc..2915570c6d6 100644 --- a/packages/twenty-front/src/modules/settings/accounts/components/SettingsAccountsEditImapSmtpCaldavConnection.tsx +++ b/packages/twenty-front/src/modules/settings/accounts/components/SettingsAccountsEditImapSmtpCaldavConnection.tsx @@ -5,7 +5,7 @@ import { useParams } from 'react-router-dom'; import { SaveAndCancelButtons } from '@/settings/components/SaveAndCancelButtons/SaveAndCancelButtons'; import { SettingsPageContainer } from '@/settings/components/SettingsPageContainer'; -import { SubMenuTopBarContainer } from '@/ui/layout/page/components/SubMenuTopBarContainer'; +import { SettingsPageLayout } from '@/settings/components/layout/SettingsPageLayout'; import { SettingsPath } from 'twenty-shared/types'; import { Loader } from 'twenty-ui/feedback'; @@ -64,7 +64,7 @@ export const SettingsAccountsEditImapSmtpCaldavConnection = () => { const renderForm = () => ( // oxlint-disable-next-line react/jsx-props-no-spreading - { existingProtocols={existingProtocols} /> - + ); diff --git a/packages/twenty-front/src/modules/settings/accounts/components/SettingsAccountsMessageChannelsContainer.tsx b/packages/twenty-front/src/modules/settings/accounts/components/SettingsAccountsMessageChannelsContainer.tsx index 0c695d6b9cd..328ec49708d 100644 --- a/packages/twenty-front/src/modules/settings/accounts/components/SettingsAccountsMessageChannelsContainer.tsx +++ b/packages/twenty-front/src/modules/settings/accounts/components/SettingsAccountsMessageChannelsContainer.tsx @@ -1,86 +1,30 @@ -import { styled } from '@linaria/react'; - +import { type MessageChannel } from '@/accounts/types/MessageChannel'; import { SettingsAccountsMessageChannelDetails } from '@/settings/accounts/components/SettingsAccountsMessageChannelDetails'; import { SettingsAccountsSelectedMessageChannelEffect } from '@/settings/accounts/components/SettingsAccountsSelectedMessageChannelEffect'; -import { SettingsNewAccountSection } from '@/settings/accounts/components/SettingsNewAccountSection'; import { SETTINGS_ACCOUNT_MESSAGE_CHANNELS_TAB_LIST_COMPONENT_ID } from '@/settings/accounts/constants/SettingsAccountMessageChannelsTabListComponentId'; -import { useMyMessageChannels } from '@/settings/accounts/hooks/useMyMessageChannels'; -import { settingsAccountsSelectedMessageChannelState } from '@/settings/accounts/states/settingsAccountsSelectedMessageChannelState'; -import { TabList } from '@/ui/layout/tab-list/components/TabList'; -import { activeTabIdComponentState } from '@/ui/layout/tab-list/states/activeTabIdComponentState'; -import { useAtomComponentStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomComponentStateValue'; -import { useSetAtomState } from '@/ui/utilities/state/jotai/hooks/useSetAtomState'; -import React, { useCallback } from 'react'; -import { - MessageChannelSyncStage, - MessageChannelType, -} from 'twenty-shared/types'; -import { isDefined } from 'twenty-shared/utils'; -import { themeCssVariables } from 'twenty-ui/theme-constants'; +import { useSettingsActiveTabId } from '@/settings/components/layout/useSettingsActiveTabId'; +import React from 'react'; -const StyledMessageContainer = styled.div` - padding-bottom: ${themeCssVariables.spacing[6]}; -`; +type SettingsAccountsMessageChannelsContainerProps = { + messageChannels: MessageChannel[]; +}; -export const SettingsAccountsMessageChannelsContainer = () => { - const activeTabId = useAtomComponentStateValue( - activeTabIdComponentState, +export const SettingsAccountsMessageChannelsContainer = ({ + messageChannels, +}: SettingsAccountsMessageChannelsContainerProps) => { + const activeTabId = useSettingsActiveTabId( SETTINGS_ACCOUNT_MESSAGE_CHANNELS_TAB_LIST_COMPONENT_ID, + messageChannels.map((channel) => channel.id), ); - const setSettingsAccountsSelectedMessageChannel = useSetAtomState( - settingsAccountsSelectedMessageChannelState, - ); - - const { channels: allMessageChannels } = useMyMessageChannels(); - - const messageChannels = allMessageChannels.filter( - (channel) => - channel.isSyncEnabled && - channel.syncStage !== MessageChannelSyncStage.PENDING_CONFIGURATION && - channel.type !== MessageChannelType.EMAIL_GROUP, - ); - - const tabs = messageChannels.map((messageChannel) => ({ - id: messageChannel.id, - title: messageChannel.handle, - })); - - const handleTabChange = useCallback( - (tabId: string) => { - const selectedMessageChannel = messageChannels.find( - (channel) => channel.id === tabId, - ); - if (isDefined(selectedMessageChannel)) { - setSettingsAccountsSelectedMessageChannel(selectedMessageChannel); - } - }, - [messageChannels, setSettingsAccountsSelectedMessageChannel], - ); - - if (!messageChannels.length) { - return ; - } return ( <> - {tabs.length > 1 && ( - - - - )} {messageChannels.map((messageChannel) => ( - {(messageChannels.length === 1 || - messageChannel.id === activeTabId) && ( + {messageChannel.id === activeTabId && ( diff --git a/packages/twenty-front/src/modules/settings/accounts/components/SettingsAccountsNewEmailGroupChannel.tsx b/packages/twenty-front/src/modules/settings/accounts/components/SettingsAccountsNewEmailGroupChannel.tsx index fa07c2abc00..66395637af5 100644 --- a/packages/twenty-front/src/modules/settings/accounts/components/SettingsAccountsNewEmailGroupChannel.tsx +++ b/packages/twenty-front/src/modules/settings/accounts/components/SettingsAccountsNewEmailGroupChannel.tsx @@ -12,7 +12,7 @@ import { SaveAndCancelButtons } from '@/settings/components/SaveAndCancelButtons import { SettingsPageContainer } from '@/settings/components/SettingsPageContainer'; import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar'; import { SettingsTextInput } from '@/ui/input/components/SettingsTextInput'; -import { SubMenuTopBarContainer } from '@/ui/layout/page/components/SubMenuTopBarContainer'; +import { SettingsPageLayout } from '@/settings/components/layout/SettingsPageLayout'; import { useNavigateSettings } from '~/hooks/useNavigateSettings'; export const SettingsAccountsNewEmailGroupChannel = () => { @@ -45,12 +45,12 @@ export const SettingsAccountsNewEmailGroupChannel = () => { }, [createEmailGroupChannel, handle, navigate, enqueueErrorSnackBar, t]); return ( - { /> - + ); }; diff --git a/packages/twenty-front/src/modules/settings/accounts/components/SettingsAccountsNewImapSmtpCaldavConnection.tsx b/packages/twenty-front/src/modules/settings/accounts/components/SettingsAccountsNewImapSmtpCaldavConnection.tsx index d8e1c23e73e..bda25b5aef6 100644 --- a/packages/twenty-front/src/modules/settings/accounts/components/SettingsAccountsNewImapSmtpCaldavConnection.tsx +++ b/packages/twenty-front/src/modules/settings/accounts/components/SettingsAccountsNewImapSmtpCaldavConnection.tsx @@ -3,7 +3,7 @@ import { FormProvider } from 'react-hook-form'; import { SaveAndCancelButtons } from '@/settings/components/SaveAndCancelButtons/SaveAndCancelButtons'; import { SettingsPageContainer } from '@/settings/components/SettingsPageContainer'; -import { SubMenuTopBarContainer } from '@/ui/layout/page/components/SubMenuTopBarContainer'; +import { SettingsPageLayout } from '@/settings/components/layout/SettingsPageLayout'; import { SettingsPath } from 'twenty-shared/types'; import { getSettingsPath } from 'twenty-shared/utils'; @@ -30,7 +30,7 @@ export const SettingsAccountsNewImapSmtpCaldavConnection = () => { return ( // oxlint-disable-next-line react/jsx-props-no-spreading - { - + ); }; diff --git a/packages/twenty-front/src/modules/settings/accounts/components/SettingsAccountsSelectedMessageChannelEffect.tsx b/packages/twenty-front/src/modules/settings/accounts/components/SettingsAccountsSelectedMessageChannelEffect.tsx index 023b41169bc..040b7fac187 100644 --- a/packages/twenty-front/src/modules/settings/accounts/components/SettingsAccountsSelectedMessageChannelEffect.tsx +++ b/packages/twenty-front/src/modules/settings/accounts/components/SettingsAccountsSelectedMessageChannelEffect.tsx @@ -1,8 +1,7 @@ import { type MessageChannel } from '@/accounts/types/MessageChannel'; import { SETTINGS_ACCOUNT_MESSAGE_CHANNELS_TAB_LIST_COMPONENT_ID } from '@/settings/accounts/constants/SettingsAccountMessageChannelsTabListComponentId'; import { settingsAccountsSelectedMessageChannelState } from '@/settings/accounts/states/settingsAccountsSelectedMessageChannelState'; -import { activeTabIdComponentState } from '@/ui/layout/tab-list/states/activeTabIdComponentState'; -import { useAtomComponentStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomComponentStateValue'; +import { useSettingsActiveTabId } from '@/settings/components/layout/useSettingsActiveTabId'; import { useSetAtomState } from '@/ui/utilities/state/jotai/hooks/useSetAtomState'; import { useEffect } from 'react'; @@ -13,9 +12,9 @@ type SettingsAccountsSelectedMessageChannelEffectProps = { export const SettingsAccountsSelectedMessageChannelEffect = ({ messageChannels, }: SettingsAccountsSelectedMessageChannelEffectProps) => { - const activeTabId = useAtomComponentStateValue( - activeTabIdComponentState, + const activeTabId = useSettingsActiveTabId( SETTINGS_ACCOUNT_MESSAGE_CHANNELS_TAB_LIST_COMPONENT_ID, + messageChannels.map((channel) => channel.id), ); const setSettingsAccountsSelectedMessageChannel = useSetAtomState( @@ -27,13 +26,13 @@ export const SettingsAccountsSelectedMessageChannelEffect = ({ return; } - const currentSelectionStillExists = activeTabId - ? messageChannels.some((channel) => channel.id === activeTabId) - : false; + const activeChannel = messageChannels.find( + (channel) => channel.id === activeTabId, + ); - if (!currentSelectionStillExists) { - setSettingsAccountsSelectedMessageChannel(messageChannels[0]); - } + setSettingsAccountsSelectedMessageChannel( + activeChannel ?? messageChannels[0], + ); }, [messageChannels, activeTabId, setSettingsAccountsSelectedMessageChannel]); return null; diff --git a/packages/twenty-front/src/modules/settings/components/SettingsPageContainer.tsx b/packages/twenty-front/src/modules/settings/components/SettingsPageContainer.tsx index e25f724a425..cbb2b4b5aca 100644 --- a/packages/twenty-front/src/modules/settings/components/SettingsPageContainer.tsx +++ b/packages/twenty-front/src/modules/settings/components/SettingsPageContainer.tsx @@ -8,6 +8,8 @@ import { SettingsPath } from 'twenty-shared/types'; import { getSettingsPath, isDefined } from 'twenty-shared/utils'; import { themeCssVariables } from 'twenty-ui/theme-constants'; +const SETTINGS_CONTENT_MAX_WIDTH = 760; + const StyledSettingsPageContainer = styled.div<{ width?: number; isMobile?: boolean; @@ -16,6 +18,8 @@ const StyledSettingsPageContainer = styled.div<{ display: flex; flex-direction: column; gap: ${themeCssVariables.spacing[8]}; + margin: 0 auto; + max-width: ${SETTINGS_CONTENT_MAX_WIDTH}px; overflow: auto; padding: ${themeCssVariables.spacing[6]} ${themeCssVariables.spacing[8]} ${themeCssVariables.spacing[8]}; diff --git a/packages/twenty-front/src/modules/settings/components/layout/SettingsPageHeader.tsx b/packages/twenty-front/src/modules/settings/components/layout/SettingsPageHeader.tsx new file mode 100644 index 00000000000..99485695259 --- /dev/null +++ b/packages/twenty-front/src/modules/settings/components/layout/SettingsPageHeader.tsx @@ -0,0 +1,87 @@ +import { useNavigationDrawerExpanded } from '@/navigation/hooks/useNavigationDrawerExpanded'; +import { SIDE_PANEL_TOP_BAR_HEIGHT } from '@/side-panel/constants/SidePanelTopBarHeight'; +import { + Breadcrumb, + type BreadcrumbProps, +} from '@/ui/navigation/bread-crumb/components/Breadcrumb'; +import { NavigationDrawerCollapseButton } from '@/ui/navigation/navigation-drawer/components/NavigationDrawerCollapseButton'; +import { useIsMobile } from '@/ui/utilities/responsive/hooks/useIsMobile'; +import { styled } from '@linaria/react'; +import { type ReactNode } from 'react'; +import { isDefined } from 'twenty-shared/utils'; +import { themeCssVariables } from 'twenty-ui/theme-constants'; + +type SettingsPageHeaderProps = { + links: BreadcrumbProps['links']; + title?: ReactNode; + tag?: ReactNode; + actionButton?: ReactNode; +}; + +// minmax(0, 1fr) side tracks (not 1fr) let a long breadcrumb truncate instead of +// pushing the centered title off its shared axis with the tabs and body. +const StyledHeader = styled.div` + align-items: center; + background-color: ${themeCssVariables.background.secondary}; + border-bottom: 1px solid ${themeCssVariables.border.color.medium}; + box-sizing: border-box; + display: grid; + gap: ${themeCssVariables.spacing[2]}; + grid-template-columns: minmax(0, 1fr) auto minmax(0, 1fr); + min-height: ${SIDE_PANEL_TOP_BAR_HEIGHT}px; + padding: 0 ${themeCssVariables.spacing[3]}; + width: 100%; +`; + +const StyledLeft = styled.div` + align-items: center; + display: flex; + gap: ${themeCssVariables.spacing[1]}; + min-width: 0; + overflow: hidden; +`; + +const StyledTitle = styled.div` + align-items: center; + color: ${themeCssVariables.font.color.primary}; + display: flex; + font-size: ${themeCssVariables.font.size.md}; + font-weight: ${themeCssVariables.font.weight.semiBold}; + gap: ${themeCssVariables.spacing[2]}; + min-width: 0; + text-align: center; +`; + +const StyledRight = styled.div` + align-items: center; + display: flex; + gap: ${themeCssVariables.spacing[2]}; + justify-content: flex-end; + min-width: 0; +`; + +export const SettingsPageHeader = ({ + links, + title, + tag, + actionButton, +}: SettingsPageHeaderProps) => { + const isMobile = useIsMobile(); + const isNavigationDrawerExpanded = useNavigationDrawerExpanded(); + + return ( + + + {!isNavigationDrawerExpanded && ( + + )} + + + + {!isMobile && isDefined(title) && title} + {!isMobile && tag} + + {actionButton} + + ); +}; diff --git a/packages/twenty-front/src/modules/settings/components/layout/SettingsPageLayout.tsx b/packages/twenty-front/src/modules/settings/components/layout/SettingsPageLayout.tsx new file mode 100644 index 00000000000..11fd2d3aebe --- /dev/null +++ b/packages/twenty-front/src/modules/settings/components/layout/SettingsPageLayout.tsx @@ -0,0 +1,95 @@ +import { CommandMenuForMobile } from '@/command-menu/components/CommandMenuForMobile'; +import { useCommandMenuHotKeys } from '@/command-menu/hooks/useCommandMenuHotKeys'; +import { InformationBannerWrapper } from '@/information-banner/components/InformationBannerWrapper'; +import { SettingsPageHeader } from '@/settings/components/layout/SettingsPageHeader'; +import { SettingsSecondaryBar } from '@/settings/components/layout/SettingsSecondaryBar'; +import { SidePanelForDesktop } from '@/side-panel/components/SidePanelForDesktop'; +import { type BreadcrumbProps } from '@/ui/navigation/bread-crumb/components/Breadcrumb'; +import { useIsMobile } from '@/ui/utilities/responsive/hooks/useIsMobile'; +import { styled } from '@linaria/react'; +import { type JSX, type ReactNode } from 'react'; +import { isDefined } from 'twenty-shared/utils'; +import { themeCssVariables } from 'twenty-ui/theme-constants'; + +type SettingsPageLayoutProps = { + links: BreadcrumbProps['links']; + title?: ReactNode; + actionButton?: ReactNode; + secondaryBar?: ReactNode; + children: ReactNode; + tag?: JSX.Element; +}; + +const StyledRoot = styled.div<{ isMobile: boolean }>` + display: flex; + flex: 1; + flex-direction: row; + min-height: 0; + min-width: 0; + padding: ${({ isMobile }) => + isMobile ? themeCssVariables.spacing[1] : themeCssVariables.spacing[2]}; +`; + +const StyledMainCardWrapper = styled.div` + display: flex; + flex: 1 1 0; + min-width: 0; + width: 0; +`; + +const StyledCard = styled.div` + background: ${themeCssVariables.background.primary}; + border: 1px solid ${themeCssVariables.border.color.medium}; + border-radius: ${themeCssVariables.border.radius.md}; + box-sizing: border-box; + display: flex; + flex: 1; + flex-direction: column; + min-height: 0; + overflow: hidden; + width: 100%; +`; + +const StyledBodyContent = styled.div` + display: flex; + flex: 1; + flex-direction: column; + min-height: 0; + width: 100%; +`; + +export const SettingsPageLayout = ({ + links, + title, + actionButton, + secondaryBar, + children, + tag, +}: SettingsPageLayoutProps) => { + const isMobile = useIsMobile(); + + useCommandMenuHotKeys(); + + return ( + + + + + {isDefined(secondaryBar) && ( + {secondaryBar} + )} + + + {children} + + + + {isMobile ? : } + + ); +}; diff --git a/packages/twenty-front/src/modules/settings/components/layout/SettingsSecondaryBar.tsx b/packages/twenty-front/src/modules/settings/components/layout/SettingsSecondaryBar.tsx new file mode 100644 index 00000000000..027edd9e90f --- /dev/null +++ b/packages/twenty-front/src/modules/settings/components/layout/SettingsSecondaryBar.tsx @@ -0,0 +1,29 @@ +import { styled } from '@linaria/react'; +import { type ReactNode } from 'react'; +import { themeCssVariables } from 'twenty-ui/theme-constants'; + +// Bottom separator is an ::after (not border-bottom), and the bar keeps full +// height (default align-items) so the active tab's underline lands exactly on it. +const StyledSecondaryBar = styled.div` + box-sizing: border-box; + display: flex; + flex-shrink: 0; + min-height: ${themeCssVariables.spacing[10]}; + padding: 0 ${themeCssVariables.spacing[3]}; + position: relative; + width: 100%; + + &::after { + background-color: ${themeCssVariables.border.color.light}; + bottom: 0; + content: ''; + height: 1px; + left: 0; + position: absolute; + right: 0; + } +`; + +export const SettingsSecondaryBar = ({ children }: { children: ReactNode }) => ( + {children} +); diff --git a/packages/twenty-front/src/modules/settings/components/layout/SettingsTabBar.tsx b/packages/twenty-front/src/modules/settings/components/layout/SettingsTabBar.tsx new file mode 100644 index 00000000000..3bb97f1d1ca --- /dev/null +++ b/packages/twenty-front/src/modules/settings/components/layout/SettingsTabBar.tsx @@ -0,0 +1,64 @@ +import { useSettingsActiveTabId } from '@/settings/components/layout/useSettingsActiveTabId'; +import { TabListFromUrlOptionalEffect } from '@/ui/layout/tab-list/components/TabListFromUrlOptionalEffect'; +import { TAB_LIST_GAP } from '@/ui/layout/tab-list/constants/TabListGap'; +import { TabListComponentInstanceContext } from '@/ui/layout/tab-list/states/contexts/TabListComponentInstanceContext'; +import { type SingleTabProps } from '@/ui/layout/tab-list/types/SingleTabProps'; +import { styled } from '@linaria/react'; +import { TabButton } from 'twenty-ui/input'; + +type SettingsTabBarProps = { + tabs: SingleTabProps[]; + componentInstanceId: string; +}; + +const StyledTabBar = styled.div` + display: flex; + flex: 1; + gap: ${TAB_LIST_GAP}px; + justify-content: center; + min-width: 0; +`; + +export const SettingsTabBar = ({ + tabs, + componentInstanceId, +}: SettingsTabBarProps) => { + const visibleTabs = tabs.filter((tab) => !tab.hide); + const visibleTabIds = visibleTabs.map((tab) => tab.id); + + const activeTabId = useSettingsActiveTabId( + componentInstanceId, + visibleTabIds, + ); + + if (visibleTabs.length === 0) { + return null; + } + + return ( + + + + {visibleTabs.map((tab) => ( + + ))} + + + ); +}; diff --git a/packages/twenty-front/src/modules/settings/components/layout/SettingsWizardStepBar.tsx b/packages/twenty-front/src/modules/settings/components/layout/SettingsWizardStepBar.tsx new file mode 100644 index 00000000000..7d503a756ff --- /dev/null +++ b/packages/twenty-front/src/modules/settings/components/layout/SettingsWizardStepBar.tsx @@ -0,0 +1,63 @@ +import { styled } from '@linaria/react'; +import { type ReactNode } from 'react'; +import { IconChevronLeft } from 'twenty-ui/display'; +import { LightIconButton } from 'twenty-ui/input'; +import { themeCssVariables } from 'twenty-ui/theme-constants'; + +type SettingsWizardStepBarProps = { + label: ReactNode; + onBack?: () => void; + trailing?: ReactNode; +}; + +const StyledStepBar = styled.div` + align-items: center; + display: grid; + flex: 1; + grid-template-columns: minmax(0, 1fr) auto minmax(0, 1fr); + min-width: 0; +`; + +const StyledLeft = styled.div` + align-items: center; + display: flex; + min-width: 0; +`; + +const StyledLabel = styled.div` + color: ${themeCssVariables.font.color.primary}; + font-size: ${themeCssVariables.font.size.md}; + font-weight: ${themeCssVariables.font.weight.semiBold}; + justify-self: center; + min-width: 0; + text-align: center; +`; + +const StyledRight = styled.div` + align-items: center; + display: flex; + gap: ${themeCssVariables.spacing[2]}; + justify-content: flex-end; + min-width: 0; +`; + +export const SettingsWizardStepBar = ({ + label, + onBack, + trailing, +}: SettingsWizardStepBarProps) => ( + + + {onBack && ( + + )} + + {label} + {trailing} + +); diff --git a/packages/twenty-front/src/modules/settings/components/layout/useSettingsActiveTabId.ts b/packages/twenty-front/src/modules/settings/components/layout/useSettingsActiveTabId.ts new file mode 100644 index 00000000000..9649ec71ef4 --- /dev/null +++ b/packages/twenty-front/src/modules/settings/components/layout/useSettingsActiveTabId.ts @@ -0,0 +1,29 @@ +import { activeTabIdComponentState } from '@/ui/layout/tab-list/states/activeTabIdComponentState'; +import { useAtomComponentStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomComponentStateValue'; +import { useLocation } from 'react-router-dom'; +import { isDefined } from 'twenty-shared/utils'; + +// Resolves the active tab synchronously so the content never renders a null/blank +// frame before the effects settle: the URL hash if it points to a tab (so deep-links +// win), else the stored tab if still valid, else the first tab. +export const useSettingsActiveTabId = ( + componentInstanceId: string, + tabIds: string[], +): string | null => { + const activeTabId = useAtomComponentStateValue( + activeTabIdComponentState, + componentInstanceId, + ); + const { hash } = useLocation(); + + const hashTabId = hash.replace('#', ''); + if (tabIds.includes(hashTabId)) { + return hashTabId; + } + + if (isDefined(activeTabId) && tabIds.includes(activeTabId)) { + return activeTabId; + } + + return tabIds[0] ?? null; +}; diff --git a/packages/twenty-front/src/modules/settings/developers/components/SettingsDevelopersWebhookForm.tsx b/packages/twenty-front/src/modules/settings/developers/components/SettingsDevelopersWebhookForm.tsx index d955f1f52a0..de3a460ef59 100644 --- a/packages/twenty-front/src/modules/settings/developers/components/SettingsDevelopersWebhookForm.tsx +++ b/packages/twenty-front/src/modules/settings/developers/components/SettingsDevelopersWebhookForm.tsx @@ -9,7 +9,7 @@ import { SettingsTextInput } from '@/ui/input/components/SettingsTextInput'; import { TextArea } from '@/ui/input/components/TextArea'; import { ConfirmationModal } from '@/ui/layout/modal/components/ConfirmationModal'; import { useModal } from '@/ui/layout/modal/hooks/useModal'; -import { SubMenuTopBarContainer } from '@/ui/layout/page/components/SubMenuTopBarContainer'; +import { SettingsPageLayout } from '@/settings/components/layout/SettingsPageLayout'; import { Trans, useLingui } from '@lingui/react/macro'; import { SettingsPath } from 'twenty-shared/types'; import { @@ -72,13 +72,12 @@ export const SettingsDevelopersWebhookForm = ({ return ( // oxlint-disable-next-line react/jsx-props-no-spreading - )} - + {!isCreationMode && ( { }; return ( - Workspace, - href: getSettingsPath(SettingsPath.Workspace), + href: getSettingsPath(SettingsPath.General), }, { children: Apps, @@ -286,6 +286,6 @@ export const SettingPublicDomain = () => { /> - + ); }; diff --git a/packages/twenty-front/src/modules/settings/domains/components/SettingsCustomDomain.tsx b/packages/twenty-front/src/modules/settings/domains/components/SettingsCustomDomain.tsx index 94bcefaffa6..6b4486054dd 100644 --- a/packages/twenty-front/src/modules/settings/domains/components/SettingsCustomDomain.tsx +++ b/packages/twenty-front/src/modules/settings/domains/components/SettingsCustomDomain.tsx @@ -7,7 +7,7 @@ import { SettingsDomainRecords } from '@/settings/domains/components/SettingsDom import { useSettingsCustomDomain } from '@/settings/domains/hooks/useSettingsCustomDomain'; import { customDomainRecordsState } from '@/settings/domains/states/customDomainRecordsState'; import { TextInput } from '@/ui/input/components/TextInput'; -import { SubMenuTopBarContainer } from '@/ui/layout/page/components/SubMenuTopBarContainer'; +import { SettingsPageLayout } from '@/settings/components/layout/SettingsPageLayout'; import { useAtomStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomStateValue'; import { Trans, useLingui } from '@lingui/react/macro'; import { styled } from '@linaria/react'; @@ -62,22 +62,22 @@ export const SettingsCustomDomain = () => { } = useSettingsCustomDomain(); return ( - Workspace, - href: getSettingsPath(SettingsPath.Workspace), + href: getSettingsPath(SettingsPath.General), }, { children: General, - href: getSettingsPath(SettingsPath.Workspace), + href: getSettingsPath(SettingsPath.General), }, { children: Custom Domain }, ]} actionButton={ navigate(SettingsPath.Workspace)} + onCancel={() => navigate(SettingsPath.General)} isSaveDisabled={isSaveDisabled} isLoading={isSubmitting} onSave={handleSave} @@ -133,6 +133,6 @@ export const SettingsCustomDomain = () => { )} - + ); }; diff --git a/packages/twenty-front/src/modules/settings/domains/components/SettingsSubdomain.tsx b/packages/twenty-front/src/modules/settings/domains/components/SettingsSubdomain.tsx index 722e15071fa..e0f7fed6bd3 100644 --- a/packages/twenty-front/src/modules/settings/domains/components/SettingsSubdomain.tsx +++ b/packages/twenty-front/src/modules/settings/domains/components/SettingsSubdomain.tsx @@ -8,7 +8,7 @@ import { } from '@/settings/domains/hooks/useSettingsSubdomain'; import { TextInput } from '@/ui/input/components/TextInput'; import { ConfirmationModal } from '@/ui/layout/modal/components/ConfirmationModal'; -import { SubMenuTopBarContainer } from '@/ui/layout/page/components/SubMenuTopBarContainer'; +import { SettingsPageLayout } from '@/settings/components/layout/SettingsPageLayout'; import { useAtomStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomStateValue'; import { Trans, useLingui } from '@lingui/react/macro'; import { styled } from '@linaria/react'; @@ -41,22 +41,22 @@ export const SettingsSubdomain = () => { return ( <> - Workspace, - href: getSettingsPath(SettingsPath.Workspace), + href: getSettingsPath(SettingsPath.General), }, { children: General, - href: getSettingsPath(SettingsPath.Workspace), + href: getSettingsPath(SettingsPath.General), }, { children: Subdomain }, ]} actionButton={ navigate(SettingsPath.Workspace)} + onCancel={() => navigate(SettingsPath.General)} isSaveDisabled={isSaveDisabled} isLoading={isSubmitting} onSave={handleSave} @@ -86,7 +86,7 @@ export const SettingsSubdomain = () => { - + { items: [ { label: t`General`, - path: SettingsPath.Workspace, + path: SettingsPath.General, Icon: IconSettings, isHidden: !permissionMap[PermissionFlagType.WORKSPACE], }, @@ -168,14 +166,12 @@ const useSettingsNavigationItems = (): SettingsNavigationSection[] => { path: SettingsPath.Applications, Icon: IconPlug, isHidden: !permissionMap[PermissionFlagType.APPLICATIONS], - modifier: 'new', }, { label: t`AI`, path: SettingsPath.AI, Icon: IconSparkles, isHidden: !permissionMap[PermissionFlagType.AI], - modifier: 'new', }, { label: t`Email`, @@ -185,13 +181,6 @@ const useSettingsNavigationItems = (): SettingsNavigationSection[] => { !isEmailGroupFeatureEnabled || !permissionMap[PermissionFlagType.WORKSPACE], }, - { - label: t`Security`, - path: SettingsPath.Security, - Icon: IconKey, - isAdvanced: true, - isHidden: !permissionMap[PermissionFlagType.SECURITY], - }, ], }, { @@ -204,9 +193,9 @@ const useSettingsNavigationItems = (): SettingsNavigationSection[] => { isHidden: !isAdminEnabled, }, { - label: t`Updates`, - path: SettingsPath.Updates, - Icon: IconRocket, + label: t`Community`, + path: SettingsPath.Community, + Icon: IconUsers, isHidden: !permissionMap[PermissionFlagType.WORKSPACE], }, { diff --git a/packages/twenty-front/src/modules/settings/roles/role-permissions/object-level-permissions/object-form/components/SettingsRolePermissionsObjectLevelObjectForm.tsx b/packages/twenty-front/src/modules/settings/roles/role-permissions/object-level-permissions/object-form/components/SettingsRolePermissionsObjectLevelObjectForm.tsx index 80c5b603939..2d133125943 100644 --- a/packages/twenty-front/src/modules/settings/roles/role-permissions/object-level-permissions/object-form/components/SettingsRolePermissionsObjectLevelObjectForm.tsx +++ b/packages/twenty-front/src/modules/settings/roles/role-permissions/object-level-permissions/object-form/components/SettingsRolePermissionsObjectLevelObjectForm.tsx @@ -6,10 +6,11 @@ import { SettingsRolePermissionsObjectLevelObjectFieldPermissionTable } from '@/ import { SettingsRolePermissionsObjectLevelObjectFormObjectLevel } from '@/settings/roles/role-permissions/object-level-permissions/object-form/components/SettingsRolePermissionsObjectLevelObjectFormObjectLevel'; import { SettingsRolePermissionsObjectLevelRecordLevelSection } from '@/settings/roles/role-permissions/object-level-permissions/record-level-permissions/components/SettingsRolePermissionsObjectLevelRecordLevelSection'; import { settingsDraftRoleFamilyState } from '@/settings/roles/states/settingsDraftRoleFamilyState'; -import { SubMenuTopBarContainer } from '@/ui/layout/page/components/SubMenuTopBarContainer'; +import { SettingsPageLayout } from '@/settings/components/layout/SettingsPageLayout'; +import { SettingsWizardStepBar } from '@/settings/components/layout/SettingsWizardStepBar'; import { useAtomFamilyStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomFamilyStateValue'; import { t } from '@lingui/core/macro'; -import { useSearchParams } from 'react-router-dom'; +import { useNavigate, useSearchParams } from 'react-router-dom'; import { useAtomStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomStateValue'; import { SettingsPath } from 'twenty-shared/types'; import { @@ -35,6 +36,7 @@ export const SettingsRolePermissionsObjectLevelObjectForm = ({ objectMetadataId, }: SettingsRolePermissionsObjectLevelObjectFormProps) => { const [searchParams] = useSearchParams(); + const navigate = useNavigate(); const fromAgentId = searchParams.get('fromAgent'); const currentWorkspace = useAtomStateValue(currentWorkspaceState); @@ -74,7 +76,7 @@ export const SettingsRolePermissionsObjectLevelObjectForm = ({ ? [ { children: t`Workspace`, - href: getSettingsPath(SettingsPath.Workspace), + href: getSettingsPath(SettingsPath.General), }, { children: t`AI`, @@ -93,7 +95,7 @@ export const SettingsRolePermissionsObjectLevelObjectForm = ({ : [ { children: t`Workspace`, - href: getSettingsPath(SettingsPath.Workspace), + href: getSettingsPath(SettingsPath.General), }, { children: t`Members`, @@ -119,6 +121,13 @@ export const SettingsRolePermissionsObjectLevelObjectForm = ({ ? getSettingsPath(SettingsPath.AiAgentDetail, { agentId: agent.id }) : getSettingsPath(SettingsPath.RoleDetail, { roleId }); + const previousStepPath = `${getSettingsPath(SettingsPath.RoleAddObjectLevel, { + roleId, + })}${fromAgentId ? `?fromAgent=${fromAgentId}` : ''}`; + + const headerTitle = + fromAgentId && isDefined(agent) ? agent.label : settingsDraftRole.label; + const objectPredicates = settingsDraftRole.rowLevelPermissionPredicates?.filter( (predicate) => predicate.objectMetadataId === objectMetadataItem.id, @@ -138,17 +147,23 @@ export const SettingsRolePermissionsObjectLevelObjectForm = ({ const isFinishDisabled = hasInvalidPredicate; return ( - navigate(previousStepPath)} + trailing={ +