refactor: use react-hook-form to validate Settings/DataModel/Field (#4916)

Closes #4295
This commit is contained in:
Thaïs
2024-05-07 11:44:46 +02:00
committed by GitHub
parent 9c25c1beae
commit d0759ad7cc
18 changed files with 13234 additions and 13369 deletions

View File

@ -0,0 +1,87 @@
import { Controller, useFormContext } from 'react-hook-form';
import styled from '@emotion/styled';
import { z } from 'zod';
import { FieldMetadataItem } from '@/object-metadata/types/FieldMetadataItem';
import { fieldMetadataItemSchema } from '@/object-metadata/validation-schemas/fieldMetadataItemSchema';
import { IconPicker } from '@/ui/input/components/IconPicker';
import { TextArea } from '@/ui/input/components/TextArea';
import { TextInput } from '@/ui/input/components/TextInput';
export const settingsDataModelFieldAboutFormSchema =
fieldMetadataItemSchema.pick({
description: true,
icon: true,
label: true,
});
type SettingsDataModelFieldAboutFormValues = z.infer<
typeof settingsDataModelFieldAboutFormSchema
>;
type SettingsDataModelFieldAboutFormProps = {
disabled?: boolean;
fieldMetadataItem?: FieldMetadataItem;
};
const StyledInputsContainer = styled.div`
display: flex;
gap: ${({ theme }) => theme.spacing(2)};
margin-bottom: ${({ theme }) => theme.spacing(2)};
width: 100%;
`;
export const SettingsDataModelFieldAboutForm = ({
disabled,
fieldMetadataItem,
}: SettingsDataModelFieldAboutFormProps) => {
const { control } = useFormContext<SettingsDataModelFieldAboutFormValues>();
return (
<>
<StyledInputsContainer>
<Controller
name="icon"
control={control}
defaultValue={fieldMetadataItem?.icon ?? 'IconUsers'}
render={({ field: { onChange, value } }) => (
<IconPicker
disabled={disabled}
selectedIconKey={value ?? ''}
onChange={({ iconKey }) => onChange(iconKey)}
variant="primary"
/>
)}
/>
<Controller
name="label"
control={control}
defaultValue={fieldMetadataItem?.label}
render={({ field: { onChange, value } }) => (
<TextInput
placeholder="Employees"
value={value}
onChange={onChange}
disabled={disabled}
fullWidth
/>
)}
/>
</StyledInputsContainer>
<Controller
name="description"
control={control}
defaultValue={fieldMetadataItem?.description}
render={({ field: { onChange, value } }) => (
<TextArea
placeholder="Write a description"
minRows={4}
value={value ?? undefined}
onChange={onChange}
disabled={disabled}
/>
)}
/>
</>
);
};

View File

@ -0,0 +1,47 @@
import styled from '@emotion/styled';
import { Meta, StoryObj } from '@storybook/react';
import { ComponentDecorator } from 'twenty-ui';
import { FormProviderDecorator } from '~/testing/decorators/FormProviderDecorator';
import { IconsProviderDecorator } from '~/testing/decorators/IconsProviderDecorator';
import { mockedPersonObjectMetadataItem } from '~/testing/mock-data/metadata';
import { SettingsDataModelFieldAboutForm } from '../SettingsDataModelFieldAboutForm';
const StyledContainer = styled.div`
flex: 1;
`;
const meta: Meta<typeof SettingsDataModelFieldAboutForm> = {
title: 'Modules/Settings/DataModel/SettingsDataModelFieldAboutForm',
component: SettingsDataModelFieldAboutForm,
decorators: [
(Story) => (
<StyledContainer>
<Story />
</StyledContainer>
),
FormProviderDecorator,
IconsProviderDecorator,
ComponentDecorator,
],
};
export default meta;
type Story = StoryObj<typeof SettingsDataModelFieldAboutForm>;
export const Default: Story = {};
export const WithDefaultValues: Story = {
args: {
fieldMetadataItem: mockedPersonObjectMetadataItem.fields.find(
({ name }) => name === 'name',
)!,
},
};
export const Disabled: Story = {
args: {
disabled: true,
},
};

View File

@ -1,5 +1,7 @@
import { act, renderHook } from '@testing-library/react';
import { FieldMetadataType } from '~/generated/graphql';
import { useFieldMetadataForm } from '../useFieldMetadataForm';
describe('useFieldMetadataForm', () => {
@ -14,8 +16,6 @@ describe('useFieldMetadataForm', () => {
expect(result.current.isInitialized).toBe(true);
expect(result.current.formValues).toEqual({
icon: 'IconUsers',
label: '',
type: 'TEXT',
currency: { currencyCode: 'USD' },
relation: {
@ -45,7 +45,7 @@ describe('useFieldMetadataForm', () => {
expect(result.current.hasSelectFormChanged).toBe(false);
act(() => {
result.current.handleFormChange({ label: 'New Label' });
result.current.handleFormChange({ type: FieldMetadataType.Number });
});
expect(result.current.hasFieldFormChanged).toBe(true);

View File

@ -15,16 +15,11 @@ import { isDeeplyEqual } from '~/utils/isDeeplyEqual';
import { SettingsDataModelFieldSettingsFormValues } from '../components/SettingsDataModelFieldSettingsFormCard';
type FormValues = {
description?: string;
icon: string;
label: string;
defaultValue: any;
type: SettingsSupportedFieldType;
} & SettingsDataModelFieldSettingsFormValues;
export const fieldMetadataFormDefaultValues: FormValues = {
icon: 'IconUsers',
label: '',
type: FieldMetadataType.Text,
currency: { currencyCode: CurrencyCode.USD },
relation: {
@ -43,9 +38,6 @@ const relationTargetFieldSchema = z.object({
defaultValue: z.any(),
});
const fieldSchema = z.object({
description: z.string().optional(),
icon: z.string().startsWith('Icon'),
label: z.string().min(1),
defaultValue: z.any(),
type: z.enum(
Object.values(FieldMetadataType) as [

View File

@ -0,0 +1,3 @@
import { settingsDataModelFieldAboutFormSchema } from '@/settings/data-model/fields/forms/components/SettingsDataModelFieldAboutForm';
export const settingsFieldFormSchema = settingsDataModelFieldAboutFormSchema;