fix: validate emails in record-fields (#7245)

fix: #7149 

Introduced a minimal field validation framework for record-fields.
Currently only shows errors for email field.

<img width="350" alt="image"
src="https://github.com/user-attachments/assets/1a1fa790-71a4-4764-a791-9878be3274f1">
<img width="347" alt="image"
src="https://github.com/user-attachments/assets/e22d24f2-d1a7-4303-8c41-7aac3cde9ce8">

---------

Co-authored-by: sid0-0 <a@b.com>
Co-authored-by: bosiraphael <raphael.bosi@gmail.com>
Co-authored-by: Félix Malfait <felix@twenty.com>
This commit is contained in:
sid0-0
2024-10-03 22:25:29 +05:30
committed by GitHub
parent 04579144ca
commit a946c6a33d
5 changed files with 87 additions and 31 deletions

View File

@ -1,6 +1,7 @@
import { useEmailsField } from '@/object-record/record-field/meta-types/hooks/useEmailsField';
import { EmailsFieldMenuItem } from '@/object-record/record-field/meta-types/input/components/EmailsFieldMenuItem';
import { useMemo } from 'react';
import { emailSchema } from '@/object-record/record-field/validation-schemas/emailSchema';
import { useCallback, useMemo } from 'react';
import { isDefined } from 'twenty-ui';
import { FieldMetadataType } from '~/generated-metadata/graphql';
import { MultiItemFieldInput } from './MultiItemFieldInput';
@ -29,6 +30,14 @@ export const EmailsFieldInput = ({ onCancel }: EmailsFieldInputProps) => {
});
};
const validateInput = useCallback(
(input: string) => ({
isValid: emailSchema.safeParse(input).success,
errorMessage: '',
}),
[],
);
const isPrimaryEmail = (index: number) => index === 0 && emails?.length > 1;
return (
@ -38,6 +47,7 @@ export const EmailsFieldInput = ({ onCancel }: EmailsFieldInputProps) => {
onCancel={onCancel}
placeholder="Email"
fieldMetadataType={FieldMetadataType.Emails}
validateInput={validateInput}
renderItem={({
value: email,
index,

View File

@ -51,7 +51,10 @@ export const LinksFieldInput = ({ onCancel }: LinksFieldInputProps) => {
onCancel={onCancel}
placeholder="URL"
fieldMetadataType={FieldMetadataType.Links}
validateInput={(input) => absoluteUrlSchema.safeParse(input).success}
validateInput={(input) => ({
isValid: absoluteUrlSchema.safeParse(input).success,
errorMessage: '',
})}
formatInput={(input) => ({ url: input, label: '' })}
renderItem={({
value: link,

View File

@ -30,7 +30,7 @@ type MultiItemFieldInputProps<T> = {
onPersist: (updatedItems: T[]) => void;
onCancel?: () => void;
placeholder: string;
validateInput?: (input: string) => boolean;
validateInput?: (input: string) => { isValid: boolean; errorMessage: string };
formatInput?: (input: string) => T;
renderItem: (props: {
value: T;
@ -74,8 +74,21 @@ export const MultiItemFieldInput = <T,>({
const [isInputDisplayed, setIsInputDisplayed] = useState(false);
const [inputValue, setInputValue] = useState('');
const [itemToEditIndex, setItemToEditIndex] = useState(-1);
const [errorData, setErrorData] = useState({
isValid: true,
errorMessage: '',
});
const isAddingNewItem = itemToEditIndex === -1;
const handleOnChange = (value: string) => {
setInputValue(value);
if (!validateInput) return;
if (errorData.isValid) {
setErrorData(errorData);
}
};
const handleAddButtonClick = () => {
setItemToEditIndex(-1);
setIsInputDisplayed(true);
@ -105,7 +118,13 @@ export const MultiItemFieldInput = <T,>({
};
const handleSubmitInput = () => {
if (validateInput !== undefined && !validateInput(inputValue)) return;
if (validateInput !== undefined) {
const validationData = validateInput(inputValue) ?? { isValid: true };
if (!validationData.isValid) {
setErrorData(validationData);
return;
}
}
const newItem = formatInput
? formatInput(inputValue)
@ -160,6 +179,7 @@ export const MultiItemFieldInput = <T,>({
placeholder={placeholder}
value={inputValue}
hotkeyScope={hotkeyScope}
hasError={!errorData.isValid}
renderInput={
renderInput
? (props) =>
@ -170,7 +190,7 @@ export const MultiItemFieldInput = <T,>({
})
: undefined
}
onChange={(event) => setInputValue(event.target.value)}
onChange={(event) => handleOnChange(event.target.value)}
onEnter={handleSubmitInput}
rightComponent={
<LightIconButton

View File

@ -0,0 +1,3 @@
import { z } from 'zod';
export const emailSchema = z.string().email();