Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ORV2-3042 - contact info field validation #1671

Open
wants to merge 6 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { useFormContext } from "react-hook-form";
import "./PhoneNumberInput.scss";
import { ORBC_FormTypes } from "../../../types/common";
import { CustomOutlinedInputProps } from "./CustomOutlinedInput";
import { getFormattedPhoneNumber } from "../../../helpers/phone/getFormattedPhoneNumber";

/**
* An onRouteBC customized MUI OutlineInput component
Expand All @@ -16,7 +17,7 @@ export const PhoneNumberInput = <T extends ORBC_FormTypes>(

// Everytime the user types, update the format of the users input
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
const formattedValue = formatPhoneNumber(e.target.value);
const formattedValue = getFormattedPhoneNumber(e.target.value);
setValue<string>(props.name, formattedValue, { shouldValidate: true });
};

Expand All @@ -33,42 +34,3 @@ export const PhoneNumberInput = <T extends ORBC_FormTypes>(
/>
);
};

/**
* Function to format the users input to be in te correct phone number format
* as the user types
*/
export const formatPhoneNumber = (input?: string): string => {
if (!input) return "";
// only allows 0-9 inputs
const currentValue = input.replace(/[^\d]/g, "");
const cvLength = currentValue.length;

// Ignore formatting if the value length is greater than a standard Canada/US phone number
// (11 digits incl. country code)
if (cvLength > 11) {
return currentValue;
}
// returns: "x ",
if (cvLength < 1) return currentValue;

// returns: "x", "xx", "xxx"
if (cvLength < 4) return `${currentValue.slice(0, 3)}`;

// returns: "(xxx)", "(xxx) x", "(xxx) xx", "(xxx) xxx",
if (cvLength < 7)
return `(${currentValue.slice(0, 3)}) ${currentValue.slice(3)}`;

// returns: "(xxx) xxx-", "(xxx) xxx-x", "(xxx) xxx-xx", "(xxx) xxx-xxx", "(xxx) xxx-xxxx"
if (cvLength < 11)
return `(${currentValue.slice(0, 3)}) ${currentValue.slice(
3,
6,
)}-${currentValue.slice(6, 10)}`;

// returns: "+x (xxx) xxx-xxxx"
return `+${currentValue.slice(0, 1)} (${currentValue.slice(
1,
4,
)}) ${currentValue.slice(4, 7)}-${currentValue.slice(7, 11)}`;
};
7 changes: 7 additions & 0 deletions frontend/src/common/helpers/numeric/filterNonDigits.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
/**
* Filter out any non-digit characters from a string.
* @param input Any string input
* @returns String containing only digits
*/
export const filterNonDigits = (input: string) =>
input.replace(/[^0-9]/g, "");
49 changes: 49 additions & 0 deletions frontend/src/common/helpers/phone/getFormattedPhoneNumber.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import { Nullable } from "../../types/common";
import { filterNonDigits } from "../numeric/filterNonDigits";

/**
* Get the formatted phone number from a provided phone number string.
* @param input Inputted string that could contain phone number
* @returns Formatted phone number
*/
export const getFormattedPhoneNumber = (input?: Nullable<string>): string => {
if (!input) return "";

// Only accept digits as part of phone numbers
const parsedPhoneDigits: string = filterNonDigits(input);
const phoneDigitsLength = parsedPhoneDigits.length;

// Ignore formatting if the value length is greater than a standard Canada/US phone number
// (11 digits including country code)
if (phoneDigitsLength > 11) {
return parsedPhoneDigits;
}

// If there are no digits in the resulting parsed phone number, return ""
if (phoneDigitsLength < 1) return parsedPhoneDigits;

// If there are 1-3 digits in the parsed phone number, return them as is
// ie. "x", "xx", or "xxx"
if (phoneDigitsLength < 4) return `${parsedPhoneDigits.slice(0, 3)}`;

// If there are 4-6 digits in the parsed phone number, return the first 3 digits as area code (in brackets followed by space)
// followed by the rest of the digits as just digits with no formatting
// ie. "(xxx) x", "(xxx) xx", "(xxx) xxx",
if (phoneDigitsLength < 7)
return `(${parsedPhoneDigits.slice(0, 3)}) ${parsedPhoneDigits.slice(3)}`;

// If there are 7-10 digits, return the first 6 digits based on the above formatting rules,
// followed by a dash and the remaining digits will be unformatted
// ie. "(xxx) xxx-x", "(xxx) xxx-xx", "(xxx) xxx-xxx", "(xxx) xxx-xxxx"
if (phoneDigitsLength < 11)
return `(${parsedPhoneDigits.slice(0, 3)}) ${parsedPhoneDigits.slice(
3,
6,
)}-${parsedPhoneDigits.slice(6, 10)}`;

// With exactly 11 digits, format the phone number like this: "+x (xxx) xxx-xxxx"
return `+${parsedPhoneDigits.slice(0, 1)} (${parsedPhoneDigits.slice(
1,
4,
)}) ${parsedPhoneDigits.slice(4, 7)}-${parsedPhoneDigits.slice(7, 11)}`;
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { Nullable } from "../../types/common";
import { validatePhoneNumber } from "./validatePhoneNumber";

/**
* Validate an optional phone number.
* @param phone Provided phone number, if any
* @returns true if phone number is valid or empty, error message otherwise
*/
export const validateOptionalPhoneNumber = (phone?: Nullable<string>) => {
if (!phone) return true; // phone number is optional, so empty is accepted

return validatePhoneNumber(phone);
};
16 changes: 16 additions & 0 deletions frontend/src/common/helpers/phone/validatePhoneExtension.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { Nullable } from "../../types/common";
import { invalidExtension, invalidExtensionLength } from "../validationMessages";

/**
* Validate optional phone extension.
* @param ext Provided phone extension, if any
* @returns true if phone extension is valid, error message otherwise
*/
export const validatePhoneExtension = (ext?: Nullable<string>) => {
if (!ext) return true; // empty or not-provided phone extension is acceptable

if (ext.length > 5) return invalidExtensionLength(5);

// Must have exactly 1-5 digits
return /^[0-9]{1,5}$/.test(ext) || invalidExtension();
};
16 changes: 16 additions & 0 deletions frontend/src/common/helpers/phone/validatePhoneNumber.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { filterNonDigits } from "../numeric/filterNonDigits";
import { invalidPhoneLength } from "../validationMessages";

/**
* Validate phone number.
* @param phone Provided phone number to validate
* @returns true if phone number is valid, otherwise returns error message
*/
export const validatePhoneNumber = (phone: string) => {
const filteredPhone = filterNonDigits(phone);
return (
(filteredPhone.length >= 10 &&
filteredPhone.length <= 20) ||
invalidPhoneLength(10, 20)
);
};
2 changes: 2 additions & 0 deletions frontend/src/common/helpers/validationMessages.ts
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,8 @@ export const invalidPhoneLength = (min: number, max: number) => {
return replacePlaceholders(messageTemplate, placeholders, min, max);
};

export const invalidExtension = () => validationMessages.extension.defaultMessage;

export const invalidExtensionLength = (max: number) => {
const { messageTemplate, placeholders } = validationMessages.extension.length;
return replacePlaceholders(messageTemplate, placeholders, max);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,20 +12,22 @@ import {

import "./PermitResendDialog.scss";
import { getDefaultRequiredVal } from "../../../../common/helpers/util";
import { Optional } from "../../../../common/types/common";
import { validateOptionalPhoneNumber } from "../../../../common/helpers/phone/validateOptionalPhoneNumber";
import {
invalidPhoneLength,
requiredMessage,
selectionRequired,
} from "../../../../common/helpers/validationMessages";

import {
CustomFormComponent,
getErrorMessage,
} from "../../../../common/components/form/CustomFormComponents";

import {
EMAIL_NOTIFICATION_TYPES,
EmailNotificationType,
} from "../../../permits/types/EmailNotificationType";
import { Optional } from "../../../../common/types/common";

interface PermitResendFormData {
permitId: string;
Expand Down Expand Up @@ -227,14 +229,9 @@ export default function PermitResendDialog({
rules: {
required: false,
validate: {
validateFax: (fax?: string) =>
fax == null ||
fax === "" ||
(fax != null &&
fax !== "" &&
unformatFax(fax).length >= 10 &&
unformatFax(fax).length <= 11) ||
invalidPhoneLength(10, 11),
validateFax: (fax?: string) => {
return validateOptionalPhoneNumber(fax);
},
},
},
}}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,15 +1,17 @@
import isEmail from "validator/lib/isEmail";

import { CustomFormComponent } from "../../../../../common/components/form/CustomFormComponents";
import { CountryAndProvince } from "../../../../../common/components/form/CountryAndProvince";
import { validatePhoneNumber } from "../../../../../common/helpers/phone/validatePhoneNumber";
import { validateOptionalPhoneNumber } from "../../../../../common/helpers/phone/validateOptionalPhoneNumber";
import { validatePhoneExtension } from "../../../../../common/helpers/phone/validatePhoneExtension";
import {
invalidCityLength,
invalidEmail,
invalidExtensionLength,
invalidFirstNameLength,
invalidLastNameLength,
invalidPhoneLength,
requiredMessage,
} from "../../../../../common/helpers/validationMessages";
import { CountryAndProvince } from "../../../../../common/components/form/CountryAndProvince";

/**
* Reusable form for editing user information.
Expand Down Expand Up @@ -42,6 +44,7 @@ export const ReusableUserInfoForm = ({
}}
className="my-info-form__input"
/>

<CustomFormComponent
type="input"
feature={FEATURE}
Expand All @@ -59,6 +62,7 @@ export const ReusableUserInfoForm = ({
}}
className="my-info-form__input"
/>

<CustomFormComponent
type="input"
feature={FEATURE}
Expand All @@ -75,6 +79,7 @@ export const ReusableUserInfoForm = ({
}}
className="my-info-form__input"
/>

<div className="side-by-side-inputs">
<CustomFormComponent
type="phone"
Expand All @@ -84,15 +89,14 @@ export const ReusableUserInfoForm = ({
rules: {
required: { value: true, message: requiredMessage() },
validate: {
validatePhone1: (phone: string) =>
(phone.length >= 10 && phone.length <= 20) ||
invalidPhoneLength(10, 20),
validatePhone1: validatePhoneNumber,
},
},
label: "Primary Phone",
}}
className="my-info-form__input my-info-form__input--left"
/>

<CustomFormComponent
type="ext"
feature={FEATURE}
Expand All @@ -102,17 +106,15 @@ export const ReusableUserInfoForm = ({
required: false,
validate: {
validateExt1: (ext?: string) =>
ext == null ||
ext === "" ||
(ext != null && ext !== "" && ext.length <= 5) ||
invalidExtensionLength(5),
validatePhoneExtension(ext),
},
},
label: "Ext",
}}
className="my-info-form__input my-info-form__input--right"
/>
</div>

<div className="side-by-side-inputs">
<CustomFormComponent
type="phone"
Expand All @@ -122,20 +124,16 @@ export const ReusableUserInfoForm = ({
rules: {
required: false,
validate: {
validatePhone2: (phone2?: string) =>
phone2 == null ||
phone2 === "" ||
(phone2 != null &&
phone2 !== "" &&
phone2.length >= 10 &&
phone2.length <= 20) ||
invalidPhoneLength(10, 20),
validatePhone2: (phone?: string) => {
return validateOptionalPhoneNumber(phone);
},
},
},
label: "Alternate Phone",
}}
className="my-info-form__input my-info-form__input--left"
/>

<CustomFormComponent
type="ext"
feature={FEATURE}
Expand All @@ -145,17 +143,15 @@ export const ReusableUserInfoForm = ({
required: false,
validate: {
validateExt2: (ext?: string) =>
ext == null ||
ext === "" ||
(ext != null && ext !== "" && ext.length <= 5) ||
invalidExtensionLength(5),
validatePhoneExtension(ext),
},
},
label: "Ext",
}}
className="my-info-form__input my-info-form__input--right"
/>
</div>

<CustomFormComponent
type="phone"
feature={FEATURE}
Expand All @@ -164,26 +160,23 @@ export const ReusableUserInfoForm = ({
rules: {
required: false,
validate: {
validateFax: (fax?: string) =>
fax == null ||
fax === "" ||
(fax != null &&
fax !== "" &&
fax.length >= 10 &&
fax.length <= 20) ||
invalidPhoneLength(10, 20),
validateFax: (fax?: string) => {
return validateOptionalPhoneNumber(fax);
},
},
},
label: "Fax",
}}
className="my-info-form__input my-info-form__input--left"
/>

<CountryAndProvince
feature={FEATURE}
countryField="countryCode"
provinceField="provinceCode"
width="100%"
/>

<CustomFormComponent
type="input"
feature={FEATURE}
Expand Down
Loading
Loading