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 #7945

Merged
merged 5 commits into from
Feb 22, 2023
Merged
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 @@ -84,7 +84,7 @@ export const SingleProductDetails = () => {

return (
<div { ...blockProps }>
<ul className="tabs" role="tablist">
<ul className="wc-tabs tabs" role="tablist">
{ tabsTitle }
</ul>
{ tabsContent }
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
.wp-block-woocommerce-product-details {
ul.tabs {
ul.wc-tabs {
list-style: none;
padding: 0 0 0 1em;
margin: 0 0 1.618em;
Expand All @@ -9,35 +9,27 @@

li {
border: 1px solid $gray-200;
background-color: $white;
display: inline-block;
position: relative;
z-index: 0;
border-radius: 4px 4px 0 0;
margin: 0;
padding: 0.5em 1em;
opacity: 0.5;

a {
display: inline-block;
font-weight: 700;
color: $black;
text-decoration: none;

&:hover {
text-decoration: none;
color: color.adjust($black, $lightness: 10%);
}
}

&.active {
background: $gray-100;
z-index: 2;
border-bottom-color: $gray-200;
opacity: 1;

a {
color: inherit;
text-shadow: inherit;
}
}
Expand Down
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 @@ -214,6 +214,46 @@ const AddressForm = ( {
);
}

const customValidationHandler = (
inputObject: HTMLInputElement
) => {
if (
! isPostcode( {
postcode: values.postcode,
country: values.country,
} )
) {
inputObject.setCustomValidity(
__(
'Please provide a valid postcode',
'woo-gutenberg-products-block'
)
);
return false;
}
return true;
};

if ( field.key === 'postcode' ) {
return (
<ValidatedTextInput
id={ `${ id }-${ field.key }` }
key={ `${ id }-${ field.key }` }
value={ values.postcode }
label={ field.label }
required={ field.required }
onChange={ ( newValue: string ) =>
onChange( {
...values,
[ field.key ]: newValue,
} )
}
customValidation={ customValidationHandler }
errorMessage={ field.errorMessage }
/>
);
}

return (
<ValidatedTextInput
key={ field.key }
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;
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;
}

export type StateInputWithStatesProps = StateInputProps & {
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
37 changes: 37 additions & 0 deletions packages/checkout/utils/validation/getValidityMessageForInput.ts
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 = (

Choose a reason for hiding this comment

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

Copy link
Member Author

Choose a reason for hiding this comment

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

This change is not related to this PR, isn't it, @tarunvijwani?

Copy link

@tarunvijwani tarunvijwani Feb 20, 2023

Choose a reason for hiding this comment

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

Not sure which PR was responsible for it. I noticed it in this PR file changes. Thank you, I see you fixed it ef2a3e9 commit. 👍

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 './mustContain';
export { default as getValidityMessageForInput } from './getValidityMessageForInput';
export { default as isPostcode } from './isPostcode';
31 changes: 31 additions & 0 deletions packages/checkout/utils/validation/isPostcode.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
/**
* External dependencies
*/
import { POSTCODE_REGEXES } from 'postcode-validator/lib/cjs/postcode-regexes.js';

const getCustomRegexes = () => {
POSTCODE_REGEXES.set( 'BA', /^([7-8]{1})([0-9]{4})$/ );
POSTCODE_REGEXES.set(
'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

Check warning

Code scanning / CodeQL

Overly permissive regular expression range

Suspicious character range that is equivalent to \[A-Z\\[\\\\]^_`a-z\].
);
POSTCODE_REGEXES.set( 'IN', /^[1-9]{1}[0-9]{2}\s{0,1}[0-9]{3}$/ );
POSTCODE_REGEXES.set( 'JP', /^([0-9]{3})([-]?)([0-9]{4})$/ );
POSTCODE_REGEXES.set( 'LI', /^(94[8-9][0-9])$/ );
POSTCODE_REGEXES.set( 'NL', /^([1-9][0-9]{3})(\s?)(?!SA|SD|SS)[A-Z]{2}$/i );
POSTCODE_REGEXES.set( 'SI', /^([1-9][0-9]{3})$/ );

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

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

export default isPostcode;
26 changes: 26 additions & 0 deletions packages/checkout/utils/validation/mustContain.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