diff --git a/packages/twenty-front/src/modules/object-record/record-field/meta-types/input/components/MultiItemBaseInput.tsx b/packages/twenty-front/src/modules/object-record/record-field/meta-types/input/components/MultiItemBaseInput.tsx index 88cec276ebe1..9df21b0e2ab1 100644 --- a/packages/twenty-front/src/modules/object-record/record-field/meta-types/input/components/MultiItemBaseInput.tsx +++ b/packages/twenty-front/src/modules/object-record/record-field/meta-types/input/components/MultiItemBaseInput.tsx @@ -78,6 +78,7 @@ export type MultiItemBaseInputProps = Omit & { onChange: (value: string) => void; autoFocus: HTMLInputProps['autoFocus']; placeholder: HTMLInputProps['placeholder']; + hasError?: boolean; }) => React.ReactNode; error?: string | null; hasError?: boolean; @@ -134,6 +135,7 @@ export const MultiItemBaseInput = forwardRef< onChange, autoFocus, placeholder, + hasError, }) ) : ( ` ${({ hasItem, theme }) => hasItem && @@ -28,6 +32,13 @@ const StyledCustomPhoneInputContainer = styled.div<{ border: 1px solid ${theme.border.color.medium}; height: 30px; `} + + ${({ hasError, hasItem, theme }) => + hasError && + hasItem && + css` + border: 1px solid ${theme.border.color.danger}; + `} `; const StyledCustomPhoneInput = styled(ReactPhoneNumberInput)` @@ -93,10 +104,23 @@ export const PhonesFieldInput = ({ }); }; + const validateInput = (input: string) => ({ + isValid: phoneSchema.safeParse(input).success, + errorMessage: '', + }); + const getShowPrimaryIcon = (index: number) => index === 0 && phones.length > 1; const getShowSetAsPrimaryButton = (index: number) => index > 0; + const setIsFieldInError = useSetRecoilComponentStateV2( + recordFieldInputIsFieldInErrorComponentState, + ); + + const handleError = (hasError: boolean, values: any[]) => { + setIsFieldInError(hasError && values.length === 0); + }; + return ( { const phone = parsePhoneNumber(input); if (phone !== undefined) { @@ -138,9 +163,12 @@ export const PhonesFieldInput = ({ onDelete={handleDelete} /> )} - renderInput={({ value, onChange, autoFocus, placeholder }) => { + renderInput={({ value, onChange, autoFocus, placeholder, hasError }) => { return ( - + ); }} + onError={handleError} /> ); }; diff --git a/packages/twenty-front/src/modules/object-record/record-field/validation-schemas/phoneSchema.ts b/packages/twenty-front/src/modules/object-record/record-field/validation-schemas/phoneSchema.ts new file mode 100644 index 000000000000..a94f4343d78b --- /dev/null +++ b/packages/twenty-front/src/modules/object-record/record-field/validation-schemas/phoneSchema.ts @@ -0,0 +1,12 @@ +import { parsePhoneNumberWithError } from 'libphonenumber-js'; +import { z } from 'zod'; + +export const phoneSchema = z.string().refine((value) => { + if (!value || value.trim() === '') return false; + try { + const phone = parsePhoneNumberWithError(value); + return phone.isValid(); + } catch { + return false; + } +});