-
Notifications
You must be signed in to change notification settings - Fork 2.4k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Prevent creating a custom field with an existing name #6094
Comments
To prevent creating a custom field with an existing name, follow these steps:
import { useEffect, useState } from 'react';
import { FormProvider, useForm } from 'react-hook-form';
import { useNavigate, useParams } from 'react-router-dom';
import { useApolloClient } from '@apollo/client';
import styled from '@emotion/styled';
import { zodResolver } from '@hookform/resolvers/zod';
import pick from 'lodash.pick';
import { H2Title, IconSettings } from 'twenty-ui';
import { z } from 'zod';
import { useCreateOneRelationMetadataItem } from '@/object-metadata/hooks/useCreateOneRelationMetadataItem';
import { useFieldMetadataItem } from '@/object-metadata/hooks/useFieldMetadataItem';
import { useFilteredObjectMetadataItems } from '@/object-metadata/hooks/useFilteredObjectMetadataItems';
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
import { useFindManyRecords } from '@/object-record/hooks/useFindManyRecords';
import { RecordFieldValueSelectorContextProvider } from '@/object-record/record-store/contexts/RecordFieldValueSelectorContext';
import { SaveAndCancelButtons } from '@/settings/components/SaveAndCancelButtons/SaveAndCancelButtons';
import { SettingsHeaderContainer } from '@/settings/components/SettingsHeaderContainer';
import { SettingsPageContainer } from '@/settings/components/SettingsPageContainer';
import { SettingsDataModelFieldAboutForm } from '@/settings/data-model/fields/forms/components/SettingsDataModelFieldAboutForm';
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 { SettingsSupportedFieldType } from '@/settings/data-model/types/SettingsSupportedFieldType';
import { AppPath } from '@/types/AppPath';
import { SnackBarVariant } from '@/ui/feedback/snack-bar-manager/components/SnackBar';
import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
import { SubMenuTopBarContainer } from '@/ui/layout/page/SubMenuTopBarContainer';
import { Section } from '@/ui/layout/section/components/Section';
import { Breadcrumb } from '@/ui/navigation/bread-crumb/components/Breadcrumb';
import { View } from '@/views/types/View';
import { ViewType } from '@/views/types/ViewType';
import { FieldMetadataType } from '~/generated-metadata/graphql';
import { isDefined } from '~/utils/isDefined';
import { isUndefinedOrNull } from '~/utils/isUndefinedOrNull';
const StyledSettingsObjectFieldTypeSelect = styled(
SettingsDataModelFieldTypeSelect,
)`
margin-bottom: ${({ theme }) => theme.spacing(4)};
`;
export const SettingsObjectNewFieldStep2 = () => {
const navigate = useNavigate();
const { objectSlug = '' } = useParams();
const { enqueueSnackBar } = useSnackBar();
const { findActiveObjectMetadataItemBySlug } =
useFilteredObjectMetadataItems();
const activeObjectMetadataItem =
findActiveObjectMetadataItemBySlug(objectSlug);
const { createMetadataField } = useFieldMetadataItem();
const formConfig = useForm<SettingsDataModelNewFieldFormValues>({
mode: 'onTouched',
resolver: zodResolver(settingsFieldFormSchema),
});
useEffect(() => {
if (!activeObjectMetadataItem) {
navigate(AppPath.NotFound);
}
}, [activeObjectMetadataItem, navigate]);
const [existingFieldNames, setExistingFieldNames] = useState<string[]>([]);
useEffect(() => {
if (activeObjectMetadataItem) {
setExistingFieldNames(activeObjectMetadataItem.fields.map(field => field.label));
}
}, [activeObjectMetadataItem]);
const validateFieldName = (name: string) => {
return !existingFieldNames.includes(name) || 'This name is already taken';
};
const canSave =
formConfig.formState.isValid && !formConfig.formState.isSubmitting;
const handleSave = async (
formValues: SettingsDataModelNewFieldFormValues,
) => {
try {
if (
formValues.type === FieldMetadataType.Relation &&
'relation' in formValues
) {
const { relation: relationFormValues, ...fieldFormValues } = formValues;
await createOneRelationMetadata({
relationType: relationFormValues.type,
field: pick(fieldFormValues, ['icon', 'label', 'description']),
objectMetadataId: activeObjectMetadataItem.id,
connect: {
field: {
icon: relationFormValues.field.icon,
label: relationFormValues.field.label,
},
objectMetadataId: relationFormValues.objectMetadataId,
},
});
await apolloClient.refetchQueries({
include: ['FindManyViews', 'CombinedFindManyRecords'],
});
} else {
await createMetadataField({
...formValues,
objectMetadataId: activeObjectMetadataItem.id,
});
await apolloClient.refetchQueries({
include: ['FindManyViews', 'CombinedFindManyRecords'],
});
}
navigate(`/settings/objects/${objectSlug}`);
} catch (error) {
enqueueSnackBar((error as Error).message, {
variant: SnackBarVariant.Error,
});
}
};
const excludedFieldTypes: SettingsSupportedFieldType[] = (
[
FieldMetadataType.Numeric,
FieldMetadataType.Probability,
] as const
).filter(isDefined);
return (
<RecordFieldValueSelectorContextProvider>
<FormProvider {...formConfig}>
<SubMenuTopBarContainer Icon={IconSettings} title="Settings">
<SettingsPageContainer>
<SettingsHeaderContainer>
<Breadcrumb
links={[
{ children: 'Objects', href: '/settings/objects' },
{
children: activeObjectMetadataItem.labelPlural,
href: `/settings/objects/${objectSlug}`,
},
{ children: 'New Field' },
]}
/>
{!activeObjectMetadataItem.isRemote && (
<SaveAndCancelButtons
isSaveDisabled={!canSave}
onCancel={() => navigate(`/settings/objects/${objectSlug}`)}
onSave={formConfig.handleSubmit(handleSave)}
/>
)}
</SettingsHeaderContainer>
<Section>
<H2Title
title="Name and description"
description="The name and description of this field"
/>
<SettingsDataModelFieldAboutForm
validateFieldName={validateFieldName}
/>
</Section>
<Section>
<H2Title
title="Type and values"
description="The field's type and values."
/>
<StyledSettingsObjectFieldTypeSelect
excludedFieldTypes={excludedFieldTypes}
/>
<SettingsDataModelFieldSettingsFormCard
fieldMetadataItem={{
icon: formConfig.watch('icon'),
label: formConfig.watch('label') || 'Employees',
type: formConfig.watch('type'),
}}
objectMetadataItem={activeObjectMetadataItem}
/>
</Section>
</SettingsPageContainer>
</SubMenuTopBarContainer>
</FormProvider>
</RecordFieldValueSelectorContextProvider>
);
};
import { useFormContext } from 'react-hook-form';
export const SettingsDataModelFieldAboutForm = ({ validateFieldName }) => {
const { register, formState: { errors } } = useFormContext();
return (
<div>
<input
{...register('label', { validate: validateFieldName })}
placeholder="Field Name"
/>
{errors.label && <span>{errors.label.message}</span>}
</div>
);
}; This will ensure that the form displays an error message if the field name already exists and disables the Save button to prevent submission. References/packages/twenty-front/src/pages/settings/data-model/SettingsObjectNewField/SettingsObjectNewFieldStep2.tsx
|
Fixes #6094 Description: Added logic inside SettingsObjectNewFieldStep2.tsx to prevent form submission Current Behaviours: <img width="947" alt="Screenshot 2024-07-03 at 1 45 31 PM" src="https://github.com/twentyhq/twenty/assets/95612797/bef54bc4-fc83-48f3-894a-34445ec64723"> --------- Co-authored-by: Marie Stoppa <[email protected]>
Scope & Context
Field creation in data model.
Current behavior
We can input whatever name we want in the name input in field creation form.
Expected behavior
We want the field to become in error state with a message displayed that says that this name is already taken.
We should also deactivate the Save button to prevent from sending anything to the backend which would throw an error anyway.
The text was updated successfully, but these errors were encountered: