Skip to content
This repository has been archived by the owner on Feb 23, 2024. It is now read-only.

Add client side postcode validation #8503

Merged
merged 28 commits into from
Mar 14, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
591f4d9
Add client side postcode validation
nielslange Feb 22, 2023
4c53bb8
Prevent server-side validation
nielslange Feb 22, 2023
4dbfb7c
Adjust translation
nielslange Feb 23, 2023
779a51c
Only validate postcode if country is available
nielslange Feb 23, 2023
fb437fd
Specify return type of isPostcode()
nielslange Feb 23, 2023
8028cce
Convert function to static variable set
nielslange Feb 23, 2023
e1b4470
Merge branch 'trunk' into add/7722-add-client-side-postcode-validation
nielslange Feb 23, 2023
711bddd
Merge branch 'trunk' into add/7722-add-client-side-postcode-validation
nielslange Feb 24, 2023
f7c6cc1
Refactor <ValidatedTextInput> for postcode validation
nielslange Feb 24, 2023
59be85f
Refactor customValidationHandler
nielslange Feb 24, 2023
da07670
Merge branch 'trunk' into add/7722-add-client-side-postcode-validation
nielslange Feb 27, 2023
08a1b63
Merge branch 'trunk' into add/7722-add-client-side-postcode-validation
nielslange Mar 1, 2023
a1b0146
Merge branch 'trunk' into add/7722-add-client-side-postcode-validation
nielslange Mar 2, 2023
c754683
Merge branch 'trunk' into add/7722-add-client-side-postcode-validation
nielslange Mar 6, 2023
d9d17fb
Merge branch 'trunk' into add/7722-add-client-side-postcode-validation
nielslange Mar 6, 2023
a6077cd
Merge branch 'trunk' into add/7722-add-client-side-postcode-validation
nielslange Mar 7, 2023
b6dca6f
Use customValidationHandler as intermediate function
nielslange Mar 7, 2023
c543789
Merge branch 'trunk' into add/7722-add-client-side-postcode-validation
nielslange Mar 7, 2023
6db40a8
Merge branch 'trunk' into add/7722-add-client-side-postcode-validation
nielslange Mar 8, 2023
0902665
Hyphenate file names
nielslange Mar 8, 2023
36eb53b
Merge branch 'trunk' into add/7722-add-client-side-postcode-validation
nielslange Mar 8, 2023
7945760
Update packages/checkout/utils/validation/is-postcode.ts
nielslange Mar 8, 2023
4b39785
Merge branch 'trunk' into add/7722-add-client-side-postcode-validation
nielslange Mar 8, 2023
370263f
Merge branch 'trunk' into add/7722-add-client-side-postcode-validation
nielslange Mar 9, 2023
c849572
Normalise postcode on input
nielslange Mar 10, 2023
de8ee24
Merge branch 'trunk' into add/7722-add-client-side-postcode-validation
nielslange Mar 10, 2023
3cc951b
Merge branch 'trunk' into add/7722-add-client-side-postcode-validation
nielslange Mar 14, 2023
c29f8b8
Fix usage of out of date value from input field
mikejolley Mar 14, 2023
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
@@ -1,7 +1,7 @@
/**
* External dependencies
*/
import { ValidatedTextInput } from '@woocommerce/blocks-checkout';
import { ValidatedTextInput, isPostcode } from '@woocommerce/blocks-checkout';
import {
BillingCountryInput,
ShippingCountryInput,
Expand Down Expand Up @@ -144,6 +144,35 @@ const AddressForm = ( {

id = id || instanceId;

/**
* Custom validation handler for fields with field specific handling.
*/
const customValidationHandler = (
inputObject: HTMLInputElement,
field: string,
customValues: {
country: string;
}
): boolean => {
if (
field === 'postcode' &&
customValues.country &&
! isPostcode( {
postcode: inputObject.value,
country: customValues.country,
} )
) {
inputObject.setCustomValidity(
__(
'Please enter a valid postcode',
'woo-gutenberg-products-block'
)
);
return false;
}
return true;
};

return (
<div id={ id } className="wc-block-components-address-form">
{ addressFormFields.map( ( field ) => {
Expand Down Expand Up @@ -229,9 +258,19 @@ const AddressForm = ( {
onChange={ ( newValue: string ) =>
onChange( {
...values,
[ field.key ]: newValue,
[ field.key ]:
field.key === 'postcode'
? newValue.trimStart().toUpperCase()
: newValue,
Comment on lines +262 to +264
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When the input field is postcode, then remove leading spaces and convert lowercase to uppercase characters on input. This part ensures that the client-side postcode validation of WooCommerce Blocks, is in sync with the server-side postcode validation of WooCommerce.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@mikejolley Based on our internal discussion, I've updated the PR. Would you mind reviewing it again?

} )
}
customValidation={ ( inputObject: HTMLInputElement ) =>
customValidationHandler(
inputObject,
field.key,
values
)
}
errorMessage={ field.errorMessage }
required={ field.required }
/>
Expand Down
2 changes: 1 addition & 1 deletion assets/js/base/components/combobox/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ export interface ComboboxProps {
autoComplete?: string;
className?: string;
errorId: string | null;
errorMessage?: string;
errorMessage?: string | undefined;
nielslange marked this conversation as resolved.
Show resolved Hide resolved
id: string;
instanceId?: string;
label: string;
Expand Down
2 changes: 1 addition & 1 deletion assets/js/base/components/state-input/StateInputProps.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ export interface StateInputProps {
country: string;
onChange: ( value: string ) => void;
required?: boolean;
errorMessage?: string;
errorMessage?: string | undefined;
errorId?: string;
}

Expand Down
11 changes: 11 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -251,6 +251,7 @@
"dompurify": "^2.4.0",
"downshift": "6.1.7",
"html-react-parser": "3.0.4",
"postcode-validator": "3.7.0",
"preact": "^10.11.3",
"react-number-format": "4.9.3",
"reakit": "1.3.11",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
/**
* External dependencies
*/
import { __, sprintf } from '@wordpress/i18n';

/**
* Converts an input's validityState to a string to display on the frontend.
*
* This returns custom messages for invalid/required fields. Other error types use defaults from the browser (these
* could be implemented in the future but are not currently used by the block checkout).
*/
const getValidityMessageForInput = (
label: string,
inputElement: HTMLInputElement
): string => {
const { valid, customError, valueMissing, badInput, typeMismatch } =
inputElement.validity;

// No errors, or custom error - return early.
if ( valid || customError ) {
return inputElement.validationMessage;
}

const invalidFieldMessage = sprintf(
/* translators: %s field label */
__( 'Please enter a valid %s', 'woo-gutenberg-products-block' ),
label.toLowerCase()
);

if ( valueMissing || badInput || typeMismatch ) {
return invalidFieldMessage;
}

return inputElement.validationMessage || invalidFieldMessage;
};

export default getValidityMessageForInput;
61 changes: 3 additions & 58 deletions packages/checkout/utils/validation/index.ts
Original file line number Diff line number Diff line change
@@ -1,58 +1,3 @@
/**
* External dependencies
*/
import { __, sprintf } from '@wordpress/i18n';

/**
* Ensures that a given value contains a string, or throws an error.
*/
export const mustContain = (
value: string,
requiredValue: string
): true | never => {
if ( ! value.includes( requiredValue ) ) {
throw Error(
sprintf(
/* translators: %1$s value passed to filter, %2$s : value that must be included. */
__(
'Returned value must include %1$s, you passed "%2$s"',
'woo-gutenberg-products-block'
),
requiredValue,
value
)
);
}
return true;
};

/**
* Converts an input's validityState to a string to display on the frontend.
*
* This returns custom messages for invalid/required fields. Other error types use defaults from the browser (these
* could be implemented in the future but are not currently used by the block checkout).
*/
export const getValidityMessageForInput = (
label: string,
inputElement: HTMLInputElement
): string => {
const { valid, customError, valueMissing, badInput, typeMismatch } =
inputElement.validity;

// No errors, or custom error - return early.
if ( valid || customError ) {
return inputElement.validationMessage;
}

const invalidFieldMessage = sprintf(
/* translators: %s field label */
__( 'Please enter a valid %s', 'woo-gutenberg-products-block' ),
label.toLowerCase()
);

if ( valueMissing || badInput || typeMismatch ) {
return invalidFieldMessage;
}

return inputElement.validationMessage || invalidFieldMessage;
};
export { default as mustContain } from './must-contain';
export { default as getValidityMessageForInput } from './get-validity-message-for-input';
export { default as isPostcode } from './is-postcode';
34 changes: 34 additions & 0 deletions packages/checkout/utils/validation/is-postcode.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
/**
* External dependencies
*/
import { POSTCODE_REGEXES } from 'postcode-validator/lib/cjs/postcode-regexes.js';

const CUSTOM_REGEXES = new Map< string, RegExp >( [
[ 'BA', /^([7-8]{1})([0-9]{4})$/ ],
[
'GB',
/^([A-Z]){1}([0-9]{1,2}|[A-Z][0-9][A-Z]|[A-Z][0-9]{2}|[A-Z][0-9]|[0-9][A-Z]){1}([ ])?([0-9][A-Z]{2}){1}|BFPO(?:\s)?([0-9]{1,4})$|BFPO(c\/o[0-9]{1,3})$/i,
],
[ 'IN', /^[1-9]{1}[0-9]{2}\s{0,1}[0-9]{3}$/ ],
[ 'JP', /^([0-9]{3})([-]?)([0-9]{4})$/ ],
[ 'LI', /^(94[8-9][0-9])$/ ],
[ 'NL', /^([1-9][0-9]{3})(\s?)(?!SA|SD|SS)[A-Z]{2}$/i ],
[ 'SI', /^([1-9][0-9]{3})$/ ],
] );

const DEFAULT_REGEXES = new Map< string, RegExp >( [
...POSTCODE_REGEXES,
...CUSTOM_REGEXES,
] );

export interface IsPostcodeProps {
postcode: string;
country: string;
}

const isPostcode = ( { postcode, country }: IsPostcodeProps ): boolean => {
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
return DEFAULT_REGEXES.get( country )!.test( postcode );
};

export default isPostcode;
26 changes: 26 additions & 0 deletions packages/checkout/utils/validation/must-contain.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
/**
* External dependencies
*/
import { __, sprintf } from '@wordpress/i18n';

/**
* Ensures that a given value contains a string, or throws an error.
*/
const mustContain = ( value: string, requiredValue: string ): true | never => {
if ( ! value.includes( requiredValue ) ) {
throw Error(
sprintf(
/* translators: %1$s value passed to filter, %2$s : value that must be included. */
__(
'Returned value must include %1$s, you passed "%2$s"',
'woo-gutenberg-products-block'
),
requiredValue,
value
)
);
}
return true;
};

export default mustContain;
Loading