Skip to content

Commit

Permalink
Adds regex validation for PhoneNumberFormField same as in backend a…
Browse files Browse the repository at this point in the history
…nd validate `onBlur` instead of `onSubmit` (#5789)

* Adds custom as you type validation for `PhoneNumberFormField`

* validation for support numbers

* fix priority

* validate onBlur

* improved validation and formatting

* skip formatting if country disabled

* fix length

* fix

* fix cypress
  • Loading branch information
rithviknishad authored and Ashesh3 committed Jul 12, 2023
1 parent a70cb41 commit 4fffe36
Show file tree
Hide file tree
Showing 2 changed files with 95 additions and 10 deletions.
41 changes: 37 additions & 4 deletions src/Components/Form/FieldValidators.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,17 +29,50 @@ export const MultiValidator = <T,>(
return validator;
};

export const AnyValidator = <T,>(
validators: FieldValidator<T>[]
): FieldValidator<T> => {
const validator = (value: T) => {
for (const validate of validators) {
const error = validate(value);
if (!error) return;
}
return validators[0](value);
};
return validator;
};

export const RequiredFieldValidator = (message = "Field is required") => {
return <T,>(value: T): FieldError => {
if (!value) return message;
if (Array.isArray(value) && value.length === 0) return message;
};
};

export const EmailValidator = (message = "Invalid email address") => {
export const RegexValidator = (regex: RegExp, message = "Invalid input") => {
return (value: string): FieldError => {
const pattern =
/^(([^<>()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;
return pattern.test(value) ? undefined : message;
return regex.test(value) ? undefined : message;
};
};

const EMAIL_REGEX =
/^(([^<>()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;

export const EmailValidator = (message = "Invalid email address") => {
return RegexValidator(EMAIL_REGEX, message);
};

const PHONE_NUMBER_REGEX =
/^(?:(?:(?:\+|0{0,2})91|0{0,2})(?:\()?\d{3}(?:\))?[-]?\d{3}[-]?\d{4})$/;

export const PhoneNumberValidator = (message = "Invalid phone number") => {
return RegexValidator(PHONE_NUMBER_REGEX, message);
};

const SUPPORT_PHONE_NUMBER_REGEX = /^1800[-]?\d{3}[-]?\d{3,4}$/;

export const SupportPhoneNumberValidator = (
message = "Invalid support phone number"
) => {
return RegexValidator(SUPPORT_PHONE_NUMBER_REGEX, message);
};
64 changes: 58 additions & 6 deletions src/Components/Form/FormFields/PhoneNumberFormField.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,19 @@
import { FormFieldBaseProps, useFormFieldPropsResolver } from "./Utils";
import FormField from "./FormField";
import { AsYouType } from "libphonenumber-js";
import { useMemo } from "react";
import {
AsYouType,
isValidPhoneNumber,
parsePhoneNumber,
} from "libphonenumber-js";
import { useMemo, useState } from "react";
import { classNames } from "../../../Utils/utils";
import phoneCodesJson from "../../../Common/static/countryPhoneAndFlags.json";
import {
AnyValidator,
FieldError,
PhoneNumberValidator,
SupportPhoneNumberValidator,
} from "../FieldValidators";

interface CountryData {
flag: string;
Expand All @@ -17,10 +27,12 @@ interface Props extends FormFieldBaseProps<string> {
placeholder?: string;
autoComplete?: string;
disableCountry?: boolean;
disableValidation?: boolean;
}

export default function PhoneNumberFormField(props: Props) {
const field = useFormFieldPropsResolver(props as any);
const [error, setError] = useState<FieldError>();

const asYouType = useMemo(() => {
const asYouType = new AsYouType();
Expand All @@ -37,14 +49,40 @@ export default function PhoneNumberFormField(props: Props) {
return asYouType;
}, []);

const validate = useMemo(
() => (value: string | undefined, event: "blur" | "change") => {
if (!value || props.disableValidation) {
return;
}

const newError = AnyValidator([
PhoneNumberValidator(),
SupportPhoneNumberValidator(),
])(value);

if (!newError) {
return;
} else if (event === "blur") {
return newError;
}
},
[props.disableValidation]
);

const setValue = (value: string) => {
value = value.replaceAll(" ", "");

asYouType.reset();
asYouType.input(value);

const error = validate(value, "change");
field.handleChange(value);

setError(error);
};

return (
<FormField field={field}>
<FormField field={{ ...field, error: field.error || error }}>
<div className="relative rounded-md shadow-sm">
<input
type="tel"
Expand All @@ -58,9 +96,10 @@ export default function PhoneNumberFormField(props: Props) {
)}
maxLength={field.value?.startsWith("1800") ? 11 : 15}
placeholder={props.placeholder}
value={field.value}
value={formatPhoneNumber(field.value, props.disableCountry)}
onChange={(e) => setValue(e.target.value)}
disabled={field.disabled}
onBlur={() => setError(validate(field.value, "blur"))}
/>
{!props.disableCountry && (
<div className="absolute inset-y-0 right-0 flex items-center">
Expand All @@ -83,9 +122,9 @@ export default function PhoneNumberFormField(props: Props) {
setValue(conditionPhoneCode(phoneCodes[e.target.value].code));
}}
>
{Object.entries(phoneCodes).map(([country, { flag, code }]) => (
{Object.entries(phoneCodes).map(([country, { flag }]) => (
<option key={country} value={country}>
{flag} {conditionPhoneCode(code)}
{flag} {country}
</option>
))}
<option value="Other">Other</option>
Expand All @@ -102,3 +141,16 @@ const conditionPhoneCode = (code: string) => {
code = code.split(" ")[0];
return code.startsWith("+") ? code : "+" + code;
};

const formatPhoneNumber = (value: string, disableCountry?: boolean) => {
if (value === undefined || value === null) {
return disableCountry ? "" : "+91 ";
}

if (!isValidPhoneNumber(value) || disableCountry) {
return value;
}

const phoneNumber = parsePhoneNumber(value);
return phoneNumber.formatInternational();
};

0 comments on commit 4fffe36

Please sign in to comment.