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:
@@ -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>
|
||||
|
||||
+11
-49
@@ -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 />
|
||||
)} */}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
+3
-3
@@ -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>
|
||||
);
|
||||
|
||||
|
||||
+12
-68
@@ -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}
|
||||
/>
|
||||
|
||||
+4
-4
@@ -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
-3
@@ -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>
|
||||
);
|
||||
};
|
||||
|
||||
+9
-10
@@ -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>
|
||||
);
|
||||
};
|
||||
+63
@@ -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>
|
||||
);
|
||||
+29
@@ -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;
|
||||
};
|
||||
+4
-5
@@ -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`}
|
||||
|
||||
+4
-4
@@ -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>
|
||||
);
|
||||
};
|
||||
|
||||
+6
-6
@@ -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],
|
||||
},
|
||||
{
|
||||
|
||||
+30
-15
@@ -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>
|
||||
);
|
||||
};
|
||||
|
||||
+136
-152
@@ -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>
|
||||
</>
|
||||
);
|
||||
};
|
||||
-84
@@ -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>
|
||||
);
|
||||
};
|
||||
+1
-1
@@ -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> = {
|
||||
|
||||
+4
-4
@@ -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>
|
||||
);
|
||||
};
|
||||
|
||||
+3
-3
@@ -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
-3
@@ -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>
|
||||
);
|
||||
};
|
||||
|
||||
+3
-3
@@ -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>
|
||||
);
|
||||
};
|
||||
|
||||
+3
-3
@@ -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>
|
||||
);
|
||||
};
|
||||
|
||||
+3
-3
@@ -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>
|
||||
);
|
||||
};
|
||||
|
||||
+3
-3
@@ -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>
|
||||
);
|
||||
};
|
||||
|
||||
+3
-3
@@ -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>
|
||||
);
|
||||
};
|
||||
|
||||
+3
-3
@@ -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
-3
@@ -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>
|
||||
);
|
||||
};
|
||||
|
||||
+3
-3
@@ -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>
|
||||
);
|
||||
};
|
||||
|
||||
+3
-3
@@ -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
-3
@@ -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>
|
||||
);
|
||||
};
|
||||
|
||||
+19
-20
@@ -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>
|
||||
);
|
||||
};
|
||||
+4
-4
@@ -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>
|
||||
);
|
||||
};
|
||||
|
||||
+4
-4
@@ -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>
|
||||
);
|
||||
};
|
||||
|
||||
+4
-4
@@ -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>
|
||||
);
|
||||
};
|
||||
|
||||
+4
-4
@@ -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>
|
||||
);
|
||||
};
|
||||
|
||||
+4
-4
@@ -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
-4
@@ -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}
|
||||
|
||||
+2
-2
@@ -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}</>
|
||||
),
|
||||
}));
|
||||
|
||||
+4
-4
@@ -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>
|
||||
);
|
||||
};
|
||||
|
||||
+4
-4
@@ -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>
|
||||
);
|
||||
};
|
||||
+4
-4
@@ -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
-9
@@ -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>
|
||||
);
|
||||
};
|
||||
|
||||
+4
-4
@@ -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>
|
||||
);
|
||||
};
|
||||
|
||||
+4
-4
@@ -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>
|
||||
);
|
||||
};
|
||||
|
||||
+4
-4
@@ -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>
|
||||
);
|
||||
};
|
||||
|
||||
+4
-4
@@ -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}
|
||||
|
||||
+4
-4
@@ -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>
|
||||
);
|
||||
};
|
||||
|
||||
+1
-1
@@ -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>,
|
||||
|
||||
+1
-1
@@ -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>,
|
||||
|
||||
+4
-4
@@ -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
-4
@@ -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>
|
||||
);
|
||||
};
|
||||
+4
-4
@@ -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>
|
||||
);
|
||||
};
|
||||
+4
-4
@@ -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>
|
||||
);
|
||||
};
|
||||
|
||||
+4
-4
@@ -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>
|
||||
);
|
||||
};
|
||||
|
||||
+4
-4
@@ -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>
|
||||
);
|
||||
};
|
||||
|
||||
+5
-5
@@ -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>
|
||||
);
|
||||
};
|
||||
|
||||
+13
-6
@@ -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>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
+3
-3
@@ -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>
|
||||
);
|
||||
};
|
||||
+3
-3
@@ -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>
|
||||
);
|
||||
};
|
||||
+3
-3
@@ -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>
|
||||
);
|
||||
};
|
||||
|
||||
+4
-4
@@ -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
Reference in New Issue
Block a user