feat(settings): move settings chrome into a single rounded card (#21131)

## 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.
This commit is contained in:
Félix Malfait
2026-06-02 14:22:57 +02:00
committed by GitHub
parent e721ebe300
commit 431f6ae98f
107 changed files with 1367 additions and 1008 deletions
@@ -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) => (
>
<Route path={SettingsPath.Accounts} element={<SettingsAccounts />} />
<Route
path={SettingsPath.NewAccount}
element={<SettingsNewAccount />}
/>
<Route
path={SettingsPath.AccountsConfiguration}
element={<SettingsAccountsConfiguration />}
path={SettingsPath.AccountsEmails}
element={<SettingsAccountsEmails />}
/>
<Route
path={SettingsPath.AccountsCalendars}
element={<SettingsAccountsCalendars />}
/>
<Route
path={SettingsPath.AccountsEmails}
element={<SettingsAccountsEmails />}
path={SettingsPath.NewAccount}
element={<SettingsNewAccount />}
/>
<Route
path={SettingsPath.AccountsConfiguration}
element={<SettingsAccountsConfiguration />}
/>
<Route
path={SettingsPath.NewImapSmtpCaldavConnection}
@@ -655,7 +651,7 @@ export const SettingsRoutes = ({ isAdminPageEnabled }: SettingsRoutesProps) => (
/>
}
>
<Route path={SettingsPath.Workspace} element={<SettingsWorkspace />} />
<Route path={SettingsPath.General} element={<SettingsGeneral />} />
<Route
path={SettingsPath.WorkspaceEmail}
element={<SettingsWorkspaceEmail />}
@@ -916,6 +912,13 @@ export const SettingsRoutes = ({ isAdminPageEnabled }: SettingsRoutesProps) => (
/>
</Route>
<Route
path="security"
element={
<Navigate to={getSettingsPath(SettingsPath.Security)} replace />
}
/>
<Route
element={
<SettingsProtectedRouteWrapper
@@ -923,7 +926,6 @@ export const SettingsRoutes = ({ isAdminPageEnabled }: SettingsRoutesProps) => (
/>
}
>
<Route path={SettingsPath.Security} element={<SettingsSecurity />} />
<Route
path={SettingsPath.NewSSOIdentityProvider}
element={<SettingsSecuritySSOIdentifyProvider />}
@@ -1018,7 +1020,7 @@ export const SettingsRoutes = ({ isAdminPageEnabled }: SettingsRoutesProps) => (
/>
}
>
<Route path={SettingsPath.Updates} element={<SettingsUpdates />} />
<Route path={SettingsPath.Community} element={<SettingsCommunity />} />
</Route>
</Routes>
</Suspense>
@@ -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 <SettingsNewAccountSection />;
}
return (
<>
{tabs.length > 1 && (
<StyledCalenderContainer>
<TabList
tabs={tabs}
componentInstanceId={
SETTINGS_ACCOUNT_CALENDAR_CHANNELS_TAB_LIST_COMPONENT_ID
}
/>
</StyledCalenderContainer>
)}
{calendarChannels.map((calendarChannel) => (
<React.Fragment key={calendarChannel.id}>
{(calendarChannels.length === 1 ||
calendarChannel.id === activeTabId) && (
{calendarChannel.id === activeTabId && (
<SettingsAccountsCalendarChannelDetails
calendarChannel={calendarChannel}
/>
)}
</React.Fragment>
))}
{/* TODO: remove or keep? */}
{/* {activeTabId === 'general' && (
<SettingsAccountsCalendarChannelsGeneral />
)} */}
</>
);
};
@@ -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
<FormProvider {...formMethods}>
<SubMenuTopBarContainer
<SettingsPageLayout
title={t`Edit Account`}
links={[
{
@@ -94,7 +94,7 @@ export const SettingsAccountsEditImapSmtpCaldavConnection = () => {
existingProtocols={existingProtocols}
/>
</SettingsPageContainer>
</SubMenuTopBarContainer>
</SettingsPageLayout>
</FormProvider>
);
@@ -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 <SettingsNewAccountSection />;
}
return (
<>
<SettingsAccountsSelectedMessageChannelEffect
messageChannels={messageChannels}
/>
{tabs.length > 1 && (
<StyledMessageContainer>
<TabList
tabs={tabs}
componentInstanceId={
SETTINGS_ACCOUNT_MESSAGE_CHANNELS_TAB_LIST_COMPONENT_ID
}
onChangeTab={handleTabChange}
/>
</StyledMessageContainer>
)}
{messageChannels.map((messageChannel) => (
<React.Fragment key={messageChannel.id}>
{(messageChannels.length === 1 ||
messageChannel.id === activeTabId) && (
{messageChannel.id === activeTabId && (
<SettingsAccountsMessageChannelDetails
messageChannel={messageChannel}
/>
@@ -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 (
<SubMenuTopBarContainer
<SettingsPageLayout
title={t`New Email Handle`}
links={[
{
children: t`Workspace`,
href: getSettingsPath(SettingsPath.Workspace),
href: getSettingsPath(SettingsPath.General),
},
{
children: t`Email`,
@@ -84,6 +84,6 @@ export const SettingsAccountsNewEmailGroupChannel = () => {
/>
</Section>
</SettingsPageContainer>
</SubMenuTopBarContainer>
</SettingsPageLayout>
);
};
@@ -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
<FormProvider {...formMethods}>
<SubMenuTopBarContainer
<SettingsPageLayout
title={t`New Account`}
links={[
{
@@ -56,7 +56,7 @@ export const SettingsAccountsNewImapSmtpCaldavConnection = () => {
<SettingsPageContainer>
<SettingsAccountsConnectionForm control={control} isEditing={false} />
</SettingsPageContainer>
</SubMenuTopBarContainer>
</SettingsPageLayout>
</FormProvider>
);
};
@@ -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;
@@ -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]};
@@ -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 (
<StyledHeader>
<StyledLeft>
{!isNavigationDrawerExpanded && (
<NavigationDrawerCollapseButton direction="right" />
)}
<Breadcrumb links={links} />
</StyledLeft>
<StyledTitle>
{!isMobile && isDefined(title) && title}
{!isMobile && tag}
</StyledTitle>
<StyledRight>{actionButton}</StyledRight>
</StyledHeader>
);
};
@@ -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 (
<StyledRoot isMobile={isMobile}>
<StyledMainCardWrapper>
<StyledCard>
<SettingsPageHeader
links={links}
title={title}
tag={tag}
actionButton={actionButton}
/>
{isDefined(secondaryBar) && (
<SettingsSecondaryBar>{secondaryBar}</SettingsSecondaryBar>
)}
<StyledBodyContent>
<InformationBannerWrapper />
{children}
</StyledBodyContent>
</StyledCard>
</StyledMainCardWrapper>
{isMobile ? <CommandMenuForMobile /> : <SidePanelForDesktop />}
</StyledRoot>
);
};
@@ -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 }) => (
<StyledSecondaryBar>{children}</StyledSecondaryBar>
);
@@ -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 (
<TabListComponentInstanceContext.Provider
value={{ instanceId: componentInstanceId }}
>
<TabListFromUrlOptionalEffect
isInSidePanel={false}
tabListIds={visibleTabIds}
/>
<StyledTabBar>
{visibleTabs.map((tab) => (
<TabButton
key={tab.id}
id={tab.id}
title={tab.title}
LeftIcon={tab.Icon}
logo={tab.logo}
active={tab.id === activeTabId}
disabled={tab.disabled}
pill={tab.pill}
to={`#${tab.id}`}
tooltipContent={tab.tooltipContent}
/>
))}
</StyledTabBar>
</TabListComponentInstanceContext.Provider>
);
};
@@ -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) => (
<StyledStepBar>
<StyledLeft>
{onBack && (
<LightIconButton
Icon={IconChevronLeft}
size="small"
accent="tertiary"
onClick={onBack}
/>
)}
</StyledLeft>
<StyledLabel>{label}</StyledLabel>
<StyledRight>{trailing}</StyledRight>
</StyledStepBar>
);
@@ -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;
};
@@ -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
<FormProvider {...formConfig}>
<SubMenuTopBarContainer
<SettingsPageLayout
title={getTitle()}
reserveTitleSpace
links={[
{
children: t`Workspace`,
href: getSettingsPath(SettingsPath.Workspace),
href: getSettingsPath(SettingsPath.General),
},
{
children: t`APIs & Webhooks`,
@@ -194,7 +193,7 @@ export const SettingsDevelopersWebhookForm = ({
</Section>
)}
</SettingsPageContainer>
</SubMenuTopBarContainer>
</SettingsPageLayout>
{!isCreationMode && (
<ConfirmationModal
confirmationPlaceholder={t`yes`}
@@ -3,7 +3,7 @@ import { H2Title, IconReload, IconTrash } from 'twenty-ui/display';
import { Trans, useLingui } from '@lingui/react/macro';
import { getSettingsPath, isDefined } from 'twenty-shared/utils';
import { SettingsPath } from 'twenty-shared/types';
import { SubMenuTopBarContainer } from '@/ui/layout/page/components/SubMenuTopBarContainer';
import { SettingsPageLayout } from '@/settings/components/layout/SettingsPageLayout';
import { SettingsPageContainer } from '@/settings/components/SettingsPageContainer';
import { Select } from '@/ui/input/components/Select';
import { TextInput } from '@/ui/input/components/TextInput';
@@ -194,12 +194,12 @@ export const SettingPublicDomain = () => {
};
return (
<SubMenuTopBarContainer
<SettingsPageLayout
title={t`Public domain`}
links={[
{
children: <Trans>Workspace</Trans>,
href: getSettingsPath(SettingsPath.Workspace),
href: getSettingsPath(SettingsPath.General),
},
{
children: <Trans>Apps</Trans>,
@@ -286,6 +286,6 @@ export const SettingPublicDomain = () => {
/>
</Section>
</SettingsPageContainer>
</SubMenuTopBarContainer>
</SettingsPageLayout>
);
};
@@ -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 (
<SubMenuTopBarContainer
<SettingsPageLayout
title={t`Custom Domain`}
links={[
{
children: <Trans>Workspace</Trans>,
href: getSettingsPath(SettingsPath.Workspace),
href: getSettingsPath(SettingsPath.General),
},
{
children: <Trans>General</Trans>,
href: getSettingsPath(SettingsPath.Workspace),
href: getSettingsPath(SettingsPath.General),
},
{ children: <Trans>Custom Domain</Trans> },
]}
actionButton={
<SaveAndCancelButtons
onCancel={() => navigate(SettingsPath.Workspace)}
onCancel={() => navigate(SettingsPath.General)}
isSaveDisabled={isSaveDisabled}
isLoading={isSubmitting}
onSave={handleSave}
@@ -133,6 +133,6 @@ export const SettingsCustomDomain = () => {
)}
</Section>
</SettingsPageContainer>
</SubMenuTopBarContainer>
</SettingsPageLayout>
);
};
@@ -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 (
<>
<SubMenuTopBarContainer
<SettingsPageLayout
title={t`Subdomain`}
links={[
{
children: <Trans>Workspace</Trans>,
href: getSettingsPath(SettingsPath.Workspace),
href: getSettingsPath(SettingsPath.General),
},
{
children: <Trans>General</Trans>,
href: getSettingsPath(SettingsPath.Workspace),
href: getSettingsPath(SettingsPath.General),
},
{ children: <Trans>Subdomain</Trans> },
]}
actionButton={
<SaveAndCancelButtons
onCancel={() => navigate(SettingsPath.Workspace)}
onCancel={() => navigate(SettingsPath.General)}
isSaveDisabled={isSaveDisabled}
isLoading={isSubmitting}
onSave={handleSave}
@@ -86,7 +86,7 @@ export const SettingsSubdomain = () => {
</StyledDomainFormWrapper>
</Section>
</SettingsPageContainer>
</SubMenuTopBarContainer>
</SettingsPageLayout>
<ConfirmationModal
modalInstanceId={SUBDOMAIN_CHANGE_CONFIRMATION_MODAL_ID}
title={t`Change subdomain?`}
@@ -26,12 +26,10 @@ import {
IconDoorEnter,
IconHelpCircle,
IconHierarchy2,
IconKey,
IconLayout,
IconMail,
IconMessage,
IconPlug,
IconRocket,
IconServer,
IconSettings,
IconSparkles,
@@ -121,7 +119,7 @@ const useSettingsNavigationItems = (): SettingsNavigationSection[] => {
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],
},
{
@@ -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 (
<SubMenuTopBarContainer
title={t`2. Set ${objectLabelPlural} permissions`}
<SettingsPageLayout
title={headerTitle}
links={breadcrumbLinks}
actionButton={
<Button
title={t`Finish`}
variant="secondary"
size="small"
accent="blue"
to={isFinishDisabled ? undefined : finishButtonPath}
disabled={isFinishDisabled}
secondaryBar={
<SettingsWizardStepBar
label={t`2. Set ${objectLabelPlural} permissions`}
onBack={() => navigate(previousStepPath)}
trailing={
<Button
title={t`Finish`}
variant="primary"
size="small"
accent="blue"
to={isFinishDisabled ? undefined : finishButtonPath}
disabled={isFinishDisabled}
/>
}
/>
}
>
@@ -167,6 +182,6 @@ export const SettingsRolePermissionsObjectLevelObjectForm = ({
hasOrganizationPlan={isRLSBillingEntitlementEnabled}
/>
</SettingsPageContainer>
</SubMenuTopBarContainer>
</SettingsPageLayout>
);
};
@@ -10,8 +10,8 @@ import { settingsDraftRoleFamilyState } from '@/settings/roles/states/settingsDr
import { settingsPersistedRoleFamilyState } from '@/settings/roles/states/settingsPersistedRoleFamilyState';
import { settingsRolesIsLoadingState } from '@/settings/roles/states/settingsRolesIsLoadingState';
import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
import { SubMenuTopBarContainer } from '@/ui/layout/page/components/SubMenuTopBarContainer';
import { TabList } from '@/ui/layout/tab-list/components/TabList';
import { SettingsPageLayout } from '@/settings/components/layout/SettingsPageLayout';
import { SettingsTabBar } from '@/settings/components/layout/SettingsTabBar';
import { activeTabIdComponentState } from '@/ui/layout/tab-list/states/activeTabIdComponentState';
import { useAtomComponentStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomComponentStateValue';
import { useLoadCurrentUser } from '@/users/hooks/useLoadCurrentUser';
@@ -134,12 +134,20 @@ export const SettingsRole = ({ roleId, isCreateMode }: SettingsRoleProps) => {
}
return (
<SubMenuTopBarContainer
<SettingsPageLayout
title={<SettingsRoleLabelContainer roleId={roleId} />}
secondaryBar={
<SettingsTabBar
tabs={tabs}
componentInstanceId={
SETTINGS_ROLE_DETAIL_TABS.COMPONENT_INSTANCE_ID + '-' + roleId
}
/>
}
links={[
{
children: t`Workspace`,
href: getSettingsPath(SettingsPath.Workspace),
href: getSettingsPath(SettingsPath.General),
},
{
children: t`Members`,
@@ -165,13 +173,6 @@ export const SettingsRole = ({ roleId, isCreateMode }: SettingsRoleProps) => {
}
>
<SettingsPageContainer>
<TabList
tabs={tabs}
className="tab-list"
componentInstanceId={
SETTINGS_ROLE_DETAIL_TABS.COMPONENT_INSTANCE_ID + '-' + roleId
}
/>
{activeTabId === SETTINGS_ROLE_DETAIL_TABS.TABS_IDS.ASSIGNMENT && (
<SettingsRoleAssignment roleId={roleId} isCreateMode={isCreateMode} />
)}
@@ -189,6 +190,6 @@ export const SettingsRole = ({ roleId, isCreateMode }: SettingsRoleProps) => {
/>
)}
</SettingsPageContainer>
</SubMenuTopBarContainer>
</SettingsPageLayout>
);
};
@@ -1,5 +1,5 @@
import { styled } from '@linaria/react';
import { Trans, useLingui } from '@lingui/react/macro';
import { useLingui } from '@lingui/react/macro';
import { Link } from 'react-router-dom';
import { useDebouncedCallback } from 'use-debounce';
@@ -12,7 +12,6 @@ import { SettingsEnterpriseFeatureGateCard } from '@/settings/components/Setting
import { SettingsOptionCardContentButton } from '@/settings/components/SettingsOptions/SettingsOptionCardContentButton';
import { SettingsOptionCardContentCounter } from '@/settings/components/SettingsOptions/SettingsOptionCardContentCounter';
import { SettingsOptionCardContentToggle } from '@/settings/components/SettingsOptions/SettingsOptionCardContentToggle';
import { SettingsPageContainer } from '@/settings/components/SettingsPageContainer';
import { SettingsRoleDefaultRole } from '@/settings/roles/components/SettingsRolesDefaultRole';
import { SettingsRolesQueryEffect } from '@/settings/roles/components/SettingsRolesQueryEffect';
import { useSettingsAllRoles } from '@/settings/roles/hooks/useSettingsAllRoles';
@@ -23,7 +22,6 @@ import { SettingsSecurityEditableProfileFields } from '@/settings/security/compo
import { SSOIdentitiesProvidersState } from '@/settings/security/states/SSOIdentitiesProvidersState';
import { ToggleImpersonate } from '@/settings/workspace/components/ToggleImpersonate';
import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
import { SubMenuTopBarContainer } from '@/ui/layout/page/components/SubMenuTopBarContainer';
import { useAtomState } from '@/ui/utilities/state/jotai/hooks/useAtomState';
import { useAtomStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomStateValue';
import { CombinedGraphQLErrors } from '@apollo/client/errors';
@@ -69,7 +67,7 @@ const StyledLinkContainer = styled.div`
}
`;
export const SettingsSecurity = () => {
export const SettingsSecuritySettings = () => {
const { t } = useLingui();
const { enqueueErrorSnackBar } = useSnackBar();
@@ -198,80 +196,14 @@ export const SettingsSecurity = () => {
const isEventLogsEnabled = hasEnterpriseAccess && isClickHouseConfigured;
return (
<SubMenuTopBarContainer
title={t`Security`}
links={[
{
children: <Trans>Workspace</Trans>,
href: getSettingsPath(SettingsPath.Workspace),
},
{ children: <Trans>Security</Trans> },
]}
>
<SettingsPageContainer>
<SettingsRolesQueryEffect />
<StyledMainContent>
<StyledSectionContainer>
<Section>
<H2Title
title={t`SSO`}
description={t`Configure an SSO connection`}
adornment={
<Tag
text={t`Enterprise`}
color="transparent"
Icon={IconLock}
variant="border"
/>
}
/>
<SettingsSSOIdentitiesProvidersListCard />
</Section>
</StyledSectionContainer>
<Section>
<StyledContainer>
<H2Title
title={t`Authentication`}
description={t`Customize your workspace security`}
/>
<SettingsSecurityAuthProvidersOptionsList />
</StyledContainer>
</Section>
<Section>
<StyledContainer>
<H2Title
title={t`Editable Profile Fields`}
description={t`Choose which profile fields users with the Edit Profile permission can modify`}
/>
<SettingsSecurityEditableProfileFields />
</StyledContainer>
</Section>
<SettingsRoleDefaultRole roles={roles} />
{shouldShowBypassSection && (
<Section>
<StyledContainer>
<H2Title
title={t`SSO Bypass`}
description={t`Configure fallback login methods for users with SSO bypass permissions`}
/>
<SettingsSecurityAuthBypassOptionsList />
</StyledContainer>
</Section>
)}
{isMultiWorkspaceEnabled && (
<Section>
<H2Title
title={t`Support`}
description={t`Manage support access settings`}
/>
<ToggleImpersonate />
</Section>
)}
<>
<SettingsRolesQueryEffect />
<StyledMainContent>
<StyledSectionContainer>
<Section>
<H2Title
title={t`Audit Logs`}
description={t`View workspace activity logs`}
title={t`SSO`}
description={t`Configure an SSO connection`}
adornment={
<Tag
text={t`Enterprise`}
@@ -281,86 +213,138 @@ export const SettingsSecurity = () => {
/>
}
/>
{hasEnterpriseAccess ? (
<Card rounded>
<SettingsOptionCardContentButton
Icon={IconHistory}
title={t`Workspace Events`}
description={
!isClickHouseConfigured
? t`ClickHouse is required for audit logs. Contact your administrator.`
: t`View and filter events, page views, object changes`
}
Button={
<StyledLinkContainer>
<Link
to={getSettingsPath(SettingsPath.EventLogs)}
data-disabled={!isEventLogsEnabled}
>
<Button
title={t`View Logs`}
variant="secondary"
size="small"
disabled={!isEventLogsEnabled}
/>
</Link>
</StyledLinkContainer>
}
/>
{isEventLogsEnabled && (
<>
<Separator />
<SettingsOptionCardContentCounter
Icon={IconClockHour8}
title={t`Log retention`}
description={t`Number of days to retain audit logs (30-1095 days)`}
value={currentWorkspace?.eventLogRetentionDays ?? 90}
onChange={handleEventLogRetentionDaysChange}
minValue={30}
maxValue={1095}
showButtons={false}
/>
</>
)}
</Card>
) : (
<SettingsEnterpriseFeatureGateCard
title={t`Enterprise feature`}
description={t`Upgrade to Enterprise to access audit logs.`}
buttonTitle={t`Activate`}
/>
)}
<SettingsSSOIdentitiesProvidersListCard />
</Section>
</StyledSectionContainer>
<Section>
<StyledContainer>
<H2Title
title={t`Authentication`}
description={t`Customize your workspace security`}
/>
<SettingsSecurityAuthProvidersOptionsList />
</StyledContainer>
</Section>
<Section>
<StyledContainer>
<H2Title
title={t`Editable Profile Fields`}
description={t`Choose which profile fields users with the Edit Profile permission can modify`}
/>
<SettingsSecurityEditableProfileFields />
</StyledContainer>
</Section>
<SettingsRoleDefaultRole roles={roles} />
{shouldShowBypassSection && (
<Section>
<StyledContainer>
<H2Title
title={t`SSO Bypass`}
description={t`Configure fallback login methods for users with SSO bypass permissions`}
/>
<SettingsSecurityAuthBypassOptionsList />
</StyledContainer>
</Section>
)}
{isMultiWorkspaceEnabled && (
<Section>
<H2Title
title={t`Other`}
description={t`Other security settings`}
title={t`Support`}
description={t`Manage support access settings`}
/>
<Card rounded>
<SettingsOptionCardContentCounter
Icon={IconTrash}
title={t`Erasure of soft-deleted records`}
description={t`Permanent deletion. Enter the number of days.`}
value={currentWorkspace?.trashRetentionDays ?? 14}
onChange={handleTrashRetentionDaysChange}
minValue={0}
showButtons={false}
/>
<Separator />
<SettingsOptionCardContentToggle
Icon={IconMail}
title={t`Sync Internal Emails`}
description={t`Include emails where all participants share the same domain.`}
checked={
currentWorkspace?.isInternalMessagesImportEnabled ?? false
}
onChange={handleSyncInternalEmailsChange}
advancedMode
/>
</Card>
<ToggleImpersonate />
</Section>
</StyledMainContent>
</SettingsPageContainer>
</SubMenuTopBarContainer>
)}
<Section>
<H2Title
title={t`Audit Logs`}
description={t`View workspace activity logs`}
adornment={
<Tag
text={t`Enterprise`}
color="transparent"
Icon={IconLock}
variant="border"
/>
}
/>
{hasEnterpriseAccess ? (
<Card rounded>
<SettingsOptionCardContentButton
Icon={IconHistory}
title={t`Workspace Events`}
description={
!isClickHouseConfigured
? t`ClickHouse is required for audit logs. Contact your administrator.`
: t`View and filter events, page views, object changes`
}
Button={
<StyledLinkContainer>
<Link
to={getSettingsPath(SettingsPath.EventLogs)}
data-disabled={!isEventLogsEnabled}
>
<Button
title={t`View Logs`}
variant="secondary"
size="small"
disabled={!isEventLogsEnabled}
/>
</Link>
</StyledLinkContainer>
}
/>
{isEventLogsEnabled && (
<>
<Separator />
<SettingsOptionCardContentCounter
Icon={IconClockHour8}
title={t`Log retention`}
description={t`Number of days to retain audit logs (30-1095 days)`}
value={currentWorkspace?.eventLogRetentionDays ?? 90}
onChange={handleEventLogRetentionDaysChange}
minValue={30}
maxValue={1095}
showButtons={false}
/>
</>
)}
</Card>
) : (
<SettingsEnterpriseFeatureGateCard
title={t`Enterprise feature`}
description={t`Upgrade to Enterprise to access audit logs.`}
buttonTitle={t`Activate`}
/>
)}
</Section>
<Section>
<H2Title title={t`Other`} description={t`Other security settings`} />
<Card rounded>
<SettingsOptionCardContentCounter
Icon={IconTrash}
title={t`Erasure of soft-deleted records`}
description={t`Permanent deletion. Enter the number of days.`}
value={currentWorkspace?.trashRetentionDays ?? 14}
onChange={handleTrashRetentionDaysChange}
minValue={0}
showButtons={false}
/>
<Separator />
<SettingsOptionCardContentToggle
Icon={IconMail}
title={t`Sync Internal Emails`}
description={t`Include emails where all participants share the same domain.`}
checked={
currentWorkspace?.isInternalMessagesImportEnabled ?? false
}
onChange={handleSyncInternalEmailsChange}
advancedMode
/>
</Card>
</Section>
</StyledMainContent>
</>
);
};
@@ -1,84 +0,0 @@
import { InformationBannerWrapper } from '@/information-banner/components/InformationBannerWrapper';
import { MainContainerLayoutWithSidePanel } from '@/object-record/components/MainContainerLayoutWithSidePanel';
import {
Breadcrumb,
type BreadcrumbProps,
} from '@/ui/navigation/bread-crumb/components/Breadcrumb';
import { isDefined } from 'twenty-shared/utils';
import { styled } from '@linaria/react';
import { type JSX, type ReactNode } from 'react';
import { PageHeader } from './PageHeader';
import { themeCssVariables } from 'twenty-ui/theme-constants';
type SubMenuTopBarContainerProps = {
children: JSX.Element | JSX.Element[];
title?: string | JSX.Element;
reserveTitleSpace?: boolean;
actionButton?: ReactNode;
className?: string;
links: BreadcrumbProps['links'];
tag?: JSX.Element;
};
const SETTINGS_CONTENT_MAX_WIDTH = 760;
const StyledContainer = styled.div`
display: flex;
flex-direction: column;
width: 100%;
`;
// flex: 1 + min-height: 0 are required for PagePanel's overflow chain — the
// child must participate in the flex height calc rather than collapse to content.
const StyledBodyContentWrapper = styled.div`
display: flex;
flex: 1;
flex-direction: column;
margin: 0 auto;
max-width: ${SETTINGS_CONTENT_MAX_WIDTH}px;
min-height: 0;
width: 100%;
`;
const StyledTitle = styled.span<{ reserveTitleSpace?: boolean }>`
color: ${themeCssVariables.font.color.primary};
display: flex;
font-size: ${themeCssVariables.font.size.lg};
font-weight: ${themeCssVariables.font.weight.semiBold};
gap: ${themeCssVariables.spacing[2]};
line-height: 1.2;
margin: ${themeCssVariables.spacing[8]} ${themeCssVariables.spacing[8]}
${themeCssVariables.spacing[2]};
min-height: ${({ reserveTitleSpace }) =>
reserveTitleSpace ? themeCssVariables.spacing[5] : 'none'};
`;
export const SubMenuTopBarContainer = ({
children,
title,
tag,
reserveTitleSpace,
actionButton,
className,
links,
}: SubMenuTopBarContainerProps) => {
return (
<StyledContainer className={className}>
<PageHeader title={<Breadcrumb links={links} />}>
{actionButton}
</PageHeader>
<MainContainerLayoutWithSidePanel>
<StyledBodyContentWrapper>
<InformationBannerWrapper />
{(isDefined(title) || reserveTitleSpace === true) && (
<StyledTitle reserveTitleSpace={reserveTitleSpace}>
{title}
{tag}
</StyledTitle>
)}
{children}
</StyledBodyContentWrapper>
</MainContainerLayoutWithSidePanel>
</StyledContainer>
);
};
@@ -164,7 +164,7 @@ export const Settings: Story = {
<NavigationDrawerSectionTitle label="Workspace" />
<NavigationDrawerItem
label="General"
to={getSettingsPath(SettingsPath.Workspace)}
to={getSettingsPath(SettingsPath.General)}
Icon={IconSettings}
/>
<NavigationDrawerItem
@@ -1,58 +0,0 @@
import { useLingui } from '@lingui/react/macro';
import { SettingsPageContainer } from '@/settings/components/SettingsPageContainer';
import { SettingsWorkspaceDomainCard } from '@/settings/domains/components/SettingsWorkspaceDomainCard';
import { DeleteWorkspace } from '@/settings/profile/components/DeleteWorkspace';
import { NameField } from '@/settings/workspace/components/NameField';
import { WorkspaceLogoUploader } from '@/settings/workspace/components/WorkspaceLogoUploader';
import { SubMenuTopBarContainer } from '@/ui/layout/page/components/SubMenuTopBarContainer';
import { isMultiWorkspaceEnabledState } from '@/client-config/states/isMultiWorkspaceEnabledState';
import { useAtomStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomStateValue';
import { SettingsPath } from 'twenty-shared/types';
import { getSettingsPath } from 'twenty-shared/utils';
import { H2Title } from 'twenty-ui/display';
import { Section } from 'twenty-ui/layout';
export const SettingsWorkspace = () => {
const { t } = useLingui();
const isMultiWorkspaceEnabled = useAtomStateValue(
isMultiWorkspaceEnabledState,
);
return (
<SubMenuTopBarContainer
title={t`General`}
links={[
{
children: t`Workspace`,
href: getSettingsPath(SettingsPath.Workspace),
},
{ children: t`General` },
]}
>
<SettingsPageContainer>
<Section>
<H2Title title={t`Picture`} />
<WorkspaceLogoUploader />
</Section>
<Section>
<H2Title title={t`Name`} description={t`Name of your workspace`} />
<NameField />
</Section>
{isMultiWorkspaceEnabled && (
<Section>
<H2Title
title={t`Workspace Domain`}
description={t`Edit your subdomain name or set a custom domain.`}
/>
<SettingsWorkspaceDomainCard />
</Section>
)}
<Section>
<DeleteWorkspace />
</Section>
</SettingsPageContainer>
</SubMenuTopBarContainer>
);
};
@@ -11,7 +11,7 @@ import { sleep } from '~/utils/sleep';
import { getSettingsPath } from 'twenty-shared/utils';
import { SettingsBilling } from '~/pages/settings/SettingsBilling';
import { SettingsBilling } from '~/pages/settings/billing/SettingsBilling';
import { WorkspaceDecorator } from '~/testing/decorators/WorkspaceDecorator';
const meta: Meta<PageDecoratorArgs> = {
@@ -6,11 +6,11 @@ import {
} from '~/testing/decorators/PageDecorator';
import { graphqlMocks } from '~/testing/graphqlMocks';
import { SettingsWorkspace } from '~/pages/settings/SettingsWorkspace';
import { SettingsGeneral } from '~/pages/settings/general/SettingsGeneral';
const meta: Meta<PageDecoratorArgs> = {
title: 'Pages/Settings/SettingsWorkspace',
component: SettingsWorkspace,
title: 'Pages/Settings/SettingsGeneral',
component: SettingsGeneral,
decorators: [PageDecorator],
args: { routePath: '/settings/general' },
parameters: {
@@ -20,6 +20,6 @@ const meta: Meta<PageDecoratorArgs> = {
export default meta;
export type Story = StoryObj<typeof SettingsWorkspace>;
export type Story = StoryObj<typeof SettingsGeneral>;
export const Default: Story = {};
@@ -6,7 +6,7 @@ import {
} from '~/testing/decorators/PageDecorator';
import { graphqlMocks } from '~/testing/graphqlMocks';
import { SettingsProfile } from '~/pages/settings/SettingsProfile';
import { SettingsProfile } from '~/pages/settings/profile/SettingsProfile';
const meta: Meta<PageDecoratorArgs> = {
title: 'Pages/Settings/SettingsProfile',
@@ -1,12 +1,12 @@
import { SettingsPath } from 'twenty-shared/types';
import { SettingsSectionSkeletonLoader } from '@/settings/components/SettingsSectionSkeletonLoader';
import { SettingsAccountsBlocklistSection } from '@/settings/accounts/components/SettingsAccountsBlocklistSection';
import { SettingsAccountsConnectedAccountsListCard } from '@/settings/accounts/components/SettingsAccountsConnectedAccountsListCard';
import { SettingsAccountsSettingsSection } from '@/settings/accounts/components/SettingsAccountsSettingsSection';
import { useMyConnectedAccounts } from '@/settings/accounts/hooks/useMyConnectedAccounts';
import { SettingsPageContainer } from '@/settings/components/SettingsPageContainer';
import { SubMenuTopBarContainer } from '@/ui/layout/page/components/SubMenuTopBarContainer';
import { SettingsSectionSkeletonLoader } from '@/settings/components/SettingsSectionSkeletonLoader';
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 } from 'twenty-ui/display';
import { Section } from 'twenty-ui/layout';
@@ -17,7 +17,7 @@ export const SettingsAccounts = () => {
const { accounts: allAccounts, loading } = useMyConnectedAccounts();
return (
<SubMenuTopBarContainer
<SettingsPageLayout
title={t`Account`}
links={[
{
@@ -46,6 +46,6 @@ export const SettingsAccounts = () => {
</>
)}
</SettingsPageContainer>
</SubMenuTopBarContainer>
</SettingsPageLayout>
);
};
@@ -1,34 +1,80 @@
import { SettingsAccountsCalendarChannelsContainer } from '@/settings/accounts/components/SettingsAccountsCalendarChannelsContainer';
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 { SettingsPageContainer } from '@/settings/components/SettingsPageContainer';
import { SubMenuTopBarContainer } from '@/ui/layout/page/components/SubMenuTopBarContainer';
import { Trans, useLingui } from '@lingui/react/macro';
import { SettingsPath } from 'twenty-shared/types';
import { SettingsSectionSkeletonLoader } from '@/settings/components/SettingsSectionSkeletonLoader';
import { SettingsPageLayout } from '@/settings/components/layout/SettingsPageLayout';
import { SettingsTabBar } from '@/settings/components/layout/SettingsTabBar';
import { useLingui } from '@lingui/react/macro';
import { useMemo } from 'react';
import { CalendarChannelSyncStage, SettingsPath } from 'twenty-shared/types';
import { getSettingsPath } from 'twenty-shared/utils';
import { Section } from 'twenty-ui/layout';
export const SettingsAccountsCalendars = () => {
const { t } = useLingui();
const { channels: allCalendarChannels, loading } = useMyCalendarChannels();
const calendarChannels = useMemo(
() =>
allCalendarChannels.filter(
(channel) =>
channel.syncStage !== CalendarChannelSyncStage.PENDING_CONFIGURATION,
),
[allCalendarChannels],
);
const tabs = calendarChannels.map((calendarChannel) => ({
id: calendarChannel.id,
title: calendarChannel.handle,
}));
const renderContent = () => {
if (loading) {
return <SettingsSectionSkeletonLoader />;
}
if (calendarChannels.length === 0) {
return <SettingsNewAccountSection />;
}
return (
<Section>
<SettingsAccountsCalendarChannelsContainer
calendarChannels={calendarChannels}
/>
</Section>
);
};
return (
<SubMenuTopBarContainer
<SettingsPageLayout
title={t`Calendars`}
links={[
{
children: <Trans>User</Trans>,
children: t`User`,
href: getSettingsPath(SettingsPath.ProfilePage),
},
{
children: <Trans>Accounts</Trans>,
children: t`Accounts`,
href: getSettingsPath(SettingsPath.Accounts),
},
{ children: <Trans>Calendars</Trans> },
{ children: t`Calendars` },
]}
secondaryBar={
tabs.length > 1 ? (
<SettingsTabBar
tabs={tabs}
componentInstanceId={
SETTINGS_ACCOUNT_CALENDAR_CHANNELS_TAB_LIST_COMPONENT_ID
}
/>
) : undefined
}
>
<SettingsPageContainer>
<Section>
<SettingsAccountsCalendarChannelsContainer />
</Section>
</SettingsPageContainer>
</SubMenuTopBarContainer>
<SettingsPageContainer>{renderContent()}</SettingsPageContainer>
</SettingsPageLayout>
);
};
@@ -4,7 +4,7 @@ import { type CalendarChannel } from '@/accounts/types/CalendarChannel';
import { type MessageChannel } from '@/accounts/types/MessageChannel';
import { SettingsAccountsCalendarChannelDetails } from '@/settings/accounts/components/SettingsAccountsCalendarChannelDetails';
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, isDefined } from 'twenty-shared/utils';
import { IconDeviceFloppy } from 'twenty-ui/display';
@@ -29,7 +29,7 @@ export const SettingsAccountsConfigurationStepCalendar = ({
const stepTitle = t`${stepNumber}. Calendar`;
return (
<SubMenuTopBarContainer
<SettingsPageLayout
title={stepTitle}
links={[
{
@@ -61,6 +61,6 @@ export const SettingsAccountsConfigurationStepCalendar = ({
calendarChannel={calendarChannel}
/>
</SettingsPageContainer>
</SubMenuTopBarContainer>
</SettingsPageLayout>
);
};
@@ -3,7 +3,7 @@ import { Trans, useLingui } from '@lingui/react/macro';
import { type MessageChannel } from '@/accounts/types/MessageChannel';
import { SettingsAccountsMessageChannelDetails } from '@/settings/accounts/components/SettingsAccountsMessageChannelDetails';
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';
import { IconChevronRight, IconPlus } from 'twenty-ui/display';
@@ -27,7 +27,7 @@ export const SettingsAccountsConfigurationStepEmail = ({
const { t } = useLingui();
return (
<SubMenuTopBarContainer
<SettingsPageLayout
title={t`1. Email`}
links={[
{
@@ -71,6 +71,6 @@ export const SettingsAccountsConfigurationStepEmail = ({
messageChannel={messageChannel}
/>
</SettingsPageContainer>
</SubMenuTopBarContainer>
</SettingsPageLayout>
);
};
@@ -1,16 +1,62 @@
import { SettingsAccountsMessageChannelsContainer } from '@/settings/accounts/components/SettingsAccountsMessageChannelsContainer';
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 { SettingsPageContainer } from '@/settings/components/SettingsPageContainer';
import { SubMenuTopBarContainer } from '@/ui/layout/page/components/SubMenuTopBarContainer';
import { SettingsSectionSkeletonLoader } from '@/settings/components/SettingsSectionSkeletonLoader';
import { SettingsPageLayout } from '@/settings/components/layout/SettingsPageLayout';
import { SettingsTabBar } from '@/settings/components/layout/SettingsTabBar';
import { useLingui } from '@lingui/react/macro';
import { SettingsPath } from 'twenty-shared/types';
import { useMemo } from 'react';
import {
MessageChannelSyncStage,
MessageChannelType,
SettingsPath,
} from 'twenty-shared/types';
import { getSettingsPath } from 'twenty-shared/utils';
import { Section } from 'twenty-ui/layout';
export const SettingsAccountsEmails = () => {
const { t } = useLingui();
const { channels: allMessageChannels, loading } = useMyMessageChannels();
const messageChannels = useMemo(
() =>
allMessageChannels.filter(
(channel) =>
channel.isSyncEnabled &&
channel.syncStage !== MessageChannelSyncStage.PENDING_CONFIGURATION &&
channel.type !== MessageChannelType.EMAIL_GROUP,
),
[allMessageChannels],
);
const tabs = messageChannels.map((messageChannel) => ({
id: messageChannel.id,
title: messageChannel.handle,
}));
const renderContent = () => {
if (loading) {
return <SettingsSectionSkeletonLoader />;
}
if (messageChannels.length === 0) {
return <SettingsNewAccountSection />;
}
return (
<Section>
<SettingsAccountsMessageChannelsContainer
messageChannels={messageChannels}
/>
</Section>
);
};
return (
<SubMenuTopBarContainer
<SettingsPageLayout
title={t`Emails`}
links={[
{
@@ -23,12 +69,18 @@ export const SettingsAccountsEmails = () => {
},
{ children: t`Emails` },
]}
secondaryBar={
tabs.length > 1 ? (
<SettingsTabBar
tabs={tabs}
componentInstanceId={
SETTINGS_ACCOUNT_MESSAGE_CHANNELS_TAB_LIST_COMPONENT_ID
}
/>
) : undefined
}
>
<SettingsPageContainer>
<Section>
<SettingsAccountsMessageChannelsContainer />
</Section>
</SettingsPageContainer>
</SubMenuTopBarContainer>
<SettingsPageContainer>{renderContent()}</SettingsPageContainer>
</SettingsPageLayout>
);
};
@@ -1,13 +1,13 @@
import { SettingsNewAccountSection } from '@/settings/accounts/components/SettingsNewAccountSection';
import { SettingsPageContainer } from '@/settings/components/SettingsPageContainer';
import { SubMenuTopBarContainer } from '@/ui/layout/page/components/SubMenuTopBarContainer';
import { SettingsPageLayout } from '@/settings/components/layout/SettingsPageLayout';
import { t } from '@lingui/core/macro';
import { SettingsPath } from 'twenty-shared/types';
import { getSettingsPath } from 'twenty-shared/utils';
export const SettingsNewAccount = () => {
return (
<SubMenuTopBarContainer
<SettingsPageLayout
title={t`New Account`}
links={[
{
@@ -24,6 +24,6 @@ export const SettingsNewAccount = () => {
<SettingsPageContainer>
<SettingsNewAccountSection />
</SettingsPageContainer>
</SubMenuTopBarContainer>
</SettingsPageLayout>
);
};
@@ -1,6 +1,6 @@
import { SettingsAdminContent } from '@/settings/admin-panel/components/SettingsAdminContent';
import { SettingsPageContainer } from '@/settings/components/SettingsPageContainer';
import { SubMenuTopBarContainer } from '@/ui/layout/page/components/SubMenuTopBarContainer';
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';
@@ -9,7 +9,7 @@ export const SettingsAdmin = () => {
const { t } = useLingui();
return (
<SubMenuTopBarContainer
<SettingsPageLayout
title={t`Admin Panel`}
links={[
{
@@ -22,6 +22,6 @@ export const SettingsAdmin = () => {
<SettingsPageContainer>
<SettingsAdminContent />
</SettingsPageContainer>
</SubMenuTopBarContainer>
</SettingsPageLayout>
);
};
@@ -40,7 +40,7 @@ import { SettingsPageContainer } from '@/settings/components/SettingsPageContain
import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
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 {
type AdminAiModelConfig,
SetAdminAiModelEnabledDocument,
@@ -303,7 +303,7 @@ export const SettingsAdminAiProviderDetail = () => {
}
return (
<SubMenuTopBarContainer
<SettingsPageLayout
links={[
{
children: t`Other`,
@@ -439,6 +439,6 @@ export const SettingsAdminAiProviderDetail = () => {
confirmButtonText={t`Remove`}
confirmButtonAccent="danger"
/>
</SubMenuTopBarContainer>
</SettingsPageLayout>
);
};
@@ -8,7 +8,7 @@ import {
} from '~/generated-admin/graphql';
import { useApolloAdminClient } from '@/settings/admin-panel/apollo/hooks/useApolloAdminClient';
import { APPLICATION_REGISTRATION_ADMIN_PATH } from '@/settings/admin-panel/apps/constants/ApplicationRegistrationAdminPath';
import { SubMenuTopBarContainer } from '@/ui/layout/page/components/SubMenuTopBarContainer';
import { SettingsPageLayout } from '@/settings/components/layout/SettingsPageLayout';
import { getSettingsPath } from 'twenty-shared/utils';
import { SettingsPath } from 'twenty-shared/types';
import { SettingsSkeletonLoader } from '@/settings/components/SettingsSkeletonLoader';
@@ -73,7 +73,7 @@ export const SettingsAdminApplicationRegistrationConfigVariableDetail = () => {
};
return (
<SubMenuTopBarContainer
<SettingsPageLayout
links={[
{
children: t`Other`,
@@ -101,6 +101,6 @@ export const SettingsAdminApplicationRegistrationConfigVariableDetail = () => {
variable={variable}
onUpdateVariable={onUpdateVariable}
/>
</SubMenuTopBarContainer>
</SettingsPageLayout>
);
};
@@ -4,7 +4,7 @@ import { FindOneAdminApplicationRegistrationDocument } from '~/generated-admin/g
import { getSettingsPath, isDefined } from 'twenty-shared/utils';
import { SettingsPath } from 'twenty-shared/types';
import { useLingui } from '@lingui/react/macro';
import { SubMenuTopBarContainer } from '@/ui/layout/page/components/SubMenuTopBarContainer';
import { SettingsPageLayout } from '@/settings/components/layout/SettingsPageLayout';
import { useApolloAdminClient } from '@/settings/admin-panel/apollo/hooks/useApolloAdminClient';
import { APPLICATION_REGISTRATION_ADMIN_PATH } from '@/settings/admin-panel/apps/constants/ApplicationRegistrationAdminPath';
import { SettingsPageContainer } from '@/settings/components/SettingsPageContainer';
@@ -102,7 +102,7 @@ export const SettingsAdminApplicationRegistrationDetail = () => {
};
return (
<SubMenuTopBarContainer
<SettingsPageLayout
title={
<StyledTitleContainer>
<Avatar
@@ -134,6 +134,6 @@ export const SettingsAdminApplicationRegistrationDetail = () => {
/>
{renderActiveTabContent()}
</SettingsPageContainer>
</SubMenuTopBarContainer>
</SettingsPageLayout>
);
};
@@ -9,7 +9,7 @@ import { ConfigVariableValueInput } from '@/settings/admin-panel/config-variable
import { useConfigVariableActions } from '@/settings/admin-panel/config-variables/hooks/useConfigVariableActions';
import { ConfigVariableEdit } from '@/settings/config-variables/components/ConfigVariableEdit';
import { SettingsSkeletonLoader } from '@/settings/components/SettingsSkeletonLoader';
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 { SettingsPath, type ConfigVariableValue } from 'twenty-shared/types';
import { getSettingsPath, isDefined } from 'twenty-shared/utils';
@@ -97,7 +97,7 @@ export const SettingsAdminConfigVariableDetails = () => {
};
return (
<SubMenuTopBarContainer
<SettingsPageLayout
links={[
{
children: t`Other`,
@@ -144,6 +144,6 @@ export const SettingsAdminConfigVariableDetails = () => {
/>
}
/>
</SubMenuTopBarContainer>
</SettingsPageLayout>
);
};
@@ -4,7 +4,7 @@ import { SettingsAdminIndicatorHealthStatusContent } from '@/settings/admin-pane
import { SettingsAdminIndicatorHealthContext } from '@/settings/admin-panel/health-status/contexts/SettingsAdminIndicatorHealthContext';
import { SettingsPageContainer } from '@/settings/components/SettingsPageContainer';
import { SettingsSkeletonLoader } from '@/settings/components/SettingsSkeletonLoader';
import { SubMenuTopBarContainer } from '@/ui/layout/page/components/SubMenuTopBarContainer';
import { SettingsPageLayout } from '@/settings/components/layout/SettingsPageLayout';
import { styled } from '@linaria/react';
import { useLingui } from '@lingui/react/macro';
import { useParams } from 'react-router-dom';
@@ -47,7 +47,7 @@ export const SettingsAdminIndicatorHealthStatus = () => {
}
return (
<SubMenuTopBarContainer
<SettingsPageLayout
links={[
{
children: t`Other`,
@@ -99,6 +99,6 @@ export const SettingsAdminIndicatorHealthStatus = () => {
</Section>
</SettingsAdminIndicatorHealthContext.Provider>
</SettingsPageContainer>
</SubMenuTopBarContainer>
</SettingsPageLayout>
);
};
@@ -2,7 +2,7 @@ import { useApolloAdminClient } from '@/settings/admin-panel/apollo/hooks/useApo
import { SettingsPageContainer } from '@/settings/components/SettingsPageContainer';
import { SettingsTableCard } from '@/settings/components/SettingsTableCard';
import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
import { SubMenuTopBarContainer } from '@/ui/layout/page/components/SubMenuTopBarContainer';
import { SettingsPageLayout } from '@/settings/components/layout/SettingsPageLayout';
import { useMutation, useQuery } from '@apollo/client/react';
import { styled } from '@linaria/react';
import { t } from '@lingui/core/macro';
@@ -63,7 +63,7 @@ export const SettingsAdminInferredVersion = () => {
};
return (
<SubMenuTopBarContainer
<SettingsPageLayout
links={[
{
children: t`Other`,
@@ -104,6 +104,6 @@ export const SettingsAdminInferredVersion = () => {
</StyledRefreshButtonContainer>
</Section>
</SettingsPageContainer>
</SubMenuTopBarContainer>
</SettingsPageLayout>
);
};
@@ -3,7 +3,7 @@ import { getUpgradeHealthStatusBadge } from '@/settings/admin-panel/utils/getUpg
import { SettingsPageContainer } from '@/settings/components/SettingsPageContainer';
import { SettingsTableCard } from '@/settings/components/SettingsTableCard';
import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
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 { UserContext } from '@/users/contexts/UserContext';
import { useMutation, useQuery } from '@apollo/client/react';
@@ -94,7 +94,7 @@ export const SettingsAdminInstanceStatus = () => {
};
return (
<SubMenuTopBarContainer
<SettingsPageLayout
links={[
{
children: t`Other`,
@@ -177,6 +177,6 @@ export const SettingsAdminInstanceStatus = () => {
</StyledRefreshButtonContainer>
</Section>
</SettingsPageContainer>
</SubMenuTopBarContainer>
</SettingsPageLayout>
);
};
@@ -23,7 +23,7 @@ import { SettingsPageContainer } from '@/settings/components/SettingsPageContain
import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
import { Select } from '@/ui/input/components/Select';
import { TextInput } from '@/ui/input/components/TextInput';
import { SubMenuTopBarContainer } from '@/ui/layout/page/components/SubMenuTopBarContainer';
import { SettingsPageLayout } from '@/settings/components/layout/SettingsPageLayout';
import { Checkbox, Toggle } from 'twenty-ui/input';
const StyledComboInputContainer = styled.div`
@@ -278,7 +278,7 @@ export const SettingsAdminNewAiModel = () => {
return (
<form onSubmit={form.handleSubmit(handleSave)}>
<SubMenuTopBarContainer
<SettingsPageLayout
title={t`New Model`}
links={[
{
@@ -539,7 +539,7 @@ export const SettingsAdminNewAiModel = () => {
/>
</Section>
</SettingsPageContainer>
</SubMenuTopBarContainer>
</SettingsPageLayout>
</form>
);
};
@@ -25,7 +25,7 @@ import { SettingsPageContainer } from '@/settings/components/SettingsPageContain
import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
import { Select } from '@/ui/input/components/Select';
import { TextInput } from '@/ui/input/components/TextInput';
import { SubMenuTopBarContainer } from '@/ui/layout/page/components/SubMenuTopBarContainer';
import { SettingsPageLayout } from '@/settings/components/layout/SettingsPageLayout';
type ModelsDevProvider = { id: string; modelCount: number; npm: AiSdkPackage };
@@ -229,7 +229,7 @@ export const SettingsAdminNewAiProvider = () => {
return (
<form onSubmit={form.handleSubmit(handleSave)}>
<SubMenuTopBarContainer
<SettingsPageLayout
title={t`New AI Provider`}
links={[
{
@@ -444,7 +444,7 @@ export const SettingsAdminNewAiProvider = () => {
</>
)}
</SettingsPageContainer>
</SubMenuTopBarContainer>
</SettingsPageLayout>
</form>
);
};
@@ -1,6 +1,6 @@
import { SettingsAdminQueueJobsTable } from '@/settings/admin-panel/health-status/components/SettingsAdminQueueJobsTable';
import { SettingsPageContainer } from '@/settings/components/SettingsPageContainer';
import { SubMenuTopBarContainer } from '@/ui/layout/page/components/SubMenuTopBarContainer';
import { SettingsPageLayout } from '@/settings/components/layout/SettingsPageLayout';
import { plural, t } from '@lingui/core/macro';
import { useState } from 'react';
import { useParams } from 'react-router-dom';
@@ -52,7 +52,7 @@ export const SettingsAdminQueueDetail = () => {
: t`Loading retention configuration...`;
return (
<SubMenuTopBarContainer
<SettingsPageLayout
title={t`Queue: ${queueName}`}
links={[
{
@@ -83,6 +83,6 @@ export const SettingsAdminQueueDetail = () => {
/>
</Section>
</SettingsPageContainer>
</SubMenuTopBarContainer>
</SettingsPageLayout>
);
};
@@ -19,7 +19,7 @@ import { useHandleImpersonate } from '@/settings/admin-panel/hooks/useHandleImpe
import { SettingsPageContainer } from '@/settings/components/SettingsPageContainer';
import { SettingsSkeletonLoader } from '@/settings/components/SettingsSkeletonLoader';
import { SettingsTableCard } from '@/settings/components/SettingsTableCard';
import { SubMenuTopBarContainer } from '@/ui/layout/page/components/SubMenuTopBarContainer';
import { SettingsPageLayout } from '@/settings/components/layout/SettingsPageLayout';
import { TabList } from '@/ui/layout/tab-list/components/TabList';
import { activeTabIdComponentState } from '@/ui/layout/tab-list/states/activeTabIdComponentState';
import { DEFAULT_WORKSPACE_LOGO } from '@/ui/navigation/navigation-drawer/constants/DefaultWorkspaceLogo';
@@ -123,7 +123,7 @@ export const SettingsAdminUserDetail = () => {
}
return (
<SubMenuTopBarContainer
<SettingsPageLayout
links={[
{
children: t`Other`,
@@ -191,6 +191,6 @@ export const SettingsAdminUserDetail = () => {
</>
)}
</SettingsPageContainer>
</SubMenuTopBarContainer>
</SettingsPageLayout>
);
};
@@ -11,7 +11,7 @@ import { SettingsAdminChatThreadMessageList } from '@/settings/admin-panel/compo
import { GET_ADMIN_CHAT_THREAD_MESSAGES } from '@/settings/admin-panel/graphql/queries/getAdminChatThreadMessages';
import { SettingsPageContainer } from '@/settings/components/SettingsPageContainer';
import { SettingsSkeletonLoader } from '@/settings/components/SettingsSkeletonLoader';
import { SubMenuTopBarContainer } from '@/ui/layout/page/components/SubMenuTopBarContainer';
import { SettingsPageLayout } from '@/settings/components/layout/SettingsPageLayout';
import { H2Title } from 'twenty-ui/display';
import { Section } from 'twenty-ui/layout';
import { type GetAdminChatThreadMessagesQuery } from '~/generated-admin/graphql';
@@ -40,7 +40,7 @@ export const SettingsAdminWorkspaceChatThread = () => {
}
return (
<SubMenuTopBarContainer
<SettingsPageLayout
links={[
{
children: t`Other`,
@@ -67,6 +67,6 @@ export const SettingsAdminWorkspaceChatThread = () => {
<SettingsAdminChatThreadMessageList messages={messages} />
</Section>
</SettingsPageContainer>
</SubMenuTopBarContainer>
</SettingsPageLayout>
);
};
@@ -21,7 +21,7 @@ import { useHandleImpersonate } from '@/settings/admin-panel/hooks/useHandleImpe
import { SettingsPageContainer } from '@/settings/components/SettingsPageContainer';
import { SettingsSkeletonLoader } from '@/settings/components/SettingsSkeletonLoader';
import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
import { SubMenuTopBarContainer } from '@/ui/layout/page/components/SubMenuTopBarContainer';
import { SettingsPageLayout } from '@/settings/components/layout/SettingsPageLayout';
import { TabList } from '@/ui/layout/tab-list/components/TabList';
import { activeTabIdComponentState } from '@/ui/layout/tab-list/states/activeTabIdComponentState';
import { Table } from '@/ui/layout/table/components/Table';
@@ -201,7 +201,7 @@ export const SettingsAdminWorkspaceDetail = () => {
}
return (
<SubMenuTopBarContainer
<SettingsPageLayout
links={[
{
children: t`Other`,
@@ -411,6 +411,6 @@ export const SettingsAdminWorkspaceDetail = () => {
</Section>
)}
</SettingsPageContainer>
</SubMenuTopBarContainer>
</SettingsPageLayout>
);
};
@@ -3,7 +3,7 @@ import { SettingsAdminWorkspacesByHealthAccordion } from '@/settings/admin-panel
import { SettingsAdminWorkspacesStatusSummaryCard } from '@/settings/admin-panel/health-status/components/SettingsAdminWorkspacesStatusSummaryCard';
import { SettingsPageContainer } from '@/settings/components/SettingsPageContainer';
import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
import { SubMenuTopBarContainer } from '@/ui/layout/page/components/SubMenuTopBarContainer';
import { SettingsPageLayout } from '@/settings/components/layout/SettingsPageLayout';
import { useMutation, useQuery } from '@apollo/client/react';
import { styled } from '@linaria/react';
import { plural, t } from '@lingui/core/macro';
@@ -70,7 +70,7 @@ export const SettingsAdminWorkspacesStatus = () => {
};
return (
<SubMenuTopBarContainer
<SettingsPageLayout
links={[
{
children: t`Other`,
@@ -131,6 +131,6 @@ export const SettingsAdminWorkspacesStatus = () => {
</StyledAccordionCardsContainer>
</Section>
</SettingsPageContainer>
</SubMenuTopBarContainer>
</SettingsPageLayout>
);
};
@@ -1,9 +1,8 @@
import { SettingsDiscoveryHeroCard } from '@/settings/components/SettingsDiscoveryHeroCard';
import { SettingsPageContainer } from '@/settings/components/SettingsPageContainer';
import { SubMenuTopBarContainer } from '@/ui/layout/page/components/SubMenuTopBarContainer';
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 { SettingsPageLayout } from '@/settings/components/layout/SettingsPageLayout';
import { SettingsTabBar } from '@/settings/components/layout/SettingsTabBar';
import { useSettingsActiveTabId } from '@/settings/components/layout/useSettingsActiveTabId';
import { SettingsPath } from 'twenty-shared/types';
import { getSettingsPath } from 'twenty-shared/utils';
@@ -35,11 +34,6 @@ const SETTINGS_AI_HERO_INSTANCE_ID_PREFIX = 'settings-ai-hero';
export const SettingsAI = () => {
const { handleCreateTool, isCreatingTool } = useCreateTool();
const activeTabId = useAtomComponentStateValue(
activeTabIdComponentState,
SETTINGS_AI_TABS.COMPONENT_INSTANCE_ID,
);
const tabs = [
{
id: SETTINGS_AI_TABS.TABS_IDS.OVERVIEW,
@@ -68,7 +62,11 @@ export const SettingsAI = () => {
},
];
const resolvedTabId = activeTabId ?? SETTINGS_AI_TABS.TABS_IDS.OVERVIEW;
const resolvedTabId =
useSettingsActiveTabId(
SETTINGS_AI_TABS.COMPONENT_INSTANCE_ID,
tabs.map((tab) => tab.id),
) ?? SETTINGS_AI_TABS.TABS_IDS.OVERVIEW;
const isOverviewTab = resolvedTabId === SETTINGS_AI_TABS.TABS_IDS.OVERVIEW;
const isModelsTab = resolvedTabId === SETTINGS_AI_TABS.TABS_IDS.MODELS;
const isSkillsTab = resolvedTabId === SETTINGS_AI_TABS.TABS_IDS.SKILLS;
@@ -76,8 +74,14 @@ export const SettingsAI = () => {
const isUsageTab = resolvedTabId === SETTINGS_AI_TABS.TABS_IDS.USAGE;
return (
<SubMenuTopBarContainer
<SettingsPageLayout
title={t`AI`}
secondaryBar={
<SettingsTabBar
tabs={tabs}
componentInstanceId={SETTINGS_AI_TABS.COMPONENT_INSTANCE_ID}
/>
}
actionButton={
isSkillsTab ? (
<UndecoratedLink to={getSettingsPath(SettingsPath.AiNewSkill)}>
@@ -102,7 +106,7 @@ export const SettingsAI = () => {
links={[
{
children: t`Workspace`,
href: getSettingsPath(SettingsPath.Workspace),
href: getSettingsPath(SettingsPath.General),
},
{ children: t`AI` },
]}
@@ -136,16 +140,12 @@ export const SettingsAI = () => {
playButtonAriaLabel={t`Watch AI demo`}
/>
</Section>
<TabList
tabs={tabs}
componentInstanceId={SETTINGS_AI_TABS.COMPONENT_INSTANCE_ID}
/>
{isOverviewTab && <SettingsAiOverviewTab />}
{isModelsTab && <SettingsAiModelsTab />}
{isSkillsTab && <SettingsAgentSkillsTab />}
{isToolsTab && <SettingsAgentToolsTab />}
{isUsageTab && <SettingsAiUsageTab />}
</SettingsPageContainer>
</SubMenuTopBarContainer>
</SettingsPageLayout>
);
};
@@ -10,7 +10,7 @@ import { useSaveDraftRoleToDB } from '@/settings/roles/role/hooks/useSaveDraftRo
import { settingsDraftRoleFamilyState } from '@/settings/roles/states/settingsDraftRoleFamilyState';
import { settingsPersistedRoleFamilyState } from '@/settings/roles/states/settingsPersistedRoleFamilyState';
import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
import { SubMenuTopBarContainer } from '@/ui/layout/page/components/SubMenuTopBarContainer';
import { SettingsPageLayout } from '@/settings/components/layout/SettingsPageLayout';
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';
@@ -401,7 +401,7 @@ export const SettingsAgentForm = ({ mode }: { mode: 'create' | 'edit' }) => {
return (
<>
<SettingsRolesQueryEffect />
<SubMenuTopBarContainer
<SettingsPageLayout
title={title}
actionButton={
isCreateMode ? (
@@ -417,7 +417,7 @@ export const SettingsAgentForm = ({ mode }: { mode: 'create' | 'edit' }) => {
links={[
{
children: t`Workspace`,
href: getSettingsPath(SettingsPath.Workspace),
href: getSettingsPath(SettingsPath.General),
},
{ children: t`AI`, href: getSettingsPath(SettingsPath.AI) },
{ children: breadcrumbText },
@@ -470,7 +470,7 @@ export const SettingsAgentForm = ({ mode }: { mode: 'create' | 'edit' }) => {
)}
</Section>
</SettingsPageContainer>
</SubMenuTopBarContainer>
</SettingsPageLayout>
</>
);
};
@@ -1,7 +1,7 @@
import { AiChatAssistantMessageRenderer } from '@/ai/components/AiChatAssistantMessageRenderer';
import { mapDBMessagesToUIMessages } from '@/ai/utils/mapDBMessagesToUIMessages';
import { SettingsPageContainer } from '@/settings/components/SettingsPageContainer';
import { SubMenuTopBarContainer } from '@/ui/layout/page/components/SubMenuTopBarContainer';
import { SettingsPageLayout } from '@/settings/components/layout/SettingsPageLayout';
import { Table } from '@/ui/layout/table/components/Table';
import { TableCell } from '@/ui/layout/table/components/TableCell';
import { TableHeader } from '@/ui/layout/table/components/TableHeader';
@@ -74,12 +74,12 @@ export const SettingsAgentTurnDetail = () => {
if (loading) {
return (
<SubMenuTopBarContainer
<SettingsPageLayout
title={t`Turn Details`}
links={[
{
children: t`Workspace`,
href: getSettingsPath(SettingsPath.Workspace),
href: getSettingsPath(SettingsPath.General),
},
{ children: t`AI`, href: getSettingsPath(SettingsPath.AI) },
{
@@ -95,18 +95,18 @@ export const SettingsAgentTurnDetail = () => {
<SettingsPageContainer>
<Skeleton height={200} />
</SettingsPageContainer>
</SubMenuTopBarContainer>
</SettingsPageLayout>
);
}
if (!turn) {
return (
<SubMenuTopBarContainer
<SettingsPageLayout
title={t`Turn Not Found`}
links={[
{
children: t`Workspace`,
href: getSettingsPath(SettingsPath.Workspace),
href: getSettingsPath(SettingsPath.General),
},
{ children: t`AI`, href: getSettingsPath(SettingsPath.AI) },
{ children: t`Turn` },
@@ -115,17 +115,17 @@ export const SettingsAgentTurnDetail = () => {
<SettingsPageContainer>
<div>{t`Turn not found`}</div>
</SettingsPageContainer>
</SubMenuTopBarContainer>
</SettingsPageLayout>
);
}
return (
<SubMenuTopBarContainer
<SettingsPageLayout
title={t`Turn Details`}
links={[
{
children: t`Workspace`,
href: getSettingsPath(SettingsPath.Workspace),
href: getSettingsPath(SettingsPath.General),
},
{ children: t`AI`, href: getSettingsPath(SettingsPath.AI) },
{
@@ -241,6 +241,6 @@ export const SettingsAgentTurnDetail = () => {
)}
</Section>
</SettingsPageContainer>
</SubMenuTopBarContainer>
</SettingsPageLayout>
);
};
@@ -3,7 +3,7 @@ import { styled } from '@linaria/react';
import { currentWorkspaceMemberState } from '@/auth/states/currentWorkspaceMemberState';
import { FormAdvancedTextFieldInput } from '@/object-record/record-field/ui/form-types/components/FormAdvancedTextFieldInput';
import { SettingsPageContainer } from '@/settings/components/SettingsPageContainer';
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 { useQuery } from '@apollo/client/react';
import { t } from '@lingui/core/macro';
@@ -71,11 +71,11 @@ export const SettingsAiPrompts = () => {
: '';
return (
<SubMenuTopBarContainer
<SettingsPageLayout
links={[
{
children: t`Workspace`,
href: getSettingsPath(SettingsPath.Workspace),
href: getSettingsPath(SettingsPath.General),
},
{ children: t`AI`, href: getSettingsPath(SettingsPath.AI) },
{ children: t`System Prompt` },
@@ -156,6 +156,6 @@ export const SettingsAiPrompts = () => {
</StyledFormContainer>
</Section>
</SettingsPageContainer>
</SubMenuTopBarContainer>
</SettingsPageLayout>
);
};
@@ -6,7 +6,7 @@ import { UsageDailyChartSection } from '@/settings/usage/components/UsageDailyCh
import { UsageSectionSkeleton } from '@/settings/usage/components/UsageSectionSkeleton';
import { AI_OPERATION_TYPES } from '@/settings/usage/constants/AiOperationTypes';
import { useUsageAnalyticsData } from '@/settings/usage/hooks/useUsageAnalyticsData';
import { SubMenuTopBarContainer } from '@/ui/layout/page/components/SubMenuTopBarContainer';
import { SettingsPageLayout } from '@/settings/components/layout/SettingsPageLayout';
import { t } from '@lingui/core/macro';
import { Trans, useLingui } from '@lingui/react/macro';
import { useParams } from 'react-router-dom';
@@ -38,7 +38,7 @@ export const SettingsAiUsageUserDetail = () => {
const breadcrumbLinks = [
{
children: <Trans>Workspace</Trans>,
href: getSettingsPath(SettingsPath.Workspace),
href: getSettingsPath(SettingsPath.General),
},
{
children: <Trans>AI</Trans>,
@@ -49,7 +49,7 @@ export const SettingsAiUsageUserDetail = () => {
if (isInitialLoading) {
return (
<SubMenuTopBarContainer
<SettingsPageLayout
title={tLingui`AI User Usage`}
links={breadcrumbLinks}
>
@@ -57,12 +57,12 @@ export const SettingsAiUsageUserDetail = () => {
<UsageSectionSkeleton />
<UsageSectionSkeleton />
</SettingsPageContainer>
</SubMenuTopBarContainer>
</SettingsPageLayout>
);
}
return (
<SubMenuTopBarContainer title={displayName} links={breadcrumbLinks}>
<SettingsPageLayout title={displayName} links={breadcrumbLinks}>
<SettingsPageContainer>
{!hasAnyData && (
<Section>
@@ -102,6 +102,6 @@ export const SettingsAiUsageUserDetail = () => {
sectionId="ai-user-model"
/>
</SettingsPageContainer>
</SubMenuTopBarContainer>
</SettingsPageLayout>
);
};
@@ -13,7 +13,7 @@ import { IconPicker } from '@/ui/input/components/IconPicker';
import { SettingsTextInput } from '@/ui/input/components/SettingsTextInput';
import { TextArea } from '@/ui/input/components/TextArea';
import { TitleInput } from '@/ui/input/components/TitleInput';
import { SubMenuTopBarContainer } from '@/ui/layout/page/components/SubMenuTopBarContainer';
import { SettingsPageLayout } from '@/settings/components/layout/SettingsPageLayout';
import { t } from '@lingui/core/macro';
import { AppPath, SettingsPath } from 'twenty-shared/types';
import { getSettingsPath, isDefined } from 'twenty-shared/utils';
@@ -422,7 +422,7 @@ export const SettingsSkillForm = ({ mode }: { mode: 'create' | 'edit' }) => {
);
return (
<SubMenuTopBarContainer
<SettingsPageLayout
title={title}
actionButton={
isCreateMode ? (
@@ -438,7 +438,7 @@ export const SettingsSkillForm = ({ mode }: { mode: 'create' | 'edit' }) => {
links={[
{
children: t`Workspace`,
href: getSettingsPath(SettingsPath.Workspace),
href: getSettingsPath(SettingsPath.General),
},
{ children: t`AI`, href: getSettingsPath(SettingsPath.AI) },
{ children: breadcrumbText },
@@ -602,6 +602,6 @@ export const SettingsSkillForm = ({ mode }: { mode: 'create' | 'edit' }) => {
confirmButtonText={t`Delete`}
loading={isSubmitting}
/>
</SubMenuTopBarContainer>
</SettingsPageLayout>
);
};
@@ -14,7 +14,7 @@ import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
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 { useAtomStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomStateValue';
import { SettingsPath } from 'twenty-shared/types';
import { getSettingsPath, isDefined, isValidUuid } from 'twenty-shared/utils';
@@ -169,7 +169,7 @@ export const SettingsToolDetail = () => {
};
return (
<SubMenuTopBarContainer
<SettingsPageLayout
title={
isCustomTool ? (
<SettingsLogicFunctionLabelContainer
@@ -183,7 +183,7 @@ export const SettingsToolDetail = () => {
links={[
{
children: t`Workspace`,
href: getSettingsPath(SettingsPath.Workspace),
href: getSettingsPath(SettingsPath.General),
},
{
children: t`AI`,
@@ -263,6 +263,6 @@ export const SettingsToolDetail = () => {
confirmButtonText={t`Delete`}
loading={isDeleting}
/>
</SubMenuTopBarContainer>
</SettingsPageLayout>
);
};
@@ -4,13 +4,12 @@ import { SettingsApiKeysTable } from '@/settings/developers/components/SettingsA
import { SettingsWebhooksTable } from '@/settings/developers/components/SettingsWebhooksTable';
import { PlaygroundSetupForm } from '@/settings/playground/components/PlaygroundSetupForm';
import { SettingsMcpSetup } from '@/settings/playground/components/SettingsMcpSetup';
import { SubMenuTopBarContainer } from '@/ui/layout/page/components/SubMenuTopBarContainer';
import { SettingsPageLayout } from '@/settings/components/layout/SettingsPageLayout';
import PlaygroundCoverDark from '@/settings/playground/assets/cover-dark.png';
import PlaygroundCoverLight from '@/settings/playground/assets/cover-light.png';
import { TabList } from '@/ui/layout/tab-list/components/TabList';
import { activeTabIdComponentState } from '@/ui/layout/tab-list/states/activeTabIdComponentState';
import { SettingsTabBar } from '@/settings/components/layout/SettingsTabBar';
import { useSettingsActiveTabId } from '@/settings/components/layout/useSettingsActiveTabId';
import { useIsMobile } from '@/ui/utilities/responsive/hooks/useIsMobile';
import { useAtomComponentStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomComponentStateValue';
import { styled } from '@linaria/react';
import { useLingui } from '@lingui/react/macro';
import { SettingsPath } from 'twenty-shared/types';
@@ -26,7 +25,7 @@ import {
import { Button } from 'twenty-ui/input';
import { Section } from 'twenty-ui/layout';
import { MOBILE_VIEWPORT, themeCssVariables } from 'twenty-ui/theme-constants';
import { SETTINGS_API_WEBHOOKS_TABS } from '~/pages/settings/workspace/constants/SettingsApiWebhooksTabs';
import { SETTINGS_API_WEBHOOKS_TABS } from '~/pages/settings/api-webhooks/constants/SettingsApiWebhooksTabs';
type TabKey =
(typeof SETTINGS_API_WEBHOOKS_TABS.TABS_IDS)[keyof typeof SETTINGS_API_WEBHOOKS_TABS.TABS_IDS];
@@ -60,13 +59,6 @@ export const SettingsApiWebhooks = () => {
const isMobile = useIsMobile();
const { t } = useLingui();
const activeTabId = useAtomComponentStateValue(
activeTabIdComponentState,
SETTINGS_API_WEBHOOKS_TABS.COMPONENT_INSTANCE_ID,
);
const activeTab: TabKey =
(activeTabId as TabKey) ?? SETTINGS_API_WEBHOOKS_TABS.TABS_IDS.API;
const tabs = [
{
id: SETTINGS_API_WEBHOOKS_TABS.TABS_IDS.API,
@@ -85,13 +77,25 @@ export const SettingsApiWebhooks = () => {
},
];
const activeTab: TabKey =
(useSettingsActiveTabId(
SETTINGS_API_WEBHOOKS_TABS.COMPONENT_INSTANCE_ID,
tabs.map((tab) => tab.id),
) as TabKey) ?? SETTINGS_API_WEBHOOKS_TABS.TABS_IDS.API;
return (
<SubMenuTopBarContainer
<SettingsPageLayout
title={t`APIs & Webhooks`}
secondaryBar={
<SettingsTabBar
tabs={tabs}
componentInstanceId={SETTINGS_API_WEBHOOKS_TABS.COMPONENT_INSTANCE_ID}
/>
}
links={[
{
children: t`Workspace`,
href: getSettingsPath(SettingsPath.Workspace),
href: getSettingsPath(SettingsPath.General),
},
{ children: t`APIs & Webhooks` },
]}
@@ -120,11 +124,6 @@ export const SettingsApiWebhooks = () => {
/>
</Section>
<TabList
tabs={tabs}
componentInstanceId={SETTINGS_API_WEBHOOKS_TABS.COMPONENT_INSTANCE_ID}
/>
{activeTab === SETTINGS_API_WEBHOOKS_TABS.TABS_IDS.API && (
<StyledTabContent>
<Section>
@@ -185,6 +184,6 @@ export const SettingsApiWebhooks = () => {
</StyledTabContent>
)}
</SettingsPageContainer>
</SubMenuTopBarContainer>
</SettingsPageLayout>
);
};
@@ -1,6 +1,6 @@
import { SettingsPageContainer } from '@/settings/components/SettingsPageContainer';
import { SettingsSectionSkeletonLoader } from '@/settings/components/SettingsSectionSkeletonLoader';
import { SubMenuTopBarContainer } from '@/ui/layout/page/components/SubMenuTopBarContainer';
import { SettingsPageLayout } from '@/settings/components/layout/SettingsPageLayout';
import { useQuery } from '@apollo/client/react';
import { t } from '@lingui/core/macro';
import { useParams } from 'react-router-dom';
@@ -40,7 +40,7 @@ export const SettingsApplicationCommandMenuItemDetail = () => {
const breadcrumbLinks = [
{
children: t`Workspace`,
href: getSettingsPath(SettingsPath.Workspace),
href: getSettingsPath(SettingsPath.General),
},
{
children: t`Applications`,
@@ -52,7 +52,7 @@ export const SettingsApplicationCommandMenuItemDetail = () => {
];
return (
<SubMenuTopBarContainer
<SettingsPageLayout
title={commandMenuItem?.label ?? t`Command menu item`}
links={breadcrumbLinks}
>
@@ -76,6 +76,6 @@ export const SettingsApplicationCommandMenuItemDetail = () => {
/>
)}
</SettingsPageContainer>
</SubMenuTopBarContainer>
</SettingsPageLayout>
);
};
@@ -23,7 +23,7 @@ import { SettingsPageContainer } from '@/settings/components/SettingsPageContain
import { SettingsSectionSkeletonLoader } from '@/settings/components/SettingsSectionSkeletonLoader';
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 { Table } from '@/ui/layout/table/components/Table';
import { TableCell } from '@/ui/layout/table/components/TableCell';
import { TableHeader } from '@/ui/layout/table/components/TableHeader';
@@ -283,12 +283,12 @@ export const SettingsApplicationConnectionDetail = () => {
: [];
return (
<SubMenuTopBarContainer
<SettingsPageLayout
title={connectionLabel}
links={[
{
children: t`Workspace`,
href: getSettingsPath(SettingsPath.Workspace),
href: getSettingsPath(SettingsPath.General),
},
{
children: t`Applications`,
@@ -405,6 +405,6 @@ export const SettingsApplicationConnectionDetail = () => {
</>
)}
</SettingsPageContainer>
</SubMenuTopBarContainer>
</SettingsPageLayout>
);
};
@@ -8,7 +8,7 @@ import { objectMetadataItemsSelector } from '@/object-metadata/states/objectMeta
import { SettingsPageContainer } from '@/settings/components/SettingsPageContainer';
import { useHasPermissionFlag } from '@/settings/roles/hooks/useHasPermissionFlag';
import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
import { SubMenuTopBarContainer } from '@/ui/layout/page/components/SubMenuTopBarContainer';
import { SettingsPageLayout } from '@/settings/components/layout/SettingsPageLayout';
import { TabList } from '@/ui/layout/tab-list/components/TabList';
import { activeTabIdComponentState } from '@/ui/layout/tab-list/states/activeTabIdComponentState';
import type { SingleTabProps } from '@/ui/layout/tab-list/types/SingleTabProps';
@@ -325,7 +325,7 @@ export const SettingsApplicationDetails = () => {
return (
<CurrentApplicationContext.Provider value={application?.id ?? null}>
<SubMenuTopBarContainer
<SettingsPageLayout
title={
<SettingsApplicationDetailTitle
displayName={displayName}
@@ -336,7 +336,7 @@ export const SettingsApplicationDetails = () => {
links={[
{
children: t`Workspace`,
href: getSettingsPath(SettingsPath.Workspace),
href: getSettingsPath(SettingsPath.General),
},
{
children: t`Applications`,
@@ -349,7 +349,7 @@ export const SettingsApplicationDetails = () => {
<TabList tabs={tabs} componentInstanceId={APPLICATION_DETAIL_ID} />
{renderActiveTabContent()}
</SettingsPageContainer>
</SubMenuTopBarContainer>
</SettingsPageLayout>
</CurrentApplicationContext.Provider>
);
};
@@ -1,6 +1,6 @@
import { SettingsPageContainer } from '@/settings/components/SettingsPageContainer';
import { SettingsSectionSkeletonLoader } from '@/settings/components/SettingsSectionSkeletonLoader';
import { SubMenuTopBarContainer } from '@/ui/layout/page/components/SubMenuTopBarContainer';
import { SettingsPageLayout } from '@/settings/components/layout/SettingsPageLayout';
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';
@@ -52,7 +52,7 @@ export const SettingsApplicationFrontComponentDetail = () => {
const breadcrumbLinks = [
{
children: t`Workspace`,
href: getSettingsPath(SettingsPath.Workspace),
href: getSettingsPath(SettingsPath.General),
},
{
children: t`Applications`,
@@ -97,7 +97,7 @@ export const SettingsApplicationFrontComponentDetail = () => {
};
return (
<SubMenuTopBarContainer
<SettingsPageLayout
title={frontComponent?.name ?? t`Front component`}
links={breadcrumbLinks}
>
@@ -105,6 +105,6 @@ export const SettingsApplicationFrontComponentDetail = () => {
<TabList tabs={tabs} componentInstanceId={instanceId} />
{loading ? <SettingsSectionSkeletonLoader /> : renderActiveTabContent()}
</SettingsPageContainer>
</SubMenuTopBarContainer>
</SettingsPageLayout>
);
};
@@ -1,4 +1,4 @@
import { SubMenuTopBarContainer } from '@/ui/layout/page/components/SubMenuTopBarContainer';
import { SettingsPageLayout } from '@/settings/components/layout/SettingsPageLayout';
import { useQuery } from '@apollo/client/react';
import { useParams } from 'react-router-dom';
import { SettingsPath } from 'twenty-shared/types';
@@ -94,7 +94,7 @@ export const SettingsApplicationRegistrationDetails = () => {
};
return (
<SubMenuTopBarContainer
<SettingsPageLayout
title={
<StyledTitleContainer>
<Avatar
@@ -111,7 +111,7 @@ export const SettingsApplicationRegistrationDetails = () => {
links={[
{
children: t`Workspace`,
href: getSettingsPath(SettingsPath.Workspace),
href: getSettingsPath(SettingsPath.General),
},
{
children: t`Applications - Developer`,
@@ -132,6 +132,6 @@ export const SettingsApplicationRegistrationDetails = () => {
/>
{renderActiveTabContent()}
</SettingsPageContainer>
</SubMenuTopBarContainer>
</SettingsPageLayout>
);
};
@@ -1,10 +1,9 @@
import { SettingsDiscoveryHeroCard } from '@/settings/components/SettingsDiscoveryHeroCard';
import { useHasPermissionFlag } from '@/settings/roles/hooks/useHasPermissionFlag';
import { SettingsPageContainer } from '@/settings/components/SettingsPageContainer';
import { SubMenuTopBarContainer } from '@/ui/layout/page/components/SubMenuTopBarContainer';
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 { SettingsPageLayout } from '@/settings/components/layout/SettingsPageLayout';
import { SettingsTabBar } from '@/settings/components/layout/SettingsTabBar';
import { useSettingsActiveTabId } from '@/settings/components/layout/useSettingsActiveTabId';
import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled';
import { useLingui } from '@lingui/react/macro';
import { SettingsPath } from 'twenty-shared/types';
@@ -35,11 +34,6 @@ export const SettingsApplications = () => {
FeatureFlagKey.IS_MARKETPLACE_SETTING_TAB_VISIBLE,
);
const activeTabId = useAtomComponentStateValue(
activeTabIdComponentState,
APPLICATIONS_TAB_LIST_ID,
);
const tabs = [
...(isMarketplaceSettingTabVisible
? [{ id: 'marketplace', title: t`Marketplace`, Icon: IconDownload }]
@@ -50,6 +44,11 @@ export const SettingsApplications = () => {
: []),
];
const activeTabId = useSettingsActiveTabId(
APPLICATIONS_TAB_LIST_ID,
tabs.map((tab) => tab.id),
);
const renderActiveTabContent = () => {
switch (activeTabId) {
case 'marketplace':
@@ -68,12 +67,18 @@ export const SettingsApplications = () => {
};
return (
<SubMenuTopBarContainer
<SettingsPageLayout
title={t`Applications`}
secondaryBar={
<SettingsTabBar
tabs={tabs}
componentInstanceId={APPLICATIONS_TAB_LIST_ID}
/>
}
links={[
{
children: t`Workspace`,
href: getSettingsPath(SettingsPath.Workspace),
href: getSettingsPath(SettingsPath.General),
},
{ children: t`Applications` },
]}
@@ -107,9 +112,8 @@ export const SettingsApplications = () => {
playButtonAriaLabel={t`Watch apps demo`}
/>
</Section>
<TabList tabs={tabs} componentInstanceId={APPLICATIONS_TAB_LIST_ID} />
{renderActiveTabContent()}
</SettingsPageContainer>
</SubMenuTopBarContainer>
</SettingsPageLayout>
);
};
@@ -4,7 +4,7 @@ import { useInstallMarketplaceAppWithPermissionValidation } from '@/marketplace/
import { useUpgradeApplication } from '@/marketplace/hooks/useUpgradeApplication';
import { SettingsPageContainer } from '@/settings/components/SettingsPageContainer';
import { useHasPermissionFlag } from '@/settings/roles/hooks/useHasPermissionFlag';
import { SubMenuTopBarContainer } from '@/ui/layout/page/components/SubMenuTopBarContainer';
import { SettingsPageLayout } from '@/settings/components/layout/SettingsPageLayout';
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';
@@ -271,11 +271,11 @@ export const SettingsAvailableApplicationDetails = () => {
return (
<CurrentApplicationContext.Provider value={application?.id ?? null}>
<SubMenuTopBarContainer
<SettingsPageLayout
links={[
{
children: t`Workspace`,
href: getSettingsPath(SettingsPath.Workspace),
href: getSettingsPath(SettingsPath.General),
},
{
children: t`Applications`,
@@ -299,7 +299,7 @@ export const SettingsAvailableApplicationDetails = () => {
/>
{renderActiveTabContent()}
</SettingsPageContainer>
</SubMenuTopBarContainer>
</SettingsPageLayout>
<SettingsApplicationInstallPermissionValidationModal
modalInstanceId={modalInstanceId}
appDisplayName={displayName}
@@ -65,8 +65,8 @@ jest.mock('@/settings/components/SettingsPageContainer', () => ({
),
}));
jest.mock('@/ui/layout/page/components/SubMenuTopBarContainer', () => ({
SubMenuTopBarContainer: ({ children }: { children: ReactNode }) => (
jest.mock('@/settings/components/layout/SettingsPageLayout', () => ({
SettingsPageLayout: ({ children }: { children: ReactNode }) => (
<>{children}</>
),
}));
@@ -6,7 +6,7 @@ import {
UpdateApplicationRegistrationVariableDocument,
} from '~/generated-metadata/graphql';
import { useMutation, useQuery } from '@apollo/client/react';
import { SubMenuTopBarContainer } from '@/ui/layout/page/components/SubMenuTopBarContainer';
import { SettingsPageLayout } from '@/settings/components/layout/SettingsPageLayout';
import { getSettingsPath } from 'twenty-shared/utils';
import { SettingsPath } from 'twenty-shared/types';
import { SettingsSkeletonLoader } from '@/settings/components/SettingsSkeletonLoader';
@@ -67,11 +67,11 @@ export const SettingsApplicationRegistrationConfigVariableDetail = () => {
};
return (
<SubMenuTopBarContainer
<SettingsPageLayout
links={[
{
children: t`Workspace`,
href: getSettingsPath(SettingsPath.Workspace),
href: getSettingsPath(SettingsPath.General),
},
{
children: t`Applications - Developer`,
@@ -100,6 +100,6 @@ export const SettingsApplicationRegistrationConfigVariableDetail = () => {
variable={variable}
onUpdateVariable={onUpdateVariable}
/>
</SubMenuTopBarContainer>
</SettingsPageLayout>
);
};
@@ -1,7 +1,7 @@
import { Trans, useLingui } from '@lingui/react/macro';
import { currentWorkspaceState } from '@/auth/states/currentWorkspaceState';
import { useAtomStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomStateValue';
import { SubMenuTopBarContainer } from '@/ui/layout/page/components/SubMenuTopBarContainer';
import { SettingsPageLayout } from '@/settings/components/layout/SettingsPageLayout';
import { SettingsBillingContent } from '@/settings/billing/components/SettingsBillingContent';
import { getSettingsPath } from 'twenty-shared/utils';
import { SettingsPath } from 'twenty-shared/types';
@@ -15,17 +15,17 @@ export const SettingsBilling = () => {
const { isPlansLoaded } = usePlans();
return (
<SubMenuTopBarContainer
<SettingsPageLayout
title={t`Billing`}
links={[
{
children: <Trans>Workspace</Trans>,
href: getSettingsPath(SettingsPath.Workspace),
href: getSettingsPath(SettingsPath.General),
},
{ children: <Trans>Billing</Trans> },
]}
>
{currentWorkspace && isPlansLoaded ? <SettingsBillingContent /> : <></>}
</SubMenuTopBarContainer>
</SettingsPageLayout>
);
};
@@ -1,7 +1,7 @@
import { Trans, useLingui } from '@lingui/react/macro';
import { SettingsUsageAnalyticsSection } from '@/settings/usage/components/SettingsUsageAnalyticsSection';
import { SettingsPageContainer } from '@/settings/components/SettingsPageContainer';
import { SubMenuTopBarContainer } from '@/ui/layout/page/components/SubMenuTopBarContainer';
import { SettingsPageLayout } from '@/settings/components/layout/SettingsPageLayout';
import { getSettingsPath } from 'twenty-shared/utils';
import { SettingsPath } from 'twenty-shared/types';
@@ -9,12 +9,12 @@ export const SettingsUsage = () => {
const { t } = useLingui();
return (
<SubMenuTopBarContainer
<SettingsPageLayout
title={t`Usage`}
links={[
{
children: <Trans>Workspace</Trans>,
href: getSettingsPath(SettingsPath.Workspace),
href: getSettingsPath(SettingsPath.General),
},
{
children: <Trans>Billing</Trans>,
@@ -26,6 +26,6 @@ export const SettingsUsage = () => {
<SettingsPageContainer>
<SettingsUsageAnalyticsSection />
</SettingsPageContainer>
</SubMenuTopBarContainer>
</SettingsPageLayout>
);
};
@@ -6,7 +6,7 @@ import { UsageDailyChartSection } from '@/settings/usage/components/UsageDailyCh
import { UsageSectionSkeleton } from '@/settings/usage/components/UsageSectionSkeleton';
import { useUsageAnalyticsData } from '@/settings/usage/hooks/useUsageAnalyticsData';
import { useUsageValueFormatter } from '@/settings/usage/hooks/useUsageValueFormatter';
import { SubMenuTopBarContainer } from '@/ui/layout/page/components/SubMenuTopBarContainer';
import { SettingsPageLayout } from '@/settings/components/layout/SettingsPageLayout';
import { styled } from '@linaria/react';
import { t } from '@lingui/core/macro';
import { Trans, useLingui } from '@lingui/react/macro';
@@ -74,7 +74,7 @@ export const SettingsUsageUserDetail = () => {
const breadcrumbLinks = [
{
children: <Trans>Workspace</Trans>,
href: getSettingsPath(SettingsPath.Workspace),
href: getSettingsPath(SettingsPath.General),
},
{
children: <Trans>Billing</Trans>,
@@ -89,10 +89,7 @@ export const SettingsUsageUserDetail = () => {
if (isInitialLoading) {
return (
<SubMenuTopBarContainer
title={tLingui`User Usage`}
links={breadcrumbLinks}
>
<SettingsPageLayout title={tLingui`User Usage`} links={breadcrumbLinks}>
<SettingsPageContainer>
<SkeletonTheme
baseColor={theme.background.tertiary}
@@ -110,12 +107,12 @@ export const SettingsUsageUserDetail = () => {
<UsageSectionSkeleton />
</SkeletonTheme>
</SettingsPageContainer>
</SubMenuTopBarContainer>
</SettingsPageLayout>
);
}
return (
<SubMenuTopBarContainer title={tLingui`User Usage`} links={breadcrumbLinks}>
<SettingsPageLayout title={tLingui`User Usage`} links={breadcrumbLinks}>
<SettingsPageContainer>
<StyledUserHeader>
<Avatar
@@ -159,6 +156,6 @@ export const SettingsUsageUserDetail = () => {
sectionId="user-type"
/>
</SettingsPageContainer>
</SubMenuTopBarContainer>
</SettingsPageLayout>
);
};
@@ -0,0 +1,94 @@
import { SettingsCard } from '@/settings/components/SettingsCard';
import { SettingsPageContainer } from '@/settings/components/SettingsPageContainer';
import { SettingsLabContent } from '@/settings/lab/components/SettingsLabContent';
import { SettingsPageLayout } from '@/settings/components/layout/SettingsPageLayout';
import { styled } from '@linaria/react';
import { t } from '@lingui/core/macro';
import { useContext } from 'react';
import {
H2Title,
IconBriefcase,
type IconComponent,
IconMessage,
IconTransform,
} from 'twenty-ui/display';
import { Section } from 'twenty-ui/layout';
import { ThemeContext } from 'twenty-ui/theme-constants';
const StyledCardLink = styled.a`
text-decoration: none;
`;
type SettingsCommunityLink = {
title: string;
description: string;
href: string;
Icon: IconComponent;
cardTitle: string;
};
export const SettingsCommunity = () => {
const { theme } = useContext(ThemeContext);
const communityLinks: SettingsCommunityLink[] = [
{
title: t`Partners`,
description: t`Hire a partner to help you implement and customize Twenty.`,
href: 'https://twenty.com/partners/list',
Icon: IconBriefcase,
cardTitle: t`Browse partners`,
},
{
title: t`Discord`,
description: t`Join our community to get help and share feedback.`,
href: 'https://discord.com/invite/cx5n4Jzs57',
Icon: IconMessage,
cardTitle: t`Join our Discord`,
},
{
title: t`Releases`,
description: t`Check out our latest releases`,
href: 'https://twenty.com/releases',
Icon: IconTransform,
cardTitle: t`Read changelog`,
},
];
return (
<SettingsPageLayout
title={t`Community`}
links={[{ children: t`Other` }, { children: t`Community` }]}
>
<SettingsPageContainer>
{communityLinks.map(({ title, description, href, Icon, cardTitle }) => (
<Section key={href}>
<H2Title title={title} description={description} />
<StyledCardLink
href={href}
target="_blank"
rel="noopener noreferrer"
>
<SettingsCard
Icon={
<Icon
size={theme.icon.size.md}
stroke={theme.icon.stroke.sm}
/>
}
title={cardTitle}
/>
</StyledCardLink>
</Section>
))}
<Section>
<H2Title
title={t`Early access`}
description={t`Try our upcoming features. Note they are still in beta. Please bear with us and report any issues you find.`}
/>
<SettingsLabContent />
</Section>
</SettingsPageContainer>
</SettingsPageLayout>
);
};
@@ -10,7 +10,7 @@ import {
settingsDataModelObjectAboutFormSchema,
} from '@/settings/data-model/validation-schemas/settingsDataModelObjectAboutFormSchema';
import { isDDLLockedState } from '@/client-config/states/isDDLLockedState';
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 { zodResolver } from '@hookform/resolvers/zod';
import { useLingui } from '@lingui/react/macro';
@@ -77,12 +77,12 @@ export const SettingsNewObject = () => {
return (
// oxlint-disable-next-line react/jsx-props-no-spreading
<FormProvider {...formConfig}>
<SubMenuTopBarContainer
<SettingsPageLayout
title={t`New Object`}
links={[
{
children: t`Workspace`,
href: getSettingsPath(SettingsPath.Workspace),
href: getSettingsPath(SettingsPath.General),
},
{
children: t`Objects`,
@@ -114,7 +114,7 @@ export const SettingsNewObject = () => {
/>
</Section>
</SettingsPageContainer>
</SubMenuTopBarContainer>
</SettingsPageLayout>
</FormProvider>
);
};
@@ -6,8 +6,8 @@ import { SettingsPageContainer } from '@/settings/components/SettingsPageContain
import { ObjectFields } from '@/settings/data-model/object-details/components/tabs/ObjectFields';
import { ObjectLayout } from '@/settings/data-model/object-details/components/tabs/ObjectLayout';
import { ObjectSettings } from '@/settings/data-model/object-details/components/tabs/ObjectSettings';
import { SubMenuTopBarContainer } from '@/ui/layout/page/components/SubMenuTopBarContainer';
import { TabList } from '@/ui/layout/tab-list/components/TabList';
import { SettingsPageLayout } from '@/settings/components/layout/SettingsPageLayout';
import { SettingsTabBar } from '@/settings/components/layout/SettingsTabBar';
import { useAtomState } from '@/ui/utilities/state/jotai/hooks/useAtomState';
import { useAtomStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomStateValue';
import { styled } from '@linaria/react';
@@ -64,10 +64,11 @@ export const SettingsObjectDetailPage = () => {
objectMetadataItem,
}) || isDDLLocked;
const activeTabId = useAtomComponentStateValue(
activeTabIdComponentState,
SETTINGS_OBJECT_DETAIL_TABS.COMPONENT_INSTANCE_ID,
);
const activeTabId =
useAtomComponentStateValue(
activeTabIdComponentState,
SETTINGS_OBJECT_DETAIL_TABS.COMPONENT_INSTANCE_ID,
) ?? SETTINGS_OBJECT_DETAIL_TABS.TABS_IDS.FIELDS;
const [isDeleting, setIsDeleting] = useState(false);
@@ -132,60 +133,60 @@ export const SettingsObjectDetailPage = () => {
};
return (
<>
<SubMenuTopBarContainer
title={objectMetadataItem.labelPlural}
links={[
{
children: t`Workspace`,
href: getSettingsPath(SettingsPath.Workspace),
},
{
children: t`Objects`,
href: getSettingsPath(SettingsPath.Objects),
},
{
children: objectMetadataItem.labelPlural,
},
]}
actionButton={
!readonly &&
activeTabId === SETTINGS_OBJECT_DETAIL_TABS.TABS_IDS.FIELDS && (
<UndecoratedLink to="./new-field/select">
<Button
title={t`New Field`}
variant="primary"
size="small"
accent="blue"
Icon={IconPlus}
/>
</UndecoratedLink>
)
}
>
<SettingsPageContainer>
<TabList
tabs={tabs}
componentInstanceId={
SETTINGS_OBJECT_DETAIL_TABS.COMPONENT_INSTANCE_ID
}
rightComponent={
<Button
Icon={IconArrowUpRight}
title={t`See records`}
variant="tertiary"
size="small"
to={getAppPath(AppPath.RecordIndexPage, {
objectNamePlural: objectMetadataItem.namePlural,
})}
/>
}
<SettingsPageLayout
title={objectMetadataItem.labelPlural}
links={[
{
children: t`Workspace`,
href: getSettingsPath(SettingsPath.General),
},
{
children: t`Objects`,
href: getSettingsPath(SettingsPath.Objects),
},
{
children: objectMetadataItem.labelPlural,
},
]}
actionButton={
<>
<Button
Icon={IconArrowUpRight}
title={t`See records`}
variant="tertiary"
size="small"
to={getAppPath(AppPath.RecordIndexPage, {
objectNamePlural: objectMetadataItem.namePlural,
})}
/>
<StyledContentContainer>
{renderActiveTabContent()}
</StyledContentContainer>
</SettingsPageContainer>
</SubMenuTopBarContainer>
</>
{!readonly &&
activeTabId === SETTINGS_OBJECT_DETAIL_TABS.TABS_IDS.FIELDS && (
<UndecoratedLink to="./new-field/select">
<Button
title={t`New Field`}
variant="primary"
size="small"
accent="blue"
Icon={IconPlus}
/>
</UndecoratedLink>
)}
</>
}
secondaryBar={
<SettingsTabBar
tabs={tabs}
componentInstanceId={
SETTINGS_OBJECT_DETAIL_TABS.COMPONENT_INSTANCE_ID
}
/>
}
>
<SettingsPageContainer>
<StyledContentContainer>
{renderActiveTabContent()}
</StyledContentContainer>
</SettingsPageContainer>
</SettingsPageLayout>
);
};
@@ -25,7 +25,7 @@ import { type SettingsFieldType } from '@/settings/data-model/types/SettingsFiel
import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
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 { navigationMemorizedUrlState } from '@/ui/navigation/states/navigationMemorizedUrlState';
import { shouldNavigateBackToMemorizedUrlOnSaveState } from '@/ui/navigation/states/shouldNavigateBackToMemorizedUrlOnSaveState';
import { useAtomState } from '@/ui/utilities/state/jotai/hooks/useAtomState';
@@ -311,12 +311,12 @@ export const SettingsObjectFieldEdit = () => {
<>
{/* oxlint-disable-next-line react/jsx-props-no-spreading */}
<FormProvider {...formConfig}>
<SubMenuTopBarContainer
<SettingsPageLayout
title={fieldMetadataItem?.label}
links={[
{
children: t`Workspace`,
href: getSettingsPath(SettingsPath.Workspace),
href: getSettingsPath(SettingsPath.General),
},
{
children: t`Objects`,
@@ -429,7 +429,7 @@ export const SettingsObjectFieldEdit = () => {
</Section>
)}
</SettingsPageContainer>
</SubMenuTopBarContainer>
</SettingsPageLayout>
</FormProvider>
{fieldMetadataItem?.isCustom && (
<ConfirmationModal
@@ -1,18 +1,18 @@
import { ReactFlowProvider } from '@xyflow/react';
import { SettingsDataModelOverview } from '@/settings/data-model/graph-overview/components/SettingsDataModelOverview';
import { SubMenuTopBarContainer } from '@/ui/layout/page/components/SubMenuTopBarContainer';
import { SettingsPageLayout } from '@/settings/components/layout/SettingsPageLayout';
import { t } from '@lingui/core/macro';
import { SettingsPath } from 'twenty-shared/types';
import { getSettingsPath } from 'twenty-shared/utils';
export const SettingsObjectOverview = () => {
return (
<SubMenuTopBarContainer
<SettingsPageLayout
links={[
{
children: t`Workspace`,
href: getSettingsPath(SettingsPath.Workspace),
href: getSettingsPath(SettingsPath.General),
},
{ children: t`Objects`, href: getSettingsPath(SettingsPath.Objects) },
{
@@ -23,6 +23,6 @@ export const SettingsObjectOverview = () => {
<ReactFlowProvider>
<SettingsDataModelOverview />
</ReactFlowProvider>
</SubMenuTopBarContainer>
</SettingsPageLayout>
);
};
@@ -3,7 +3,7 @@ import { useFilteredObjectMetadataItems } from '@/object-metadata/hooks/useFilte
import { SettingsDiscoveryHeroCard } from '@/settings/components/SettingsDiscoveryHeroCard';
import { SettingsPageContainer } from '@/settings/components/SettingsPageContainer';
import { useAtomStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomStateValue';
import { SubMenuTopBarContainer } from '@/ui/layout/page/components/SubMenuTopBarContainer';
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';
@@ -52,7 +52,7 @@ export const SettingsObjects = () => {
];
return (
<SubMenuTopBarContainer
<SettingsPageLayout
title={t`Data model`}
actionButton={
isDDLLocked ? (
@@ -77,7 +77,7 @@ export const SettingsObjects = () => {
links={[
{
children: t`Workspace`,
href: getSettingsPath(SettingsPath.Workspace),
href: getSettingsPath(SettingsPath.General),
},
{ children: t`Objects` },
]}
@@ -114,6 +114,6 @@ export const SettingsObjects = () => {
</UndecoratedLink>
</Section>
</SettingsPageContainer>
</SubMenuTopBarContainer>
</SettingsPageLayout>
);
};
@@ -9,7 +9,7 @@ import { SettingsDataModelFieldIconLabelForm } from '@/settings/data-model/field
import { SettingsDataModelFieldSettingsFormCard } from '@/settings/data-model/fields/forms/components/SettingsDataModelFieldSettingsFormCard';
import { settingsFieldFormSchema } from '@/settings/data-model/fields/forms/validation-schemas/settingsFieldFormSchema';
import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
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 { zodResolver } from '@hookform/resolvers/zod';
import { useLingui } from '@lingui/react/macro';
@@ -180,12 +180,12 @@ export const SettingsObjectNewFieldConfigure = () => {
<FormProvider // oxlint-disable-next-line react/jsx-props-no-spreading
{...formConfig}
>
<SubMenuTopBarContainer
<SettingsPageLayout
title={t`2. Configure field`}
links={[
{
children: t`Workspace`,
href: getSettingsPath(SettingsPath.Workspace),
href: getSettingsPath(SettingsPath.General),
},
{
children: t`Objects`,
@@ -243,7 +243,7 @@ export const SettingsObjectNewFieldConfigure = () => {
/>
</Section>
</SettingsPageContainer>
</SubMenuTopBarContainer>
</SettingsPageLayout>
</FormProvider>
);
};
@@ -5,7 +5,7 @@ import { SETTINGS_FIELD_TYPE_CONFIGS } from '@/settings/data-model/constants/Set
import { SettingsObjectNewFieldSelector } from '@/settings/data-model/fields/forms/components/SettingsObjectNewFieldSelector';
import { type FieldType } from '@/settings/data-model/types/FieldType';
import { type SettingsFieldType } from '@/settings/data-model/types/SettingsFieldType';
import { SubMenuTopBarContainer } from '@/ui/layout/page/components/SubMenuTopBarContainer';
import { SettingsPageLayout } from '@/settings/components/layout/SettingsPageLayout';
import { zodResolver } from '@hookform/resolvers/zod';
import { t } from '@lingui/core/macro';
import { useEffect } from 'react';
@@ -64,12 +64,12 @@ export const SettingsObjectNewFieldSelect = () => {
<FormProvider // oxlint-disable-next-line react/jsx-props-no-spreading
{...formMethods}
>
<SubMenuTopBarContainer
<SettingsPageLayout
title={t`1. Select a field type`}
links={[
{
children: t`Workspace`,
href: getSettingsPath(SettingsPath.Workspace),
href: getSettingsPath(SettingsPath.General),
},
{ children: t`Objects`, href: getSettingsPath(SettingsPath.Objects) },
{
@@ -87,7 +87,7 @@ export const SettingsObjectNewFieldSelect = () => {
excludedFieldTypes={excludedFieldTypes}
/>
</SettingsPageContainer>
</SubMenuTopBarContainer>
</SettingsPageLayout>
</FormProvider>
);
};
@@ -8,7 +8,7 @@ import { SettingsPageContainer } from '@/settings/components/SettingsPageContain
import { SettingsObjectIndexFieldsForm } from '@/settings/data-model/indexes/forms/components/SettingsObjectIndexFieldsForm';
import { SettingsObjectIndexOptionsForm } from '@/settings/data-model/indexes/forms/components/SettingsObjectIndexOptionsForm';
import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
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 { zodResolver } from '@hookform/resolvers/zod';
import { useLingui } from '@lingui/react/macro';
@@ -114,12 +114,12 @@ export const SettingsObjectNewIndex = () => {
<FormProvider // oxlint-disable-next-line react/jsx-props-no-spreading
{...formConfig}
>
<SubMenuTopBarContainer
<SettingsPageLayout
title={t`New Index`}
links={[
{
children: t`Workspace`,
href: getSettingsPath(SettingsPath.Workspace),
href: getSettingsPath(SettingsPath.General),
},
{
children: t`Objects`,
@@ -169,7 +169,7 @@ export const SettingsObjectNewIndex = () => {
<SettingsObjectIndexOptionsForm />
</Section>
</SettingsPageContainer>
</SubMenuTopBarContainer>
</SettingsPageLayout>
</FormProvider>
);
};
@@ -17,7 +17,7 @@ import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
import { SettingsTextInput } from '@/ui/input/components/SettingsTextInput';
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 { getSettingsPath, isDefined } from 'twenty-shared/utils';
@@ -240,12 +240,12 @@ export const SettingsDevelopersApiKeyDetail = () => {
return (
<>
{isDefined(apiKey) && (
<SubMenuTopBarContainer
<SettingsPageLayout
title={apiKey.name || t`Unnamed API Key`}
links={[
{
children: t`Workspace`,
href: getSettingsPath(SettingsPath.Workspace),
href: getSettingsPath(SettingsPath.General),
},
{
children: t`APIs & Webhooks`,
@@ -330,7 +330,7 @@ export const SettingsDevelopersApiKeyDetail = () => {
/>
</Section>
</SettingsPageContainer>
</SubMenuTopBarContainer>
</SettingsPageLayout>
)}
<ConfirmationModal
confirmationPlaceholder={confirmationValue}
@@ -9,7 +9,7 @@ import { EXPIRATION_DATES } from '@/settings/developers/constants/ExpirationDate
import { apiKeyTokenFamilyState } from '@/settings/developers/states/apiKeyTokenFamilyState';
import { Select } from '@/ui/input/components/Select';
import { SettingsTextInput } from '@/ui/input/components/SettingsTextInput';
import { SubMenuTopBarContainer } from '@/ui/layout/page/components/SubMenuTopBarContainer';
import { SettingsPageLayout } from '@/settings/components/layout/SettingsPageLayout';
import { useMutation, useQuery } from '@apollo/client/react';
import { useLingui } from '@lingui/react/macro';
import { useStore } from 'jotai';
@@ -128,12 +128,12 @@ export const SettingsDevelopersApiKeysNew = () => {
}
return (
<SubMenuTopBarContainer
<SettingsPageLayout
title={t`New key`}
links={[
{
children: t`Workspace`,
href: getSettingsPath(SettingsPath.Workspace),
href: getSettingsPath(SettingsPath.General),
},
{
children: t`APIs & Webhooks`,
@@ -209,6 +209,6 @@ export const SettingsDevelopersApiKeysNew = () => {
/>
</Section>
</SettingsPageContainer>
</SubMenuTopBarContainer>
</SettingsPageLayout>
);
};
@@ -32,7 +32,7 @@ export const SettingsGraphQLPlayground = () => {
links={[
{
children: <Trans>Workspace</Trans>,
href: getSettingsPath(SettingsPath.Workspace),
href: getSettingsPath(SettingsPath.General),
},
{
children: <Trans>APIs & Webhooks</Trans>,
@@ -28,7 +28,7 @@ export const SettingsRestPlayground = () => {
links={[
{
children: <Trans>Workspace</Trans>,
href: getSettingsPath(SettingsPath.Workspace),
href: getSettingsPath(SettingsPath.General),
},
{
children: <Trans>APIs & Webhooks</Trans>,
@@ -1,6 +1,6 @@
import { SettingsPageContainer } from '@/settings/components/SettingsPageContainer';
import { SettingsWebhooksTable } from '@/settings/developers/components/SettingsWebhooksTable';
import { SubMenuTopBarContainer } from '@/ui/layout/page/components/SubMenuTopBarContainer';
import { SettingsPageLayout } from '@/settings/components/layout/SettingsPageLayout';
import { useIsMobile } from '@/ui/utilities/responsive/hooks/useIsMobile';
import { styled } from '@linaria/react';
import { Trans, useLingui } from '@lingui/react/macro';
@@ -32,12 +32,12 @@ export const SettingsWebhooks = () => {
const { t } = useLingui();
return (
<SubMenuTopBarContainer
<SettingsPageLayout
title={t`Webhooks`}
links={[
{
children: <Trans>Workspace</Trans>,
href: getSettingsPath(SettingsPath.Workspace),
href: getSettingsPath(SettingsPath.General),
},
{ children: <Trans>Webhooks</Trans> },
]}
@@ -62,6 +62,6 @@ export const SettingsWebhooks = () => {
</Section>
</StyledContainer>
</SettingsPageContainer>
</SubMenuTopBarContainer>
</SettingsPageLayout>
);
};
@@ -4,7 +4,7 @@ import { isEmailGroupEnabledState } from '@/client-config/states/isEmailGroupEna
import { SettingsPageContainer } from '@/settings/components/SettingsPageContainer';
import { SettingsWorkspaceEmailGroupSection } from '@/settings/workspace/components/SettingsWorkspaceEmailGroupSection';
import { SettingsWorkspaceEmailingDomainsSection } from '@/settings/workspace/components/SettingsWorkspaceEmailingDomainsSection';
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 { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled';
import { FeatureFlagKey, SettingsPath } from 'twenty-shared/types';
@@ -25,12 +25,12 @@ export const SettingsWorkspaceEmail = () => {
const showEmailGroupSection = isEmailGroupEnabled;
return (
<SubMenuTopBarContainer
<SettingsPageLayout
title={t`Email`}
links={[
{
children: t`Workspace`,
href: getSettingsPath(SettingsPath.Workspace),
href: getSettingsPath(SettingsPath.General),
},
{ children: t`Email` },
]}
@@ -39,6 +39,6 @@ export const SettingsWorkspaceEmail = () => {
{showEmailGroupSection && <SettingsWorkspaceEmailGroupSection />}
<SettingsWorkspaceEmailingDomainsSection />
</SettingsPageContainer>
</SubMenuTopBarContainer>
</SettingsPageLayout>
);
};
@@ -8,7 +8,7 @@ import { useMyMessageChannels } from '@/settings/accounts/hooks/useMyMessageChan
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 { ConfirmationModal } from '@/ui/layout/modal/components/ConfirmationModal';
import { useModal } from '@/ui/layout/modal/hooks/useModal';
import { MessageChannelType, SettingsPath } from 'twenty-shared/types';
@@ -85,12 +85,12 @@ export const SettingsWorkspaceEmailGroupChannelDetail = () => {
};
return (
<SubMenuTopBarContainer
<SettingsPageLayout
title={sourceHandle}
links={[
{
children: t`Workspace`,
href: getSettingsPath(SettingsPath.Workspace),
href: getSettingsPath(SettingsPath.General),
},
{
children: t`Email`,
@@ -160,6 +160,6 @@ export const SettingsWorkspaceEmailGroupChannelDetail = () => {
confirmButtonAccent="danger"
loading={deleting}
/>
</SubMenuTopBarContainer>
</SettingsPageLayout>
);
};
@@ -9,7 +9,7 @@ import { SettingsEmailingDomainVerificationRecords } from '@/settings/emailing-d
import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
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 { SettingsPath } from 'twenty-shared/types';
import { getSettingsPath, isDefined } from 'twenty-shared/utils';
import { IconTrash } from 'twenty-ui/display';
@@ -75,12 +75,12 @@ export const SettingsEmailingDomainDetail = () => {
};
return (
<SubMenuTopBarContainer
<SettingsPageLayout
title={emailingDomain.domain}
links={[
{
children: <Trans>Workspace</Trans>,
href: getSettingsPath(SettingsPath.Workspace),
href: getSettingsPath(SettingsPath.General),
},
{
children: <Trans>Email</Trans>,
@@ -117,6 +117,6 @@ export const SettingsEmailingDomainDetail = () => {
confirmButtonAccent="danger"
loading={deleting}
/>
</SubMenuTopBarContainer>
</SettingsPageLayout>
);
};
@@ -2,7 +2,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 { CombinedGraphQLErrors } from '@apollo/client/errors';
import { Trans, useLingui } from '@lingui/react/macro';
import { useState } from 'react';
@@ -110,7 +110,7 @@ export const SettingsNewEmailingDomain = () => {
};
return (
<SubMenuTopBarContainer
<SettingsPageLayout
title={t`New Emailing Domain`}
actionButton={
<SaveAndCancelButtons
@@ -122,7 +122,7 @@ export const SettingsNewEmailingDomain = () => {
links={[
{
children: <Trans>Workspace</Trans>,
href: getSettingsPath(SettingsPath.Workspace),
href: getSettingsPath(SettingsPath.General),
},
{
children: <Trans>Email</Trans>,
@@ -149,6 +149,6 @@ export const SettingsNewEmailingDomain = () => {
/>
</Section>
</SettingsPageContainer>
</SubMenuTopBarContainer>
</SettingsPageLayout>
);
};
@@ -16,7 +16,7 @@ import { ENTERPRISE_SUBSCRIPTION_STATUS } from '@/settings/enterprise/graphql/qu
import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
import { SettingsTextInput } from '@/ui/input/components/SettingsTextInput';
import { useModal } from '@/ui/layout/modal/hooks/useModal';
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 { useLoadCurrentUser } from '@/users/hooks/useLoadCurrentUser';
import { useLazyQuery, useMutation } from '@apollo/client/react';
@@ -668,17 +668,17 @@ export const SettingsEnterprise = ({
}
return (
<SubMenuTopBarContainer
<SettingsPageLayout
title={t`Enterprise`}
links={[
{
children: <Trans>Workspace</Trans>,
href: getSettingsPath(SettingsPath.Workspace),
href: getSettingsPath(SettingsPath.General),
},
{ children: <Trans>Enterprise</Trans> },
]}
>
<SettingsPageContainer>{innerContent}</SettingsPageContainer>
</SubMenuTopBarContainer>
</SettingsPageLayout>
);
};
@@ -0,0 +1,94 @@
import { useLingui } from '@lingui/react/macro';
import { isMultiWorkspaceEnabledState } from '@/client-config/states/isMultiWorkspaceEnabledState';
import { SettingsPageContainer } from '@/settings/components/SettingsPageContainer';
import { SettingsPageLayout } from '@/settings/components/layout/SettingsPageLayout';
import { SettingsTabBar } from '@/settings/components/layout/SettingsTabBar';
import { useSettingsActiveTabId } from '@/settings/components/layout/useSettingsActiveTabId';
import { SettingsWorkspaceDomainCard } from '@/settings/domains/components/SettingsWorkspaceDomainCard';
import { DeleteWorkspace } from '@/settings/profile/components/DeleteWorkspace';
import { useHasPermissionFlag } from '@/settings/roles/hooks/useHasPermissionFlag';
import { SettingsSecuritySettings } from '@/settings/security/components/SettingsSecuritySettings';
import { NameField } from '@/settings/workspace/components/NameField';
import { WorkspaceLogoUploader } from '@/settings/workspace/components/WorkspaceLogoUploader';
import { useAtomStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomStateValue';
import { H2Title, IconKey, IconSettings } from 'twenty-ui/display';
import { Section } from 'twenty-ui/layout';
import { PermissionFlagType } from '~/generated-metadata/graphql';
const SETTINGS_GENERAL_TABS_INSTANCE_ID = 'settings-general-tabs';
const GENERAL_TAB_GENERAL = 'general';
const GENERAL_TAB_SECURITY = 'security';
export const SettingsGeneral = () => {
const { t } = useLingui();
const isMultiWorkspaceEnabled = useAtomStateValue(
isMultiWorkspaceEnabledState,
);
const hasSecurityPermission = useHasPermissionFlag(
PermissionFlagType.SECURITY,
);
const tabs = [
{ id: GENERAL_TAB_GENERAL, title: t`General`, Icon: IconSettings },
...(hasSecurityPermission
? [{ id: GENERAL_TAB_SECURITY, title: t`Security`, Icon: IconKey }]
: []),
];
const activeTabId = useSettingsActiveTabId(
SETTINGS_GENERAL_TABS_INSTANCE_ID,
tabs.map((tab) => tab.id),
);
const renderActiveTabContent = () => {
if (hasSecurityPermission && activeTabId === GENERAL_TAB_SECURITY) {
return <SettingsSecuritySettings />;
}
return (
<>
<Section>
<H2Title title={t`Picture`} />
<WorkspaceLogoUploader />
</Section>
<Section>
<H2Title title={t`Name`} description={t`Name of your workspace`} />
<NameField />
</Section>
{isMultiWorkspaceEnabled && (
<Section>
<H2Title
title={t`Workspace Domain`}
description={t`Edit your subdomain name or set a custom domain.`}
/>
<SettingsWorkspaceDomainCard />
</Section>
)}
<Section>
<DeleteWorkspace />
</Section>
</>
);
};
return (
<SettingsPageLayout
title={t`General`}
secondaryBar={
hasSecurityPermission ? (
<SettingsTabBar
tabs={tabs}
componentInstanceId={SETTINGS_GENERAL_TABS_INSTANCE_ID}
/>
) : undefined
}
links={[{ children: t`Workspace` }, { children: t`General` }]}
>
<SettingsPageContainer>{renderActiveTabContent()}</SettingsPageContainer>
</SettingsPageLayout>
);
};
@@ -2,7 +2,7 @@ import { useEnterLayoutCustomizationMode } from '@/layout-customization/hooks/us
import { SettingsDiscoveryHeroCard } from '@/settings/components/SettingsDiscoveryHeroCard';
import { SettingsPageContainer } from '@/settings/components/SettingsPageContainer';
import { SettingsLayoutItemsStats } from '@/settings/layout/components/SettingsLayoutItemsStats';
import { SubMenuTopBarContainer } from '@/ui/layout/page/components/SubMenuTopBarContainer';
import { SettingsPageLayout } from '@/settings/components/layout/SettingsPageLayout';
import { useLingui } from '@lingui/react/macro';
import { useNavigate } from 'react-router-dom';
import { AppPath, SettingsPath } from 'twenty-shared/types';
@@ -68,7 +68,7 @@ export const SettingsLayout = () => {
];
return (
<SubMenuTopBarContainer
<SettingsPageLayout
title={t`Layout`}
actionButton={
<Button
@@ -83,7 +83,7 @@ export const SettingsLayout = () => {
links={[
{
children: t`Workspace`,
href: getSettingsPath(SettingsPath.Workspace),
href: getSettingsPath(SettingsPath.General),
},
{ children: t`Layout` },
]}
@@ -106,6 +106,6 @@ export const SettingsLayout = () => {
<SettingsLayoutItemsStats />
</Section>
</SettingsPageContainer>
</SubMenuTopBarContainer>
</SettingsPageLayout>
);
};
@@ -5,7 +5,7 @@ import { TableBody } from '@/ui/layout/table/components/TableBody';
import { TableCell } from '@/ui/layout/table/components/TableCell';
import { TableHeader } from '@/ui/layout/table/components/TableHeader';
import { TableRow } from '@/ui/layout/table/components/TableRow';
import { SubMenuTopBarContainer } from '@/ui/layout/page/components/SubMenuTopBarContainer';
import { SettingsPageLayout } from '@/settings/components/layout/SettingsPageLayout';
import { styled } from '@linaria/react';
import { t } from '@lingui/core/macro';
import { type ReactNode } from 'react';
@@ -57,7 +57,7 @@ export const SettingsLayoutDetailScaffold = ({
);
const breadcrumbLinks = [
{ children: t`Workspace`, href: getSettingsPath(SettingsPath.Workspace) },
{ children: t`Workspace`, href: getSettingsPath(SettingsPath.General) },
{
children: t`Applications`,
href: getSettingsPath(SettingsPath.Applications),
@@ -68,7 +68,7 @@ export const SettingsLayoutDetailScaffold = ({
];
return (
<SubMenuTopBarContainer title={entityName} links={breadcrumbLinks}>
<SettingsPageLayout title={entityName} links={breadcrumbLinks}>
<SettingsPageContainer>
{isLoading ? (
<SettingsSectionSkeletonLoader />
@@ -111,6 +111,6 @@ export const SettingsLayoutDetailScaffold = ({
</>
)}
</SettingsPageContainer>
</SubMenuTopBarContainer>
</SettingsPageLayout>
);
};
@@ -6,7 +6,7 @@ import { SettingsLogicFunctionLabelContainer } from '@/settings/logic-functions/
import { SettingsLogicFunctionSettingsTab } from '@/settings/logic-functions/components/tabs/SettingsLogicFunctionSettingsTab';
import { SettingsLogicFunctionTestTab } from '@/settings/logic-functions/components/tabs/SettingsLogicFunctionTestTab';
import { SettingsLogicFunctionTriggersTab } from '@/settings/logic-functions/components/tabs/SettingsLogicFunctionTriggersTab';
import { SubMenuTopBarContainer } from '@/ui/layout/page/components/SubMenuTopBarContainer';
import { SettingsPageLayout } from '@/settings/components/layout/SettingsPageLayout';
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';
@@ -104,7 +104,7 @@ export const SettingsLogicFunctionDetail = () => {
return [
{
children: t`Workspace`,
href: getSettingsPath(SettingsPath.Workspace),
href: getSettingsPath(SettingsPath.General),
},
{
children: t`Applications`,
@@ -118,7 +118,7 @@ export const SettingsLogicFunctionDetail = () => {
: [
{
children: t`Workspace`,
href: getSettingsPath(SettingsPath.Workspace),
href: getSettingsPath(SettingsPath.General),
},
{
children: t`AI`,
@@ -139,7 +139,7 @@ export const SettingsLogicFunctionDetail = () => {
return (
!loading &&
!applicationLoading && (
<SubMenuTopBarContainer
<SettingsPageLayout
title={
<SettingsLogicFunctionLabelContainer
value={formValues.name}
@@ -184,7 +184,7 @@ export const SettingsLogicFunctionDetail = () => {
/>
)}
</SettingsPageContainer>
</SubMenuTopBarContainer>
</SettingsPageLayout>
)
);
};
@@ -10,7 +10,7 @@ import { useHasPermissionFlag } from '@/settings/roles/hooks/useHasPermissionFla
import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
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 { 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';
@@ -167,12 +167,12 @@ export const SettingsWorkspaceMember = () => {
<>
<SettingsRolesQueryEffect />
{isLoading ? null : (
<SubMenuTopBarContainer
<SettingsPageLayout
title={`${member.name.firstName} ${member.name.lastName}`}
links={[
{
children: t`Workspace`,
href: getSettingsPath(SettingsPath.Workspace),
href: getSettingsPath(SettingsPath.General),
},
{
children: t`Members`,
@@ -227,7 +227,7 @@ export const SettingsWorkspaceMember = () => {
confirmButtonText={t`Remove member`}
loading={isDeleting}
/>
</SubMenuTopBarContainer>
</SettingsPageLayout>
)}
</>
);
@@ -6,10 +6,9 @@ import { IconLock, IconUserPlus, IconUsers } from 'twenty-ui/display';
import { SettingsDiscoveryHeroCard } from '@/settings/components/SettingsDiscoveryHeroCard';
import { useHasPermissionFlag } from '@/settings/roles/hooks/useHasPermissionFlag';
import { SettingsPageContainer } from '@/settings/components/SettingsPageContainer';
import { SubMenuTopBarContainer } from '@/ui/layout/page/components/SubMenuTopBarContainer';
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 { SettingsPageLayout } from '@/settings/components/layout/SettingsPageLayout';
import { SettingsTabBar } from '@/settings/components/layout/SettingsTabBar';
import { useSettingsActiveTabId } from '@/settings/components/layout/useSettingsActiveTabId';
import { Section } from 'twenty-ui/layout';
import { PermissionFlagType } from '~/generated-metadata/graphql';
import { SettingsWorkspaceMembersInviteTab } from '~/pages/settings/members/tabs/SettingsWorkspaceMembersInviteTab';
@@ -31,11 +30,6 @@ export const SettingsWorkspaceMembers = () => {
const hasRolesPermission = useHasPermissionFlag(PermissionFlagType.ROLES);
const activeTabId = useAtomComponentStateValue(
activeTabIdComponentState,
MEMBERS_TAB_LIST_ID,
);
const tabs = [
{ id: MEMBERS_TAB_TEAM_ID, title: t`Team`, Icon: IconUsers },
{ id: MEMBERS_TAB_INVITE_ID, title: t`Invite`, Icon: IconUserPlus },
@@ -44,6 +38,11 @@ export const SettingsWorkspaceMembers = () => {
: []),
];
const activeTabId = useSettingsActiveTabId(
MEMBERS_TAB_LIST_ID,
tabs.map((tab) => tab.id),
);
const renderActiveTabContent = () => {
switch (activeTabId) {
case MEMBERS_TAB_INVITE_ID:
@@ -60,12 +59,15 @@ export const SettingsWorkspaceMembers = () => {
};
return (
<SubMenuTopBarContainer
<SettingsPageLayout
title={t`Members`}
secondaryBar={
<SettingsTabBar tabs={tabs} componentInstanceId={MEMBERS_TAB_LIST_ID} />
}
links={[
{
children: <Trans>Workspace</Trans>,
href: getSettingsPath(SettingsPath.Workspace),
href: getSettingsPath(SettingsPath.General),
},
{ children: <Trans>Members</Trans> },
]}
@@ -103,9 +105,8 @@ export const SettingsWorkspaceMembers = () => {
playButtonAriaLabel={t`Watch members demo`}
/>
</Section>
<TabList tabs={tabs} componentInstanceId={MEMBERS_TAB_LIST_ID} />
{renderActiveTabContent()}
</SettingsPageContainer>
</SubMenuTopBarContainer>
</SettingsPageLayout>
);
};
@@ -2,7 +2,8 @@ import { SettingsPageContainer } from '@/settings/components/SettingsPageContain
import { SettingsRolesQueryEffect } from '@/settings/roles/components/SettingsRolesQueryEffect';
import { SettingsRolePermissionsObjectLevelObjectPicker } from '@/settings/roles/role-permissions/object-level-permissions/components/SettingsRolePermissionsObjectLevelObjectPicker';
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 { Navigate, useParams, useSearchParams } from 'react-router-dom';
@@ -37,7 +38,7 @@ export const SettingsRoleAddObjectLevel = () => {
? [
{
children: t`Workspace`,
href: getSettingsPath(SettingsPath.Workspace),
href: getSettingsPath(SettingsPath.General),
},
{
children: t`AI`,
@@ -56,7 +57,7 @@ export const SettingsRoleAddObjectLevel = () => {
: [
{
children: t`Workspace`,
href: getSettingsPath(SettingsPath.Workspace),
href: getSettingsPath(SettingsPath.General),
},
{
children: t`Members`,
@@ -73,17 +74,23 @@ export const SettingsRoleAddObjectLevel = () => {
},
];
const headerTitle =
fromAgentId && isDefined(agent)
? agent.label
: (settingsDraftRole.label ?? '');
return (
<>
<SettingsRolesQueryEffect />
<SubMenuTopBarContainer
title={t`1. Select an object`}
<SettingsPageLayout
title={headerTitle}
links={breadcrumbLinks}
secondaryBar={<SettingsWizardStepBar label={t`1. Select an object`} />}
>
<SettingsPageContainer>
<SettingsRolePermissionsObjectLevelObjectPicker roleId={roleId} />
</SettingsPageContainer>
</SubMenuTopBarContainer>
</SettingsPageLayout>
</>
);
};
@@ -8,7 +8,7 @@ import { NameFields } from '@/settings/profile/components/NameFields';
import { WorkspaceMemberPictureUploader } from '@/settings/workspace-member/components/WorkspaceMemberPictureUploader';
import { useCanChangePassword } from '@/settings/profile/hooks/useCanChangePassword';
import { useCurrentUserWorkspaceTwoFactorAuthentication } from '@/settings/two-factor-authentication/hooks/useCurrentUserWorkspaceTwoFactorAuthentication';
import { SubMenuTopBarContainer } from '@/ui/layout/page/components/SubMenuTopBarContainer';
import { SettingsPageLayout } from '@/settings/components/layout/SettingsPageLayout';
import { Trans, useLingui } from '@lingui/react/macro';
import { useAtomStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomStateValue';
import { SettingsPath } from 'twenty-shared/types';
@@ -35,7 +35,7 @@ export const SettingsProfile = () => {
}
return (
<SubMenuTopBarContainer
<SettingsPageLayout
title={t`Profile`}
links={[
{
@@ -99,6 +99,6 @@ export const SettingsProfile = () => {
<DeleteAccount />
</Section>
</SettingsPageContainer>
</SubMenuTopBarContainer>
</SettingsPageLayout>
);
};
@@ -12,7 +12,7 @@ import { TwoFactorAuthenticationVerificationForSettings } from '@/settings/two-f
import { useCurrentUserWorkspaceTwoFactorAuthentication } from '@/settings/two-factor-authentication/hooks/useCurrentUserWorkspaceTwoFactorAuthentication';
import { useTwoFactorVerificationForSettings } from '@/settings/two-factor-authentication/hooks/useTwoFactorVerificationForSettings';
import { extractSecretFromOtpUri } from '@/settings/two-factor-authentication/utils/extractSecretFromOtpUri';
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';
import { H2Title } from 'twenty-ui/display';
@@ -110,7 +110,7 @@ export const SettingsTwoFactorAuthenticationMethod = () => {
return (
// oxlint-disable-next-line react/jsx-props-no-spreading
<FormProvider {...verificationForm.formConfig}>
<SubMenuTopBarContainer
<SettingsPageLayout
title={t`Two Factor Authentication`}
links={[
{
@@ -183,7 +183,7 @@ export const SettingsTwoFactorAuthenticationMethod = () => {
</Section>
)}
</SettingsPageContainer>
</SubMenuTopBarContainer>
</SettingsPageLayout>
</FormProvider>
);
};
@@ -1,6 +1,6 @@
import { SettingsPageContainer } from '@/settings/components/SettingsPageContainer';
import { FormatPreferencesSettings } from '@/settings/experience/components/FormatPreferencesSettings';
import { SubMenuTopBarContainer } from '@/ui/layout/page/components/SubMenuTopBarContainer';
import { SettingsPageLayout } from '@/settings/components/layout/SettingsPageLayout';
import { useColorScheme } from '@/ui/theme/hooks/useColorScheme';
import { Trans, useLingui } from '@lingui/react/macro';
import { SettingsPath } from 'twenty-shared/types';
@@ -15,7 +15,7 @@ export const SettingsExperience = () => {
const { t } = useLingui();
return (
<SubMenuTopBarContainer
<SettingsPageLayout
title={t`Experience`}
links={[
{
@@ -54,6 +54,6 @@ export const SettingsExperience = () => {
</Section>
{/* Unified into FormatPreferencesSettings */}
</SettingsPageContainer>
</SubMenuTopBarContainer>
</SettingsPageLayout>
);
};
@@ -2,7 +2,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 { CombinedGraphQLErrors } from '@apollo/client/errors';
import { zodResolver } from '@hookform/resolvers/zod';
import { Trans, useLingui } from '@lingui/react/macro';
@@ -83,7 +83,7 @@ export const SettingsSecurityApprovedAccessDomain = () => {
return (
<form onSubmit={form.handleSubmit(handleSave)}>
<SubMenuTopBarContainer
<SettingsPageLayout
title={t`New Approved Access Domain`}
actionButton={
<SaveAndCancelButtons
@@ -94,7 +94,7 @@ export const SettingsSecurityApprovedAccessDomain = () => {
links={[
{
children: <Trans>Workspace</Trans>,
href: getSettingsPath(SettingsPath.Workspace),
href: getSettingsPath(SettingsPath.General),
},
{
children: <Trans>Members</Trans>,
@@ -154,7 +154,7 @@ export const SettingsSecurityApprovedAccessDomain = () => {
/>
</Section>
</SettingsPageContainer>
</SubMenuTopBarContainer>
</SettingsPageLayout>
</form>
);
};

Some files were not shown because too many files have changed in this diff Show More