diff --git a/assets/js/data/cart/index.ts b/assets/js/data/cart/index.ts index 5ddd7de7793..c072ea09578 100644 --- a/assets/js/data/cart/index.ts +++ b/assets/js/data/cart/index.ts @@ -13,7 +13,7 @@ import * as actions from './actions'; import * as resolvers from './resolvers'; import reducer, { State } from './reducers'; import type { SelectFromMap, DispatchFromMap } from '../mapped-types'; -import { pushChanges } from './push-changes'; +import { pushChanges, flushChanges } from './push-changes'; import { updatePaymentMethods, debouncedUpdatePaymentMethods, @@ -34,6 +34,17 @@ const registeredStore = registerStore< State >( STORE_KEY, { registeredStore.subscribe( pushChanges ); +// This will skip the debounce and immediately push changes to the server when a field is blurred. +document.body.addEventListener( 'focusout', ( event: FocusEvent ) => { + if ( + event.target && + event.target instanceof Element && + event.target.tagName.toLowerCase() === 'input' + ) { + flushChanges(); + } +} ); + // First we will run the updatePaymentMethods function without any debounce to ensure payment methods are ready as soon // as the cart is loaded. After that, we will unsubscribe this function and instead run the // debouncedUpdatePaymentMethods function on subsequent cart updates. diff --git a/assets/js/data/cart/push-changes.ts b/assets/js/data/cart/push-changes.ts index 06228265a8e..85373cf9e88 100644 --- a/assets/js/data/cart/push-changes.ts +++ b/assets/js/data/cart/push-changes.ts @@ -34,25 +34,28 @@ const isBillingAddress = ( return 'email' in address; }; -export const trimAddress = ( address: BillingOrShippingAddress ) => { - const trimmedAddress = { - ...address, - }; - Object.keys( address ).forEach( ( key ) => { - trimmedAddress[ key as keyof BillingOrShippingAddress ] = - address[ key as keyof BillingOrShippingAddress ].trim(); - } ); - - trimmedAddress.postcode = trimmedAddress.postcode - ? trimmedAddress.postcode.replace( ' ', '' ).toUpperCase() - : ''; - +/** + * Trims and normalizes address data for comparison. + */ +export const normalizeAddress = ( address: BillingOrShippingAddress ) => { + const trimmedAddress = Object.entries( address ).reduce( + ( acc, [ key, value ] ) => { + if ( key === 'postcode' ) { + acc[ key as keyof BillingOrShippingAddress ] = value + .replace( ' ', '' ) + .toUpperCase(); + } else { + acc[ key as keyof BillingOrShippingAddress ] = value.trim(); + } + return acc; + }, + {} as BillingOrShippingAddress + ); return trimmedAddress; }; /** - * Does a shallow compare of important address data to determine if the cart needs updating on the server. This takes - * the current and previous address into account, as well as the billing email field. + * Does a shallow compare of all address data to determine if the cart needs updating on the server. */ const isAddressDirty = < T extends CartBillingAddress | CartShippingAddress >( // An object containing all previous address information @@ -68,13 +71,12 @@ const isAddressDirty = < T extends CartBillingAddress | CartShippingAddress >( return true; } - return ( - !! address.country && - ! isShallowEqual( - trimAddress( previousAddress ), - trimAddress( address ) - ) + const addressMatches = isShallowEqual( + normalizeAddress( previousAddress ), + normalizeAddress( address ) ); + + return ! addressMatches; }; type BaseAddressKey = keyof CartBillingAddress | keyof CartShippingAddress; @@ -239,3 +241,7 @@ export const pushChanges = (): void => { updateCustomerData(); } }; + +export const flushChanges = (): void => { + updateCustomerData.flush(); +}; diff --git a/src/StoreApi/Schemas/V1/AbstractAddressSchema.php b/src/StoreApi/Schemas/V1/AbstractAddressSchema.php index 0f759762d6d..cae085878d1 100644 --- a/src/StoreApi/Schemas/V1/AbstractAddressSchema.php +++ b/src/StoreApi/Schemas/V1/AbstractAddressSchema.php @@ -167,15 +167,7 @@ public function validate_callback( $address, $request, $param ) { $errors = new \WP_Error(); $address = $this->sanitize_callback( $address, $request, $param ); - if ( empty( $address['country'] ) ) { - $errors->add( - 'missing_country', - __( 'Country is required', 'woo-gutenberg-products-block' ) - ); - return $errors; - } - - if ( ! in_array( $address['country'], array_keys( wc()->countries->get_countries() ), true ) ) { + if ( ! empty( $address['country'] ) && ! in_array( $address['country'], array_keys( wc()->countries->get_countries() ), true ) ) { $errors->add( 'invalid_country', sprintf(