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 <marie.stoppa@essec.edu>
This commit is contained in:
@ -6,7 +6,7 @@ describe('metadataLabelSchema', () => {
|
|||||||
const validMetadataLabel = 'Option 1';
|
const validMetadataLabel = 'Option 1';
|
||||||
|
|
||||||
// When
|
// When
|
||||||
const result = metadataLabelSchema.parse(validMetadataLabel);
|
const result = metadataLabelSchema().parse(validMetadataLabel);
|
||||||
|
|
||||||
// Then
|
// Then
|
||||||
expect(result).toEqual(validMetadataLabel);
|
expect(result).toEqual(validMetadataLabel);
|
||||||
@ -16,7 +16,7 @@ describe('metadataLabelSchema', () => {
|
|||||||
const validMetadataLabel = 'עִבְרִי';
|
const validMetadataLabel = 'עִבְרִי';
|
||||||
|
|
||||||
// When
|
// When
|
||||||
const result = metadataLabelSchema.parse(validMetadataLabel);
|
const result = metadataLabelSchema().parse(validMetadataLabel);
|
||||||
|
|
||||||
// Then
|
// Then
|
||||||
expect(result).toEqual(validMetadataLabel);
|
expect(result).toEqual(validMetadataLabel);
|
||||||
|
|||||||
@ -10,97 +10,99 @@ import {
|
|||||||
} from '~/generated-metadata/graphql';
|
} from '~/generated-metadata/graphql';
|
||||||
import { camelCaseStringSchema } from '~/utils/validation-schemas/camelCaseStringSchema';
|
import { camelCaseStringSchema } from '~/utils/validation-schemas/camelCaseStringSchema';
|
||||||
|
|
||||||
export const fieldMetadataItemSchema = z.object({
|
export const fieldMetadataItemSchema = (existingLabels?: string[]) => {
|
||||||
__typename: z.literal('field').optional(),
|
return z.object({
|
||||||
createdAt: z.string().datetime(),
|
__typename: z.literal('field').optional(),
|
||||||
defaultValue: z.any().optional(),
|
createdAt: z.string().datetime(),
|
||||||
description: z.string().trim().nullable().optional(),
|
defaultValue: z.any().optional(),
|
||||||
fromRelationMetadata: z
|
description: z.string().trim().nullable().optional(),
|
||||||
.object({
|
fromRelationMetadata: z
|
||||||
__typename: z.literal('relation').optional(),
|
.object({
|
||||||
id: z.string().uuid(),
|
__typename: z.literal('relation').optional(),
|
||||||
relationType: z.nativeEnum(RelationMetadataType),
|
|
||||||
toFieldMetadataId: z.string().uuid(),
|
|
||||||
toObjectMetadata: z.object({
|
|
||||||
__typename: z.literal('object').optional(),
|
|
||||||
dataSourceId: z.string().uuid(),
|
|
||||||
id: z.string().uuid(),
|
id: z.string().uuid(),
|
||||||
isRemote: z.boolean(),
|
relationType: z.nativeEnum(RelationMetadataType),
|
||||||
isSystem: z.boolean(),
|
toFieldMetadataId: z.string().uuid(),
|
||||||
namePlural: z.string().trim().min(1),
|
toObjectMetadata: z.object({
|
||||||
nameSingular: z.string().trim().min(1),
|
__typename: z.literal('object').optional(),
|
||||||
}),
|
dataSourceId: z.string().uuid(),
|
||||||
})
|
id: z.string().uuid(),
|
||||||
.nullable()
|
isRemote: z.boolean(),
|
||||||
.optional(),
|
isSystem: z.boolean(),
|
||||||
icon: z.string().startsWith('Icon').trim().nullable(),
|
namePlural: z.string().trim().min(1),
|
||||||
id: z.string().uuid(),
|
nameSingular: z.string().trim().min(1),
|
||||||
isActive: z.boolean(),
|
}),
|
||||||
isCustom: z.boolean(),
|
})
|
||||||
isNullable: z.boolean(),
|
.nullable()
|
||||||
isSystem: z.boolean(),
|
.optional(),
|
||||||
label: metadataLabelSchema,
|
icon: z.string().startsWith('Icon').trim().nullable(),
|
||||||
name: camelCaseStringSchema,
|
id: z.string().uuid(),
|
||||||
options: z
|
isActive: z.boolean(),
|
||||||
.array(
|
isCustom: z.boolean(),
|
||||||
z.object({
|
isNullable: z.boolean(),
|
||||||
color: themeColorSchema,
|
isSystem: z.boolean(),
|
||||||
|
label: metadataLabelSchema(existingLabels),
|
||||||
|
name: camelCaseStringSchema,
|
||||||
|
options: z
|
||||||
|
.array(
|
||||||
|
z.object({
|
||||||
|
color: themeColorSchema,
|
||||||
|
id: z.string().uuid(),
|
||||||
|
label: z.string().trim().min(1),
|
||||||
|
position: z.number(),
|
||||||
|
value: z.string().trim().min(1),
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
.nullable()
|
||||||
|
.optional(),
|
||||||
|
relationDefinition: z
|
||||||
|
.object({
|
||||||
|
__typename: z.literal('RelationDefinition').optional(),
|
||||||
|
relationId: z.string().uuid(),
|
||||||
|
direction: z.nativeEnum(RelationDefinitionType),
|
||||||
|
sourceFieldMetadata: z.object({
|
||||||
|
__typename: z.literal('field').optional(),
|
||||||
|
id: z.string().uuid(),
|
||||||
|
name: z.string().trim().min(1),
|
||||||
|
}),
|
||||||
|
sourceObjectMetadata: z.object({
|
||||||
|
__typename: z.literal('object').optional(),
|
||||||
|
id: z.string().uuid(),
|
||||||
|
namePlural: z.string().trim().min(1),
|
||||||
|
nameSingular: z.string().trim().min(1),
|
||||||
|
}),
|
||||||
|
targetFieldMetadata: z.object({
|
||||||
|
__typename: z.literal('field').optional(),
|
||||||
|
id: z.string().uuid(),
|
||||||
|
name: z.string().trim().min(1),
|
||||||
|
}),
|
||||||
|
targetObjectMetadata: z.object({
|
||||||
|
__typename: z.literal('object').optional(),
|
||||||
|
id: z.string().uuid(),
|
||||||
|
namePlural: z.string().trim().min(1),
|
||||||
|
nameSingular: z.string().trim().min(1),
|
||||||
|
}),
|
||||||
|
})
|
||||||
|
.nullable()
|
||||||
|
.optional(),
|
||||||
|
toRelationMetadata: z
|
||||||
|
.object({
|
||||||
|
__typename: z.literal('relation').optional(),
|
||||||
id: z.string().uuid(),
|
id: z.string().uuid(),
|
||||||
label: z.string().trim().min(1),
|
relationType: z.nativeEnum(RelationMetadataType),
|
||||||
position: z.number(),
|
fromFieldMetadataId: z.string().uuid(),
|
||||||
value: z.string().trim().min(1),
|
fromObjectMetadata: z.object({
|
||||||
}),
|
__typename: z.literal('object').optional(),
|
||||||
)
|
id: z.string().uuid(),
|
||||||
.nullable()
|
dataSourceId: z.string().uuid(),
|
||||||
.optional(),
|
isRemote: z.boolean(),
|
||||||
relationDefinition: z
|
isSystem: z.boolean(),
|
||||||
.object({
|
namePlural: z.string().trim().min(1),
|
||||||
__typename: z.literal('RelationDefinition').optional(),
|
nameSingular: z.string().trim().min(1),
|
||||||
relationId: z.string().uuid(),
|
}),
|
||||||
direction: z.nativeEnum(RelationDefinitionType),
|
})
|
||||||
sourceFieldMetadata: z.object({
|
.nullable()
|
||||||
__typename: z.literal('field').optional(),
|
.optional(),
|
||||||
id: z.string().uuid(),
|
type: z.nativeEnum(FieldMetadataType),
|
||||||
name: z.string().trim().min(1),
|
updatedAt: z.string().datetime(),
|
||||||
}),
|
}) satisfies z.ZodType<FieldMetadataItem>;
|
||||||
sourceObjectMetadata: z.object({
|
};
|
||||||
__typename: z.literal('object').optional(),
|
|
||||||
id: z.string().uuid(),
|
|
||||||
namePlural: z.string().trim().min(1),
|
|
||||||
nameSingular: z.string().trim().min(1),
|
|
||||||
}),
|
|
||||||
targetFieldMetadata: z.object({
|
|
||||||
__typename: z.literal('field').optional(),
|
|
||||||
id: z.string().uuid(),
|
|
||||||
name: z.string().trim().min(1),
|
|
||||||
}),
|
|
||||||
targetObjectMetadata: z.object({
|
|
||||||
__typename: z.literal('object').optional(),
|
|
||||||
id: z.string().uuid(),
|
|
||||||
namePlural: z.string().trim().min(1),
|
|
||||||
nameSingular: z.string().trim().min(1),
|
|
||||||
}),
|
|
||||||
})
|
|
||||||
.nullable()
|
|
||||||
.optional(),
|
|
||||||
toRelationMetadata: z
|
|
||||||
.object({
|
|
||||||
__typename: z.literal('relation').optional(),
|
|
||||||
id: z.string().uuid(),
|
|
||||||
relationType: z.nativeEnum(RelationMetadataType),
|
|
||||||
fromFieldMetadataId: z.string().uuid(),
|
|
||||||
fromObjectMetadata: z.object({
|
|
||||||
__typename: z.literal('object').optional(),
|
|
||||||
id: z.string().uuid(),
|
|
||||||
dataSourceId: z.string().uuid(),
|
|
||||||
isRemote: z.boolean(),
|
|
||||||
isSystem: z.boolean(),
|
|
||||||
namePlural: z.string().trim().min(1),
|
|
||||||
nameSingular: z.string().trim().min(1),
|
|
||||||
}),
|
|
||||||
})
|
|
||||||
.nullable()
|
|
||||||
.optional(),
|
|
||||||
type: z.nativeEnum(FieldMetadataType),
|
|
||||||
updatedAt: z.string().datetime(),
|
|
||||||
}) satisfies z.ZodType<FieldMetadataItem>;
|
|
||||||
|
|||||||
@ -1,23 +1,42 @@
|
|||||||
|
import { errors } from '@/settings/data-model/fields/forms/utils/errorMessages';
|
||||||
import { z } from 'zod';
|
import { z } from 'zod';
|
||||||
|
|
||||||
import { METADATA_LABEL_VALID_PATTERN } from '~/pages/settings/data-model/constants/MetadataLabelValidPattern';
|
import { METADATA_LABEL_VALID_PATTERN } from '~/pages/settings/data-model/constants/MetadataLabelValidPattern';
|
||||||
import { computeMetadataNameFromLabelOrThrow } from '~/pages/settings/data-model/utils/compute-metadata-name-from-label.utils';
|
import { computeMetadataNameFromLabelOrThrow } from '~/pages/settings/data-model/utils/compute-metadata-name-from-label.utils';
|
||||||
|
export const metadataLabelSchema = (existingLabels?: string[]) => {
|
||||||
export const metadataLabelSchema = z
|
return z
|
||||||
.string()
|
.string()
|
||||||
.trim()
|
.trim()
|
||||||
.min(1)
|
.min(1, errors.LabelEmpty)
|
||||||
.regex(METADATA_LABEL_VALID_PATTERN)
|
.regex(METADATA_LABEL_VALID_PATTERN, errors.LabelNotFormattable)
|
||||||
.refine(
|
.refine(
|
||||||
(label) => {
|
(label) => {
|
||||||
try {
|
try {
|
||||||
computeMetadataNameFromLabelOrThrow(label);
|
computeMetadataNameFromLabelOrThrow(label);
|
||||||
return true;
|
return true;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
message: 'Label is not formattable',
|
message: errors.LabelNotFormattable,
|
||||||
},
|
},
|
||||||
); // allows non-latin char
|
)
|
||||||
|
.refine(
|
||||||
|
(label) => {
|
||||||
|
try {
|
||||||
|
if (!existingLabels || !label?.length) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return !existingLabels.includes(
|
||||||
|
computeMetadataNameFromLabelOrThrow(label),
|
||||||
|
);
|
||||||
|
} catch (error) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
message: errors.LabelNotUnique,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|||||||
@ -10,7 +10,7 @@ export const objectMetadataItemSchema = z.object({
|
|||||||
createdAt: z.string().datetime(),
|
createdAt: z.string().datetime(),
|
||||||
dataSourceId: z.string().uuid(),
|
dataSourceId: z.string().uuid(),
|
||||||
description: z.string().trim().nullable().optional(),
|
description: z.string().trim().nullable().optional(),
|
||||||
fields: z.array(fieldMetadataItemSchema),
|
fields: z.array(fieldMetadataItemSchema()),
|
||||||
icon: z.string().startsWith('Icon').trim(),
|
icon: z.string().startsWith('Icon').trim(),
|
||||||
id: z.string().uuid(),
|
id: z.string().uuid(),
|
||||||
imageIdentifierFieldMetadataId: z.string().uuid().nullable(),
|
imageIdentifierFieldMetadataId: z.string().uuid().nullable(),
|
||||||
@ -19,8 +19,8 @@ export const objectMetadataItemSchema = z.object({
|
|||||||
isRemote: z.boolean(),
|
isRemote: z.boolean(),
|
||||||
isSystem: z.boolean(),
|
isSystem: z.boolean(),
|
||||||
labelIdentifierFieldMetadataId: z.string().uuid().nullable(),
|
labelIdentifierFieldMetadataId: z.string().uuid().nullable(),
|
||||||
labelPlural: metadataLabelSchema,
|
labelPlural: metadataLabelSchema(),
|
||||||
labelSingular: metadataLabelSchema,
|
labelSingular: metadataLabelSchema(),
|
||||||
namePlural: camelCaseStringSchema,
|
namePlural: camelCaseStringSchema,
|
||||||
nameSingular: camelCaseStringSchema,
|
nameSingular: camelCaseStringSchema,
|
||||||
updatedAt: z.string().datetime(),
|
updatedAt: z.string().datetime(),
|
||||||
|
|||||||
@ -1,22 +1,27 @@
|
|||||||
import { Controller, useFormContext } from 'react-hook-form';
|
|
||||||
import styled from '@emotion/styled';
|
import styled from '@emotion/styled';
|
||||||
|
import { Controller, useFormContext } from 'react-hook-form';
|
||||||
import { z } from 'zod';
|
import { z } from 'zod';
|
||||||
|
|
||||||
import { FieldMetadataItem } from '@/object-metadata/types/FieldMetadataItem';
|
import { FieldMetadataItem } from '@/object-metadata/types/FieldMetadataItem';
|
||||||
import { fieldMetadataItemSchema } from '@/object-metadata/validation-schemas/fieldMetadataItemSchema';
|
import { fieldMetadataItemSchema } from '@/object-metadata/validation-schemas/fieldMetadataItemSchema';
|
||||||
|
import { getErrorMessageFromError } from '@/settings/data-model/fields/forms/utils/errorMessages';
|
||||||
import { IconPicker } from '@/ui/input/components/IconPicker';
|
import { IconPicker } from '@/ui/input/components/IconPicker';
|
||||||
import { TextArea } from '@/ui/input/components/TextArea';
|
import { TextArea } from '@/ui/input/components/TextArea';
|
||||||
import { TextInput } from '@/ui/input/components/TextInput';
|
import { TextInput } from '@/ui/input/components/TextInput';
|
||||||
|
|
||||||
export const settingsDataModelFieldAboutFormSchema =
|
export const settingsDataModelFieldAboutFormSchema = (
|
||||||
fieldMetadataItemSchema.pick({
|
existingLabels?: string[],
|
||||||
|
) => {
|
||||||
|
return fieldMetadataItemSchema(existingLabels || []).pick({
|
||||||
description: true,
|
description: true,
|
||||||
icon: true,
|
icon: true,
|
||||||
label: true,
|
label: true,
|
||||||
});
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
// Correctly infer the type from the returned schema
|
||||||
type SettingsDataModelFieldAboutFormValues = z.infer<
|
type SettingsDataModelFieldAboutFormValues = z.infer<
|
||||||
typeof settingsDataModelFieldAboutFormSchema
|
ReturnType<typeof settingsDataModelFieldAboutFormSchema>
|
||||||
>;
|
>;
|
||||||
|
|
||||||
type SettingsDataModelFieldAboutFormProps = {
|
type SettingsDataModelFieldAboutFormProps = {
|
||||||
@ -32,13 +37,18 @@ const StyledInputsContainer = styled.div`
|
|||||||
width: 100%;
|
width: 100%;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
const LABEL = 'label';
|
||||||
|
|
||||||
export const SettingsDataModelFieldAboutForm = ({
|
export const SettingsDataModelFieldAboutForm = ({
|
||||||
disabled,
|
disabled,
|
||||||
fieldMetadataItem,
|
fieldMetadataItem,
|
||||||
maxLength,
|
maxLength,
|
||||||
}: SettingsDataModelFieldAboutFormProps) => {
|
}: SettingsDataModelFieldAboutFormProps) => {
|
||||||
const { control } = useFormContext<SettingsDataModelFieldAboutFormValues>();
|
const {
|
||||||
|
control,
|
||||||
|
trigger,
|
||||||
|
formState: { errors },
|
||||||
|
} = useFormContext<SettingsDataModelFieldAboutFormValues>();
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<StyledInputsContainer>
|
<StyledInputsContainer>
|
||||||
@ -56,14 +66,18 @@ export const SettingsDataModelFieldAboutForm = ({
|
|||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
<Controller
|
<Controller
|
||||||
name="label"
|
name={LABEL}
|
||||||
control={control}
|
control={control}
|
||||||
defaultValue={fieldMetadataItem?.label}
|
defaultValue={fieldMetadataItem?.label}
|
||||||
render={({ field: { onChange, value } }) => (
|
render={({ field: { onChange, value } }) => (
|
||||||
<TextInput
|
<TextInput
|
||||||
placeholder="Employees"
|
placeholder="Employees"
|
||||||
value={value}
|
value={value}
|
||||||
onChange={onChange}
|
onChange={(e) => {
|
||||||
|
onChange(e);
|
||||||
|
trigger(LABEL);
|
||||||
|
}}
|
||||||
|
error={getErrorMessageFromError(errors.label?.message)}
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
maxLength={maxLength}
|
maxLength={maxLength}
|
||||||
fullWidth
|
fullWidth
|
||||||
|
|||||||
@ -17,7 +17,7 @@ import { TextInput } from '@/ui/input/components/TextInput';
|
|||||||
|
|
||||||
export const settingsDataModelFieldRelationFormSchema = z.object({
|
export const settingsDataModelFieldRelationFormSchema = z.object({
|
||||||
relation: z.object({
|
relation: z.object({
|
||||||
field: fieldMetadataItemSchema.pick({
|
field: fieldMetadataItemSchema().pick({
|
||||||
icon: true,
|
icon: true,
|
||||||
label: true,
|
label: true,
|
||||||
}),
|
}),
|
||||||
|
|||||||
@ -0,0 +1,18 @@
|
|||||||
|
export enum errors {
|
||||||
|
LabelNotUnique = 'LABEL_NOT_UNIQUE',
|
||||||
|
LabelNotFormattable = 'LABEL_NOT_FORMATTABLE',
|
||||||
|
LabelEmpty = 'LABEL_EMPTY',
|
||||||
|
}
|
||||||
|
|
||||||
|
export const getErrorMessageFromError = (error?: string) => {
|
||||||
|
switch (error) {
|
||||||
|
case errors.LabelEmpty:
|
||||||
|
return 'Name cannot be empty.';
|
||||||
|
case errors.LabelNotFormattable:
|
||||||
|
return 'Name should start with a letter.';
|
||||||
|
case errors.LabelNotUnique:
|
||||||
|
return 'This name is already used.';
|
||||||
|
default:
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
};
|
||||||
@ -4,8 +4,10 @@ import { settingsDataModelFieldAboutFormSchema } from '@/settings/data-model/fie
|
|||||||
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 { settingsDataModelFieldTypeFormSchema } from '@/settings/data-model/fields/forms/components/SettingsDataModelFieldTypeSelect';
|
||||||
|
|
||||||
export const settingsFieldFormSchema = z
|
export const settingsFieldFormSchema = (existingLabels?: string[]) => {
|
||||||
.object({})
|
return z
|
||||||
.merge(settingsDataModelFieldAboutFormSchema)
|
.object({})
|
||||||
.merge(settingsDataModelFieldTypeFormSchema)
|
.merge(settingsDataModelFieldAboutFormSchema(existingLabels))
|
||||||
.and(settingsDataModelFieldSettingsFormSchema);
|
.merge(settingsDataModelFieldTypeFormSchema)
|
||||||
|
.and(settingsDataModelFieldSettingsFormSchema);
|
||||||
|
};
|
||||||
|
|||||||
@ -1,16 +1,15 @@
|
|||||||
|
import { useTheme } from '@emotion/react';
|
||||||
|
import styled from '@emotion/styled';
|
||||||
import {
|
import {
|
||||||
ChangeEvent,
|
ChangeEvent,
|
||||||
FocusEventHandler,
|
FocusEventHandler,
|
||||||
ForwardedRef,
|
ForwardedRef,
|
||||||
forwardRef,
|
|
||||||
InputHTMLAttributes,
|
InputHTMLAttributes,
|
||||||
|
forwardRef,
|
||||||
useRef,
|
useRef,
|
||||||
useState,
|
useState,
|
||||||
} from 'react';
|
} from 'react';
|
||||||
import { useTheme } from '@emotion/react';
|
import { IconComponent, IconEye, IconEyeOff } from 'twenty-ui';
|
||||||
import styled from '@emotion/styled';
|
|
||||||
import { IconAlertCircle, IconComponent, IconEye, IconEyeOff } from 'twenty-ui';
|
|
||||||
|
|
||||||
import { useCombinedRefs } from '~/hooks/useCombinedRefs';
|
import { useCombinedRefs } from '~/hooks/useCombinedRefs';
|
||||||
|
|
||||||
const StyledContainer = styled.div<
|
const StyledContainer = styled.div<
|
||||||
@ -35,10 +34,12 @@ const StyledInputContainer = styled.div`
|
|||||||
`;
|
`;
|
||||||
|
|
||||||
const StyledInput = styled.input<
|
const StyledInput = styled.input<
|
||||||
Pick<TextInputV2ComponentProps, 'fullWidth' | 'LeftIcon'>
|
Pick<TextInputV2ComponentProps, 'fullWidth' | 'LeftIcon' | 'error'>
|
||||||
>`
|
>`
|
||||||
background-color: ${({ theme }) => theme.background.transparent.lighter};
|
background-color: ${({ theme }) => theme.background.transparent.lighter};
|
||||||
border: 1px solid ${({ theme }) => theme.border.color.medium};
|
border: 1px solid
|
||||||
|
${({ theme, error }) =>
|
||||||
|
error ? theme.border.color.danger : theme.border.color.medium};
|
||||||
border-bottom-left-radius: ${({ theme, LeftIcon }) =>
|
border-bottom-left-radius: ${({ theme, LeftIcon }) =>
|
||||||
!LeftIcon && theme.border.radius.sm};
|
!LeftIcon && theme.border.radius.sm};
|
||||||
border-right: none;
|
border-right: none;
|
||||||
@ -86,10 +87,14 @@ const StyledLeftIconContainer = styled.div`
|
|||||||
padding-left: ${({ theme }) => theme.spacing(2)};
|
padding-left: ${({ theme }) => theme.spacing(2)};
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const StyledTrailingIconContainer = styled.div`
|
const StyledTrailingIconContainer = styled.div<
|
||||||
|
Pick<TextInputV2ComponentProps, 'error'>
|
||||||
|
>`
|
||||||
align-items: center;
|
align-items: center;
|
||||||
background-color: ${({ theme }) => theme.background.transparent.lighter};
|
background-color: ${({ theme }) => theme.background.transparent.lighter};
|
||||||
border: 1px solid ${({ theme }) => theme.border.color.medium};
|
border: 1px solid
|
||||||
|
${({ theme, error }) =>
|
||||||
|
error ? theme.border.color.danger : theme.border.color.medium};
|
||||||
border-bottom-right-radius: ${({ theme }) => theme.border.radius.sm};
|
border-bottom-right-radius: ${({ theme }) => theme.border.radius.sm};
|
||||||
border-left: none;
|
border-left: none;
|
||||||
border-top-right-radius: ${({ theme }) => theme.border.radius.sm};
|
border-top-right-radius: ${({ theme }) => theme.border.radius.sm};
|
||||||
@ -191,14 +196,10 @@ const TextInputV2Component = (
|
|||||||
value,
|
value,
|
||||||
LeftIcon,
|
LeftIcon,
|
||||||
maxLength,
|
maxLength,
|
||||||
|
error,
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<StyledTrailingIconContainer>
|
<StyledTrailingIconContainer {...{ error }}>
|
||||||
{error && (
|
|
||||||
<StyledTrailingIcon>
|
|
||||||
<IconAlertCircle size={16} color={theme.color.red} />
|
|
||||||
</StyledTrailingIcon>
|
|
||||||
)}
|
|
||||||
{!error && type === INPUT_TYPE_PASSWORD && (
|
{!error && type === INPUT_TYPE_PASSWORD && (
|
||||||
<StyledTrailingIcon
|
<StyledTrailingIcon
|
||||||
onClick={handleTogglePasswordVisibility}
|
onClick={handleTogglePasswordVisibility}
|
||||||
|
|||||||
@ -38,7 +38,7 @@ import { FieldMetadataType } from '~/generated-metadata/graphql';
|
|||||||
import { isDefined } from '~/utils/isDefined';
|
import { isDefined } from '~/utils/isDefined';
|
||||||
|
|
||||||
type SettingsDataModelFieldEditFormValues = z.infer<
|
type SettingsDataModelFieldEditFormValues = z.infer<
|
||||||
typeof settingsFieldFormSchema
|
ReturnType<typeof settingsFieldFormSchema>
|
||||||
>;
|
>;
|
||||||
|
|
||||||
const StyledSettingsObjectFieldTypeSelect = styled(
|
const StyledSettingsObjectFieldTypeSelect = styled(
|
||||||
@ -93,7 +93,7 @@ export const SettingsObjectFieldEdit = () => {
|
|||||||
|
|
||||||
const formConfig = useForm<SettingsDataModelFieldEditFormValues>({
|
const formConfig = useForm<SettingsDataModelFieldEditFormValues>({
|
||||||
mode: 'onTouched',
|
mode: 'onTouched',
|
||||||
resolver: zodResolver(settingsFieldFormSchema),
|
resolver: zodResolver(settingsFieldFormSchema()),
|
||||||
});
|
});
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|||||||
@ -36,7 +36,7 @@ import { isDefined } from '~/utils/isDefined';
|
|||||||
import { isUndefinedOrNull } from '~/utils/isUndefinedOrNull';
|
import { isUndefinedOrNull } from '~/utils/isUndefinedOrNull';
|
||||||
|
|
||||||
type SettingsDataModelNewFieldFormValues = z.infer<
|
type SettingsDataModelNewFieldFormValues = z.infer<
|
||||||
typeof settingsFieldFormSchema
|
ReturnType<typeof settingsFieldFormSchema>
|
||||||
>;
|
>;
|
||||||
|
|
||||||
const StyledSettingsObjectFieldTypeSelect = styled(
|
const StyledSettingsObjectFieldTypeSelect = styled(
|
||||||
@ -59,7 +59,11 @@ export const SettingsObjectNewFieldStep2 = () => {
|
|||||||
|
|
||||||
const formConfig = useForm<SettingsDataModelNewFieldFormValues>({
|
const formConfig = useForm<SettingsDataModelNewFieldFormValues>({
|
||||||
mode: 'onTouched',
|
mode: 'onTouched',
|
||||||
resolver: zodResolver(settingsFieldFormSchema),
|
resolver: zodResolver(
|
||||||
|
settingsFieldFormSchema(
|
||||||
|
activeObjectMetadataItem?.fields.map((value) => value.name),
|
||||||
|
),
|
||||||
|
),
|
||||||
});
|
});
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@ -169,8 +173,9 @@ export const SettingsObjectNewFieldStep2 = () => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<RecordFieldValueSelectorContextProvider>
|
<RecordFieldValueSelectorContextProvider>
|
||||||
{/* eslint-disable-next-line react/jsx-props-no-spreading */}
|
<FormProvider // eslint-disable-next-line react/jsx-props-no-spreading
|
||||||
<FormProvider {...formConfig}>
|
{...formConfig}
|
||||||
|
>
|
||||||
<SubMenuTopBarContainer Icon={IconSettings} title="Settings">
|
<SubMenuTopBarContainer Icon={IconSettings} title="Settings">
|
||||||
<SettingsPageContainer>
|
<SettingsPageContainer>
|
||||||
<SettingsHeaderContainer>
|
<SettingsHeaderContainer>
|
||||||
|
|||||||
Reference in New Issue
Block a user