New Settings Layout (#6867)

#### \
Description

- **Added "Exit Settings" Back Button**: Introduced a new back button
labeled "Exit Settings" for easy navigation back to the app content.
- **Implemented Settings Navbar Breadcrumb**: A breadcrumb navigation
bar for each settings page has been added to improve navigation within
the settings. This ensures users can easily trace their location within
the settings.
- **Persistent CTA Zone**: The Call-to-Action (CTA) zone at the top of
the page now remains visible even when the user scrolls down, preventing
unresponsive behavior and improving accessibility.
- **Page Title**: The page title has been added to each settings page
and separated from the breadcrumb, following the app's layout standards.
- we could not reproduce the Billing and CMR Migrations settings on the
app, but we updated the files according, please let's us know if is
there any way to log in with access to these pages, or if is everything
ok.
- Some breadcrumbs are not following the Figma, are following the
current app sections isntead, because we would need to change the
sidebar structure, and we should not do it on this PR

### Demo


<https://www.loom.com/share/21b20a2cd2f3471e94d61563c9901b19?sid=9dc49456-6cae-48e1-9149-8d706f00ab65>

### Refs:

#6144

Fixes #6144

---------

Co-authored-by: gitstart-twenty <gitstart-twenty@users.noreply.github.com>
Co-authored-by: Lucas Bordeau <bordeau.lucas@gmail.com>
This commit is contained in:
gitstart-app[bot]
2024-09-17 18:29:00 +02:00
committed by GitHub
parent fe4ca2133d
commit c42ea57b97
45 changed files with 504 additions and 323 deletions

View File

@@ -42,7 +42,7 @@ export const AppNavigationDrawer = ({
const drawerProps: NavigationDrawerProps = isSettingsDrawer const drawerProps: NavigationDrawerProps = isSettingsDrawer
? { ? {
isSubMenu: true, isSubMenu: true,
title: 'Settings', title: 'Exit Settings',
children: <SettingsNavigationDrawerItems />, children: <SettingsNavigationDrawerItems />,
footer: <GithubVersionLink />, footer: <GithubVersionLink />,
} }

View File

@@ -96,7 +96,7 @@ export const SettingsNavigationDrawerItems = () => {
Icon={IconUserCircle} Icon={IconUserCircle}
/> />
<SettingsNavigationDrawerItem <SettingsNavigationDrawerItem
label="Appearance" label="Experience"
path={SettingsPath.Appearance} path={SettingsPath.Appearance}
Icon={IconColorSwatch} Icon={IconColorSwatch}
/> />

View File

@@ -10,7 +10,7 @@ const StyledSettingsPageContainer = styled.div<{ width?: number }>`
flex-direction: column; flex-direction: column;
gap: ${({ theme }) => theme.spacing(8)}; gap: ${({ theme }) => theme.spacing(8)};
overflow: auto; overflow: auto;
padding: ${({ theme }) => theme.spacing(8)}; padding: ${({ theme }) => theme.spacing(6, 8, 8)};
width: ${({ width }) => { width: ${({ width }) => {
if (isDefined(width)) { if (isDefined(width)) {
return width + 'px'; return width + 'px';

View File

@@ -6,7 +6,7 @@ export const SettingsReadDocumentationButton = () => {
return ( return (
<Button <Button
title="Read documentation" title="Read documentation"
variant="primary" variant="secondary"
accent="default" accent="default"
size="small" size="small"
Icon={IconBook2} Icon={IconBook2}

View File

@@ -89,7 +89,7 @@ type PageHeaderProps = {
hasNextRecord?: boolean; hasNextRecord?: boolean;
navigateToPreviousRecord?: () => void; navigateToPreviousRecord?: () => void;
navigateToNextRecord?: () => void; navigateToNextRecord?: () => void;
Icon: IconComponent; Icon?: IconComponent;
children?: ReactNode; children?: ReactNode;
width?: number; width?: number;
}; };

View File

@@ -9,6 +9,7 @@ const StyledPanel = styled.div`
overflow-x: auto; overflow-x: auto;
overflow-y: hidden; overflow-y: hidden;
width: 100%; width: 100%;
padding-bottom: ${({ theme }) => theme.spacing(10)};
`; `;
type PagePanelProps = { type PagePanelProps = {

View File

@@ -2,48 +2,52 @@ import styled from '@emotion/styled';
import { JSX, ReactNode } from 'react'; import { JSX, ReactNode } from 'react';
import { IconComponent } from 'twenty-ui'; import { IconComponent } from 'twenty-ui';
import { useIsMobile } from '@/ui/utilities/responsive/hooks/useIsMobile';
import { InformationBannerWrapper } from '@/information-banner/components/InformationBannerWrapper'; import { InformationBannerWrapper } from '@/information-banner/components/InformationBannerWrapper';
import { OBJECT_SETTINGS_WIDTH } from '@/settings/data-model/constants/ObjectSettings'; import {
Breadcrumb,
BreadcrumbProps,
} from '@/ui/navigation/bread-crumb/components/Breadcrumb';
import { PageBody } from './PageBody'; import { PageBody } from './PageBody';
import { PageHeader } from './PageHeader'; import { PageHeader } from './PageHeader';
type SubMenuTopBarContainerProps = { type SubMenuTopBarContainerProps = {
children: JSX.Element | JSX.Element[]; children: JSX.Element | JSX.Element[];
title: string | ReactNode; title?: string;
actionButton?: ReactNode; actionButton?: ReactNode;
Icon: IconComponent; Icon: IconComponent;
className?: string; className?: string;
links: BreadcrumbProps['links'];
}; };
const StyledContainer = styled.div<{ isMobile: boolean }>` const StyledContainer = styled.div`
display: flex; display: flex;
flex-direction: column; flex-direction: column;
padding-top: ${({ theme, isMobile }) => (!isMobile ? theme.spacing(3) : 0)};
width: 100%; width: 100%;
`; `;
const StyledTitle = styled.h3`
color: ${({ theme }) => theme.font.color.primary};
font-size: ${({ theme }) => theme.font.size.lg};
font-weight: ${({ theme }) => theme.font.weight.semiBold};
line-height: 1.2;
margin: ${({ theme }) => theme.spacing(8, 8, 2)};
`;
export const SubMenuTopBarContainer = ({ export const SubMenuTopBarContainer = ({
children, children,
title, title,
actionButton, actionButton,
Icon,
className, className,
links,
}: SubMenuTopBarContainerProps) => { }: SubMenuTopBarContainerProps) => {
const isMobile = useIsMobile();
return ( return (
<StyledContainer isMobile={isMobile} className={className}> <StyledContainer className={className}>
<PageHeader <PageHeader title={<Breadcrumb links={links} />}>
title={title}
Icon={Icon}
width={OBJECT_SETTINGS_WIDTH + 4 * 8}
>
{actionButton} {actionButton}
</PageHeader> </PageHeader>
<PageBody> <PageBody>
<InformationBannerWrapper /> <InformationBannerWrapper />
{title && <StyledTitle>{title}</StyledTitle>}
{children} {children}
</PageBody> </PageBody>
</StyledContainer> </StyledContainer>

View File

@@ -1,27 +1,22 @@
import styled from '@emotion/styled'; import styled from '@emotion/styled';
import { CSSProperties, Fragment, ReactNode } from 'react'; import { Fragment, ReactNode } from 'react';
import { Link } from 'react-router-dom'; import { Link } from 'react-router-dom';
type BreadcrumbProps = { export type BreadcrumbProps = {
className?: string; className?: string;
links: { links: { children: string | ReactNode; href?: string }[];
href?: string;
styles?: CSSProperties;
children?: string | ReactNode;
}[];
}; };
const StyledWrapper = styled.nav` const StyledWrapper = styled.nav`
align-items: center; align-items: center;
color: ${({ theme }) => theme.font.color.secondary}; color: ${({ theme }) => theme.font.color.tertiary};
display: flex; display: grid;
font-size: ${({ theme }) => theme.font.size.md}; font-size: ${({ theme }) => theme.font.size.md};
// font-weight: ${({ theme }) => theme.font.weight.semiBold}; grid-auto-flow: column;
gap: ${({ theme }) => theme.spacing(2)}; grid-column-gap: ${({ theme }) => theme.spacing(1)};
line-height: ${({ theme }) => theme.text.lineHeight.lg};
white-space: nowrap;
max-width: 100%; max-width: 100%;
min-width: 0; min-width: 0;
height: ${({ theme }) => theme.spacing(8)};
`; `;
const StyledLink = styled(Link)` const StyledLink = styled(Link)`
@@ -39,7 +34,10 @@ const StyledText = styled.span`
white-space: nowrap; white-space: nowrap;
`; `;
// TODO: not sure that passing styles to the link is a good idea const StyledDivider = styled.span`
width: ${({ theme }) => theme.spacing(2)};
`;
export const Breadcrumb = ({ className, links }: BreadcrumbProps) => { export const Breadcrumb = ({ className, links }: BreadcrumbProps) => {
return ( return (
<StyledWrapper className={className}> <StyledWrapper className={className}>
@@ -49,15 +47,13 @@ export const Breadcrumb = ({ className, links }: BreadcrumbProps) => {
return ( return (
<Fragment key={index}> <Fragment key={index}>
{link.href ? ( {link.href ? (
<StyledLink style={link.styles} title={text} to={link.href}> <StyledLink title={text} to={link.href}>
{link.children} {link.children}
</StyledLink> </StyledLink>
) : ( ) : (
<StyledText style={link.styles} title={text}> <StyledText title={text}>{link.children}</StyledText>
{link.children}
</StyledText>
)} )}
{index < links.length - 1 && '/'} {index < links.length - 1 && <StyledDivider>/</StyledDivider>}
</Fragment> </Fragment>
); );
})} })}

View File

@@ -1,7 +1,7 @@
import { ReactNode, useState } from 'react';
import { css, useTheme } from '@emotion/react'; import { css, useTheme } from '@emotion/react';
import styled from '@emotion/styled'; import styled from '@emotion/styled';
import { motion } from 'framer-motion'; import { motion } from 'framer-motion';
import { ReactNode, useState } from 'react';
import { useRecoilValue } from 'recoil'; import { useRecoilValue } from 'recoil';
import { MOBILE_VIEWPORT } from 'twenty-ui'; import { MOBILE_VIEWPORT } from 'twenty-ui';
@@ -32,7 +32,7 @@ const StyledContainer = styled.div<{ isSubMenu?: boolean }>`
box-sizing: border-box; box-sizing: border-box;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
gap: ${({ theme }) => theme.spacing(3.5)}; gap: ${({ theme }) => theme.spacing(3)};
height: 100%; height: 100%;
min-width: ${DESKTOP_NAV_DRAWER_WIDTHS.menu}px; min-width: ${DESKTOP_NAV_DRAWER_WIDTHS.menu}px;
padding: ${({ theme }) => theme.spacing(3, 2, 4)}; padding: ${({ theme }) => theme.spacing(3, 2, 4)};
@@ -41,7 +41,6 @@ const StyledContainer = styled.div<{ isSubMenu?: boolean }>`
isSubMenu isSubMenu
? css` ? css`
padding-right: ${theme.spacing(8)}; padding-right: ${theme.spacing(8)};
padding-top: 41px;
` `
: ''} : ''}

View File

@@ -1,7 +1,7 @@
import { useTheme } from '@emotion/react'; import { useTheme } from '@emotion/react';
import styled from '@emotion/styled'; import styled from '@emotion/styled';
import { useRecoilValue } from 'recoil'; import { useRecoilValue } from 'recoil';
import { IconChevronLeft } from 'twenty-ui'; import { IconX } from 'twenty-ui';
import { UndecoratedLink } from '@/ui/navigation/link/components/UndecoratedLink'; import { UndecoratedLink } from '@/ui/navigation/link/components/UndecoratedLink';
import { navigationMemorizedUrlState } from '@/ui/navigation/states/navigationMemorizedUrlState'; import { navigationMemorizedUrlState } from '@/ui/navigation/states/navigationMemorizedUrlState';
@@ -18,17 +18,22 @@ const StyledIconAndButtonContainer = styled.button`
cursor: pointer; cursor: pointer;
display: flex; display: flex;
flex-direction: row; flex-direction: row;
font-size: ${({ theme }) => theme.font.size.lg}; font-weight: ${({ theme }) => theme.font.weight.medium};
font-weight: ${({ theme }) => theme.font.weight.semiBold};
gap: ${({ theme }) => theme.spacing(2)}; gap: ${({ theme }) => theme.spacing(2)};
line-height: ${({ theme }) => theme.text.lineHeight.md}; padding: ${({ theme }) => theme.spacing(1.5, 1)};
padding: ${({ theme }) => theme.spacing(1)};
width: 100%; width: 100%;
font-family: ${({ theme }) => theme.font.family};
&:hover {
background: ${({ theme }) => theme.background.transparent.light};
border-radius: ${({ theme }) => theme.border.radius.sm};
}
`; `;
const StyledContainer = styled.div` const StyledContainer = styled.div`
align-items: center;
display: flex; display: flex;
flex-direction: row; flex-direction: row;
height: ${({ theme }) => theme.spacing(8)};
justify-content: space-between; justify-content: space-between;
`; `;
@@ -42,9 +47,10 @@ export const NavigationDrawerBackButton = ({
<StyledContainer> <StyledContainer>
<UndecoratedLink to={navigationMemorizedUrl} replace> <UndecoratedLink to={navigationMemorizedUrl} replace>
<StyledIconAndButtonContainer> <StyledIconAndButtonContainer>
<IconChevronLeft <IconX
size={theme.icon.size.md} size={theme.icon.size.md}
stroke={theme.icon.stroke.lg} stroke={theme.icon.stroke.lg}
color={theme.font.color.tertiary}
/> />
<span>{title}</span> <span>{title}</span>
</StyledIconAndButtonContainer> </StyledIconAndButtonContainer>

View File

@@ -3,18 +3,16 @@ import React, { useEffect, useState } from 'react';
import rehypeStringify from 'rehype-stringify'; import rehypeStringify from 'rehype-stringify';
import remarkParse from 'remark-parse'; import remarkParse from 'remark-parse';
import remarkRehype from 'remark-rehype'; import remarkRehype from 'remark-rehype';
import { H1Title, IconRocket } from 'twenty-ui'; import { IconRocket } from 'twenty-ui';
import { unified } from 'unified'; import { unified } from 'unified';
import { visit } from 'unist-util-visit'; import { visit } from 'unist-util-visit';
import { SettingsPageContainer } from '@/settings/components/SettingsPageContainer'; import { SettingsPageContainer } from '@/settings/components/SettingsPageContainer';
import { getSettingsPagePath } from '@/settings/utils/getSettingsPagePath';
import { SettingsPath } from '@/types/SettingsPath';
import { SubMenuTopBarContainer } from '@/ui/layout/page/SubMenuTopBarContainer'; import { SubMenuTopBarContainer } from '@/ui/layout/page/SubMenuTopBarContainer';
import { ScrollWrapper } from '@/ui/utilities/scroll/components/ScrollWrapper'; import { ScrollWrapper } from '@/ui/utilities/scroll/components/ScrollWrapper';
const StyledH1Title = styled(H1Title)`
margin-bottom: 0;
`;
type ReleaseNote = { type ReleaseNote = {
slug: string; slug: string;
date: string; date: string;
@@ -108,9 +106,20 @@ export const Releases = () => {
}, []); }, []);
return ( return (
<SubMenuTopBarContainer Icon={IconRocket} title="Releases"> <SubMenuTopBarContainer
Icon={IconRocket}
title="Releases"
links={[
{
children: 'Others',
href: getSettingsPagePath(SettingsPath.Releases),
},
{
children: 'Releases',
},
]}
>
<SettingsPageContainer> <SettingsPageContainer>
<StyledH1Title title="Releases" />
<ScrollWrapper contextProviderName="releases"> <ScrollWrapper contextProviderName="releases">
<StyledReleaseContainer> <StyledReleaseContainer>
{releases.map((release) => ( {releases.map((release) => (

View File

@@ -14,7 +14,9 @@ import { currentWorkspaceState } from '@/auth/states/currentWorkspaceState';
import { SettingsBillingCoverImage } from '@/billing/components/SettingsBillingCoverImage'; import { SettingsBillingCoverImage } from '@/billing/components/SettingsBillingCoverImage';
import { useOnboardingStatus } from '@/onboarding/hooks/useOnboardingStatus'; import { useOnboardingStatus } from '@/onboarding/hooks/useOnboardingStatus';
import { SettingsPageContainer } from '@/settings/components/SettingsPageContainer'; import { SettingsPageContainer } from '@/settings/components/SettingsPageContainer';
import { getSettingsPagePath } from '@/settings/utils/getSettingsPagePath';
import { AppPath } from '@/types/AppPath'; import { AppPath } from '@/types/AppPath';
import { SettingsPath } from '@/types/SettingsPath';
import { Info } from '@/ui/display/info/components/Info'; import { Info } from '@/ui/display/info/components/Info';
import { SnackBarVariant } from '@/ui/feedback/snack-bar-manager/components/SnackBar'; import { SnackBarVariant } from '@/ui/feedback/snack-bar-manager/components/SnackBar';
import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar'; import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
@@ -140,7 +142,17 @@ export const SettingsBilling = () => {
}; };
return ( return (
<SubMenuTopBarContainer Icon={IconCurrencyDollar} title="Billing"> <SubMenuTopBarContainer
Icon={IconCurrencyDollar}
title="Billing"
links={[
{
children: 'Workspace',
href: getSettingsPagePath(SettingsPath.Workspace),
},
{ children: 'Billing' },
]}
>
<SettingsPageContainer> <SettingsPageContainer>
<StyledH1Title title="Billing" /> <StyledH1Title title="Billing" />
<SettingsBillingCoverImage /> <SettingsBillingCoverImage />

View File

@@ -6,11 +6,23 @@ import { DeleteAccount } from '@/settings/profile/components/DeleteAccount';
import { EmailField } from '@/settings/profile/components/EmailField'; import { EmailField } from '@/settings/profile/components/EmailField';
import { NameFields } from '@/settings/profile/components/NameFields'; import { NameFields } from '@/settings/profile/components/NameFields';
import { ProfilePictureUploader } from '@/settings/profile/components/ProfilePictureUploader'; import { ProfilePictureUploader } from '@/settings/profile/components/ProfilePictureUploader';
import { getSettingsPagePath } from '@/settings/utils/getSettingsPagePath';
import { SettingsPath } from '@/types/SettingsPath';
import { SubMenuTopBarContainer } from '@/ui/layout/page/SubMenuTopBarContainer'; import { SubMenuTopBarContainer } from '@/ui/layout/page/SubMenuTopBarContainer';
import { Section } from '@/ui/layout/section/components/Section'; import { Section } from '@/ui/layout/section/components/Section';
export const SettingsProfile = () => ( export const SettingsProfile = () => (
<SubMenuTopBarContainer Icon={IconUserCircle} title="Profile"> <SubMenuTopBarContainer
Icon={IconUserCircle}
title="Profile"
links={[
{
children: 'User',
href: getSettingsPagePath(SettingsPath.ProfilePage),
},
{ children: 'Profile' },
]}
>
<SettingsPageContainer> <SettingsPageContainer>
<Section> <Section>
<H2Title title="Picture" /> <H2Title title="Picture" />

View File

@@ -2,14 +2,26 @@ import { H2Title, IconSettings } from 'twenty-ui';
import { SettingsPageContainer } from '@/settings/components/SettingsPageContainer'; import { SettingsPageContainer } from '@/settings/components/SettingsPageContainer';
import { DeleteWorkspace } from '@/settings/profile/components/DeleteWorkspace'; import { DeleteWorkspace } from '@/settings/profile/components/DeleteWorkspace';
import { getSettingsPagePath } from '@/settings/utils/getSettingsPagePath';
import { NameField } from '@/settings/workspace/components/NameField'; import { NameField } from '@/settings/workspace/components/NameField';
import { ToggleImpersonate } from '@/settings/workspace/components/ToggleImpersonate'; import { ToggleImpersonate } from '@/settings/workspace/components/ToggleImpersonate';
import { WorkspaceLogoUploader } from '@/settings/workspace/components/WorkspaceLogoUploader'; import { WorkspaceLogoUploader } from '@/settings/workspace/components/WorkspaceLogoUploader';
import { SettingsPath } from '@/types/SettingsPath';
import { SubMenuTopBarContainer } from '@/ui/layout/page/SubMenuTopBarContainer'; import { SubMenuTopBarContainer } from '@/ui/layout/page/SubMenuTopBarContainer';
import { Section } from '@/ui/layout/section/components/Section'; import { Section } from '@/ui/layout/section/components/Section';
export const SettingsWorkspace = () => ( export const SettingsWorkspace = () => (
<SubMenuTopBarContainer Icon={IconSettings} title="General"> <SubMenuTopBarContainer
Icon={IconSettings}
title="General"
links={[
{
children: 'Workspace',
href: getSettingsPagePath(SettingsPath.Workspace),
},
{ children: 'General' },
]}
>
<SettingsPageContainer> <SettingsPageContainer>
<Section> <Section>
<H2Title title="Picture" /> <H2Title title="Picture" />

View File

@@ -9,6 +9,8 @@ import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSi
import { useDeleteOneRecord } from '@/object-record/hooks/useDeleteOneRecord'; import { useDeleteOneRecord } from '@/object-record/hooks/useDeleteOneRecord';
import { useFindManyRecords } from '@/object-record/hooks/useFindManyRecords'; import { useFindManyRecords } from '@/object-record/hooks/useFindManyRecords';
import { SettingsPageContainer } from '@/settings/components/SettingsPageContainer'; import { SettingsPageContainer } from '@/settings/components/SettingsPageContainer';
import { getSettingsPagePath } from '@/settings/utils/getSettingsPagePath';
import { SettingsPath } from '@/types/SettingsPath';
import { IconButton } from '@/ui/input/button/components/IconButton'; import { IconButton } from '@/ui/input/button/components/IconButton';
import { ConfirmationModal } from '@/ui/layout/modal/components/ConfirmationModal'; import { ConfirmationModal } from '@/ui/layout/modal/components/ConfirmationModal';
import { SubMenuTopBarContainer } from '@/ui/layout/page/SubMenuTopBarContainer'; import { SubMenuTopBarContainer } from '@/ui/layout/page/SubMenuTopBarContainer';
@@ -46,7 +48,17 @@ export const SettingsWorkspaceMembers = () => {
}; };
return ( return (
<SubMenuTopBarContainer Icon={IconUsers} title="Members"> <SubMenuTopBarContainer
Icon={IconUsers}
title="Members"
links={[
{
children: 'Workspace',
href: getSettingsPagePath(SettingsPath.Workspace),
},
{ children: 'Members' },
]}
>
<SettingsPageContainer> <SettingsPageContainer>
<Section> <Section>
<H2Title <H2Title

View File

@@ -30,7 +30,7 @@ export const Default: Story = {
play: async ({ canvasElement }) => { play: async ({ canvasElement }) => {
const canvas = within(canvasElement); const canvas = within(canvasElement);
await canvas.findByText('Theme', undefined, { await canvas.findByText('Appearance', undefined, {
timeout: 3000, timeout: 3000,
}); });

View File

@@ -12,6 +12,8 @@ import { SettingsAccountsBlocklistSection } from '@/settings/accounts/components
import { SettingsAccountsConnectedAccountsListCard } from '@/settings/accounts/components/SettingsAccountsConnectedAccountsListCard'; import { SettingsAccountsConnectedAccountsListCard } from '@/settings/accounts/components/SettingsAccountsConnectedAccountsListCard';
import { SettingsAccountsSettingsSection } from '@/settings/accounts/components/SettingsAccountsSettingsSection'; import { SettingsAccountsSettingsSection } from '@/settings/accounts/components/SettingsAccountsSettingsSection';
import { SettingsPageContainer } from '@/settings/components/SettingsPageContainer'; import { SettingsPageContainer } from '@/settings/components/SettingsPageContainer';
import { getSettingsPagePath } from '@/settings/utils/getSettingsPagePath';
import { SettingsPath } from '@/types/SettingsPath';
import { SubMenuTopBarContainer } from '@/ui/layout/page/SubMenuTopBarContainer'; import { SubMenuTopBarContainer } from '@/ui/layout/page/SubMenuTopBarContainer';
import { Section } from '@/ui/layout/section/components/Section'; import { Section } from '@/ui/layout/section/components/Section';
@@ -33,7 +35,17 @@ export const SettingsAccounts = () => {
}); });
return ( return (
<SubMenuTopBarContainer Icon={IconAt} title="Account"> <SubMenuTopBarContainer
Icon={IconAt}
title="Account"
links={[
{
children: 'User',
href: getSettingsPagePath(SettingsPath.ProfilePage),
},
{ children: 'Account' },
]}
>
<SettingsPageContainer> <SettingsPageContainer>
{loading ? ( {loading ? (
<SettingsAccountLoader /> <SettingsAccountLoader />

View File

@@ -4,24 +4,24 @@ import { getSettingsPagePath } from '@/settings/utils/getSettingsPagePath';
import { SettingsPath } from '@/types/SettingsPath'; import { SettingsPath } from '@/types/SettingsPath';
import { SubMenuTopBarContainer } from '@/ui/layout/page/SubMenuTopBarContainer'; import { SubMenuTopBarContainer } from '@/ui/layout/page/SubMenuTopBarContainer';
import { Section } from '@/ui/layout/section/components/Section'; import { Section } from '@/ui/layout/section/components/Section';
import { Breadcrumb } from '@/ui/navigation/bread-crumb/components/Breadcrumb';
import { IconCalendarEvent } from 'twenty-ui'; import { IconCalendarEvent } from 'twenty-ui';
export const SettingsAccountsCalendars = () => { export const SettingsAccountsCalendars = () => {
return ( return (
<SubMenuTopBarContainer <SubMenuTopBarContainer
Icon={IconCalendarEvent} Icon={IconCalendarEvent}
title={ title="Calendars"
<Breadcrumb
links={[ links={[
{
children: 'User',
href: getSettingsPagePath(SettingsPath.ProfilePage),
},
{ {
children: 'Accounts', children: 'Accounts',
href: getSettingsPagePath(SettingsPath.Accounts), href: getSettingsPagePath(SettingsPath.Accounts),
}, },
{ children: 'Calendars' }, { children: 'Calendars' },
]} ]}
/>
}
> >
<SettingsPageContainer> <SettingsPageContainer>
<Section> <Section>

View File

@@ -1,21 +1,26 @@
import { SettingsAccountsMessageChannelsContainer } from '@/settings/accounts/components/SettingsAccountsMessageChannelsContainer'; import { SettingsAccountsMessageChannelsContainer } from '@/settings/accounts/components/SettingsAccountsMessageChannelsContainer';
import { SettingsPageContainer } from '@/settings/components/SettingsPageContainer'; import { SettingsPageContainer } from '@/settings/components/SettingsPageContainer';
import { getSettingsPagePath } from '@/settings/utils/getSettingsPagePath';
import { SettingsPath } from '@/types/SettingsPath';
import { SubMenuTopBarContainer } from '@/ui/layout/page/SubMenuTopBarContainer'; import { SubMenuTopBarContainer } from '@/ui/layout/page/SubMenuTopBarContainer';
import { Section } from '@/ui/layout/section/components/Section'; import { Section } from '@/ui/layout/section/components/Section';
import { Breadcrumb } from '@/ui/navigation/bread-crumb/components/Breadcrumb';
import { IconMail } from 'twenty-ui'; import { IconMail } from 'twenty-ui';
export const SettingsAccountsEmails = () => ( export const SettingsAccountsEmails = () => (
<SubMenuTopBarContainer <SubMenuTopBarContainer
Icon={IconMail} Icon={IconMail}
title={ title="Emails"
<Breadcrumb
links={[ links={[
{ children: 'Accounts', href: '/settings/accounts' }, {
children: 'User',
href: getSettingsPagePath(SettingsPath.ProfilePage),
},
{
children: 'Accounts',
href: getSettingsPagePath(SettingsPath.Accounts),
},
{ children: 'Emails' }, { children: 'Emails' },
]} ]}
/>
}
> >
<SettingsPageContainer> <SettingsPageContainer>
<Section> <Section>

View File

@@ -1,21 +1,26 @@
import { SettingsNewAccountSection } from '@/settings/accounts/components/SettingsNewAccountSection'; import { SettingsNewAccountSection } from '@/settings/accounts/components/SettingsNewAccountSection';
import { SettingsPageContainer } from '@/settings/components/SettingsPageContainer'; import { SettingsPageContainer } from '@/settings/components/SettingsPageContainer';
import { getSettingsPagePath } from '@/settings/utils/getSettingsPagePath';
import { SettingsPath } from '@/types/SettingsPath';
import { SubMenuTopBarContainer } from '@/ui/layout/page/SubMenuTopBarContainer'; import { SubMenuTopBarContainer } from '@/ui/layout/page/SubMenuTopBarContainer';
import { Breadcrumb } from '@/ui/navigation/bread-crumb/components/Breadcrumb';
import { IconAt } from 'twenty-ui'; import { IconAt } from 'twenty-ui';
export const SettingsNewAccount = () => { export const SettingsNewAccount = () => {
return ( return (
<SubMenuTopBarContainer <SubMenuTopBarContainer
Icon={IconAt} Icon={IconAt}
title={ title="New Account"
<Breadcrumb
links={[ links={[
{ children: 'Accounts', href: '/settings/accounts' }, {
children: 'User',
href: getSettingsPagePath(SettingsPath.ProfilePage),
},
{
children: 'Accounts',
href: getSettingsPagePath(SettingsPath.Accounts),
},
{ children: `New` }, { children: `New` },
]} ]}
/>
}
> >
<SettingsPageContainer> <SettingsPageContainer>
<SettingsNewAccountSection /> <SettingsNewAccountSection />

View File

@@ -6,9 +6,10 @@ import { currentWorkspaceState } from '@/auth/states/currentWorkspaceState';
import { SettingsHeaderContainer } from '@/settings/components/SettingsHeaderContainer'; import { SettingsHeaderContainer } from '@/settings/components/SettingsHeaderContainer';
import { SettingsPageContainer } from '@/settings/components/SettingsPageContainer'; import { SettingsPageContainer } from '@/settings/components/SettingsPageContainer';
import { SettingsReadDocumentationButton } from '@/settings/developers/components/SettingsReadDocumentationButton'; import { SettingsReadDocumentationButton } from '@/settings/developers/components/SettingsReadDocumentationButton';
import { getSettingsPagePath } from '@/settings/utils/getSettingsPagePath';
import { SettingsPath } from '@/types/SettingsPath';
import { SubMenuTopBarContainer } from '@/ui/layout/page/SubMenuTopBarContainer'; import { SubMenuTopBarContainer } from '@/ui/layout/page/SubMenuTopBarContainer';
import { Section } from '@/ui/layout/section/components/Section'; import { Section } from '@/ui/layout/section/components/Section';
import { Breadcrumb } from '@/ui/navigation/bread-crumb/components/Breadcrumb';
import { useRecoilValue } from 'recoil'; import { useRecoilValue } from 'recoil';
const REVERT_PUBLIC_KEY = 'pk_live_a87fee8c-28c7-494f-99a3-996ff89f9918'; const REVERT_PUBLIC_KEY = 'pk_live_a87fee8c-28c7-494f-99a3-996ff89f9918';
@@ -18,7 +19,14 @@ export const SettingsCRMMigration = () => {
return ( return (
<SubMenuTopBarContainer <SubMenuTopBarContainer
Icon={IconSettings} Icon={IconSettings}
title={<Breadcrumb links={[{ children: 'Migrate' }]} />} title="Migrate"
links={[
{
children: 'Workspace',
href: getSettingsPagePath(SettingsPath.Workspace),
},
{ children: 'Migrate' },
]}
actionButton={<SettingsReadDocumentationButton />} actionButton={<SettingsReadDocumentationButton />}
> >
<SettingsPageContainer> <SettingsPageContainer>

View File

@@ -7,7 +7,6 @@ import { z } from 'zod';
import { useCreateOneObjectMetadataItem } from '@/object-metadata/hooks/useCreateOneObjectMetadataItem'; import { useCreateOneObjectMetadataItem } from '@/object-metadata/hooks/useCreateOneObjectMetadataItem';
import { getObjectSlug } from '@/object-metadata/utils/getObjectSlug'; import { getObjectSlug } from '@/object-metadata/utils/getObjectSlug';
import { SaveAndCancelButtons } from '@/settings/components/SaveAndCancelButtons/SaveAndCancelButtons'; import { SaveAndCancelButtons } from '@/settings/components/SaveAndCancelButtons/SaveAndCancelButtons';
import { SettingsHeaderContainer } from '@/settings/components/SettingsHeaderContainer';
import { SettingsPageContainer } from '@/settings/components/SettingsPageContainer'; import { SettingsPageContainer } from '@/settings/components/SettingsPageContainer';
import { import {
SettingsDataModelObjectAboutForm, SettingsDataModelObjectAboutForm,
@@ -20,7 +19,6 @@ import { SnackBarVariant } from '@/ui/feedback/snack-bar-manager/components/Snac
import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar'; import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
import { SubMenuTopBarContainer } from '@/ui/layout/page/SubMenuTopBarContainer'; import { SubMenuTopBarContainer } from '@/ui/layout/page/SubMenuTopBarContainer';
import { Section } from '@/ui/layout/section/components/Section'; import { Section } from '@/ui/layout/section/components/Section';
import { Breadcrumb } from '@/ui/navigation/bread-crumb/components/Breadcrumb';
const newObjectFormSchema = settingsDataModelObjectAboutFormSchema; const newObjectFormSchema = settingsDataModelObjectAboutFormSchema;
@@ -72,17 +70,18 @@ export const SettingsNewObject = () => {
<FormProvider {...formConfig}> <FormProvider {...formConfig}>
<SubMenuTopBarContainer <SubMenuTopBarContainer
Icon={IconHierarchy2} Icon={IconHierarchy2}
title={ title="New Object"
<Breadcrumb
links={[ links={[
{
children: 'Workspace',
href: getSettingsPagePath(SettingsPath.Workspace),
},
{ {
children: 'Objects', children: 'Objects',
href: settingsObjectsPagePath, href: settingsObjectsPagePath,
}, },
{ children: 'New' }, { children: 'New' },
]} ]}
/>
}
actionButton={ actionButton={
<SaveAndCancelButtons <SaveAndCancelButtons
isSaveDisabled={!canSave} isSaveDisabled={!canSave}
@@ -93,7 +92,6 @@ export const SettingsNewObject = () => {
} }
> >
<SettingsPageContainer> <SettingsPageContainer>
<SettingsHeaderContainer></SettingsHeaderContainer>
<Section> <Section>
<H2Title <H2Title
title="About" title="About"

View File

@@ -8,7 +8,6 @@ import { SettingsPath } from '@/types/SettingsPath';
import { Button } from '@/ui/input/button/components/Button'; import { Button } from '@/ui/input/button/components/Button';
import { SubMenuTopBarContainer } from '@/ui/layout/page/SubMenuTopBarContainer'; import { SubMenuTopBarContainer } from '@/ui/layout/page/SubMenuTopBarContainer';
import { Section } from '@/ui/layout/section/components/Section'; import { Section } from '@/ui/layout/section/components/Section';
import { Breadcrumb } from '@/ui/navigation/bread-crumb/components/Breadcrumb';
import { UndecoratedLink } from '@/ui/navigation/link/components/UndecoratedLink'; import { UndecoratedLink } from '@/ui/navigation/link/components/UndecoratedLink';
import styled from '@emotion/styled'; import styled from '@emotion/styled';
import { isNonEmptyArray } from '@sniptt/guards'; import { isNonEmptyArray } from '@sniptt/guards';
@@ -49,14 +48,15 @@ export const SettingsObjectDetailPageContent = ({
return ( return (
<SubMenuTopBarContainer <SubMenuTopBarContainer
Icon={IconHierarchy2} Icon={IconHierarchy2}
title={ title={objectMetadataItem.labelPlural}
<Breadcrumb
links={[ links={[
{
children: 'Workspace',
href: getSettingsPagePath(SettingsPath.Workspace),
},
{ children: 'Objects', href: '/settings/objects' }, { children: 'Objects', href: '/settings/objects' },
{ children: objectMetadataItem.labelPlural }, { children: objectMetadataItem.labelPlural },
]} ]}
/>
}
> >
<SettingsPageContainer> <SettingsPageContainer>
<Section> <Section>

View File

@@ -13,7 +13,6 @@ import { useUpdateOneObjectMetadataItem } from '@/object-metadata/hooks/useUpdat
import { getObjectSlug } from '@/object-metadata/utils/getObjectSlug'; import { getObjectSlug } from '@/object-metadata/utils/getObjectSlug';
import { RecordFieldValueSelectorContextProvider } from '@/object-record/record-store/contexts/RecordFieldValueSelectorContext'; import { RecordFieldValueSelectorContextProvider } from '@/object-record/record-store/contexts/RecordFieldValueSelectorContext';
import { SaveAndCancelButtons } from '@/settings/components/SaveAndCancelButtons/SaveAndCancelButtons'; import { SaveAndCancelButtons } from '@/settings/components/SaveAndCancelButtons/SaveAndCancelButtons';
import { SettingsHeaderContainer } from '@/settings/components/SettingsHeaderContainer';
import { SettingsPageContainer } from '@/settings/components/SettingsPageContainer'; import { SettingsPageContainer } from '@/settings/components/SettingsPageContainer';
import { import {
SettingsDataModelObjectAboutForm, SettingsDataModelObjectAboutForm,
@@ -30,7 +29,6 @@ import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
import { Button } from '@/ui/input/button/components/Button'; import { Button } from '@/ui/input/button/components/Button';
import { SubMenuTopBarContainer } from '@/ui/layout/page/SubMenuTopBarContainer'; import { SubMenuTopBarContainer } from '@/ui/layout/page/SubMenuTopBarContainer';
import { Section } from '@/ui/layout/section/components/Section'; import { Section } from '@/ui/layout/section/components/Section';
import { Breadcrumb } from '@/ui/navigation/bread-crumb/components/Breadcrumb';
const objectEditFormSchema = z const objectEditFormSchema = z
.object({}) .object({})
@@ -110,9 +108,12 @@ export const SettingsObjectEdit = () => {
<FormProvider {...formConfig}> <FormProvider {...formConfig}>
<SubMenuTopBarContainer <SubMenuTopBarContainer
Icon={IconHierarchy2} Icon={IconHierarchy2}
title={ title="Edit"
<Breadcrumb
links={[ links={[
{
children: 'Workspace',
href: getSettingsPagePath(SettingsPath.Workspace),
},
{ {
children: 'Objects', children: 'Objects',
href: settingsObjectsPagePath, href: settingsObjectsPagePath,
@@ -121,14 +122,10 @@ export const SettingsObjectEdit = () => {
children: activeObjectMetadataItem.labelPlural, children: activeObjectMetadataItem.labelPlural,
href: `${settingsObjectsPagePath}/${objectSlug}`, href: `${settingsObjectsPagePath}/${objectSlug}`,
}, },
{ children: 'Edit' }, { children: 'Edit Object' },
]} ]}
/> actionButton={
} activeObjectMetadataItem.isCustom && (
>
<SettingsPageContainer>
<SettingsHeaderContainer>
{activeObjectMetadataItem.isCustom && (
<SaveAndCancelButtons <SaveAndCancelButtons
isSaveDisabled={!canSave} isSaveDisabled={!canSave}
isCancelDisabled={isSubmitting} isCancelDisabled={isSubmitting}
@@ -137,8 +134,10 @@ export const SettingsObjectEdit = () => {
} }
onSave={formConfig.handleSubmit(handleSave)} onSave={formConfig.handleSubmit(handleSave)}
/> />
)} )
</SettingsHeaderContainer> }
>
<SettingsPageContainer>
<Section> <Section>
<H2Title <H2Title
title="About" title="About"

View File

@@ -26,13 +26,14 @@ import { SettingsDataModelFieldIconLabelForm } from '@/settings/data-model/field
import { SettingsDataModelFieldSettingsFormCard } from '@/settings/data-model/fields/forms/components/SettingsDataModelFieldSettingsFormCard'; import { SettingsDataModelFieldSettingsFormCard } from '@/settings/data-model/fields/forms/components/SettingsDataModelFieldSettingsFormCard';
import { settingsFieldFormSchema } from '@/settings/data-model/fields/forms/validation-schemas/settingsFieldFormSchema'; import { settingsFieldFormSchema } from '@/settings/data-model/fields/forms/validation-schemas/settingsFieldFormSchema';
import { SettingsSupportedFieldType } from '@/settings/data-model/types/SettingsSupportedFieldType'; import { SettingsSupportedFieldType } from '@/settings/data-model/types/SettingsSupportedFieldType';
import { getSettingsPagePath } from '@/settings/utils/getSettingsPagePath';
import { AppPath } from '@/types/AppPath'; import { AppPath } from '@/types/AppPath';
import { SettingsPath } from '@/types/SettingsPath';
import { SnackBarVariant } from '@/ui/feedback/snack-bar-manager/components/SnackBar'; import { SnackBarVariant } from '@/ui/feedback/snack-bar-manager/components/SnackBar';
import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar'; import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
import { Button } from '@/ui/input/button/components/Button'; import { Button } from '@/ui/input/button/components/Button';
import { SubMenuTopBarContainer } from '@/ui/layout/page/SubMenuTopBarContainer'; import { SubMenuTopBarContainer } from '@/ui/layout/page/SubMenuTopBarContainer';
import { Section } from '@/ui/layout/section/components/Section'; import { Section } from '@/ui/layout/section/components/Section';
import { Breadcrumb } from '@/ui/navigation/bread-crumb/components/Breadcrumb';
import { FieldMetadataType } from '~/generated-metadata/graphql'; import { FieldMetadataType } from '~/generated-metadata/graphql';
import { isDefined } from '~/utils/isDefined'; import { isDefined } from '~/utils/isDefined';
@@ -173,23 +174,24 @@ export const SettingsObjectFieldEdit = () => {
<FormProvider {...formConfig}> <FormProvider {...formConfig}>
<SubMenuTopBarContainer <SubMenuTopBarContainer
Icon={IconHierarchy2} Icon={IconHierarchy2}
title={ title={activeMetadataField?.label}
<Breadcrumb
links={[ links={[
{
children: 'Workspace',
href: getSettingsPagePath(SettingsPath.Workspace),
},
{ {
children: 'Objects', children: 'Objects',
href: '/settings/objects', href: '/settings/objects',
styles: { minWidth: 'max-content' },
}, },
{ {
children: activeObjectMetadataItem.labelPlural, children: activeObjectMetadataItem.labelPlural,
href: `/settings/objects/${objectSlug}`, href: `/settings/objects/${objectSlug}`,
styles: { maxWidth: '60%' },
}, },
{ children: activeMetadataField.label }, {
children: activeMetadataField.label,
},
]} ]}
/>
}
actionButton={ actionButton={
shouldDisplaySaveAndCancel && ( shouldDisplaySaveAndCancel && (
<SaveAndCancelButtons <SaveAndCancelButtons

View File

@@ -9,11 +9,12 @@ import { SettingsPageContainer } from '@/settings/components/SettingsPageContain
import { useFieldMetadataItem } from '@/object-metadata/hooks/useFieldMetadataItem'; import { useFieldMetadataItem } from '@/object-metadata/hooks/useFieldMetadataItem';
import { settingsObjectFieldsFamilyState } from '@/settings/data-model/object-details/states/settingsObjectFieldsFamilyState'; import { settingsObjectFieldsFamilyState } from '@/settings/data-model/object-details/states/settingsObjectFieldsFamilyState';
import { getSettingsPagePath } from '@/settings/utils/getSettingsPagePath';
import { AppPath } from '@/types/AppPath'; import { AppPath } from '@/types/AppPath';
import { SettingsPath } from '@/types/SettingsPath';
import { Button } from '@/ui/input/button/components/Button'; import { Button } from '@/ui/input/button/components/Button';
import { SubMenuTopBarContainer } from '@/ui/layout/page/SubMenuTopBarContainer'; import { SubMenuTopBarContainer } from '@/ui/layout/page/SubMenuTopBarContainer';
import { Section } from '@/ui/layout/section/components/Section'; import { Section } from '@/ui/layout/section/components/Section';
import { Breadcrumb } from '@/ui/navigation/bread-crumb/components/Breadcrumb';
import { useRecoilState } from 'recoil'; import { useRecoilState } from 'recoil';
import { SettingsObjectFieldTable } from '~/pages/settings/data-model/SettingsObjectFieldTable'; import { SettingsObjectFieldTable } from '~/pages/settings/data-model/SettingsObjectFieldTable';
@@ -86,9 +87,11 @@ export const SettingsObjectNewFieldStep1 = () => {
return ( return (
<SubMenuTopBarContainer <SubMenuTopBarContainer
Icon={IconHierarchy2} Icon={IconHierarchy2}
title={
<Breadcrumb
links={[ links={[
{
children: 'Workspace',
href: getSettingsPagePath(SettingsPath.Workspace),
},
{ children: 'Objects', href: '/settings/objects' }, { children: 'Objects', href: '/settings/objects' },
{ {
children: activeObjectMetadataItem.labelPlural, children: activeObjectMetadataItem.labelPlural,
@@ -96,8 +99,6 @@ export const SettingsObjectNewFieldStep1 = () => {
}, },
{ children: 'New Field' }, { children: 'New Field' },
]} ]}
/>
}
actionButton={ actionButton={
!activeObjectMetadataItem.isRemote && ( !activeObjectMetadataItem.isRemote && (
<SaveAndCancelButtons <SaveAndCancelButtons

View File

@@ -19,7 +19,6 @@ import { SnackBarVariant } from '@/ui/feedback/snack-bar-manager/components/Snac
import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar'; import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
import { SubMenuTopBarContainer } from '@/ui/layout/page/SubMenuTopBarContainer'; import { SubMenuTopBarContainer } from '@/ui/layout/page/SubMenuTopBarContainer';
import { Section } from '@/ui/layout/section/components/Section'; import { Section } from '@/ui/layout/section/components/Section';
import { Breadcrumb } from '@/ui/navigation/bread-crumb/components/Breadcrumb';
import { View } from '@/views/types/View'; import { View } from '@/views/types/View';
import { ViewType } from '@/views/types/ViewType'; import { ViewType } from '@/views/types/ViewType';
import { useApolloClient } from '@apollo/client'; import { useApolloClient } from '@apollo/client';
@@ -41,6 +40,7 @@ type SettingsDataModelNewFieldFormValues = z.infer<
const StyledH1Title = styled(H1Title)` const StyledH1Title = styled(H1Title)`
margin-bottom: 0; margin-bottom: 0;
padding-top: ${({ theme }) => theme.spacing(3)};
`; `;
export const SettingsObjectNewFieldStep2 = () => { export const SettingsObjectNewFieldStep2 = () => {
const navigate = useNavigate(); const navigate = useNavigate();
@@ -177,18 +177,14 @@ export const SettingsObjectNewFieldStep2 = () => {
> >
<SubMenuTopBarContainer <SubMenuTopBarContainer
Icon={IconHierarchy2} Icon={IconHierarchy2}
title={
<Breadcrumb
links={[ links={[
{ {
children: 'Objects', children: 'Objects',
href: '/settings/objects', href: '/settings/objects',
styles: { minWidth: 'max-content' },
}, },
{ {
children: activeObjectMetadataItem.labelPlural, children: activeObjectMetadataItem.labelPlural,
href: `/settings/objects/${objectSlug}`, href: `/settings/objects/${objectSlug}`,
styles: { maxWidth: '50%' },
}, },
{ {
children: ( children: (
@@ -199,8 +195,6 @@ export const SettingsObjectNewFieldStep2 = () => {
), ),
}, },
]} ]}
/>
}
actionButton={ actionButton={
!activeObjectMetadataItem.isRemote && ( !activeObjectMetadataItem.isRemote && (
<SaveAndCancelButtons <SaveAndCancelButtons

View File

@@ -1,24 +1,25 @@
import { ReactFlowProvider } from 'reactflow'; import { ReactFlowProvider } from 'reactflow';
import { SettingsDataModelOverview } from '@/settings/data-model/graph-overview/components/SettingsDataModelOverview'; import { SettingsDataModelOverview } from '@/settings/data-model/graph-overview/components/SettingsDataModelOverview';
import { getSettingsPagePath } from '@/settings/utils/getSettingsPagePath';
import { SettingsPath } from '@/types/SettingsPath';
import { SubMenuTopBarContainer } from '@/ui/layout/page/SubMenuTopBarContainer'; import { SubMenuTopBarContainer } from '@/ui/layout/page/SubMenuTopBarContainer';
import { Breadcrumb } from '@/ui/navigation/bread-crumb/components/Breadcrumb';
import { IconHierarchy2 } from 'twenty-ui'; import { IconHierarchy2 } from 'twenty-ui';
export const SettingsObjectOverview = () => { export const SettingsObjectOverview = () => {
return ( return (
<SubMenuTopBarContainer <SubMenuTopBarContainer
Icon={IconHierarchy2} Icon={IconHierarchy2}
title={
<Breadcrumb
links={[ links={[
{ children: 'Data model', href: '/settings/objects' }, {
children: 'Workspace',
href: getSettingsPagePath(SettingsPath.Workspace),
},
{ children: 'Objects', href: '/settings/objects' },
{ {
children: 'Overview', children: 'Overview',
}, },
]} ]}
/>
}
> >
<ReactFlowProvider> <ReactFlowProvider>
<SettingsDataModelOverview /> <SettingsDataModelOverview />

View File

@@ -145,6 +145,15 @@ export const SettingsObjects = () => {
/> />
</UndecoratedLink> </UndecoratedLink>
} }
links={[
{
children: 'Workspace',
href: getSettingsPagePath(SettingsPath.Workspace),
},
{
children: 'Objects',
},
]}
> >
<SettingsPageContainer> <SettingsPageContainer>
<> <>

View File

@@ -30,7 +30,6 @@ export type Story = StoryObj<typeof SettingsObjectNewFieldStep1>;
export const Default: Story = { export const Default: Story = {
play: async ({ canvasElement }) => { play: async ({ canvasElement }) => {
const canvas = within(canvasElement); const canvas = within(canvasElement);
await canvas.findByText('Settings');
await canvas.findByText('Objects'); await canvas.findByText('Objects');
await canvas.findByText('Companies'); await canvas.findByText('Companies');
await canvas.findByText('Check deactivated fields'); await canvas.findByText('Check deactivated fields');

View File

@@ -5,6 +5,8 @@ import { SettingsPageContainer } from '@/settings/components/SettingsPageContain
import { SettingsApiKeysTable } from '@/settings/developers/components/SettingsApiKeysTable'; import { SettingsApiKeysTable } from '@/settings/developers/components/SettingsApiKeysTable';
import { SettingsReadDocumentationButton } from '@/settings/developers/components/SettingsReadDocumentationButton'; import { SettingsReadDocumentationButton } from '@/settings/developers/components/SettingsReadDocumentationButton';
import { SettingsWebhooksTable } from '@/settings/developers/components/SettingsWebhooksTable'; import { SettingsWebhooksTable } from '@/settings/developers/components/SettingsWebhooksTable';
import { getSettingsPagePath } from '@/settings/utils/getSettingsPagePath';
import { SettingsPath } from '@/types/SettingsPath';
import { Button } from '@/ui/input/button/components/Button'; import { Button } from '@/ui/input/button/components/Button';
import { SubMenuTopBarContainer } from '@/ui/layout/page/SubMenuTopBarContainer'; import { SubMenuTopBarContainer } from '@/ui/layout/page/SubMenuTopBarContainer';
import { Section } from '@/ui/layout/section/components/Section'; import { Section } from '@/ui/layout/section/components/Section';
@@ -21,6 +23,13 @@ export const SettingsDevelopers = () => {
Icon={IconCode} Icon={IconCode}
title="Developers" title="Developers"
actionButton={<SettingsReadDocumentationButton />} actionButton={<SettingsReadDocumentationButton />}
links={[
{
children: 'Workspace',
href: getSettingsPagePath(SettingsPath.Workspace),
},
{ children: 'Developers' },
]}
> >
<SettingsPageContainer> <SettingsPageContainer>
<Section> <Section>

View File

@@ -25,8 +25,7 @@ export type Story = StoryObj<typeof SettingsDevelopersApiKeysNew>;
export const Default: Story = { export const Default: Story = {
play: async ({ canvasElement }) => { play: async ({ canvasElement }) => {
const canvas = within(canvasElement); const canvas = within(canvasElement);
await canvas.findByText('Settings'); await canvas.findByText('New Key');
await canvas.findByText('New API Key');
await canvas.findByText('Name'); await canvas.findByText('Name');
await canvas.findByText('Expiration Date'); await canvas.findByText('Expiration Date');

View File

@@ -17,12 +17,13 @@ import { apiKeyTokenState } from '@/settings/developers/states/generatedApiKeyTo
import { ApiKey } from '@/settings/developers/types/api-key/ApiKey'; import { ApiKey } from '@/settings/developers/types/api-key/ApiKey';
import { computeNewExpirationDate } from '@/settings/developers/utils/compute-new-expiration-date'; import { computeNewExpirationDate } from '@/settings/developers/utils/compute-new-expiration-date';
import { formatExpiration } from '@/settings/developers/utils/format-expiration'; import { formatExpiration } from '@/settings/developers/utils/format-expiration';
import { getSettingsPagePath } from '@/settings/utils/getSettingsPagePath';
import { SettingsPath } from '@/types/SettingsPath';
import { Button } from '@/ui/input/button/components/Button'; import { Button } from '@/ui/input/button/components/Button';
import { TextInput } from '@/ui/input/components/TextInput'; import { TextInput } from '@/ui/input/components/TextInput';
import { ConfirmationModal } from '@/ui/layout/modal/components/ConfirmationModal'; import { ConfirmationModal } from '@/ui/layout/modal/components/ConfirmationModal';
import { SubMenuTopBarContainer } from '@/ui/layout/page/SubMenuTopBarContainer'; import { SubMenuTopBarContainer } from '@/ui/layout/page/SubMenuTopBarContainer';
import { Section } from '@/ui/layout/section/components/Section'; import { Section } from '@/ui/layout/section/components/Section';
import { Breadcrumb } from '@/ui/navigation/bread-crumb/components/Breadcrumb';
import { useGenerateApiKeyTokenMutation } from '~/generated/graphql'; import { useGenerateApiKeyTokenMutation } from '~/generated/graphql';
const StyledInfo = styled.span` const StyledInfo = styled.span`
@@ -65,6 +66,7 @@ export const SettingsDevelopersApiKeyDetail = () => {
setApiKeyName(record.name); setApiKeyName(record.name);
}, },
}); });
const developerPath = getSettingsPagePath(SettingsPath.Developers);
const deleteIntegration = async (redirect = true) => { const deleteIntegration = async (redirect = true) => {
await updateApiKey?.({ await updateApiKey?.({
@@ -72,7 +74,7 @@ export const SettingsDevelopersApiKeyDetail = () => {
updateOneRecordInput: { revokedAt: DateTime.now().toString() }, updateOneRecordInput: { revokedAt: DateTime.now().toString() },
}); });
if (redirect) { if (redirect) {
navigate('/settings/developers'); navigate(developerPath);
} }
}; };
@@ -122,14 +124,15 @@ export const SettingsDevelopersApiKeyDetail = () => {
{apiKeyData?.name && ( {apiKeyData?.name && (
<SubMenuTopBarContainer <SubMenuTopBarContainer
Icon={IconCode} Icon={IconCode}
title={ title={apiKeyData?.name}
<Breadcrumb
links={[ links={[
{ children: 'Developers', href: '/settings/developers' }, {
children: 'Workspace',
href: getSettingsPagePath(SettingsPath.Workspace),
},
{ children: 'Developers', href: developerPath },
{ children: `${apiKeyName} API Key` }, { children: `${apiKeyName} API Key` },
]} ]}
/>
}
> >
<SettingsPageContainer> <SettingsPageContainer>
<Section> <Section>

View File

@@ -10,11 +10,12 @@ import { SettingsPageContainer } from '@/settings/components/SettingsPageContain
import { EXPIRATION_DATES } from '@/settings/developers/constants/ExpirationDates'; import { EXPIRATION_DATES } from '@/settings/developers/constants/ExpirationDates';
import { apiKeyTokenState } from '@/settings/developers/states/generatedApiKeyTokenState'; import { apiKeyTokenState } from '@/settings/developers/states/generatedApiKeyTokenState';
import { ApiKey } from '@/settings/developers/types/api-key/ApiKey'; import { ApiKey } from '@/settings/developers/types/api-key/ApiKey';
import { getSettingsPagePath } from '@/settings/utils/getSettingsPagePath';
import { SettingsPath } from '@/types/SettingsPath';
import { Select } from '@/ui/input/components/Select'; import { Select } from '@/ui/input/components/Select';
import { TextInput } from '@/ui/input/components/TextInput'; import { TextInput } from '@/ui/input/components/TextInput';
import { SubMenuTopBarContainer } from '@/ui/layout/page/SubMenuTopBarContainer'; import { SubMenuTopBarContainer } from '@/ui/layout/page/SubMenuTopBarContainer';
import { Section } from '@/ui/layout/section/components/Section'; import { Section } from '@/ui/layout/section/components/Section';
import { Breadcrumb } from '@/ui/navigation/bread-crumb/components/Breadcrumb';
import { useSetRecoilState } from 'recoil'; import { useSetRecoilState } from 'recoil';
import { Key } from 'ts-key-enum'; import { Key } from 'ts-key-enum';
import { useGenerateApiKeyTokenMutation } from '~/generated/graphql'; import { useGenerateApiKeyTokenMutation } from '~/generated/graphql';
@@ -65,14 +66,18 @@ export const SettingsDevelopersApiKeysNew = () => {
return ( return (
<SubMenuTopBarContainer <SubMenuTopBarContainer
Icon={IconCode} Icon={IconCode}
title={ title="New key"
<Breadcrumb
links={[ links={[
{ children: 'Developers', href: '/settings/developers' }, {
{ children: 'New API Key' }, children: 'Workspace',
href: getSettingsPagePath(SettingsPath.Workspace),
},
{
children: 'Developers',
href: getSettingsPagePath(SettingsPath.Developers),
},
{ children: 'New Key' },
]} ]}
/>
}
actionButton={ actionButton={
<SaveAndCancelButtons <SaveAndCancelButtons
isSaveDisabled={!canSave} isSaveDisabled={!canSave}

View File

@@ -11,6 +11,8 @@ import { useUpdateOneRecord } from '@/object-record/hooks/useUpdateOneRecord';
import { SaveAndCancelButtons } from '@/settings/components/SaveAndCancelButtons/SaveAndCancelButtons'; import { SaveAndCancelButtons } from '@/settings/components/SaveAndCancelButtons/SaveAndCancelButtons';
import { SettingsPageContainer } from '@/settings/components/SettingsPageContainer'; import { SettingsPageContainer } from '@/settings/components/SettingsPageContainer';
import { Webhook } from '@/settings/developers/types/webhook/Webhook'; import { Webhook } from '@/settings/developers/types/webhook/Webhook';
import { getSettingsPagePath } from '@/settings/utils/getSettingsPagePath';
import { SettingsPath } from '@/types/SettingsPath';
import { Button } from '@/ui/input/button/components/Button'; import { Button } from '@/ui/input/button/components/Button';
import { Select } from '@/ui/input/components/Select'; import { Select } from '@/ui/input/components/Select';
import { TextArea } from '@/ui/input/components/TextArea'; import { TextArea } from '@/ui/input/components/TextArea';
@@ -18,7 +20,6 @@ import { TextInput } from '@/ui/input/components/TextInput';
import { ConfirmationModal } from '@/ui/layout/modal/components/ConfirmationModal'; import { ConfirmationModal } from '@/ui/layout/modal/components/ConfirmationModal';
import { SubMenuTopBarContainer } from '@/ui/layout/page/SubMenuTopBarContainer'; import { SubMenuTopBarContainer } from '@/ui/layout/page/SubMenuTopBarContainer';
import { Section } from '@/ui/layout/section/components/Section'; import { Section } from '@/ui/layout/section/components/Section';
import { Breadcrumb } from '@/ui/navigation/bread-crumb/components/Breadcrumb';
const StyledFilterRow = styled.div` const StyledFilterRow = styled.div`
display: flex; display: flex;
@@ -55,9 +56,11 @@ export const SettingsDevelopersWebhooksDetail = () => {
objectNameSingular: CoreObjectNameSingular.Webhook, objectNameSingular: CoreObjectNameSingular.Webhook,
}); });
const developerPath = getSettingsPagePath(SettingsPath.Developers);
const deleteWebhook = () => { const deleteWebhook = () => {
deleteOneWebhook(webhookId); deleteOneWebhook(webhookId);
navigate('/settings/developers'); navigate(developerPath);
}; };
const fieldTypeOptions = [ const fieldTypeOptions = [
@@ -81,7 +84,7 @@ export const SettingsDevelopersWebhooksDetail = () => {
description: description, description: description,
}, },
}); });
navigate('/settings/developers'); navigate(developerPath);
}; };
return ( return (
@@ -89,19 +92,23 @@ export const SettingsDevelopersWebhooksDetail = () => {
{webhookData?.targetUrl && ( {webhookData?.targetUrl && (
<SubMenuTopBarContainer <SubMenuTopBarContainer
Icon={IconCode} Icon={IconCode}
title={ title={webhookData.targetUrl}
<Breadcrumb
links={[ links={[
{ children: 'Developers', href: '/settings/developers' }, {
children: 'Workspace',
href: getSettingsPagePath(SettingsPath.Workspace),
},
{
children: 'Developers',
href: developerPath,
},
{ children: 'Webhook' }, { children: 'Webhook' },
]} ]}
/>
}
actionButton={ actionButton={
<SaveAndCancelButtons <SaveAndCancelButtons
isSaveDisabled={!isDirty} isSaveDisabled={!isDirty}
onCancel={() => { onCancel={() => {
navigate('/settings/developers'); navigate(developerPath);
}} }}
onSave={handleSave} onSave={handleSave}
/> />

View File

@@ -7,10 +7,11 @@ import { useCreateOneRecord } from '@/object-record/hooks/useCreateOneRecord';
import { SaveAndCancelButtons } from '@/settings/components/SaveAndCancelButtons/SaveAndCancelButtons'; import { SaveAndCancelButtons } from '@/settings/components/SaveAndCancelButtons/SaveAndCancelButtons';
import { SettingsPageContainer } from '@/settings/components/SettingsPageContainer'; import { SettingsPageContainer } from '@/settings/components/SettingsPageContainer';
import { Webhook } from '@/settings/developers/types/webhook/Webhook'; import { Webhook } from '@/settings/developers/types/webhook/Webhook';
import { getSettingsPagePath } from '@/settings/utils/getSettingsPagePath';
import { SettingsPath } from '@/types/SettingsPath';
import { TextInput } from '@/ui/input/components/TextInput'; import { TextInput } from '@/ui/input/components/TextInput';
import { SubMenuTopBarContainer } from '@/ui/layout/page/SubMenuTopBarContainer'; import { SubMenuTopBarContainer } from '@/ui/layout/page/SubMenuTopBarContainer';
import { Section } from '@/ui/layout/section/components/Section'; import { Section } from '@/ui/layout/section/components/Section';
import { Breadcrumb } from '@/ui/navigation/bread-crumb/components/Breadcrumb';
import { isValidUrl } from '~/utils/url/isValidUrl'; import { isValidUrl } from '~/utils/url/isValidUrl';
export const SettingsDevelopersWebhooksNew = () => { export const SettingsDevelopersWebhooksNew = () => {
@@ -49,19 +50,23 @@ export const SettingsDevelopersWebhooksNew = () => {
return ( return (
<SubMenuTopBarContainer <SubMenuTopBarContainer
Icon={IconCode} Icon={IconCode}
title={ title="New Webhook"
<Breadcrumb
links={[ links={[
{ children: 'Developers', href: '/settings/developers' }, {
{ children: 'New webhook' }, children: 'Workspace',
href: getSettingsPagePath(SettingsPath.Workspace),
},
{
children: 'Developers',
href: getSettingsPagePath(SettingsPath.Developers),
},
{ children: 'New Webhook' },
]} ]}
/>
}
actionButton={ actionButton={
<SaveAndCancelButtons <SaveAndCancelButtons
isSaveDisabled={!canSave} isSaveDisabled={!canSave}
onCancel={() => { onCancel={() => {
navigate('/settings/developers'); navigate(getSettingsPagePath(SettingsPath.Developers));
}} }}
onSave={handleSave} onSave={handleSave}
/> />

View File

@@ -13,7 +13,6 @@ import { AppPath } from '@/types/AppPath';
import { SettingsPath } from '@/types/SettingsPath'; import { SettingsPath } from '@/types/SettingsPath';
import { SubMenuTopBarContainer } from '@/ui/layout/page/SubMenuTopBarContainer'; import { SubMenuTopBarContainer } from '@/ui/layout/page/SubMenuTopBarContainer';
import { Section } from '@/ui/layout/section/components/Section'; import { Section } from '@/ui/layout/section/components/Section';
import { Breadcrumb } from '@/ui/navigation/bread-crumb/components/Breadcrumb';
export const SettingsIntegrationDatabase = () => { export const SettingsIntegrationDatabase = () => {
const { databaseKey = '' } = useParams(); const { databaseKey = '' } = useParams();
@@ -44,17 +43,18 @@ export const SettingsIntegrationDatabase = () => {
return ( return (
<SubMenuTopBarContainer <SubMenuTopBarContainer
Icon={IconSettings} Icon={IconSettings}
title={ title={integration.text}
<Breadcrumb
links={[ links={[
{
children: 'Workspace',
href: getSettingsPagePath(SettingsPath.Workspace),
},
{ {
children: 'Integrations', children: 'Integrations',
href: getSettingsPagePath(SettingsPath.Integrations), href: getSettingsPagePath(SettingsPath.Integrations),
}, },
{ children: integration.text }, { children: integration.text },
]} ]}
/>
}
> >
<SettingsPageContainer> <SettingsPageContainer>
<SettingsIntegrationPreview <SettingsIntegrationPreview

View File

@@ -2,21 +2,26 @@ import { IconSettings } from 'twenty-ui';
import { SettingsPageContainer } from '@/settings/components/SettingsPageContainer'; import { SettingsPageContainer } from '@/settings/components/SettingsPageContainer';
import { SettingsIntegrationEditDatabaseConnectionContainer } from '@/settings/integrations/database-connection/components/SettingsIntegrationEditDatabaseConnectionContainer'; import { SettingsIntegrationEditDatabaseConnectionContainer } from '@/settings/integrations/database-connection/components/SettingsIntegrationEditDatabaseConnectionContainer';
import { getSettingsPagePath } from '@/settings/utils/getSettingsPagePath';
import { SettingsPath } from '@/types/SettingsPath';
import { SubMenuTopBarContainer } from '@/ui/layout/page/SubMenuTopBarContainer'; import { SubMenuTopBarContainer } from '@/ui/layout/page/SubMenuTopBarContainer';
import { Breadcrumb } from '@/ui/navigation/bread-crumb/components/Breadcrumb';
export const SettingsIntegrationEditDatabaseConnection = () => { export const SettingsIntegrationEditDatabaseConnection = () => {
return ( return (
<SubMenuTopBarContainer <SubMenuTopBarContainer
Icon={IconSettings} Icon={IconSettings}
title={ title="Edit connection"
<Breadcrumb
links={[ links={[
// TODO {
children: 'Workspace',
href: getSettingsPagePath(SettingsPath.Workspace),
},
{
children: 'Integrations',
href: getSettingsPagePath(SettingsPath.Integrations),
},
{ children: 'Edit connection' }, { children: 'Edit connection' },
]} ]}
/>
}
> >
<SettingsPageContainer> <SettingsPageContainer>
<SettingsIntegrationEditDatabaseConnectionContainer /> <SettingsIntegrationEditDatabaseConnectionContainer />

View File

@@ -23,7 +23,6 @@ import { SnackBarVariant } from '@/ui/feedback/snack-bar-manager/components/Snac
import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar'; import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
import { SubMenuTopBarContainer } from '@/ui/layout/page/SubMenuTopBarContainer'; import { SubMenuTopBarContainer } from '@/ui/layout/page/SubMenuTopBarContainer';
import { Section } from '@/ui/layout/section/components/Section'; import { Section } from '@/ui/layout/section/components/Section';
import { Breadcrumb } from '@/ui/navigation/bread-crumb/components/Breadcrumb';
import { CreateRemoteServerInput } from '~/generated-metadata/graphql'; import { CreateRemoteServerInput } from '~/generated-metadata/graphql';
const createRemoteServerInputPostgresSchema = const createRemoteServerInputPostgresSchema =
@@ -133,9 +132,12 @@ export const SettingsIntegrationNewDatabaseConnection = () => {
return ( return (
<SubMenuTopBarContainer <SubMenuTopBarContainer
Icon={IconSettings} Icon={IconSettings}
title={ title="New"
<Breadcrumb
links={[ links={[
{
children: 'Workspace',
href: getSettingsPagePath(SettingsPath.Workspace),
},
{ {
children: 'Integrations', children: 'Integrations',
href: settingsIntegrationsPagePath, href: settingsIntegrationsPagePath,
@@ -146,8 +148,6 @@ export const SettingsIntegrationNewDatabaseConnection = () => {
}, },
{ children: 'New' }, { children: 'New' },
]} ]}
/>
}
actionButton={ actionButton={
<SaveAndCancelButtons <SaveAndCancelButtons
isSaveDisabled={!canSave} isSaveDisabled={!canSave}

View File

@@ -2,11 +2,27 @@ import { IconSettings } from 'twenty-ui';
import { SettingsPageContainer } from '@/settings/components/SettingsPageContainer'; import { SettingsPageContainer } from '@/settings/components/SettingsPageContainer';
import { SettingsIntegrationDatabaseConnectionShowContainer } from '@/settings/integrations/database-connection/components/SettingsIntegrationDatabaseConnectionShowContainer'; import { SettingsIntegrationDatabaseConnectionShowContainer } from '@/settings/integrations/database-connection/components/SettingsIntegrationDatabaseConnectionShowContainer';
import { getSettingsPagePath } from '@/settings/utils/getSettingsPagePath';
import { SettingsPath } from '@/types/SettingsPath';
import { SubMenuTopBarContainer } from '@/ui/layout/page/SubMenuTopBarContainer'; import { SubMenuTopBarContainer } from '@/ui/layout/page/SubMenuTopBarContainer';
export const SettingsIntegrationShowDatabaseConnection = () => { export const SettingsIntegrationShowDatabaseConnection = () => {
return ( return (
<SubMenuTopBarContainer Icon={IconSettings} title="Settings"> <SubMenuTopBarContainer
Icon={IconSettings}
title="Database Connection"
links={[
{
children: 'Workspace',
href: getSettingsPagePath(SettingsPath.Workspace),
},
{
children: 'Integrations',
href: getSettingsPagePath(SettingsPath.Integrations),
},
{ children: 'Database Connection' },
]}
>
<SettingsPageContainer> <SettingsPageContainer>
<SettingsIntegrationDatabaseConnectionShowContainer /> <SettingsIntegrationDatabaseConnectionShowContainer />
</SettingsPageContainer> </SettingsPageContainer>

View File

@@ -1,6 +1,8 @@
import { SettingsPageContainer } from '@/settings/components/SettingsPageContainer'; import { SettingsPageContainer } from '@/settings/components/SettingsPageContainer';
import { SettingsIntegrationGroup } from '@/settings/integrations/components/SettingsIntegrationGroup'; import { SettingsIntegrationGroup } from '@/settings/integrations/components/SettingsIntegrationGroup';
import { useSettingsIntegrationCategories } from '@/settings/integrations/hooks/useSettingsIntegrationCategories'; import { useSettingsIntegrationCategories } from '@/settings/integrations/hooks/useSettingsIntegrationCategories';
import { getSettingsPagePath } from '@/settings/utils/getSettingsPagePath';
import { SettingsPath } from '@/types/SettingsPath';
import { SubMenuTopBarContainer } from '@/ui/layout/page/SubMenuTopBarContainer'; import { SubMenuTopBarContainer } from '@/ui/layout/page/SubMenuTopBarContainer';
import { IconApps } from 'twenty-ui'; import { IconApps } from 'twenty-ui';
@@ -8,7 +10,17 @@ export const SettingsIntegrations = () => {
const integrationCategories = useSettingsIntegrationCategories(); const integrationCategories = useSettingsIntegrationCategories();
return ( return (
<SubMenuTopBarContainer Icon={IconApps} title="Integrations"> <SubMenuTopBarContainer
Icon={IconApps}
title="Integrations"
links={[
{
children: 'Workspace',
href: getSettingsPagePath(SettingsPath.Workspace),
},
{ children: 'Integrations' },
]}
>
<SettingsPageContainer> <SettingsPageContainer>
{integrationCategories.map((group) => ( {integrationCategories.map((group) => (
<SettingsIntegrationGroup key={group.key} integrationGroup={group} /> <SettingsIntegrationGroup key={group.key} integrationGroup={group} />

View File

@@ -1,6 +1,8 @@
import { H2Title, IconColorSwatch } from 'twenty-ui'; import { H2Title, IconColorSwatch } from 'twenty-ui';
import { SettingsPageContainer } from '@/settings/components/SettingsPageContainer'; import { SettingsPageContainer } from '@/settings/components/SettingsPageContainer';
import { getSettingsPagePath } from '@/settings/utils/getSettingsPagePath';
import { SettingsPath } from '@/types/SettingsPath';
import { ColorSchemePicker } from '@/ui/input/color-scheme/components/ColorSchemePicker'; import { ColorSchemePicker } from '@/ui/input/color-scheme/components/ColorSchemePicker';
import { SubMenuTopBarContainer } from '@/ui/layout/page/SubMenuTopBarContainer'; import { SubMenuTopBarContainer } from '@/ui/layout/page/SubMenuTopBarContainer';
import { Section } from '@/ui/layout/section/components/Section'; import { Section } from '@/ui/layout/section/components/Section';
@@ -11,10 +13,20 @@ export const SettingsAppearance = () => {
const { colorScheme, setColorScheme } = useColorScheme(); const { colorScheme, setColorScheme } = useColorScheme();
return ( return (
<SubMenuTopBarContainer Icon={IconColorSwatch} title="Appearance"> <SubMenuTopBarContainer
Icon={IconColorSwatch}
title="Experience"
links={[
{
children: 'User',
href: getSettingsPagePath(SettingsPath.ProfilePage),
},
{ children: 'Experience' },
]}
>
<SettingsPageContainer> <SettingsPageContainer>
<Section> <Section>
<H2Title title="Theme" /> <H2Title title="Appearance" />
<ColorSchemePicker value={colorScheme} onChange={setColorScheme} /> <ColorSchemePicker value={colorScheme} onChange={setColorScheme} />
</Section> </Section>
<Section> <Section>

View File

@@ -4,25 +4,26 @@ import { SettingsServerlessFunctionSettingsTab } from '@/settings/serverless-fun
import { SettingsServerlessFunctionTestTab } from '@/settings/serverless-functions/components/tabs/SettingsServerlessFunctionTestTab'; import { SettingsServerlessFunctionTestTab } from '@/settings/serverless-functions/components/tabs/SettingsServerlessFunctionTestTab';
import { SettingsServerlessFunctionTestTabEffect } from '@/settings/serverless-functions/components/tabs/SettingsServerlessFunctionTestTabEffect'; import { SettingsServerlessFunctionTestTabEffect } from '@/settings/serverless-functions/components/tabs/SettingsServerlessFunctionTestTabEffect';
import { useExecuteOneServerlessFunction } from '@/settings/serverless-functions/hooks/useExecuteOneServerlessFunction'; import { useExecuteOneServerlessFunction } from '@/settings/serverless-functions/hooks/useExecuteOneServerlessFunction';
import { useGetOneServerlessFunctionSourceCode } from '@/settings/serverless-functions/hooks/useGetOneServerlessFunctionSourceCode';
import { usePublishOneServerlessFunction } from '@/settings/serverless-functions/hooks/usePublishOneServerlessFunction';
import { useServerlessFunctionUpdateFormState } from '@/settings/serverless-functions/hooks/useServerlessFunctionUpdateFormState'; import { useServerlessFunctionUpdateFormState } from '@/settings/serverless-functions/hooks/useServerlessFunctionUpdateFormState';
import { useUpdateOneServerlessFunction } from '@/settings/serverless-functions/hooks/useUpdateOneServerlessFunction'; import { useUpdateOneServerlessFunction } from '@/settings/serverless-functions/hooks/useUpdateOneServerlessFunction';
import { usePublishOneServerlessFunction } from '@/settings/serverless-functions/hooks/usePublishOneServerlessFunction';
import { settingsServerlessFunctionInputState } from '@/settings/serverless-functions/states/settingsServerlessFunctionInputState'; import { settingsServerlessFunctionInputState } from '@/settings/serverless-functions/states/settingsServerlessFunctionInputState';
import { settingsServerlessFunctionOutputState } from '@/settings/serverless-functions/states/settingsServerlessFunctionOutputState'; import { settingsServerlessFunctionOutputState } from '@/settings/serverless-functions/states/settingsServerlessFunctionOutputState';
import { getSettingsPagePath } from '@/settings/utils/getSettingsPagePath';
import { SettingsPath } from '@/types/SettingsPath';
import { SnackBarVariant } from '@/ui/feedback/snack-bar-manager/components/SnackBar'; import { SnackBarVariant } from '@/ui/feedback/snack-bar-manager/components/SnackBar';
import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar'; import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
import { SubMenuTopBarContainer } from '@/ui/layout/page/SubMenuTopBarContainer'; import { SubMenuTopBarContainer } from '@/ui/layout/page/SubMenuTopBarContainer';
import { Section } from '@/ui/layout/section/components/Section'; import { Section } from '@/ui/layout/section/components/Section';
import { TabList } from '@/ui/layout/tab/components/TabList'; import { TabList } from '@/ui/layout/tab/components/TabList';
import { useTabList } from '@/ui/layout/tab/hooks/useTabList'; import { useTabList } from '@/ui/layout/tab/hooks/useTabList';
import { Breadcrumb } from '@/ui/navigation/bread-crumb/components/Breadcrumb'; import { useState } from 'react';
import { useParams } from 'react-router-dom'; import { useParams } from 'react-router-dom';
import { useRecoilValue, useSetRecoilState } from 'recoil'; import { useRecoilValue, useSetRecoilState } from 'recoil';
import { IconCode, IconFunction, IconSettings, IconTestPipe } from 'twenty-ui'; import { IconCode, IconFunction, IconSettings, IconTestPipe } from 'twenty-ui';
import { isDefined } from '~/utils/isDefined';
import { useGetOneServerlessFunctionSourceCode } from '@/settings/serverless-functions/hooks/useGetOneServerlessFunctionSourceCode';
import { useState } from 'react';
import { usePreventOverlapCallback } from '~/hooks/usePreventOverlapCallback'; import { usePreventOverlapCallback } from '~/hooks/usePreventOverlapCallback';
import { isDefined } from '~/utils/isDefined';
const TAB_LIST_COMPONENT_ID = 'serverless-function-detail'; const TAB_LIST_COMPONENT_ID = 'serverless-function-detail';
@@ -204,14 +205,18 @@ export const SettingsServerlessFunctionDetail = () => {
!loading && ( !loading && (
<SubMenuTopBarContainer <SubMenuTopBarContainer
Icon={IconFunction} Icon={IconFunction}
title={ title={formValues.name}
<Breadcrumb
links={[ links={[
{ children: 'Functions', href: '/settings/functions' }, {
children: 'Workspace',
href: getSettingsPagePath(SettingsPath.Workspace),
},
{
children: 'Functions',
href: getSettingsPagePath(SettingsPath.ServerlessFunctions),
},
{ children: `${formValues.name}` }, { children: `${formValues.name}` },
]} ]}
/>
}
> >
<SettingsPageContainer> <SettingsPageContainer>
<Section> <Section>

View File

@@ -1,4 +1,3 @@
import { SettingsHeaderContainer } from '@/settings/components/SettingsHeaderContainer';
import { SettingsPageContainer } from '@/settings/components/SettingsPageContainer'; import { SettingsPageContainer } from '@/settings/components/SettingsPageContainer';
import { SettingsServerlessFunctionsTable } from '@/settings/serverless-functions/components/SettingsServerlessFunctionsTable'; import { SettingsServerlessFunctionsTable } from '@/settings/serverless-functions/components/SettingsServerlessFunctionsTable';
import { getSettingsPagePath } from '@/settings/utils/getSettingsPagePath'; import { getSettingsPagePath } from '@/settings/utils/getSettingsPagePath';
@@ -6,7 +5,6 @@ import { SettingsPath } from '@/types/SettingsPath';
import { Button } from '@/ui/input/button/components/Button'; import { Button } from '@/ui/input/button/components/Button';
import { SubMenuTopBarContainer } from '@/ui/layout/page/SubMenuTopBarContainer'; import { SubMenuTopBarContainer } from '@/ui/layout/page/SubMenuTopBarContainer';
import { Section } from '@/ui/layout/section/components/Section'; import { Section } from '@/ui/layout/section/components/Section';
import { Breadcrumb } from '@/ui/navigation/bread-crumb/components/Breadcrumb';
import { UndecoratedLink } from '@/ui/navigation/link/components/UndecoratedLink'; import { UndecoratedLink } from '@/ui/navigation/link/components/UndecoratedLink';
import { IconFunction, IconPlus } from 'twenty-ui'; import { IconFunction, IconPlus } from 'twenty-ui';
@@ -27,11 +25,17 @@ export const SettingsServerlessFunctions = () => {
/> />
</UndecoratedLink> </UndecoratedLink>
} }
links={[
{
children: 'Workspace',
href: getSettingsPagePath(SettingsPath.Workspace),
},
{
children: 'Functions',
},
]}
> >
<SettingsPageContainer> <SettingsPageContainer>
<SettingsHeaderContainer>
<Breadcrumb links={[{ children: 'Functions' }]} />
</SettingsHeaderContainer>
<Section> <Section>
<SettingsServerlessFunctionsTable /> <SettingsServerlessFunctionsTable />
</Section> </Section>

View File

@@ -1,7 +1,6 @@
import { SaveAndCancelButtons } from '@/settings/components/SaveAndCancelButtons/SaveAndCancelButtons'; import { SaveAndCancelButtons } from '@/settings/components/SaveAndCancelButtons/SaveAndCancelButtons';
import { SettingsPageContainer } from '@/settings/components/SettingsPageContainer'; import { SettingsPageContainer } from '@/settings/components/SettingsPageContainer';
import { SubMenuTopBarContainer } from '@/ui/layout/page/SubMenuTopBarContainer'; import { SubMenuTopBarContainer } from '@/ui/layout/page/SubMenuTopBarContainer';
import { Breadcrumb } from '@/ui/navigation/bread-crumb/components/Breadcrumb';
import { useNavigate } from 'react-router-dom'; import { useNavigate } from 'react-router-dom';
import { SettingsServerlessFunctionNewForm } from '@/settings/serverless-functions/components/SettingsServerlessFunctionNewForm'; import { SettingsServerlessFunctionNewForm } from '@/settings/serverless-functions/components/SettingsServerlessFunctionNewForm';
@@ -81,14 +80,18 @@ export const SettingsServerlessFunctionsNew = () => {
return ( return (
<SubMenuTopBarContainer <SubMenuTopBarContainer
Icon={IconFunction} Icon={IconFunction}
title={ title="New Function"
<Breadcrumb
links={[ links={[
{ children: 'Functions', href: '/settings/functions' }, {
children: 'Workspace',
href: getSettingsPagePath(SettingsPath.Workspace),
},
{
children: 'Functions',
href: getSettingsPagePath(SettingsPath.ServerlessFunctions),
},
{ children: 'New' }, { children: 'New' },
]} ]}
/>
}
actionButton={ actionButton={
<SaveAndCancelButtons <SaveAndCancelButtons
isSaveDisabled={!canSave} isSaveDisabled={!canSave}