Skip to content
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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## Draft

- CUST-4064 Add dynamic postal code validation based on country requirements [#2585](https://github.com/bigcommerce/cornerstone/pull/2585)
- Update Cornerstone documentation url [#2575](https://github.com/bigcommerce/cornerstone/pull/2575)
- Fix keyboard navigation on the swatch options [#2576](https://github.com/bigcommerce/cornerstone/pull/2576)
- CHECKOUT-9688 Introduce new coupon management section on cart page [#2578](https://github.com/bigcommerce/cornerstone/pull/2578)
Expand Down
18 changes: 13 additions & 5 deletions assets/js/theme/account.js
Original file line number Diff line number Diff line change
Expand Up @@ -232,27 +232,33 @@ export default class Account extends PageManager {

initAddressFormValidation($addressForm) {
const validationModel = validation($addressForm, this.context);
const stateSelector = 'form[data-address-form] [data-field-type="State"]';
const $stateElement = $(stateSelector);
const $stateElement = $('form[data-address-form] [data-field-type="State"]');
const $zipElement = $('form[data-address-form] [data-field-type="Zip"]');
const addressValidator = nod({
submit: 'form[data-address-form] input[type="submit"]',
tap: announceInputErrorMessage,
});

addressValidator.add(validationModel);

if ($zipElement.length > 0) {
const isZipRequired = $zipElement.prop('required');
if (!isZipRequired && addressValidator.getStatus($zipElement) !== undefined) {
addressValidator.remove($zipElement);
}
}

if ($stateElement) {
let $last;

// Requests the states for a country with AJAX
stateCountry($stateElement, this.context, (err, field) => {
if (err) {
throw new Error(err);
}

const $field = $(field);

if (addressValidator.getStatus($stateElement) !== 'undefined') {
if (addressValidator.getStatus($stateElement) !== undefined) {
addressValidator.remove($stateElement);
}

Expand All @@ -266,6 +272,8 @@ export default class Account extends PageManager {
} else {
Validators.cleanUpStateValidation(field);
}

Validators.handleZipValidation(addressValidator, $zipElement, this.validationDictionary.field_not_blank);
});
}

Expand Down Expand Up @@ -336,7 +344,7 @@ export default class Account extends PageManager {

const $field = $(field);

if (paymentMethodValidator.getStatus($stateElement) !== 'undefined') {
if (paymentMethodValidator.getStatus($stateElement) !== undefined) {
paymentMethodValidator.remove($stateElement);
}

Expand Down
13 changes: 11 additions & 2 deletions assets/js/theme/auth.js
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,7 @@ export default class Auth extends PageManager {
delay: 900,
});
const $stateElement = $('[data-field-type="State"]');
const $zipElement = $('[data-field-type="Zip"]');
const emailSelector = `${this.formCreateSelector} [data-field-type='EmailAddress']`;
const $emailElement = $(emailSelector);
const passwordSelector = `${this.formCreateSelector} [data-field-type='Password']`;
Expand All @@ -123,18 +124,24 @@ export default class Auth extends PageManager {

createAccountValidator.add(validationModel);

if ($zipElement.length > 0) {
const isZipRequired = $zipElement.prop('required');
if (!isZipRequired && createAccountValidator.getStatus($zipElement) !== undefined) {
createAccountValidator.remove($zipElement);
}
}

if ($stateElement) {
let $last;

// Requests the states for a country with AJAX
stateCountry($stateElement, this.context, (err, field) => {
if (err) {
throw new Error(err);
}

const $field = $(field);

if (createAccountValidator.getStatus($stateElement) !== 'undefined') {
if (createAccountValidator.getStatus($stateElement) !== undefined) {
createAccountValidator.remove($stateElement);
}

Expand All @@ -148,6 +155,8 @@ export default class Auth extends PageManager {
} else {
Validators.cleanUpStateValidation(field);
}

Validators.handleZipValidation(createAccountValidator, $zipElement, this.validationDictionary.field_not_blank);
});
}

Expand Down
2 changes: 1 addition & 1 deletion assets/js/theme/cart/shipping-estimator.js
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,7 @@ export default class ShippingEstimator {

const $field = $(field);

if (this.shippingValidator.getStatus(this.$state) !== 'undefined') {
if (this.shippingValidator.getStatus(this.$state) !== undefined) {
this.shippingValidator.remove(this.$state);
}

Expand Down
60 changes: 58 additions & 2 deletions assets/js/theme/common/state-country.js
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,45 @@ function addOptions(statesArray, $selectElement, options) {
}
}

/**
* Makes the zip/postal code field required and shows the required indicator
* @param {jQuery} $zipElement The zip/postal code field element
* @param {Object} context The context object containing translated strings
*/
function makeZipRequired($zipElement, context) {
$zipElement.prop('required', true);
if ($zipElement.prev().find('small').length === 0) {
$zipElement.prev().append(`<small>${context.required}</small>`);
} else {
$zipElement.prev().find('small').show();
}
}

/**
* Makes the zip/postal code field optional and hides the required indicator
*
* DOM Structure Expectation:
* The function assumes the following DOM structure:
* <label>
* <span>Zip/Postal Code</span>
* <small>*</small> <!-- required indicator -->
* </label>
* <input data-field-type="Zip" />
*
* @param {jQuery} $zipElement The zip/postal code field element
*/
function makeZipOptional($zipElement) {
$zipElement.prop('required', false);

const $prevElement = $zipElement.prev();
if ($prevElement.length > 0) {
const $requiredIndicator = $prevElement.find('small');
if ($requiredIndicator.length > 0) {
$requiredIndicator.hide();
}
}
}

/**
*
* @param {jQuery} stateElement
Expand Down Expand Up @@ -135,9 +174,13 @@ export default function (stateElement, context = {}, options, callback) {
}

const $currentInput = $('[data-field-type="State"]');
const $zipInput = $('[data-field-type="Zip"]');

const requiresState = response.data.requiresSubdivision !== undefined
? response.data.requiresSubdivision
: !_.isEmpty(response.data.states);

if (!_.isEmpty(response.data.states)) {
// The element may have been replaced with a select, reselect it
if (requiresState) {
const $selectElement = makeStateRequired($currentInput, context);

addOptions(response.data, $selectElement, options);
Expand All @@ -147,6 +190,19 @@ export default function (stateElement, context = {}, options, callback) {

callback(null, newElement);
}

if ($zipInput.length > 0) {
// Default to true when requiresPostalCodes is undefined to maintain original behavior
const requiresZip = response.data.requiresPostalCodes !== undefined
? response.data.requiresPostalCodes
: true;

if (requiresZip) {
makeZipRequired($zipInput, context);
} else {
makeZipOptional($zipInput);
}
}
});
});
}
26 changes: 26 additions & 0 deletions assets/js/theme/common/utils/form-utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -331,6 +331,32 @@ const Validators = {
}
});
},

/**
* Handles zip/postal code validation based on whether it's required
* Removes existing validation, then adds it back if required or cleans up if not
* @param {Nod} validator - The nod validator instance
* @param {jQuery} $zipElement - The zip/postal code field element
* @param {string} errorText - The error message to display if validation fails
*/
handleZipValidation: (validator, $zipElement, errorText) => {
if ($zipElement.length === 0) {
return;
}

// Remove existing validation if present
if (validator.getStatus($zipElement) !== undefined) {
validator.remove($zipElement);
}

const isZipRequired = $zipElement.prop('required');

if (isZipRequired) {
Validators.setStateCountryValidation(validator, $zipElement, errorText);
} else {
Validators.cleanUpStateValidation($zipElement);
}
},
};

export { Validators, insertStateHiddenField, announceInputErrorMessage };
2 changes: 1 addition & 1 deletion package-lock.json

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