Remove step 1 of new object field (#7397)

fixes #7356 
fixes #6967 
fixes #7102
fixes #7121 
fixes #7505
This commit is contained in:
nitin
2024-10-09 14:54:49 +05:30
committed by GitHub
parent 9b9e03fbf6
commit 58cbcbfe70
46 changed files with 374 additions and 574 deletions

View File

@ -1,6 +1,7 @@
import { lazy, Suspense } from 'react'; import { lazy, Suspense } from 'react';
import { Route, Routes } from 'react-router-dom'; import { Route, Routes } from 'react-router-dom';
import { SettingsSkeletonLoader } from '@/settings/components/SettingsSkeletonLoader';
import { AppPath } from '@/types/AppPath'; import { AppPath } from '@/types/AppPath';
import { SettingsPath } from '@/types/SettingsPath'; import { SettingsPath } from '@/types/SettingsPath';
@ -202,22 +203,21 @@ const SettingsIntegrationShowDatabaseConnection = lazy(() =>
})), })),
); );
const SettingsObjectNewFieldStep1 = lazy(() => const SettingsObjectNewFieldSelect = lazy(() =>
import( import(
'~/pages/settings/data-model/SettingsObjectNewField/SettingsObjectNewFieldStep1' '~/pages/settings/data-model/SettingsObjectNewField/SettingsObjectNewFieldSelect'
).then((module) => ({ ).then((module) => ({
default: module.SettingsObjectNewFieldStep1, default: module.SettingsObjectNewFieldSelect,
})), })),
); );
const SettingsObjectNewFieldStep2 = lazy(() => const SettingsObjectNewFieldConfigure = lazy(() =>
import( import(
'~/pages/settings/data-model/SettingsObjectNewField/SettingsObjectNewFieldStep2' '~/pages/settings/data-model/SettingsObjectNewField/SettingsObjectNewFieldConfigure'
).then((module) => ({ ).then((module) => ({
default: module.SettingsObjectNewFieldStep2, default: module.SettingsObjectNewFieldConfigure,
})), })),
); );
const SettingsObjectFieldEdit = lazy(() => const SettingsObjectFieldEdit = lazy(() =>
import('~/pages/settings/data-model/SettingsObjectFieldEdit').then( import('~/pages/settings/data-model/SettingsObjectFieldEdit').then(
(module) => ({ (module) => ({
@ -245,7 +245,7 @@ export const SettingsRoutes = ({
isCRMMigrationEnabled, isCRMMigrationEnabled,
isServerlessFunctionSettingsEnabled, isServerlessFunctionSettingsEnabled,
}: SettingsRoutesProps) => ( }: SettingsRoutesProps) => (
<Suspense fallback={null}> <Suspense fallback={<SettingsSkeletonLoader />}>
<Routes> <Routes>
<Route path={SettingsPath.ProfilePage} element={<SettingsProfile />} /> <Route path={SettingsPath.ProfilePage} element={<SettingsProfile />} />
<Route path={SettingsPath.Appearance} element={<SettingsAppearance />} /> <Route path={SettingsPath.Appearance} element={<SettingsAppearance />} />
@ -345,12 +345,12 @@ export const SettingsRoutes = ({
element={<SettingsIntegrationShowDatabaseConnection />} element={<SettingsIntegrationShowDatabaseConnection />}
/> />
<Route <Route
path={SettingsPath.ObjectNewFieldStep1} path={SettingsPath.ObjectNewFieldSelect}
element={<SettingsObjectNewFieldStep1 />} element={<SettingsObjectNewFieldSelect />}
/> />
<Route <Route
path={SettingsPath.ObjectNewFieldStep2} path={SettingsPath.ObjectNewFieldConfigure}
element={<SettingsObjectNewFieldStep2 />} element={<SettingsObjectNewFieldConfigure />}
/> />
<Route <Route
path={SettingsPath.ObjectFieldEdit} path={SettingsPath.ObjectFieldEdit}

View File

@ -1,9 +0,0 @@
import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
export const getDisabledFieldMetadataItems = (
objectMetadataItem: Pick<ObjectMetadataItem, 'fields'>,
) =>
objectMetadataItem.fields.filter(
(fieldMetadataItem) =>
!fieldMetadataItem.isActive && !fieldMetadataItem.isSystem,
);

View File

@ -0,0 +1,52 @@
import { SKELETON_LOADER_HEIGHT_SIZES } from '@/activities/components/SkeletonLoader';
import { PageBody } from '@/ui/layout/page/PageBody';
import { PageHeader } from '@/ui/layout/page/PageHeader';
import { useTheme } from '@emotion/react';
import styled from '@emotion/styled';
import Skeleton, { SkeletonTheme } from 'react-loading-skeleton';
const StyledContainer = styled.div`
display: flex;
flex-direction: column;
width: 100%;
`;
const StyledTitleLoaderContainer = styled.div`
margin: ${({ theme }) => theme.spacing(8, 8, 2)};
`;
export const SettingsSkeletonLoader = () => {
const theme = useTheme();
return (
<StyledContainer>
<PageHeader
title={
<SkeletonTheme
baseColor={theme.background.tertiary}
highlightColor={theme.background.transparent.lighter}
borderRadius={4}
>
<Skeleton
height={SKELETON_LOADER_HEIGHT_SIZES.standard.m}
width={120}
/>{' '}
</SkeletonTheme>
}
/>
<PageBody>
<StyledTitleLoaderContainer>
<SkeletonTheme
baseColor={theme.background.tertiary}
highlightColor={theme.background.transparent.lighter}
borderRadius={4}
>
<Skeleton
height={SKELETON_LOADER_HEIGHT_SIZES.standard.m}
width={200}
/>
</SkeletonTheme>
</StyledTitleLoaderContainer>
</PageBody>
</StyledContainer>
);
};

View File

@ -6,20 +6,17 @@ import { useDropdown } from '@/ui/layout/dropdown/hooks/useDropdown';
import { MenuItem } from '@/ui/navigation/menu-item/components/MenuItem'; import { MenuItem } from '@/ui/navigation/menu-item/components/MenuItem';
import { useTheme } from '@emotion/react'; import { useTheme } from '@emotion/react';
import styled from '@emotion/styled'; import styled from '@emotion/styled';
import { useLocation, useNavigate, useParams } from 'react-router-dom';
import { IconChevronDown } from 'twenty-ui'; import { IconChevronDown } from 'twenty-ui';
type SettingsDataModelNewFieldBreadcrumbDropDownProps = {
isConfigureStep: boolean;
onBreadcrumbClick: (isConfigureStep: boolean) => void;
};
const StyledContainer = styled.div` const StyledContainer = styled.div`
align-items: center; align-items: center;
color: ${({ theme }) => theme.font.color.secondary}; color: ${({ theme }) => theme.font.color.tertiary};
cursor: pointer; cursor: pointer;
display: flex; display: flex;
font-size: ${({ theme }) => theme.font.size.md}; font-size: ${({ theme }) => theme.font.size.md};
`; `;
const StyledButtonContainer = styled.div` const StyledButtonContainer = styled.div`
position: relative; position: relative;
width: 100%; width: 100%;
@ -48,19 +45,24 @@ const StyledButton = styled(Button)`
padding-right: ${({ theme }) => theme.spacing(6)}; padding-right: ${({ theme }) => theme.spacing(6)};
`; `;
export const SettingsDataModelNewFieldBreadcrumbDropDown = ({ export const SettingsDataModelNewFieldBreadcrumbDropDown = () => {
isConfigureStep,
onBreadcrumbClick,
}: SettingsDataModelNewFieldBreadcrumbDropDownProps) => {
const dropdownId = `settings-object-new-field-breadcrumb-dropdown`; const dropdownId = `settings-object-new-field-breadcrumb-dropdown`;
const { closeDropdown } = useDropdown(dropdownId); const { closeDropdown } = useDropdown(dropdownId);
const navigate = useNavigate();
const location = useLocation();
const { objectSlug = '' } = useParams();
const theme = useTheme();
const handleClick = (step: boolean) => { const isConfigureStep = location.pathname.includes('/configure');
onBreadcrumbClick(step);
const handleClick = (isConfigureStep: boolean) => {
if (isConfigureStep) {
navigate(`/settings/objects/${objectSlug}/new-field/configure`);
} else {
navigate(`/settings/objects/${objectSlug}/new-field/select`);
}
closeDropdown(); closeDropdown();
}; };
const theme = useTheme();
return ( return (
<StyledContainer> <StyledContainer>

View File

@ -10,36 +10,25 @@ import { useCurrencySettingsFormInitialValues } from '@/settings/data-model/fiel
import { useSelectSettingsFormInitialValues } from '@/settings/data-model/fields/forms/select/hooks/useSelectSettingsFormInitialValues'; import { useSelectSettingsFormInitialValues } from '@/settings/data-model/fields/forms/select/hooks/useSelectSettingsFormInitialValues';
import { SettingsFieldType } from '@/settings/data-model/types/SettingsFieldType'; import { SettingsFieldType } from '@/settings/data-model/types/SettingsFieldType';
import { TextInput } from '@/ui/input/components/TextInput'; import { TextInput } from '@/ui/input/components/TextInput';
import { UndecoratedLink } from '@/ui/navigation/link/components/UndecoratedLink';
import { useTheme } from '@emotion/react'; import { useTheme } from '@emotion/react';
import styled from '@emotion/styled'; import styled from '@emotion/styled';
import { Section } from '@react-email/components'; import { Section } from '@react-email/components';
import { useState } from 'react'; import { useState } from 'react';
import { Controller, useFormContext } from 'react-hook-form'; import { Controller, useFormContext } from 'react-hook-form';
import { H2Title, IconSearch } from 'twenty-ui'; import { H2Title, IconSearch } from 'twenty-ui';
import { z } from 'zod';
import { FieldMetadataType } from '~/generated-metadata/graphql'; import { FieldMetadataType } from '~/generated-metadata/graphql';
import { SettingsDataModelFieldTypeFormValues } from '~/pages/settings/data-model/SettingsObjectNewField/SettingsObjectNewFieldSelect';
export const settingsDataModelFieldTypeFormSchema = z.object({ type SettingsObjectNewFieldSelectorProps = {
type: z.enum(
Object.keys(SETTINGS_FIELD_TYPE_CONFIGS) as [
SettingsFieldType,
...SettingsFieldType[],
],
),
});
export type SettingsDataModelFieldTypeFormValues = z.infer<
typeof settingsDataModelFieldTypeFormSchema
>;
type SettingsDataModelFieldTypeSelectProps = {
className?: string; className?: string;
excludedFieldTypes?: SettingsFieldType[]; excludedFieldTypes?: SettingsFieldType[];
fieldMetadataItem?: Pick< fieldMetadataItem?: Pick<
FieldMetadataItem, FieldMetadataItem,
'defaultValue' | 'options' | 'type' 'defaultValue' | 'options' | 'type'
>; >;
onFieldTypeSelect: () => void;
objectSlug: string;
}; };
const StyledTypeSelectContainer = styled.div` const StyledTypeSelectContainer = styled.div`
@ -68,12 +57,11 @@ const StyledSearchInput = styled(TextInput)`
width: 100%; width: 100%;
`; `;
export const SettingsDataModelFieldTypeSelect = ({ export const SettingsObjectNewFieldSelector = ({
className,
excludedFieldTypes = [], excludedFieldTypes = [],
fieldMetadataItem, fieldMetadataItem,
onFieldTypeSelect, objectSlug,
}: SettingsDataModelFieldTypeSelectProps) => { }: SettingsObjectNewFieldSelectorProps) => {
const theme = useTheme(); const theme = useTheme();
const { control } = useFormContext<SettingsDataModelFieldTypeFormValues>(); const { control } = useFormContext<SettingsDataModelFieldTypeFormValues>();
const [searchQuery, setSearchQuery] = useState(''); const [searchQuery, setSearchQuery] = useState('');
@ -112,59 +100,60 @@ export const SettingsDataModelFieldTypeSelect = ({
}; };
return ( return (
<Controller <>
name="type" {' '}
control={control} <Section>
defaultValue={ <StyledSearchInput
fieldMetadataItem && fieldMetadataItem.type in fieldTypeConfigs LeftIcon={IconSearch}
? (fieldMetadataItem.type as SettingsFieldType) placeholder="Search a type"
: FieldMetadataType.Text value={searchQuery}
} onChange={setSearchQuery}
render={({ field: { onChange } }) => ( />
<StyledTypeSelectContainer className={className}> </Section>
<Section> <Controller
<StyledSearchInput name="type"
LeftIcon={IconSearch} control={control}
placeholder="Search a type" render={() => (
value={searchQuery} <StyledTypeSelectContainer>
onChange={setSearchQuery} {SETTINGS_FIELD_TYPE_CATEGORIES.map((category) => (
/> <Section key={category}>
</Section> <H2Title
{SETTINGS_FIELD_TYPE_CATEGORIES.map((category) => ( title={category}
<Section key={category}> description={
<H2Title SETTINGS_FIELD_TYPE_CATEGORY_DESCRIPTIONS[category]
title={category} }
description={ />
SETTINGS_FIELD_TYPE_CATEGORY_DESCRIPTIONS[category] <StyledContainer>
} {fieldTypeConfigs
/> .filter(([, config]) => config.category === category)
<StyledContainer> .map(([key, config]) => (
{fieldTypeConfigs <StyledCardContainer key={key}>
.filter(([, config]) => config.category === category) <UndecoratedLink
.map(([key, config]) => ( to={`/settings/objects/${objectSlug}/new-field/configure?fieldType=${key}`}
<StyledCardContainer> fullWidth
<SettingsCard onClick={() =>
key={key} resetDefaultValueField(key as SettingsFieldType)
onClick={() => { }
onChange(key as SettingsFieldType); >
resetDefaultValueField(key as SettingsFieldType); <SettingsCard
onFieldTypeSelect(); key={key}
}} Icon={
Icon={ <config.Icon
<config.Icon size={theme.icon.size.xl}
size={theme.icon.size.xl} stroke={theme.icon.stroke.sm}
stroke={theme.icon.stroke.sm} />
}
title={config.label}
/> />
} </UndecoratedLink>
title={config.label} </StyledCardContainer>
/> ))}
</StyledCardContainer> </StyledContainer>
))} </Section>
</StyledContainer> ))}
</Section> </StyledTypeSelectContainer>
))} )}
</StyledTypeSelectContainer> />
)} </>
/>
); );
}; };

View File

@ -1,58 +0,0 @@
import { Meta, StoryObj } from '@storybook/react';
import { expect, userEvent, within } from '@storybook/test';
import { ComponentDecorator } from 'twenty-ui';
import { FieldMetadataType } from '~/generated-metadata/graphql';
import { FormProviderDecorator } from '~/testing/decorators/FormProviderDecorator';
import { graphqlMocks } from '~/testing/graphqlMocks';
import { SettingsDataModelFieldTypeSelect } from '../SettingsDataModelFieldTypeSelect';
const meta: Meta<typeof SettingsDataModelFieldTypeSelect> = {
title:
'Modules/Settings/DataModel/Fields/Forms/SettingsDataModelFieldTypeSelect',
component: SettingsDataModelFieldTypeSelect,
decorators: [FormProviderDecorator, ComponentDecorator],
parameters: {
container: { width: 512 },
msw: graphqlMocks,
},
};
export default meta;
type Story = StoryObj<typeof SettingsDataModelFieldTypeSelect>;
export const Default: Story = {};
export const WithOpenSelect: Story = {
play: async () => {
const canvas = within(document.body);
const inputField = await canvas.findByText('Text');
await userEvent.click(inputField);
const input = await canvas.findByText('Unique ID');
await userEvent.click(input);
await userEvent.click(inputField);
},
};
export const WithExcludedFieldTypes: Story = {
args: {
excludedFieldTypes: [FieldMetadataType.Uuid, FieldMetadataType.Numeric],
},
play: async () => {
const canvas = within(document.body);
const inputField = await canvas.findByText('Text');
await userEvent.click(inputField);
await canvas.findByText('Number');
expect(canvas.queryByText('Unique ID')).toBeNull();
expect(canvas.queryByText('Numeric')).toBeNull();
},
};

View File

@ -1,9 +1,8 @@
import { z } from 'zod';
import { settingsDataModelFieldDescriptionFormSchema } from '@/settings/data-model/fields/forms/components/SettingsDataModelFieldDescriptionForm'; import { settingsDataModelFieldDescriptionFormSchema } from '@/settings/data-model/fields/forms/components/SettingsDataModelFieldDescriptionForm';
import { settingsDataModelFieldIconLabelFormSchema } from '@/settings/data-model/fields/forms/components/SettingsDataModelFieldIconLabelForm'; import { settingsDataModelFieldIconLabelFormSchema } from '@/settings/data-model/fields/forms/components/SettingsDataModelFieldIconLabelForm';
import { settingsDataModelFieldSettingsFormSchema } from '@/settings/data-model/fields/forms/components/SettingsDataModelFieldSettingsFormCard'; import { settingsDataModelFieldSettingsFormSchema } from '@/settings/data-model/fields/forms/components/SettingsDataModelFieldSettingsFormCard';
import { settingsDataModelFieldTypeFormSchema } from '@/settings/data-model/fields/forms/components/SettingsDataModelFieldTypeSelect'; import { z } from 'zod';
import { settingsDataModelFieldTypeFormSchema } from '~/pages/settings/data-model/SettingsObjectNewField/SettingsObjectNewFieldSelect';
export const settingsFieldFormSchema = (existingOtherLabels?: string[]) => { export const settingsFieldFormSchema = (existingOtherLabels?: string[]) => {
return z return z

View File

@ -12,8 +12,8 @@ export enum SettingsPath {
ObjectOverview = 'objects/overview', ObjectOverview = 'objects/overview',
ObjectDetail = 'objects/:objectSlug', ObjectDetail = 'objects/:objectSlug',
ObjectEdit = 'objects/:objectSlug/edit', ObjectEdit = 'objects/:objectSlug/edit',
ObjectNewFieldStep1 = 'objects/:objectSlug/new-field/step-1', ObjectNewFieldSelect = 'objects/:objectSlug/new-field/select',
ObjectNewFieldStep2 = 'objects/:objectSlug/new-field/step-2', ObjectNewFieldConfigure = 'objects/:objectSlug/new-field/configure',
ObjectFieldEdit = 'objects/:objectSlug/:fieldSlug', ObjectFieldEdit = 'objects/:objectSlug/:fieldSlug',
NewObject = 'objects/new', NewObject = 'objects/new',
NewServerlessFunction = 'functions/new', NewServerlessFunction = 'functions/new',

View File

@ -1,6 +1,5 @@
import styled from '@emotion/styled'; import styled from '@emotion/styled';
import { JSX, ReactNode } from 'react'; import { JSX, ReactNode } from 'react';
import { IconComponent } from 'twenty-ui';
import { InformationBannerWrapper } from '@/information-banner/components/InformationBannerWrapper'; import { InformationBannerWrapper } from '@/information-banner/components/InformationBannerWrapper';
import { import {
@ -14,7 +13,6 @@ type SubMenuTopBarContainerProps = {
children: JSX.Element | JSX.Element[]; children: JSX.Element | JSX.Element[];
title?: string; title?: string;
actionButton?: ReactNode; actionButton?: ReactNode;
Icon: IconComponent;
className?: string; className?: string;
links: BreadcrumbProps['links']; links: BreadcrumbProps['links'];
}; };

View File

@ -38,7 +38,7 @@ export const useGetAvailableFieldsForKanban = () => {
navigate( navigate(
`/settings/objects/${getObjectSlug( `/settings/objects/${getObjectSlug(
objectMetadataItem, objectMetadataItem,
)}/new-field/step-2?fieldType=${FieldMetadataType.Select}`, )}/new-field/configure?fieldType=${FieldMetadataType.Select}`,
); );
} else { } else {
navigate(`/settings/objects`); navigate(`/settings/objects`);

View File

@ -3,7 +3,6 @@ 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 { IconRocket } from 'twenty-ui';
import { unified } from 'unified'; import { unified } from 'unified';
import { visit } from 'unist-util-visit'; import { visit } from 'unist-util-visit';
@ -107,7 +106,6 @@ export const Releases = () => {
return ( return (
<SubMenuTopBarContainer <SubMenuTopBarContainer
Icon={IconRocket}
title="Releases" title="Releases"
links={[ links={[
{ {

View File

@ -5,7 +5,6 @@ import {
IconCalendarEvent, IconCalendarEvent,
IconCircleX, IconCircleX,
IconCreditCard, IconCreditCard,
IconCurrencyDollar,
} from 'twenty-ui'; } from 'twenty-ui';
import { currentWorkspaceState } from '@/auth/states/currentWorkspaceState'; import { currentWorkspaceState } from '@/auth/states/currentWorkspaceState';
@ -137,7 +136,6 @@ export const SettingsBilling = () => {
return ( return (
<SubMenuTopBarContainer <SubMenuTopBarContainer
Icon={IconCurrencyDollar}
title="Billing" title="Billing"
links={[ links={[
{ {

View File

@ -1,4 +1,4 @@
import { H2Title, IconUserCircle } from 'twenty-ui'; import { H2Title } from 'twenty-ui';
import { SettingsPageContainer } from '@/settings/components/SettingsPageContainer'; import { SettingsPageContainer } from '@/settings/components/SettingsPageContainer';
import { ChangePassword } from '@/settings/profile/components/ChangePassword'; import { ChangePassword } from '@/settings/profile/components/ChangePassword';
@ -13,7 +13,6 @@ import { Section } from '@/ui/layout/section/components/Section';
export const SettingsProfile = () => ( export const SettingsProfile = () => (
<SubMenuTopBarContainer <SubMenuTopBarContainer
Icon={IconUserCircle}
title="Profile" title="Profile"
links={[ links={[
{ {

View File

@ -1,4 +1,4 @@
import { H2Title, IconSettings } from 'twenty-ui'; import { H2Title } 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';
@ -13,7 +13,6 @@ import { GithubVersionLink } from '@/ui/navigation/link/components/GithubVersion
export const SettingsWorkspace = () => ( export const SettingsWorkspace = () => (
<SubMenuTopBarContainer <SubMenuTopBarContainer
Icon={IconSettings}
title="General" title="General"
links={[ links={[
{ {

View File

@ -1,17 +1,16 @@
import { useTheme } from '@emotion/react';
import styled from '@emotion/styled'; import styled from '@emotion/styled';
import { isNonEmptyArray } from '@sniptt/guards';
import { useState } from 'react'; import { useState } from 'react';
import { useRecoilValue, useSetRecoilState } from 'recoil'; import { useRecoilValue, useSetRecoilState } from 'recoil';
import { import {
H2Title,
IconTrash,
IconUsers,
IconReload,
IconMail,
Avatar, Avatar,
H2Title,
IconMail,
IconReload,
IconTrash,
MOBILE_VIEWPORT, MOBILE_VIEWPORT,
} from 'twenty-ui'; } from 'twenty-ui';
import { isNonEmptyArray } from '@sniptt/guards';
import { useTheme } from '@emotion/react';
import { currentWorkspaceMemberState } from '@/auth/states/currentWorkspaceMemberState'; import { currentWorkspaceMemberState } from '@/auth/states/currentWorkspaceMemberState';
import { currentWorkspaceState } from '@/auth/states/currentWorkspaceState'; import { currentWorkspaceState } from '@/auth/states/currentWorkspaceState';
@ -21,26 +20,26 @@ 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 { getSettingsPagePath } from '@/settings/utils/getSettingsPagePath';
import { SettingsPath } from '@/types/SettingsPath'; import { SettingsPath } from '@/types/SettingsPath';
import { SnackBarVariant } from '@/ui/feedback/snack-bar-manager/components/SnackBar';
import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
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';
import { Section } from '@/ui/layout/section/components/Section'; import { Section } from '@/ui/layout/section/components/Section';
import { Table } from '@/ui/layout/table/components/Table';
import { TableHeader } from '@/ui/layout/table/components/TableHeader';
import { WorkspaceMember } from '@/workspace-member/types/WorkspaceMember'; import { WorkspaceMember } from '@/workspace-member/types/WorkspaceMember';
import { WorkspaceInviteLink } from '@/workspace/components/WorkspaceInviteLink'; import { WorkspaceInviteLink } from '@/workspace/components/WorkspaceInviteLink';
import { WorkspaceInviteTeam } from '@/workspace/components/WorkspaceInviteTeam'; import { WorkspaceInviteTeam } from '@/workspace/components/WorkspaceInviteTeam';
import { useGetWorkspaceInvitationsQuery } from '~/generated/graphql';
import { SnackBarVariant } from '@/ui/feedback/snack-bar-manager/components/SnackBar';
import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
import { Table } from '@/ui/layout/table/components/Table';
import { TableHeader } from '@/ui/layout/table/components/TableHeader';
import { workspaceInvitationsState } from '../../modules/workspace-invitation/states/workspaceInvitationsStates';
import { TableRow } from '../../modules/ui/layout/table/components/TableRow';
import { TableCell } from '../../modules/ui/layout/table/components/TableCell';
import { Status } from '../../modules/ui/display/status/components/Status';
import { formatDistanceToNow } from 'date-fns'; import { formatDistanceToNow } from 'date-fns';
import { useResendWorkspaceInvitation } from '../../modules/workspace-invitation/hooks/useResendWorkspaceInvitation'; import { useGetWorkspaceInvitationsQuery } from '~/generated/graphql';
import { isDefined } from '~/utils/isDefined'; import { isDefined } from '~/utils/isDefined';
import { Status } from '../../modules/ui/display/status/components/Status';
import { TableCell } from '../../modules/ui/layout/table/components/TableCell';
import { TableRow } from '../../modules/ui/layout/table/components/TableRow';
import { useDeleteWorkspaceInvitation } from '../../modules/workspace-invitation/hooks/useDeleteWorkspaceInvitation'; import { useDeleteWorkspaceInvitation } from '../../modules/workspace-invitation/hooks/useDeleteWorkspaceInvitation';
import { useResendWorkspaceInvitation } from '../../modules/workspace-invitation/hooks/useResendWorkspaceInvitation';
import { workspaceInvitationsState } from '../../modules/workspace-invitation/states/workspaceInvitationsStates';
const StyledButtonContainer = styled.div` const StyledButtonContainer = styled.div`
align-items: center; align-items: center;
@ -167,7 +166,6 @@ export const SettingsWorkspaceMembers = () => {
return ( return (
<SubMenuTopBarContainer <SubMenuTopBarContainer
Icon={IconUsers}
title="Members" title="Members"
links={[ links={[
{ {

View File

@ -1,5 +1,5 @@
import { useRecoilValue } from 'recoil'; import { useRecoilValue } from 'recoil';
import { H2Title, IconAt } from 'twenty-ui'; import { H2Title } from 'twenty-ui';
import { ConnectedAccount } from '@/accounts/types/ConnectedAccount'; import { ConnectedAccount } from '@/accounts/types/ConnectedAccount';
import { currentWorkspaceMemberState } from '@/auth/states/currentWorkspaceMemberState'; import { currentWorkspaceMemberState } from '@/auth/states/currentWorkspaceMemberState';
@ -36,7 +36,6 @@ export const SettingsAccounts = () => {
return ( return (
<SubMenuTopBarContainer <SubMenuTopBarContainer
Icon={IconAt}
title="Account" title="Account"
links={[ links={[
{ {

View File

@ -4,12 +4,10 @@ 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 { IconCalendarEvent } from 'twenty-ui';
export const SettingsAccountsCalendars = () => { export const SettingsAccountsCalendars = () => {
return ( return (
<SubMenuTopBarContainer <SubMenuTopBarContainer
Icon={IconCalendarEvent}
title="Calendars" title="Calendars"
links={[ links={[
{ {

View File

@ -4,11 +4,9 @@ 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 { IconMail } from 'twenty-ui';
export const SettingsAccountsEmails = () => ( export const SettingsAccountsEmails = () => (
<SubMenuTopBarContainer <SubMenuTopBarContainer
Icon={IconMail}
title="Emails" title="Emails"
links={[ links={[
{ {

View File

@ -3,12 +3,10 @@ import { SettingsPageContainer } from '@/settings/components/SettingsPageContain
import { getSettingsPagePath } from '@/settings/utils/getSettingsPagePath'; 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 { IconAt } from 'twenty-ui';
export const SettingsNewAccount = () => { export const SettingsNewAccount = () => {
return ( return (
<SubMenuTopBarContainer <SubMenuTopBarContainer
Icon={IconAt}
title="New Account" title="New Account"
links={[ links={[
{ {

View File

@ -1,6 +1,5 @@
// @ts-expect-error external library has a typing issue // @ts-expect-error external library has a typing issue
import { RevertConnect } from '@revertdotdev/revert-react'; import { RevertConnect } from '@revertdotdev/revert-react';
import { IconSettings } from 'twenty-ui';
import { currentWorkspaceState } from '@/auth/states/currentWorkspaceState'; import { currentWorkspaceState } from '@/auth/states/currentWorkspaceState';
import { SettingsHeaderContainer } from '@/settings/components/SettingsHeaderContainer'; import { SettingsHeaderContainer } from '@/settings/components/SettingsHeaderContainer';
@ -18,7 +17,6 @@ export const SettingsCRMMigration = () => {
const currentWorkspace = useRecoilValue(currentWorkspaceState); const currentWorkspace = useRecoilValue(currentWorkspaceState);
return ( return (
<SubMenuTopBarContainer <SubMenuTopBarContainer
Icon={IconSettings}
title="Migrate" title="Migrate"
links={[ links={[
{ {

View File

@ -1,7 +1,7 @@
import { zodResolver } from '@hookform/resolvers/zod'; import { zodResolver } from '@hookform/resolvers/zod';
import { FormProvider, useForm } from 'react-hook-form'; import { FormProvider, useForm } from 'react-hook-form';
import { useNavigate } from 'react-router-dom'; import { useNavigate } from 'react-router-dom';
import { H2Title, IconHierarchy2 } from 'twenty-ui'; import { H2Title } from 'twenty-ui';
import { z } from 'zod'; import { z } from 'zod';
import { useCreateOneObjectMetadataItem } from '@/object-metadata/hooks/useCreateOneObjectMetadataItem'; import { useCreateOneObjectMetadataItem } from '@/object-metadata/hooks/useCreateOneObjectMetadataItem';
@ -69,7 +69,6 @@ export const SettingsNewObject = () => {
// eslint-disable-next-line react/jsx-props-no-spreading // eslint-disable-next-line react/jsx-props-no-spreading
<FormProvider {...formConfig}> <FormProvider {...formConfig}>
<SubMenuTopBarContainer <SubMenuTopBarContainer
Icon={IconHierarchy2}
title="New Object" title="New Object"
links={[ links={[
{ {

View File

@ -1,6 +1,5 @@
import { useUpdateOneObjectMetadataItem } from '@/object-metadata/hooks/useUpdateOneObjectMetadataItem'; import { useUpdateOneObjectMetadataItem } from '@/object-metadata/hooks/useUpdateOneObjectMetadataItem';
import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem'; import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
import { getDisabledFieldMetadataItems } from '@/object-metadata/utils/getDisabledFieldMetadataItems';
import { SettingsPageContainer } from '@/settings/components/SettingsPageContainer'; import { SettingsPageContainer } from '@/settings/components/SettingsPageContainer';
import { SettingsObjectSummaryCard } from '@/settings/data-model/object-details/components/SettingsObjectSummaryCard'; import { SettingsObjectSummaryCard } from '@/settings/data-model/object-details/components/SettingsObjectSummaryCard';
import { getSettingsPagePath } from '@/settings/utils/getSettingsPagePath'; import { getSettingsPagePath } from '@/settings/utils/getSettingsPagePath';
@ -10,9 +9,8 @@ import { SubMenuTopBarContainer } from '@/ui/layout/page/SubMenuTopBarContainer'
import { Section } from '@/ui/layout/section/components/Section'; import { Section } from '@/ui/layout/section/components/Section';
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 { useNavigate } from 'react-router-dom'; import { useNavigate } from 'react-router-dom';
import { H2Title, IconHierarchy2, IconPlus } from 'twenty-ui'; import { H2Title, IconPlus } from 'twenty-ui';
import { SettingsObjectFieldTable } from '~/pages/settings/data-model/SettingsObjectFieldTable'; import { SettingsObjectFieldTable } from '~/pages/settings/data-model/SettingsObjectFieldTable';
const StyledDiv = styled.div` const StyledDiv = styled.div`
@ -40,14 +38,10 @@ export const SettingsObjectDetailPageContent = ({
navigate(getSettingsPagePath(SettingsPath.Objects)); navigate(getSettingsPagePath(SettingsPath.Objects));
}; };
const disabledFieldMetadataItems =
getDisabledFieldMetadataItems(objectMetadataItem);
const shouldDisplayAddFieldButton = !objectMetadataItem.isRemote; const shouldDisplayAddFieldButton = !objectMetadataItem.isRemote;
return ( return (
<SubMenuTopBarContainer <SubMenuTopBarContainer
Icon={IconHierarchy2}
title={objectMetadataItem.labelPlural} title={objectMetadataItem.labelPlural}
links={[ links={[
{ {
@ -80,13 +74,7 @@ export const SettingsObjectDetailPageContent = ({
/> />
{shouldDisplayAddFieldButton && ( {shouldDisplayAddFieldButton && (
<StyledDiv> <StyledDiv>
<UndecoratedLink <UndecoratedLink to={'./new-field/select'}>
to={
isNonEmptyArray(disabledFieldMetadataItems)
? './new-field/step-1'
: './new-field/step-2'
}
>
<Button <Button
Icon={IconPlus} Icon={IconPlus}
title="Add Field" title="Add Field"

View File

@ -5,7 +5,7 @@ import pick from 'lodash.pick';
import { useEffect } from 'react'; import { useEffect } from 'react';
import { FormProvider, useForm } from 'react-hook-form'; import { FormProvider, useForm } from 'react-hook-form';
import { useNavigate, useParams } from 'react-router-dom'; import { useNavigate, useParams } from 'react-router-dom';
import { H2Title, IconArchive, IconHierarchy2 } from 'twenty-ui'; import { H2Title, IconArchive } from 'twenty-ui';
import { z } from 'zod'; import { z } from 'zod';
import { useFilteredObjectMetadataItems } from '@/object-metadata/hooks/useFilteredObjectMetadataItems'; import { useFilteredObjectMetadataItems } from '@/object-metadata/hooks/useFilteredObjectMetadataItems';
@ -107,7 +107,6 @@ export const SettingsObjectEdit = () => {
<RecordFieldValueSelectorContextProvider> <RecordFieldValueSelectorContextProvider>
<FormProvider {...formConfig}> <FormProvider {...formConfig}>
<SubMenuTopBarContainer <SubMenuTopBarContainer
Icon={IconHierarchy2}
title="Edit" title="Edit"
links={[ links={[
{ {

View File

@ -5,12 +5,7 @@ import pick from 'lodash.pick';
import { useEffect } from 'react'; import { useEffect } from 'react';
import { FormProvider, useForm } from 'react-hook-form'; import { FormProvider, useForm } from 'react-hook-form';
import { useNavigate, useParams } from 'react-router-dom'; import { useNavigate, useParams } from 'react-router-dom';
import { import { H2Title, IconArchive, IconArchiveOff } from 'twenty-ui';
H2Title,
IconArchive,
IconArchiveOff,
IconHierarchy2,
} from 'twenty-ui';
import { z } from 'zod'; import { z } from 'zod';
import { useFieldMetadataItem } from '@/object-metadata/hooks/useFieldMetadataItem'; import { useFieldMetadataItem } from '@/object-metadata/hooks/useFieldMetadataItem';
@ -186,7 +181,6 @@ export const SettingsObjectFieldEdit = () => {
{/* eslint-disable-next-line react/jsx-props-no-spreading */} {/* eslint-disable-next-line react/jsx-props-no-spreading */}
<FormProvider {...formConfig}> <FormProvider {...formConfig}>
<SubMenuTopBarContainer <SubMenuTopBarContainer
Icon={IconHierarchy2}
title={fieldMetadataItem?.label} title={fieldMetadataItem?.label}
links={[ links={[
{ {

View File

@ -11,7 +11,6 @@ import { FIELD_NAME_MAXIMUM_LENGTH } from '@/settings/data-model/constants/Field
import { SettingsDataModelFieldDescriptionForm } from '@/settings/data-model/fields/forms/components/SettingsDataModelFieldDescriptionForm'; import { SettingsDataModelFieldDescriptionForm } from '@/settings/data-model/fields/forms/components/SettingsDataModelFieldDescriptionForm';
import { SettingsDataModelFieldIconLabelForm } from '@/settings/data-model/fields/forms/components/SettingsDataModelFieldIconLabelForm'; import { SettingsDataModelFieldIconLabelForm } from '@/settings/data-model/fields/forms/components/SettingsDataModelFieldIconLabelForm';
import { SettingsDataModelFieldSettingsFormCard } from '@/settings/data-model/fields/forms/components/SettingsDataModelFieldSettingsFormCard'; import { SettingsDataModelFieldSettingsFormCard } from '@/settings/data-model/fields/forms/components/SettingsDataModelFieldSettingsFormCard';
import { SettingsDataModelFieldTypeSelect } from '@/settings/data-model/fields/forms/components/SettingsDataModelFieldTypeSelect';
import { settingsFieldFormSchema } from '@/settings/data-model/fields/forms/validation-schemas/settingsFieldFormSchema'; import { settingsFieldFormSchema } from '@/settings/data-model/fields/forms/validation-schemas/settingsFieldFormSchema';
import { SettingsFieldType } from '@/settings/data-model/types/SettingsFieldType'; import { SettingsFieldType } from '@/settings/data-model/types/SettingsFieldType';
import { AppPath } from '@/types/AppPath'; import { AppPath } from '@/types/AppPath';
@ -22,42 +21,36 @@ import { Section } from '@/ui/layout/section/components/Section';
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';
import styled from '@emotion/styled';
import { zodResolver } from '@hookform/resolvers/zod'; import { zodResolver } from '@hookform/resolvers/zod';
import pick from 'lodash.pick'; import pick from 'lodash.pick';
import { useEffect, useState } from 'react'; import { useEffect, useState } from 'react';
import { FormProvider, useForm } from 'react-hook-form'; import { FormProvider, useForm } from 'react-hook-form';
import { useNavigate, useParams, useSearchParams } from 'react-router-dom'; import { useNavigate, useParams, useSearchParams } from 'react-router-dom';
import { H1Title, H1TitleFontColor, H2Title, IconHierarchy2 } from 'twenty-ui'; import { H2Title } from 'twenty-ui';
import { z } from 'zod'; import { z } from 'zod';
import { FieldMetadataType } from '~/generated-metadata/graphql'; import { FieldMetadataType } from '~/generated-metadata/graphql';
import { isDefined } from '~/utils/isDefined';
import { isUndefinedOrNull } from '~/utils/isUndefinedOrNull'; import { isUndefinedOrNull } from '~/utils/isUndefinedOrNull';
// TODO: fix this type
type SettingsDataModelNewFieldFormValues = z.infer< type SettingsDataModelNewFieldFormValues = z.infer<
ReturnType<typeof settingsFieldFormSchema> ReturnType<typeof settingsFieldFormSchema>
> & > &
any; any;
const StyledH1Title = styled(H1Title)` export const SettingsObjectNewFieldConfigure = () => {
margin-bottom: 0;
padding-top: ${({ theme }) => theme.spacing(3)};
`;
export const SettingsObjectNewFieldStep2 = () => {
const navigate = useNavigate(); const navigate = useNavigate();
const { objectSlug = '' } = useParams(); const { objectSlug = '' } = useParams();
const [searchParams] = useSearchParams(); const [searchParams] = useSearchParams();
const fieldType = searchParams.get('fieldType') as SettingsFieldType; const fieldType =
(searchParams.get('fieldType') as SettingsFieldType) ||
FieldMetadataType.Text;
const { enqueueSnackBar } = useSnackBar(); const { enqueueSnackBar } = useSnackBar();
const [isConfigureStep, setIsConfigureStep] = useState(false);
const { findActiveObjectMetadataItemBySlug } = const { findActiveObjectMetadataItemBySlug } =
useFilteredObjectMetadataItems(); useFilteredObjectMetadataItems();
const activeObjectMetadataItem = const activeObjectMetadataItem =
findActiveObjectMetadataItemBySlug(objectSlug); findActiveObjectMetadataItemBySlug(objectSlug);
const { createMetadataField } = useFieldMetadataItem(); const { createMetadataField } = useFieldMetadataItem();
const apolloClient = useApolloClient();
const formConfig = useForm<SettingsDataModelNewFieldFormValues>({ const formConfig = useForm<SettingsDataModelNewFieldFormValues>({
mode: 'onTouched', mode: 'onTouched',
@ -66,14 +59,14 @@ export const SettingsObjectNewFieldStep2 = () => {
activeObjectMetadataItem?.fields.map((value) => value.name), activeObjectMetadataItem?.fields.map((value) => value.name),
), ),
), ),
defaultValues: {
type: fieldType,
icon: 'IconUsers',
label: '',
description: '',
},
}); });
useEffect(() => {
if (!activeObjectMetadataItem) {
navigate(AppPath.NotFound);
}
}, [activeObjectMetadataItem, navigate]);
const [, setObjectViews] = useState<View[]>([]); const [, setObjectViews] = useState<View[]>([]);
const [, setRelationObjectViews] = useState<View[]>([]); const [, setRelationObjectViews] = useState<View[]>([]);
@ -85,7 +78,6 @@ export const SettingsObjectNewFieldStep2 = () => {
}, },
onCompleted: async (views) => { onCompleted: async (views) => {
if (isUndefinedOrNull(views)) return; if (isUndefinedOrNull(views)) return;
setObjectViews(views); setObjectViews(views);
}, },
}); });
@ -103,15 +95,17 @@ export const SettingsObjectNewFieldStep2 = () => {
}, },
onCompleted: async (views) => { onCompleted: async (views) => {
if (isUndefinedOrNull(views)) return; if (isUndefinedOrNull(views)) return;
setRelationObjectViews(views); setRelationObjectViews(views);
}, },
}); });
const { createOneRelationMetadataItem: createOneRelationMetadata } = const { createOneRelationMetadataItem: createOneRelationMetadata } =
useCreateOneRelationMetadataItem(); useCreateOneRelationMetadataItem();
const apolloClient = useApolloClient(); useEffect(() => {
if (!activeObjectMetadataItem) {
navigate(AppPath.NotFound);
}
}, [activeObjectMetadataItem, navigate]);
if (!activeObjectMetadataItem) return null; if (!activeObjectMetadataItem) return null;
@ -160,17 +154,7 @@ export const SettingsObjectNewFieldStep2 = () => {
}); });
} }
}; };
if (!activeObjectMetadataItem) return null;
const excludedFieldTypes: SettingsFieldType[] = (
[
FieldMetadataType.Link,
FieldMetadataType.Numeric,
FieldMetadataType.RichText,
FieldMetadataType.Actor,
FieldMetadataType.Email,
FieldMetadataType.Phone,
] as const
).filter(isDefined);
return ( return (
<RecordFieldValueSelectorContextProvider> <RecordFieldValueSelectorContextProvider>
@ -178,96 +162,57 @@ export const SettingsObjectNewFieldStep2 = () => {
{...formConfig} {...formConfig}
> >
<SubMenuTopBarContainer <SubMenuTopBarContainer
Icon={IconHierarchy2} title="2. Configure field"
links={[ links={[
{ { children: 'Workspace', href: '/settings/workspace' },
children: 'Objects', { children: 'Objects', href: '/settings/objects' },
href: '/settings/objects',
},
{ {
children: activeObjectMetadataItem.labelPlural, children: activeObjectMetadataItem.labelPlural,
href: `/settings/objects/${objectSlug}`, href: `/settings/objects/${objectSlug}`,
}, },
{
children: ( { children: <SettingsDataModelNewFieldBreadcrumbDropDown /> },
<SettingsDataModelNewFieldBreadcrumbDropDown
isConfigureStep={isConfigureStep}
onBreadcrumbClick={setIsConfigureStep}
/>
),
},
]} ]}
actionButton={ actionButton={
!activeObjectMetadataItem.isRemote && ( <SaveAndCancelButtons
<SaveAndCancelButtons isSaveDisabled={!canSave}
isSaveDisabled={!canSave} isCancelDisabled={isSubmitting}
isCancelDisabled={isSubmitting} onCancel={() =>
onCancel={() => { navigate(`/settings/objects/${objectSlug}/new-field/select`)
if (!isConfigureStep) { }
navigate(`/settings/objects/${objectSlug}`); onSave={formConfig.handleSubmit(handleSave)}
} else { />
setIsConfigureStep(false);
}
}}
onSave={formConfig.handleSubmit(handleSave)}
/>
)
} }
> >
<SettingsPageContainer> <SettingsPageContainer>
<StyledH1Title <Section>
title={ <H2Title
!isConfigureStep title="Icon and Name"
? '1. Select a field type' description="The name and icon of this field"
: '2. Configure field' />
} <SettingsDataModelFieldIconLabelForm
fontColor={H1TitleFontColor.Primary} maxLength={FIELD_NAME_MAXIMUM_LENGTH}
/> />
</Section>
{!isConfigureStep ? ( <Section>
<SettingsDataModelFieldTypeSelect <H2Title title="Values" description="The values of this field" />
excludedFieldTypes={excludedFieldTypes} <SettingsDataModelFieldSettingsFormCard
isCreatingField
fieldMetadataItem={{ fieldMetadataItem={{
icon: formConfig.watch('icon'),
label: formConfig.watch('label') || 'New Field',
type: fieldType as FieldMetadataType, type: fieldType as FieldMetadataType,
}} }}
onFieldTypeSelect={() => setIsConfigureStep(true)} objectMetadataItem={activeObjectMetadataItem}
/> />
) : ( </Section>
<> <Section>
<Section> <H2Title
<H2Title title="Description"
title="Icon and Name" description="The description of this field"
description="The name and icon of this field" />
/> <SettingsDataModelFieldDescriptionForm />
<SettingsDataModelFieldIconLabelForm </Section>
maxLength={FIELD_NAME_MAXIMUM_LENGTH}
/>
</Section>
<Section>
<H2Title
title="Values"
description="The values of this field"
/>
<SettingsDataModelFieldSettingsFormCard
isCreatingField
fieldMetadataItem={{
icon: formConfig.watch('icon'),
label: formConfig.watch('label') || 'Employees',
type: formConfig.watch('type'),
}}
objectMetadataItem={activeObjectMetadataItem}
/>
</Section>
<Section>
<H2Title
title="Description"
description="The description of this field"
/>
<SettingsDataModelFieldDescriptionForm />
</Section>
</>
)}
</SettingsPageContainer> </SettingsPageContainer>
</SubMenuTopBarContainer> </SubMenuTopBarContainer>
</FormProvider> </FormProvider>

View File

@ -0,0 +1,90 @@
import { useFilteredObjectMetadataItems } from '@/object-metadata/hooks/useFilteredObjectMetadataItems';
import { RecordFieldValueSelectorContextProvider } from '@/object-record/record-store/contexts/RecordFieldValueSelectorContext';
import { SettingsPageContainer } from '@/settings/components/SettingsPageContainer';
import { SettingsDataModelNewFieldBreadcrumbDropDown } from '@/settings/data-model/components/SettingsDataModelNewFieldBreadcrumbDropDown';
import { SETTINGS_FIELD_TYPE_CONFIGS } from '@/settings/data-model/constants/SettingsFieldTypeConfigs';
import { SettingsObjectNewFieldSelector } from '@/settings/data-model/fields/forms/components/SettingsObjectNewFieldSelector';
import { SettingsFieldType } from '@/settings/data-model/types/SettingsFieldType';
import { AppPath } from '@/types/AppPath';
import { SubMenuTopBarContainer } from '@/ui/layout/page/SubMenuTopBarContainer';
import { zodResolver } from '@hookform/resolvers/zod';
import { useEffect } from 'react';
import { FormProvider, useForm } from 'react-hook-form';
import { useNavigate, useParams } from 'react-router-dom';
import { isDefined } from 'twenty-ui';
import { z } from 'zod';
import { FieldMetadataType } from '~/generated-metadata/graphql';
export const settingsDataModelFieldTypeFormSchema = z.object({
type: z.enum(
Object.keys(SETTINGS_FIELD_TYPE_CONFIGS) as [
SettingsFieldType,
...SettingsFieldType[],
],
),
});
export type SettingsDataModelFieldTypeFormValues = z.infer<
typeof settingsDataModelFieldTypeFormSchema
>;
export const SettingsObjectNewFieldSelect = () => {
const navigate = useNavigate();
const { objectSlug = '' } = useParams();
const { findActiveObjectMetadataItemBySlug } =
useFilteredObjectMetadataItems();
const activeObjectMetadataItem =
findActiveObjectMetadataItemBySlug(objectSlug);
const formMethods = useForm({
resolver: zodResolver(settingsDataModelFieldTypeFormSchema),
defaultValues: {
type: FieldMetadataType.Text,
},
});
const excludedFieldTypes: SettingsFieldType[] = (
[
FieldMetadataType.Link,
FieldMetadataType.Numeric,
FieldMetadataType.RichText,
FieldMetadataType.Actor,
FieldMetadataType.Email,
FieldMetadataType.Phone,
] as const
).filter(isDefined);
useEffect(() => {
if (!activeObjectMetadataItem) {
navigate(AppPath.NotFound);
}
}, [activeObjectMetadataItem, navigate]);
if (!activeObjectMetadataItem) return null;
return (
<RecordFieldValueSelectorContextProvider>
<FormProvider // eslint-disable-next-line react/jsx-props-no-spreading
{...formMethods}
>
<SubMenuTopBarContainer
title="1. Select a field type"
links={[
{ children: 'Workspace', href: '/settings/workspace' },
{ children: 'Objects', href: '/settings/objects' },
{
children: activeObjectMetadataItem.labelPlural,
href: `/settings/objects/${objectSlug}`,
},
{ children: <SettingsDataModelNewFieldBreadcrumbDropDown /> },
]}
>
<SettingsPageContainer>
<SettingsObjectNewFieldSelector
objectSlug={objectSlug}
excludedFieldTypes={excludedFieldTypes}
/>
</SettingsPageContainer>
</SubMenuTopBarContainer>
</FormProvider>
</RecordFieldValueSelectorContextProvider>
);
};

View File

@ -1,133 +0,0 @@
import styled from '@emotion/styled';
import { useEffect } from 'react';
import { useNavigate, useParams } from 'react-router-dom';
import { H2Title, IconHierarchy2, IconPlus } from 'twenty-ui';
import { useFilteredObjectMetadataItems } from '@/object-metadata/hooks/useFilteredObjectMetadataItems';
import { SaveAndCancelButtons } from '@/settings/components/SaveAndCancelButtons/SaveAndCancelButtons';
import { SettingsPageContainer } from '@/settings/components/SettingsPageContainer';
import { useFieldMetadataItem } from '@/object-metadata/hooks/useFieldMetadataItem';
import { settingsObjectFieldsFamilyState } from '@/settings/data-model/object-details/states/settingsObjectFieldsFamilyState';
import { getSettingsPagePath } from '@/settings/utils/getSettingsPagePath';
import { AppPath } from '@/types/AppPath';
import { SettingsPath } from '@/types/SettingsPath';
import { Button } from '@/ui/input/button/components/Button';
import { SubMenuTopBarContainer } from '@/ui/layout/page/SubMenuTopBarContainer';
import { Section } from '@/ui/layout/section/components/Section';
import { useRecoilState } from 'recoil';
import { SettingsObjectFieldTable } from '~/pages/settings/data-model/SettingsObjectFieldTable';
const StyledSection = styled(Section)`
display: flex;
flex-direction: column;
`;
const StyledAddCustomFieldButton = styled(Button)`
align-self: flex-end;
margin-top: ${({ theme }) => theme.spacing(2)};
`;
export const SettingsObjectNewFieldStep1 = () => {
const navigate = useNavigate();
const { objectSlug = '' } = useParams();
const { findActiveObjectMetadataItemBySlug } =
useFilteredObjectMetadataItems();
const activeObjectMetadataItem =
findActiveObjectMetadataItemBySlug(objectSlug);
const [settingsObjectFields] = useRecoilState(
settingsObjectFieldsFamilyState({
objectMetadataItemId: activeObjectMetadataItem?.id,
}),
);
const { activateMetadataField, deactivateMetadataField } =
useFieldMetadataItem();
const canSave = settingsObjectFields?.some(
(field, index) =>
field.isActive !== activeObjectMetadataItem?.fields[index].isActive,
);
const handleSave = async () => {
if (!activeObjectMetadataItem || !settingsObjectFields) {
return;
}
await Promise.all(
settingsObjectFields.map((fieldMetadataItem, index) => {
if (
fieldMetadataItem.isActive ===
activeObjectMetadataItem.fields[index].isActive
) {
return undefined;
}
return fieldMetadataItem.isActive
? activateMetadataField(fieldMetadataItem)
: deactivateMetadataField(fieldMetadataItem);
}),
);
navigate(`/settings/objects/${objectSlug}`);
};
useEffect(() => {
if (!activeObjectMetadataItem) {
navigate(AppPath.NotFound);
return;
}
}, [activeObjectMetadataItem, navigate]);
if (!activeObjectMetadataItem) return null;
return (
<SubMenuTopBarContainer
Icon={IconHierarchy2}
links={[
{
children: 'Workspace',
href: getSettingsPagePath(SettingsPath.Workspace),
},
{ children: 'Objects', href: '/settings/objects' },
{
children: activeObjectMetadataItem.labelPlural,
href: `/settings/objects/${objectSlug}`,
},
{ children: 'New Field' },
]}
actionButton={
!activeObjectMetadataItem.isRemote && (
<SaveAndCancelButtons
isSaveDisabled={!canSave}
onCancel={() => navigate(`/settings/objects/${objectSlug}`)}
onSave={handleSave}
/>
)
}
>
<SettingsPageContainer>
<StyledSection>
<H2Title
title="Check deactivated fields"
description="Before creating a custom field, check if it already exists in the deactivated section."
/>
<SettingsObjectFieldTable
objectMetadataItem={activeObjectMetadataItem}
mode="new-field"
/>
<StyledAddCustomFieldButton
Icon={IconPlus}
title="Add Custom Field"
size="small"
variant="secondary"
to={`/settings/objects/${objectSlug}/new-field/step-2`}
/>
</StyledSection>
</SettingsPageContainer>
</SubMenuTopBarContainer>
);
};

View File

@ -4,12 +4,10 @@ import { SettingsDataModelOverview } from '@/settings/data-model/graph-overview/
import { getSettingsPagePath } from '@/settings/utils/getSettingsPagePath'; 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 { IconHierarchy2 } from 'twenty-ui';
export const SettingsObjectOverview = () => { export const SettingsObjectOverview = () => {
return ( return (
<SubMenuTopBarContainer <SubMenuTopBarContainer
Icon={IconHierarchy2}
links={[ links={[
{ {
children: 'Workspace', children: 'Workspace',

View File

@ -1,12 +1,6 @@
import { useTheme } from '@emotion/react'; import { useTheme } from '@emotion/react';
import styled from '@emotion/styled'; import styled from '@emotion/styled';
import { import { H2Title, IconChevronRight, IconPlus, IconSearch } from 'twenty-ui';
H2Title,
IconChevronRight,
IconHierarchy2,
IconPlus,
IconSearch,
} from 'twenty-ui';
import { useDeleteOneObjectMetadataItem } from '@/object-metadata/hooks/useDeleteOneObjectMetadataItem'; import { useDeleteOneObjectMetadataItem } from '@/object-metadata/hooks/useDeleteOneObjectMetadataItem';
import { useFilteredObjectMetadataItems } from '@/object-metadata/hooks/useFilteredObjectMetadataItems'; import { useFilteredObjectMetadataItems } from '@/object-metadata/hooks/useFilteredObjectMetadataItems';
@ -133,7 +127,6 @@ export const SettingsObjects = () => {
); );
return ( return (
<SubMenuTopBarContainer <SubMenuTopBarContainer
Icon={IconHierarchy2}
title="Data model" title="Data model"
actionButton={ actionButton={
<UndecoratedLink to={getSettingsPagePath(SettingsPath.NewObject)}> <UndecoratedLink to={getSettingsPagePath(SettingsPath.NewObject)}>

View File

@ -1,5 +1,6 @@
import { Meta, StoryObj } from '@storybook/react'; import { Meta, StoryObj } from '@storybook/react';
import { userEvent, within } from '@storybook/test'; import { userEvent, within } from '@storybook/test';
import { SettingsObjectNewFieldConfigure } from '~/pages/settings/data-model/SettingsObjectNewField/SettingsObjectNewFieldConfigure';
import { import {
PageDecorator, PageDecorator,
@ -7,15 +8,13 @@ import {
} from '~/testing/decorators/PageDecorator'; } from '~/testing/decorators/PageDecorator';
import { graphqlMocks } from '~/testing/graphqlMocks'; import { graphqlMocks } from '~/testing/graphqlMocks';
import { SettingsObjectNewFieldStep2 } from '../../SettingsObjectNewField/SettingsObjectNewFieldStep2';
const meta: Meta<PageDecoratorArgs> = { const meta: Meta<PageDecoratorArgs> = {
title: title:
'Pages/Settings/DataModel/SettingsObjectNewField/SettingsObjectNewFieldStep2', 'Pages/Settings/DataModel/SettingsObjectNewField/SettingsObjectNewFieldConfigure',
component: SettingsObjectNewFieldStep2, component: SettingsObjectNewFieldConfigure,
decorators: [PageDecorator], decorators: [PageDecorator],
args: { args: {
routePath: '/settings/objects/:objectSlug/new-field/step-2', routePath: '/settings/objects/:objectSlug/new-field/configure',
routeParams: { ':objectSlug': 'companies' }, routeParams: { ':objectSlug': 'companies' },
}, },
parameters: { parameters: {
@ -25,21 +24,11 @@ const meta: Meta<PageDecoratorArgs> = {
export default meta; export default meta;
export type Story = StoryObj<typeof SettingsObjectNewFieldStep2>; export type Story = StoryObj<typeof SettingsObjectNewFieldConfigure>;
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('Objects');
await canvas.findByText('1. Select a field type');
const searchInput = await canvas.findByPlaceholderText('Search a type');
await userEvent.type(searchInput, 'Num');
const numberTypeButton = await canvas.findByText('Number');
await userEvent.click(numberTypeButton);
await canvas.findByText('2. Configure field'); await canvas.findByText('2. Configure field');
@ -49,11 +38,10 @@ export const Default: Story = {
const descriptionInput = await canvas.findByPlaceholderText( const descriptionInput = await canvas.findByPlaceholderText(
'Write a description', 'Write a description',
); );
await userEvent.type(descriptionInput, 'Test description'); await userEvent.type(descriptionInput, 'Test description');
const saveButton = await canvas.findByText('Save'); const saveButton = await canvas.findByText('Save');
await new Promise((resolve) => setTimeout(resolve, 5000));
await userEvent.click(saveButton); await userEvent.click(saveButton);
}, },
}; };

View File

@ -0,0 +1,41 @@
import { Meta, StoryObj } from '@storybook/react';
import { userEvent, within } from '@storybook/test';
import { SettingsObjectNewFieldSelect } from '~/pages/settings/data-model/SettingsObjectNewField/SettingsObjectNewFieldSelect';
import {
PageDecorator,
PageDecoratorArgs,
} from '~/testing/decorators/PageDecorator';
import { graphqlMocks } from '~/testing/graphqlMocks';
const meta: Meta<PageDecoratorArgs> = {
title:
'Pages/Settings/DataModel/SettingsObjectNewField/SettingsObjectNewFieldSelect',
component: SettingsObjectNewFieldSelect,
decorators: [PageDecorator],
args: {
routePath: '/settings/objects/:objectSlug/new-field/select',
routeParams: { ':objectSlug': 'companies' },
},
parameters: {
msw: graphqlMocks,
},
};
export default meta;
export type Story = StoryObj<typeof SettingsObjectNewFieldSelect>;
export const Default: Story = {
play: async ({ canvasElement }) => {
const canvas = within(canvasElement);
await canvas.findByText('Objects');
await canvas.findByText('1. Select a field type');
const searchInput = await canvas.findByPlaceholderText('Search a type');
await userEvent.type(searchInput, 'Rela');
await new Promise((resolve) => setTimeout(resolve, 1500));
await userEvent.clear(searchInput);
await userEvent.type(searchInput, 'Num');
await new Promise((resolve) => setTimeout(resolve, 1500));
},
};

View File

@ -1,38 +0,0 @@
import { Meta, StoryObj } from '@storybook/react';
import { within } from '@storybook/test';
import {
PageDecorator,
PageDecoratorArgs,
} from '~/testing/decorators/PageDecorator';
import { graphqlMocks } from '~/testing/graphqlMocks';
import { SettingsObjectNewFieldStep1 } from '../../SettingsObjectNewField/SettingsObjectNewFieldStep1';
const meta: Meta<PageDecoratorArgs> = {
title:
'Pages/Settings/DataModel/SettingsObjectNewField/SettingsObjectNewFieldStep1',
component: SettingsObjectNewFieldStep1,
decorators: [PageDecorator],
args: {
routePath: '/settings/objects/:objectSlug/new-field/step-1',
routeParams: { ':objectSlug': 'companies' },
},
parameters: {
msw: graphqlMocks,
},
};
export default meta;
export type Story = StoryObj<typeof SettingsObjectNewFieldStep1>;
export const Default: Story = {
play: async ({ canvasElement }) => {
const canvas = within(canvasElement);
await canvas.findByText('Objects');
await canvas.findByText('Companies');
await canvas.findByText('Check deactivated fields');
await canvas.findByText('Add Custom Field');
},
};

View File

@ -1,5 +1,5 @@
import styled from '@emotion/styled'; import styled from '@emotion/styled';
import { H2Title, IconCode, IconPlus } from 'twenty-ui'; import { H2Title, IconPlus } from 'twenty-ui';
import { SettingsPageContainer } from '@/settings/components/SettingsPageContainer'; import { SettingsPageContainer } from '@/settings/components/SettingsPageContainer';
import { SettingsApiKeysTable } from '@/settings/developers/components/SettingsApiKeysTable'; import { SettingsApiKeysTable } from '@/settings/developers/components/SettingsApiKeysTable';
@ -20,7 +20,6 @@ const StyledButtonContainer = styled.div`
export const SettingsDevelopers = () => { export const SettingsDevelopers = () => {
return ( return (
<SubMenuTopBarContainer <SubMenuTopBarContainer
Icon={IconCode}
title="Developers" title="Developers"
actionButton={<SettingsReadDocumentationButton />} actionButton={<SettingsReadDocumentationButton />}
links={[ links={[

View File

@ -4,7 +4,7 @@ import { DateTime } from 'luxon';
import { useState } from 'react'; import { useState } from 'react';
import { useNavigate, useParams } from 'react-router-dom'; import { useNavigate, useParams } from 'react-router-dom';
import { useRecoilState } from 'recoil'; import { useRecoilState } from 'recoil';
import { H2Title, IconCode, IconRepeat, IconTrash } from 'twenty-ui'; import { H2Title, IconRepeat, IconTrash } from 'twenty-ui';
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular'; import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
import { useCreateOneRecord } from '@/object-record/hooks/useCreateOneRecord'; import { useCreateOneRecord } from '@/object-record/hooks/useCreateOneRecord';
@ -145,7 +145,6 @@ export const SettingsDevelopersApiKeyDetail = () => {
<> <>
{apiKeyData?.name && ( {apiKeyData?.name && (
<SubMenuTopBarContainer <SubMenuTopBarContainer
Icon={IconCode}
title={apiKeyData?.name} title={apiKeyData?.name}
links={[ links={[
{ {

View File

@ -1,7 +1,7 @@
import { DateTime } from 'luxon'; import { DateTime } from 'luxon';
import { useState } from 'react'; import { useState } from 'react';
import { useNavigate } from 'react-router-dom'; import { useNavigate } from 'react-router-dom';
import { H2Title, IconCode } from 'twenty-ui'; import { H2Title } from 'twenty-ui';
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular'; import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
import { useCreateOneRecord } from '@/object-record/hooks/useCreateOneRecord'; import { useCreateOneRecord } from '@/object-record/hooks/useCreateOneRecord';
@ -65,7 +65,6 @@ export const SettingsDevelopersApiKeysNew = () => {
const canSave = !!formValues.name && createOneApiKey; const canSave = !!formValues.name && createOneApiKey;
return ( return (
<SubMenuTopBarContainer <SubMenuTopBarContainer
Icon={IconCode}
title="New key" title="New key"
links={[ links={[
{ {

View File

@ -1,7 +1,7 @@
import styled from '@emotion/styled'; import styled from '@emotion/styled';
import { useState } from 'react'; import { useState } from 'react';
import { useNavigate, useParams } from 'react-router-dom'; import { useNavigate, useParams } from 'react-router-dom';
import { H2Title, IconCode, IconTrash } from 'twenty-ui'; import { H2Title, IconTrash } from 'twenty-ui';
import { useObjectMetadataItems } from '@/object-metadata/hooks/useObjectMetadataItems'; import { useObjectMetadataItems } from '@/object-metadata/hooks/useObjectMetadataItems';
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular'; import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
@ -93,7 +93,6 @@ export const SettingsDevelopersWebhooksDetail = () => {
return ( return (
<SubMenuTopBarContainer <SubMenuTopBarContainer
Icon={IconCode}
title={webhookData.targetUrl} title={webhookData.targetUrl}
links={[ links={[
{ {

View File

@ -1,6 +1,6 @@
import { useState } from 'react'; import { useState } from 'react';
import { useNavigate } from 'react-router-dom'; import { useNavigate } from 'react-router-dom';
import { H2Title, IconCode, isDefined } from 'twenty-ui'; import { H2Title, isDefined } from 'twenty-ui';
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular'; import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
import { useCreateOneRecord } from '@/object-record/hooks/useCreateOneRecord'; import { useCreateOneRecord } from '@/object-record/hooks/useCreateOneRecord';
@ -64,7 +64,6 @@ export const SettingsDevelopersWebhooksNew = () => {
return ( return (
<SubMenuTopBarContainer <SubMenuTopBarContainer
Icon={IconCode}
title="New Webhook" title="New Webhook"
links={[ links={[
{ {

View File

@ -1,6 +1,6 @@
import { useEffect } from 'react'; import { useEffect } from 'react';
import { useNavigate, useParams } from 'react-router-dom'; import { useNavigate, useParams } from 'react-router-dom';
import { H2Title, IconSettings } from 'twenty-ui'; import { H2Title } from 'twenty-ui';
import { useGetDatabaseConnections } from '@/databases/hooks/useGetDatabaseConnections'; import { useGetDatabaseConnections } from '@/databases/hooks/useGetDatabaseConnections';
import { SettingsPageContainer } from '@/settings/components/SettingsPageContainer'; import { SettingsPageContainer } from '@/settings/components/SettingsPageContainer';
@ -42,7 +42,6 @@ export const SettingsIntegrationDatabase = () => {
return ( return (
<SubMenuTopBarContainer <SubMenuTopBarContainer
Icon={IconSettings}
title={integration.text} title={integration.text}
links={[ links={[
{ {

View File

@ -1,5 +1,3 @@
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 { getSettingsPagePath } from '@/settings/utils/getSettingsPagePath';
@ -9,7 +7,6 @@ import { SubMenuTopBarContainer } from '@/ui/layout/page/SubMenuTopBarContainer'
export const SettingsIntegrationEditDatabaseConnection = () => { export const SettingsIntegrationEditDatabaseConnection = () => {
return ( return (
<SubMenuTopBarContainer <SubMenuTopBarContainer
Icon={IconSettings}
title="Edit connection" title="Edit connection"
links={[ links={[
{ {

View File

@ -2,7 +2,7 @@ import { zodResolver } from '@hookform/resolvers/zod';
import { useEffect } from 'react'; import { useEffect } from 'react';
import { FormProvider, useForm } from 'react-hook-form'; import { FormProvider, useForm } from 'react-hook-form';
import { useNavigate, useParams } from 'react-router-dom'; import { useNavigate, useParams } from 'react-router-dom';
import { H2Title, IconSettings } from 'twenty-ui'; import { H2Title } from 'twenty-ui';
import { z } from 'zod'; import { z } from 'zod';
import { useCreateOneDatabaseConnection } from '@/databases/hooks/useCreateOneDatabaseConnection'; import { useCreateOneDatabaseConnection } from '@/databases/hooks/useCreateOneDatabaseConnection';
@ -131,7 +131,6 @@ export const SettingsIntegrationNewDatabaseConnection = () => {
return ( return (
<SubMenuTopBarContainer <SubMenuTopBarContainer
Icon={IconSettings}
title="New" title="New"
links={[ links={[
{ {

View File

@ -1,5 +1,3 @@
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 { getSettingsPagePath } from '@/settings/utils/getSettingsPagePath';
@ -9,7 +7,6 @@ import { SubMenuTopBarContainer } from '@/ui/layout/page/SubMenuTopBarContainer'
export const SettingsIntegrationShowDatabaseConnection = () => { export const SettingsIntegrationShowDatabaseConnection = () => {
return ( return (
<SubMenuTopBarContainer <SubMenuTopBarContainer
Icon={IconSettings}
title="Database Connection" title="Database Connection"
links={[ links={[
{ {

View File

@ -4,14 +4,12 @@ import { useSettingsIntegrationCategories } from '@/settings/integrations/hooks/
import { getSettingsPagePath } from '@/settings/utils/getSettingsPagePath'; 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 { IconApps } from 'twenty-ui';
export const SettingsIntegrations = () => { export const SettingsIntegrations = () => {
const integrationCategories = useSettingsIntegrationCategories(); const integrationCategories = useSettingsIntegrationCategories();
return ( return (
<SubMenuTopBarContainer <SubMenuTopBarContainer
Icon={IconApps}
title="Integrations" title="Integrations"
links={[ links={[
{ {

View File

@ -1,4 +1,4 @@
import { H2Title, IconColorSwatch } from 'twenty-ui'; import { H2Title } from 'twenty-ui';
import { SettingsPageContainer } from '@/settings/components/SettingsPageContainer'; import { SettingsPageContainer } from '@/settings/components/SettingsPageContainer';
import { getSettingsPagePath } from '@/settings/utils/getSettingsPagePath'; import { getSettingsPagePath } from '@/settings/utils/getSettingsPagePath';
@ -14,7 +14,6 @@ export const SettingsAppearance = () => {
return ( return (
<SubMenuTopBarContainer <SubMenuTopBarContainer
Icon={IconColorSwatch}
title="Experience" title="Experience"
links={[ links={[
{ {

View File

@ -21,7 +21,7 @@ import { useTabList } from '@/ui/layout/tab/hooks/useTabList';
import { useState } from 'react'; 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, IconSettings, IconTestPipe } from 'twenty-ui';
import { usePreventOverlapCallback } from '~/hooks/usePreventOverlapCallback'; import { usePreventOverlapCallback } from '~/hooks/usePreventOverlapCallback';
import { isDefined } from '~/utils/isDefined'; import { isDefined } from '~/utils/isDefined';
import { isDeeplyEqual } from '~/utils/isDeeplyEqual'; import { isDeeplyEqual } from '~/utils/isDeeplyEqual';
@ -227,7 +227,6 @@ export const SettingsServerlessFunctionDetail = () => {
return ( return (
!loading && ( !loading && (
<SubMenuTopBarContainer <SubMenuTopBarContainer
Icon={IconFunction}
title={formValues.name} title={formValues.name}
links={[ links={[
{ {

View File

@ -5,12 +5,11 @@ 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 { UndecoratedLink } from '@/ui/navigation/link/components/UndecoratedLink'; import { UndecoratedLink } from '@/ui/navigation/link/components/UndecoratedLink';
import { IconFunction, IconPlus } from 'twenty-ui'; import { IconPlus } from 'twenty-ui';
export const SettingsServerlessFunctions = () => { export const SettingsServerlessFunctions = () => {
return ( return (
<SubMenuTopBarContainer <SubMenuTopBarContainer
Icon={IconFunction}
title="Functions" title="Functions"
actionButton={ actionButton={
<UndecoratedLink <UndecoratedLink

View File

@ -12,7 +12,6 @@ import { SettingsPath } from '@/types/SettingsPath';
import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys'; import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys';
import { useState } from 'react'; import { useState } from 'react';
import { Key } from 'ts-key-enum'; import { Key } from 'ts-key-enum';
import { IconFunction } from 'twenty-ui';
import { useHotkeyScopeOnMount } from '~/hooks/useHotkeyScopeOnMount'; import { useHotkeyScopeOnMount } from '~/hooks/useHotkeyScopeOnMount';
import { isDefined } from '~/utils/isDefined'; import { isDefined } from '~/utils/isDefined';
@ -77,7 +76,6 @@ export const SettingsServerlessFunctionsNew = () => {
return ( return (
<SubMenuTopBarContainer <SubMenuTopBarContainer
Icon={IconFunction}
title="New Function" title="New Function"
links={[ links={[
{ {