fix: phone input validation error should display red borders like email input (#13275)

ISSUE 

- closes #13032

---------

Co-authored-by: Lucas Bordeau <bordeau.lucas@gmail.com>
This commit is contained in:
Nabhag Motivaras
2025-07-23 19:22:41 +05:30
committed by GitHub
parent 186891883e
commit 1e5d2f9b21
3 changed files with 45 additions and 2 deletions

View File

@ -78,6 +78,7 @@ export type MultiItemBaseInputProps = Omit<HTMLInputProps, 'onChange'> & {
onChange: (value: string) => void; onChange: (value: string) => void;
autoFocus: HTMLInputProps['autoFocus']; autoFocus: HTMLInputProps['autoFocus'];
placeholder: HTMLInputProps['placeholder']; placeholder: HTMLInputProps['placeholder'];
hasError?: boolean;
}) => React.ReactNode; }) => React.ReactNode;
error?: string | null; error?: string | null;
hasError?: boolean; hasError?: boolean;
@ -134,6 +135,7 @@ export const MultiItemBaseInput = forwardRef<
onChange, onChange,
autoFocus, autoFocus,
placeholder, placeholder,
hasError,
}) })
) : ( ) : (
<StyledInput <StyledInput

View File

@ -1,5 +1,8 @@
import { usePhonesField } from '@/object-record/record-field/meta-types/hooks/usePhonesField'; import { usePhonesField } from '@/object-record/record-field/meta-types/hooks/usePhonesField';
import { PhonesFieldMenuItem } from '@/object-record/record-field/meta-types/input/components/PhonesFieldMenuItem'; import { PhonesFieldMenuItem } from '@/object-record/record-field/meta-types/input/components/PhonesFieldMenuItem';
import { recordFieldInputIsFieldInErrorComponentState } from '@/object-record/record-field/states/recordFieldInputIsFieldInErrorComponentState';
import { phoneSchema } from '@/object-record/record-field/validation-schemas/phoneSchema';
import { useSetRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentStateV2';
import styled from '@emotion/styled'; import styled from '@emotion/styled';
import { E164Number, parsePhoneNumber } from 'libphonenumber-js'; import { E164Number, parsePhoneNumber } from 'libphonenumber-js';
import ReactPhoneNumberInput from 'react-phone-number-input'; import ReactPhoneNumberInput from 'react-phone-number-input';
@ -19,6 +22,7 @@ export const DEFAULT_PHONE_CALLING_CODE = '1';
const StyledCustomPhoneInputContainer = styled.div<{ const StyledCustomPhoneInputContainer = styled.div<{
hasItem: boolean; hasItem: boolean;
hasError?: boolean;
}>` }>`
${({ hasItem, theme }) => ${({ hasItem, theme }) =>
hasItem && hasItem &&
@ -28,6 +32,13 @@ const StyledCustomPhoneInputContainer = styled.div<{
border: 1px solid ${theme.border.color.medium}; border: 1px solid ${theme.border.color.medium};
height: 30px; height: 30px;
`} `}
${({ hasError, hasItem, theme }) =>
hasError &&
hasItem &&
css`
border: 1px solid ${theme.border.color.danger};
`}
`; `;
const StyledCustomPhoneInput = styled(ReactPhoneNumberInput)` 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) => const getShowPrimaryIcon = (index: number) =>
index === 0 && phones.length > 1; index === 0 && phones.length > 1;
const getShowSetAsPrimaryButton = (index: number) => index > 0; const getShowSetAsPrimaryButton = (index: number) => index > 0;
const setIsFieldInError = useSetRecoilComponentStateV2(
recordFieldInputIsFieldInErrorComponentState,
);
const handleError = (hasError: boolean, values: any[]) => {
setIsFieldInError(hasError && values.length === 0);
};
return ( return (
<MultiItemFieldInput <MultiItemFieldInput
items={phones} items={phones}
@ -105,6 +129,7 @@ export const PhonesFieldInput = ({
onCancel={onCancel} onCancel={onCancel}
placeholder="Phone" placeholder="Phone"
fieldMetadataType={FieldMetadataType.PHONES} fieldMetadataType={FieldMetadataType.PHONES}
validateInput={validateInput}
formatInput={(input) => { formatInput={(input) => {
const phone = parsePhoneNumber(input); const phone = parsePhoneNumber(input);
if (phone !== undefined) { if (phone !== undefined) {
@ -138,9 +163,12 @@ export const PhonesFieldInput = ({
onDelete={handleDelete} onDelete={handleDelete}
/> />
)} )}
renderInput={({ value, onChange, autoFocus, placeholder }) => { renderInput={({ value, onChange, autoFocus, placeholder, hasError }) => {
return ( return (
<StyledCustomPhoneInputContainer hasItem={!!phones.length}> <StyledCustomPhoneInputContainer
hasItem={!!phones.length}
hasError={hasError}
>
<StyledCustomPhoneInput <StyledCustomPhoneInput
autoFocus={autoFocus} autoFocus={autoFocus}
placeholder={placeholder} placeholder={placeholder}
@ -154,6 +182,7 @@ export const PhonesFieldInput = ({
</StyledCustomPhoneInputContainer> </StyledCustomPhoneInputContainer>
); );
}} }}
onError={handleError}
/> />
); );
}; };

View File

@ -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;
}
});