[REFACTOR] Split in two distinct forms Settings Object Model page (#10653)

# Introduction

This PR contains around ~+300 tests + snapshot additions
Please check both object model creation and edition

Closes https://github.com/twentyhq/core-team-issues/issues/355

Refactored into two agnostic forms the Object Model settings page for
instance `/settings/objects/notes#settings`.

## `SettingsDataModelObjectAboutForm`
Added a new abstraction `SettingsUpdateDataModelObjectAboutForm` to wrap
`SettingsDataModelObjectAboutForm` in an `update` context


![image](https://github.com/user-attachments/assets/137b4f85-d5d8-442f-ad81-27653af99c03)
Schema:
```ts
const requiredFormFields = objectMetadataItemSchema.pick({
  description: true,
  icon: true,
  labelPlural: true,
  labelSingular: true,
});
const optionalFormFields = objectMetadataItemSchema
  .pick({
    nameSingular: true,
    namePlural: true,
    isLabelSyncedWithName: true,
  })
  .partial();
export const settingsDataModelObjectAboutFormSchema =
  requiredFormFields.merge(optionalFormFields);
```
##  `SettingsDataModelObjectSettingsFormCard`
Update on change

![image](https://github.com/user-attachments/assets/179da504-7680-498d-818d-d7f80d77736b)
Schema:
```ts
export const settingsDataModelObjectIdentifiersFormSchema =
  objectMetadataItemSchema.pick({
    labelIdentifierFieldMetadataId: true,
    imageIdentifierFieldMetadataId: true,
  });
```

## Error management and validation schema
Improved the frontend validation form in order to attest that:
- Names are in camelCase
- Names are differents
- Names are not empty string ***SHOULD BE DONE SERVER SIDE TOO*** ( will
in a next PR, atm it literally breaks any workspace )
- Labels are differents
- Labels aren't empty strings

Hide the error messages as we need to decide what kind of styling we
want for our errors with forms
( Example with error labels )

![image](https://github.com/user-attachments/assets/d54534f8-8163-42d9-acdc-976a5e723498)
This commit is contained in:
Paul Rastoin
2025-03-07 10:14:25 +01:00
committed by GitHub
parent 21c7d2081d
commit 776632fe79
17 changed files with 646 additions and 382 deletions

View File

@ -1,16 +1,16 @@
import { zodResolver } from '@hookform/resolvers/zod';
import { FormProvider, useForm } from 'react-hook-form';
import { H2Title, Section } from 'twenty-ui';
import { z } from 'zod';
import { useCreateOneObjectMetadataItem } from '@/object-metadata/hooks/useCreateOneObjectMetadataItem';
import { SaveAndCancelButtons } from '@/settings/components/SaveAndCancelButtons/SaveAndCancelButtons';
import { SettingsPageContainer } from '@/settings/components/SettingsPageContainer';
import { SETTINGS_OBJECT_MODEL_IS_LABEL_SYNCED_WITH_NAME_LABEL_DEFAULT_VALUE } from '@/settings/constants/SettingsObjectModel';
import { SettingsDataModelObjectAboutForm } from '@/settings/data-model/objects/forms/components/SettingsDataModelObjectAboutForm';
import {
SettingsDataModelObjectAboutForm,
SettingsDataModelObjectAboutFormValues,
settingsDataModelObjectAboutFormSchema,
} from '@/settings/data-model/objects/forms/components/SettingsDataModelObjectAboutForm';
import { settingsCreateObjectInputSchema } from '@/settings/data-model/validation-schemas/settingsCreateObjectInputSchema';
} from '@/settings/data-model/validation-schemas/settingsDataModelObjectAboutFormSchema';
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';
@ -19,10 +19,6 @@ import { useLingui } from '@lingui/react/macro';
import { useNavigateSettings } from '~/hooks/useNavigateSettings';
import { getSettingsPath } from '~/utils/navigation/getSettingsPath';
const newObjectFormSchema = settingsDataModelObjectAboutFormSchema;
type SettingsDataModelNewObjectFormValues = z.infer<typeof newObjectFormSchema>;
export const SettingsNewObject = () => {
const { t } = useLingui();
const navigate = useNavigateSettings();
@ -30,21 +26,23 @@ export const SettingsNewObject = () => {
const { createOneObjectMetadataItem } = useCreateOneObjectMetadataItem();
const formConfig = useForm<SettingsDataModelNewObjectFormValues>({
mode: 'onTouched',
resolver: zodResolver(newObjectFormSchema),
const formConfig = useForm<SettingsDataModelObjectAboutFormValues>({
mode: 'onSubmit',
resolver: zodResolver(settingsDataModelObjectAboutFormSchema),
defaultValues: {
isLabelSyncedWithName:
SETTINGS_OBJECT_MODEL_IS_LABEL_SYNCED_WITH_NAME_LABEL_DEFAULT_VALUE,
},
});
const { isValid, isSubmitting } = formConfig.formState;
const canSave = isValid && !isSubmitting;
const handleSave = async (
formValues: SettingsDataModelNewObjectFormValues,
formValues: SettingsDataModelObjectAboutFormValues,
) => {
try {
const { data: response } = await createOneObjectMetadataItem(
settingsCreateObjectInputSchema.parse(formValues),
);
const { data: response } = await createOneObjectMetadataItem(formValues);
navigate(
response ? SettingsPath.ObjectDetail : SettingsPath.Objects,
@ -53,6 +51,8 @@ export const SettingsNewObject = () => {
: undefined,
);
} catch (error) {
// eslint-disable-next-line no-console
console.error(error);
enqueueSnackBar((error as Error).message, {
variant: SnackBarVariant.Error,
});
@ -90,7 +90,9 @@ export const SettingsNewObject = () => {
title={t`About`}
description={t`Define the name and description of your object`}
/>
<SettingsDataModelObjectAboutForm />
<SettingsDataModelObjectAboutForm
onNewDirtyField={() => formConfig.trigger()}
/>
</Section>
</SettingsPageContainer>
</SubMenuTopBarContainer>