/** @typedef { import('@woocommerce/type-defs/hooks').StoreCart } StoreCart */
/**
* External dependencies
*/
import { isEqual } from 'lodash';
import { useRef } from '@wordpress/element';
import { CART_STORE_KEY as storeKey } from '@woocommerce/block-data';
import { useSelect } from '@wordpress/data';
import { decodeEntities } from '@wordpress/html-entities';
import type {
StoreCart,
CartResponseTotals,
CartResponseFeeItem,
CartResponseBillingAddress,
CartResponseShippingAddress,
} from '@woocommerce/types';
import {
emptyHiddenAddressFields,
fromEntriesPolyfill,
} from '@woocommerce/base-utils';
/**
* Internal dependencies
*/
import { useEditorContext } from '../../providers/editor-context';
declare module '@wordpress/html-entities' {
// eslint-disable-next-line @typescript-eslint/no-shadow
export function decodeEntities< T >( coupon: T ): T;
}
const defaultShippingAddress: CartResponseShippingAddress = {
first_name: '',
last_name: '',
company: '',
address_1: '',
address_2: '',
city: '',
state: '',
postcode: '',
country: '',
};
const defaultBillingAddress: CartResponseBillingAddress = {
...defaultShippingAddress,
email: '',
phone: '',
};
const defaultCartTotals: CartResponseTotals = {
total_items: '',
total_items_tax: '',
total_fees: '',
total_fees_tax: '',
total_discount: '',
total_discount_tax: '',
total_shipping: '',
total_shipping_tax: '',
total_price: '',
total_tax: '',
tax_lines: [],
currency_code: '',
currency_symbol: '',
currency_minor_unit: 2,
currency_decimal_separator: '',
currency_thousand_separator: '',
currency_prefix: '',
currency_suffix: '',
};
const decodeValues = (
object: Record< string, unknown >
): Record< string, unknown > =>
fromEntriesPolyfill(
Object.entries( object ).map( ( [ key, value ] ) => [
key,
decodeEntities( value ),
] )
);
/**
* @constant
* @type {StoreCart} Object containing cart data.
*/
export const defaultCartData: StoreCart = {
cartCoupons: [],
cartItems: [],
cartFees: [],
cartItemsCount: 0,
cartItemsWeight: 0,
cartNeedsPayment: true,
cartNeedsShipping: true,
cartItemErrors: [],
cartTotals: defaultCartTotals,
cartIsLoading: true,
cartErrors: [],
billingAddress: defaultBillingAddress,
shippingAddress: defaultShippingAddress,
shippingRates: [],
shippingRatesLoading: false,
cartHasCalculatedShipping: false,
paymentRequirements: [],
receiveCart: () => undefined,
extensions: {},
};
/**
* This is a custom hook that is wired up to the `wc/store/cart` data
* store.
*
* @param {Object} options An object declaring the various
* collection arguments.
* @param {boolean} options.shouldSelect If false, the previous results will be
* returned and internal selects will not
* fire.
*
* @return {StoreCart} Object containing cart data.
*/
export const useStoreCart = (
options: { shouldSelect: boolean } = { shouldSelect: true }
): StoreCart => {
const { isEditor, previewData } = useEditorContext();
const previewCart = previewData?.previewCart || {};
const { shouldSelect } = options;
const currentResults = useRef();
const results: StoreCart = useSelect(
( select, { dispatch } ) => {
if ( ! shouldSelect ) {
return defaultCartData;
}
if ( isEditor ) {
return {
cartCoupons: previewCart.coupons,
cartItems: previewCart.items,
cartFees: previewCart.fees,
cartItemsCount: previewCart.items_count,
cartItemsWeight: previewCart.items_weight,
cartNeedsPayment: previewCart.needs_payment,
cartNeedsShipping: previewCart.needs_shipping,
cartItemErrors: [],
cartTotals: previewCart.totals,
cartIsLoading: false,
cartErrors: [],
billingAddress: defaultBillingAddress,
shippingAddress: defaultShippingAddress,
extensions: {},
shippingRates: previewCart.shipping_rates,
shippingRatesLoading: false,
cartHasCalculatedShipping:
previewCart.has_calculated_shipping,
paymentRequirements: previewCart.paymentRequirements,
receiveCart:
typeof previewCart?.receiveCart === 'function'
? previewCart.receiveCart
: () => undefined,
};
}
const store = select( storeKey );
const cartData = store.getCartData();
const cartErrors = store.getCartErrors();
const cartTotals = store.getCartTotals();
const cartIsLoading = ! store.hasFinishedResolution(
'getCartData'
);
const shippingRatesLoading = store.isCustomerDataUpdating();
const { receiveCart } = dispatch( storeKey );
const billingAddress = decodeValues( cartData.billingAddress );
const shippingAddress = cartData.needsShipping
? decodeValues( cartData.shippingAddress )
: billingAddress;
const cartFees = cartData.fees.map( ( fee: CartResponseFeeItem ) =>
decodeValues( fee )
);
return {
cartCoupons: cartData.coupons,
cartItems: cartData.items || [],
cartFees,
cartItemsCount: cartData.itemsCount,
cartItemsWeight: cartData.itemsWeight,
cartNeedsPayment: cartData.needsPayment,
cartNeedsShipping: cartData.needsShipping,
cartItemErrors: cartData.errors || [],
cartTotals,
cartIsLoading,
cartErrors,
billingAddress: emptyHiddenAddressFields( billingAddress ),
shippingAddress: emptyHiddenAddressFields( shippingAddress ),
extensions: cartData.extensions || {},
shippingRates: cartData.shippingRates || [],
shippingRatesLoading,
cartHasCalculatedShipping: cartData.hasCalculatedShipping,
paymentRequirements: cartData.paymentRequirements || [],
receiveCart,
};
},
[ shouldSelect ]
);
if (
! currentResults.current ||
! isEqual( currentResults.current, results )
) {
currentResults.current = results;
}
return currentResults.current;
};