Remove step 1 of new object field (#7397)
fixes #7356 fixes #6967 fixes #7102 fixes #7121 fixes #7505
This commit is contained in:
@ -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}
|
||||||
|
|||||||
@ -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,
|
|
||||||
);
|
|
||||||
@ -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>
|
||||||
|
);
|
||||||
|
};
|
||||||
@ -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>
|
||||||
|
|||||||
@ -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>
|
/>
|
||||||
)}
|
</>
|
||||||
/>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
@ -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();
|
|
||||||
},
|
|
||||||
};
|
|
||||||
@ -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
|
||||||
|
|||||||
@ -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',
|
||||||
|
|||||||
@ -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'];
|
||||||
};
|
};
|
||||||
|
|||||||
@ -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`);
|
||||||
|
|||||||
@ -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={[
|
||||||
{
|
{
|
||||||
|
|||||||
@ -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={[
|
||||||
{
|
{
|
||||||
|
|||||||
@ -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={[
|
||||||
{
|
{
|
||||||
|
|||||||
@ -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={[
|
||||||
{
|
{
|
||||||
|
|||||||
@ -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={[
|
||||||
{
|
{
|
||||||
|
|||||||
@ -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={[
|
||||||
{
|
{
|
||||||
|
|||||||
@ -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={[
|
||||||
{
|
{
|
||||||
|
|||||||
@ -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={[
|
||||||
{
|
{
|
||||||
|
|||||||
@ -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={[
|
||||||
{
|
{
|
||||||
|
|||||||
@ -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={[
|
||||||
{
|
{
|
||||||
|
|||||||
@ -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={[
|
||||||
{
|
{
|
||||||
|
|||||||
@ -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"
|
||||||
|
|||||||
@ -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={[
|
||||||
{
|
{
|
||||||
|
|||||||
@ -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={[
|
||||||
{
|
{
|
||||||
|
|||||||
@ -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>
|
||||||
@ -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>
|
||||||
|
);
|
||||||
|
};
|
||||||
@ -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>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
@ -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',
|
||||||
|
|||||||
@ -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)}>
|
||||||
|
|||||||
@ -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);
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
@ -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));
|
||||||
|
},
|
||||||
|
};
|
||||||
@ -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');
|
|
||||||
},
|
|
||||||
};
|
|
||||||
@ -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={[
|
||||||
|
|||||||
@ -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={[
|
||||||
{
|
{
|
||||||
|
|||||||
@ -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={[
|
||||||
{
|
{
|
||||||
|
|||||||
@ -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={[
|
||||||
{
|
{
|
||||||
|
|||||||
@ -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={[
|
||||||
{
|
{
|
||||||
|
|||||||
@ -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={[
|
||||||
{
|
{
|
||||||
|
|||||||
@ -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={[
|
||||||
{
|
{
|
||||||
|
|||||||
@ -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={[
|
||||||
{
|
{
|
||||||
|
|||||||
@ -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={[
|
||||||
{
|
{
|
||||||
|
|||||||
@ -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={[
|
||||||
{
|
{
|
||||||
|
|||||||
@ -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={[
|
||||||
{
|
{
|
||||||
|
|||||||
@ -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={[
|
||||||
{
|
{
|
||||||
|
|||||||
@ -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
|
||||||
|
|||||||
@ -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={[
|
||||||
{
|
{
|
||||||
|
|||||||
Reference in New Issue
Block a user