woocommerce/plugins/woocommerce-blocks/assets/js/base/utils/address.ts

205 lines
5.5 KiB
TypeScript
Raw Normal View History

/**
* External dependencies
*/
import prepareFormFields from '@woocommerce/base-components/cart-checkout/form/prepare-form-fields';
import { isEmail } from '@wordpress/url';
import type {
CartResponseBillingAddress,
CartResponseShippingAddress,
} from '@woocommerce/types';
import { ShippingAddress, BillingAddress } from '@woocommerce/settings';
import { decodeEntities } from '@wordpress/html-entities';
import {
SHIPPING_COUNTRIES,
SHIPPING_STATES,
ADDRESS_FORM_KEYS,
} from '@woocommerce/block-settings';
/**
* Compare two addresses and see if they are the same.
*/
export const isSameAddress = < T extends ShippingAddress | BillingAddress >(
address1: T,
address2: T
): boolean => {
return ADDRESS_FORM_KEYS.every( ( field: string ) => {
return address1[ field as keyof T ] === address2[ field as keyof T ];
} );
};
/**
* pluckAddress takes a full address object and returns relevant fields for calculating
* shipping, so we can track when one of them change to update rates.
*
* @param {Object} address An object containing all address information
* @param {string} address.country The country.
* @param {string} address.state The state.
* @param {string} address.city The city.
* @param {string} address.postcode The postal code.
*
* @return {Object} pluckedAddress An object containing shipping address that are needed to fetch an address.
*/
Sync shipping address with billing address when shipping address fields are disabled (https://github.com/woocommerce/woocommerce-blocks/pull/3358) * Correct docblock description * Sync shipping address changes with billing data * Update inline documentation * Revert address sync because it fails when shipping is disabled explicitely * Avoid loading shipping address from customer is shipping is disabled * Rather than update order from the wc/store/checkout request, update the customer object This is turn is synced to order, but also allows the cart calcultions to use the posted data. This means that taxes will be updated based on address data even if not displayed on the checkout. * Add action that combines billing and shipping updates * Add route for updating billing and shipping address * Sync billing data to server on change * Shared constants for billing data * Skip address update if missing country * Allow null values to skip formatting * Add billing to cart schema * Removed unwanted hooks from previous commit * Decoding is handled in useStoreCart * Remove hook * Make shipping context hold state * Make billing context hold state * Add address processors * Cart does not have billing * Update tests, remove some unrelated changes affecting the diff * Revert "Update inline documentation" This reverts commit 0393f49316de3152c6dcf6fda1192c06a74f1b55. * Make shippingRatesAreResolving conditonal based on API request * Shared address processor in cart and checkout * Rename REST endpoint * CustomerDataProvider and hook * Update shipping address type defs * Rename customer address endpoint, and remove update-shipping * Update tests * Fix tests by restoring country validation * typo * Update assets/js/base/hooks/cart/use-store-cart.js Co-authored-by: Darren Ethier <darren@roughsmootheng.in> * Simplify debounce and request handling * Remove state from address sync This will mean billing is "forgotten" if using the checbox, but this greatly simplifies logic. * Rename shipping rates loading to customer data loading * Sync based on useStoreCart data * Made cart API less strict on addresses * Fix useCheckoutAddress sync * Add note on currentShippingAsBilling * Use incoming isCart * Add more detailed inline comment for shippingAsBilling toggle event * Combine customer billing and shipping ref * Update address docblock * Error handling in pluckAddress * Fix cart response after rebase * Update customer tests * Update src/StoreApi/Routes/CartUpdateCustomer.php Co-authored-by: Darren Ethier <darren@roughsmootheng.in> Co-authored-by: Darren Ethier <darren@roughsmootheng.in>
2020-11-20 15:13:35 +00:00
export const pluckAddress = ( {
country = '',
state = '',
city = '',
postcode = '',
}: CartResponseBillingAddress | CartResponseShippingAddress ): {
country: string;
state: string;
city: string;
postcode: string;
} => ( {
country: country.trim(),
state: state.trim(),
city: city.trim(),
Sync shipping address with billing address when shipping address fields are disabled (https://github.com/woocommerce/woocommerce-blocks/pull/3358) * Correct docblock description * Sync shipping address changes with billing data * Update inline documentation * Revert address sync because it fails when shipping is disabled explicitely * Avoid loading shipping address from customer is shipping is disabled * Rather than update order from the wc/store/checkout request, update the customer object This is turn is synced to order, but also allows the cart calcultions to use the posted data. This means that taxes will be updated based on address data even if not displayed on the checkout. * Add action that combines billing and shipping updates * Add route for updating billing and shipping address * Sync billing data to server on change * Shared constants for billing data * Skip address update if missing country * Allow null values to skip formatting * Add billing to cart schema * Removed unwanted hooks from previous commit * Decoding is handled in useStoreCart * Remove hook * Make shipping context hold state * Make billing context hold state * Add address processors * Cart does not have billing * Update tests, remove some unrelated changes affecting the diff * Revert "Update inline documentation" This reverts commit 0393f49316de3152c6dcf6fda1192c06a74f1b55. * Make shippingRatesAreResolving conditonal based on API request * Shared address processor in cart and checkout * Rename REST endpoint * CustomerDataProvider and hook * Update shipping address type defs * Rename customer address endpoint, and remove update-shipping * Update tests * Fix tests by restoring country validation * typo * Update assets/js/base/hooks/cart/use-store-cart.js Co-authored-by: Darren Ethier <darren@roughsmootheng.in> * Simplify debounce and request handling * Remove state from address sync This will mean billing is "forgotten" if using the checbox, but this greatly simplifies logic. * Rename shipping rates loading to customer data loading * Sync based on useStoreCart data * Made cart API less strict on addresses * Fix useCheckoutAddress sync * Add note on currentShippingAsBilling * Use incoming isCart * Add more detailed inline comment for shippingAsBilling toggle event * Combine customer billing and shipping ref * Update address docblock * Error handling in pluckAddress * Fix cart response after rebase * Update customer tests * Update src/StoreApi/Routes/CartUpdateCustomer.php Co-authored-by: Darren Ethier <darren@roughsmootheng.in> Co-authored-by: Darren Ethier <darren@roughsmootheng.in>
2020-11-20 15:13:35 +00:00
postcode: postcode ? postcode.replace( ' ', '' ).toUpperCase() : '',
} );
/**
* pluckEmail takes a full address object and returns only the email address, if set and valid. Otherwise returns an empty string.
*
* @param {Object} address An object containing all address information
* @param {string} address.email The email address.
* @return {string} The email address.
*/
export const pluckEmail = ( {
email = '',
}: CartResponseBillingAddress ): string =>
isEmail( email ) ? email.trim() : '';
/**
* Type-guard.
*/
const isValidAddressKey = (
key: string,
address: CartResponseBillingAddress | CartResponseShippingAddress
): key is keyof typeof address => {
return key in address;
};
/**
* Sets fields to an empty string in an address if they are hidden by the settings in countryLocale.
*
* @param {Object} address The address to empty fields from.
* @return {Object} The address with hidden fields values removed.
*/
export const emptyHiddenAddressFields = <
T extends CartResponseBillingAddress | CartResponseShippingAddress
>(
address: T
): T => {
const addressForm = prepareFormFields(
ADDRESS_FORM_KEYS,
{},
address.country
);
const newAddress = Object.assign( {}, address ) as T;
addressForm.forEach( ( { key = '', hidden = false } ) => {
if ( hidden && isValidAddressKey( key, address ) ) {
newAddress[ key ] = '';
}
} );
return newAddress;
};
/**
* Sets fields to an empty string in an address.
*
* @param {Object} address The address to empty fields from.
* @return {Object} The address with all fields values removed.
*/
export const emptyAddressFields = <
T extends CartResponseBillingAddress | CartResponseShippingAddress
>(
address: T
): T => {
const addressForm = prepareFormFields(
ADDRESS_FORM_KEYS,
{},
address.country
);
const newAddress = Object.assign( {}, address ) as T;
addressForm.forEach( ( { key = '' } ) => {
// Clear address fields except country and state to keep consistency with shortcode Checkout.
if (
key !== 'country' &&
key !== 'state' &&
isValidAddressKey( key, address )
) {
newAddress[ key ] = '';
}
} );
return newAddress;
};
/*
* Formats a shipping address for display.
*
* @param {Object} address The address to format.
* @return {string | null} The formatted address or null if no address is provided.
*/
export const formatShippingAddress = (
address: ShippingAddress | BillingAddress
): string | null => {
// We bail early if we don't have an address.
if ( Object.values( address ).length === 0 ) {
return null;
}
const formattedCountry =
typeof SHIPPING_COUNTRIES[ address.country ] === 'string'
? decodeEntities( SHIPPING_COUNTRIES[ address.country ] )
: '';
const formattedState =
typeof SHIPPING_STATES[ address.country ] === 'object' &&
typeof SHIPPING_STATES[ address.country ][ address.state ] === 'string'
? decodeEntities(
SHIPPING_STATES[ address.country ][ address.state ]
)
: address.state;
const addressParts = [];
addressParts.push( address.postcode.toUpperCase() );
addressParts.push( address.city );
addressParts.push( formattedState );
addressParts.push( formattedCountry );
const formattedLocation = addressParts.filter( Boolean ).join( ', ' );
if ( ! formattedLocation ) {
return null;
}
return formattedLocation;
};
/**
* Checks that all required fields in an address are completed based on the settings in countryLocale.
*/
export const isAddressComplete = (
address: ShippingAddress | BillingAddress
): boolean => {
if ( ! address.country ) {
return false;
}
const addressForm = prepareFormFields(
ADDRESS_FORM_KEYS,
{},
address.country
);
return addressForm.every(
( { key = '', hidden = false, required = false } ) => {
if ( hidden || ! required ) {
return true;
}
return isValidAddressKey( key, address ) && address[ key ] !== '';
}
);
};