Support controlling address card state from data store in Checkout block (#51386)

* fix line height

* Support controlling address card from data store

* expand billing address on unsync

* Add changefile(s) from automation for the following project(s): woocommerce-blocks

* disable linter rule

* remove empty section

* remove used vars

---------

Co-authored-by: github-actions <github-actions@github.com>
This commit is contained in:
Seghir Nadir 2024-09-17 12:56:15 +02:00 committed by GitHub
parent e354295387
commit 66523872f3
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
13 changed files with 180 additions and 58 deletions

View File

@ -25,7 +25,11 @@ interface CheckoutAddress {
setBillingAddress: ( data: Partial< BillingAddress > ) => void; setBillingAddress: ( data: Partial< BillingAddress > ) => void;
setEmail: ( value: string ) => void; setEmail: ( value: string ) => void;
useShippingAsBilling: boolean; useShippingAsBilling: boolean;
editingBillingAddress: boolean;
editingShippingAddress: boolean;
setUseShippingAsBilling: ( useShippingAsBilling: boolean ) => void; setUseShippingAsBilling: ( useShippingAsBilling: boolean ) => void;
setEditingBillingAddress: ( isEditing: boolean ) => void;
setEditingShippingAddress: ( isEditing: boolean ) => void;
defaultFields: AddressFields; defaultFields: AddressFields;
showShippingFields: boolean; showShippingFields: boolean;
showBillingFields: boolean; showBillingFields: boolean;
@ -40,15 +44,25 @@ interface CheckoutAddress {
*/ */
export const useCheckoutAddress = (): CheckoutAddress => { export const useCheckoutAddress = (): CheckoutAddress => {
const { needsShipping } = useShippingData(); const { needsShipping } = useShippingData();
const { useShippingAsBilling, prefersCollection } = useSelect( const {
( select ) => ( { useShippingAsBilling,
useShippingAsBilling: prefersCollection,
select( CHECKOUT_STORE_KEY ).getUseShippingAsBilling(), editingBillingAddress,
prefersCollection: select( CHECKOUT_STORE_KEY ).prefersCollection(), editingShippingAddress,
} ) } = useSelect( ( select ) => ( {
); useShippingAsBilling:
const { __internalSetUseShippingAsBilling } = select( CHECKOUT_STORE_KEY ).getUseShippingAsBilling(),
useDispatch( CHECKOUT_STORE_KEY ); prefersCollection: select( CHECKOUT_STORE_KEY ).prefersCollection(),
editingBillingAddress:
select( CHECKOUT_STORE_KEY ).getEditingBillingAddress(),
editingShippingAddress:
select( CHECKOUT_STORE_KEY ).getEditingShippingAddress(),
} ) );
const {
__internalSetUseShippingAsBilling,
setEditingBillingAddress,
setEditingShippingAddress,
} = useDispatch( CHECKOUT_STORE_KEY );
const { const {
billingAddress, billingAddress,
setBillingAddress, setBillingAddress,
@ -77,6 +91,10 @@ export const useCheckoutAddress = (): CheckoutAddress => {
defaultFields, defaultFields,
useShippingAsBilling, useShippingAsBilling,
setUseShippingAsBilling: __internalSetUseShippingAsBilling, setUseShippingAsBilling: __internalSetUseShippingAsBilling,
editingBillingAddress,
editingShippingAddress,
setEditingBillingAddress,
setEditingShippingAddress,
needsShipping, needsShipping,
showShippingFields: showShippingFields:
! forcedBillingAddress && needsShipping && ! prefersCollection, ! forcedBillingAddress && needsShipping && ! prefersCollection,

View File

@ -7,14 +7,12 @@ import {
useCheckoutAddress, useCheckoutAddress,
useEditorContext, useEditorContext,
noticeContexts, noticeContexts,
useShippingData,
} from '@woocommerce/base-context'; } from '@woocommerce/base-context';
import Noninteractive from '@woocommerce/base-components/noninteractive'; import Noninteractive from '@woocommerce/base-components/noninteractive';
import type { ShippingAddress, FormFieldsConfig } from '@woocommerce/settings'; import type { ShippingAddress, FormFieldsConfig } from '@woocommerce/settings';
import { StoreNoticesContainer } from '@woocommerce/blocks-components'; import { StoreNoticesContainer } from '@woocommerce/blocks-components';
import { useSelect } from '@wordpress/data'; import { useSelect } from '@wordpress/data';
import { CART_STORE_KEY } from '@woocommerce/block-data'; import { CART_STORE_KEY } from '@woocommerce/block-data';
import isShallowEqual from '@wordpress/is-shallow-equal';
/** /**
* Internal dependencies * Internal dependencies
@ -36,14 +34,9 @@ const Block = ( {
showPhoneField: boolean; showPhoneField: boolean;
requirePhoneField: boolean; requirePhoneField: boolean;
} ): JSX.Element => { } ): JSX.Element => {
const { const { billingAddress, setShippingAddress, useBillingAsShipping } =
shippingAddress, useCheckoutAddress();
billingAddress,
setShippingAddress,
useBillingAsShipping,
} = useCheckoutAddress();
const { isEditor } = useEditorContext(); const { isEditor } = useEditorContext();
const { needsShipping } = useShippingData();
// Syncs shipping address with billing address if "Force shipping to the customer billing address" is enabled. // Syncs shipping address with billing address if "Force shipping to the customer billing address" is enabled.
useEffectOnce( () => { useEffectOnce( () => {
@ -101,19 +94,6 @@ const Block = ( {
}; };
} ); } );
// Default editing state for CustomerAddress component comes from the current address and whether or not we're in the editor.
const hasAddress = !! (
billingAddress.address_1 &&
( billingAddress.first_name || billingAddress.last_name )
);
const { email, ...billingAddressWithoutEmail } = billingAddress;
const billingMatchesShipping = isShallowEqual(
billingAddressWithoutEmail,
shippingAddress
);
const defaultEditingAddress =
isEditor || ! hasAddress || ( needsShipping && billingMatchesShipping );
return ( return (
<> <>
<StoreNoticesContainer context={ noticeContext } /> <StoreNoticesContainer context={ noticeContext } />
@ -121,7 +101,6 @@ const Block = ( {
{ cartDataLoaded ? ( { cartDataLoaded ? (
<CustomerAddress <CustomerAddress
addressFieldsConfig={ addressFieldsConfig } addressFieldsConfig={ addressFieldsConfig }
defaultEditing={ defaultEditingAddress }
/> />
) : null } ) : null }
</WrapperComponent> </WrapperComponent>

View File

@ -1,7 +1,7 @@
/** /**
* External dependencies * External dependencies
*/ */
import { useState, useCallback, useEffect } from '@wordpress/element'; import { useCallback, useEffect } from '@wordpress/element';
import { Form } from '@woocommerce/base-components/cart-checkout'; import { Form } from '@woocommerce/base-components/cart-checkout';
import { useCheckoutAddress, useStoreEvents } from '@woocommerce/base-context'; import { useCheckoutAddress, useStoreEvents } from '@woocommerce/base-context';
import type { import type {
@ -20,19 +20,18 @@ import AddressCard from '../../address-card';
const CustomerAddress = ( { const CustomerAddress = ( {
addressFieldsConfig, addressFieldsConfig,
defaultEditing = false,
}: { }: {
addressFieldsConfig: FormFieldsConfig; addressFieldsConfig: FormFieldsConfig;
defaultEditing?: boolean;
} ) => { } ) => {
const { const {
billingAddress, billingAddress,
setShippingAddress, setShippingAddress,
setBillingAddress, setBillingAddress,
useBillingAsShipping, useBillingAsShipping,
editingBillingAddress: editing,
setEditingBillingAddress: setEditing,
} = useCheckoutAddress(); } = useCheckoutAddress();
const { dispatchCheckoutEvent } = useStoreEvents(); const { dispatchCheckoutEvent } = useStoreEvents();
const [ editing, setEditing ] = useState( defaultEditing );
// Forces editing state if store has errors. // Forces editing state if store has errors.
const { hasValidationErrors, invalidProps } = useSelect( ( select ) => { const { hasValidationErrors, invalidProps } = useSelect( ( select ) => {
@ -55,7 +54,7 @@ const CustomerAddress = ( {
if ( invalidProps.length > 0 && editing === false ) { if ( invalidProps.length > 0 && editing === false ) {
setEditing( true ); setEditing( true );
} }
}, [ editing, hasValidationErrors, invalidProps.length ] ); }, [ editing, hasValidationErrors, invalidProps.length, setEditing ] );
const onChangeAddress = useCallback( const onChangeAddress = useCallback(
( values: AddressFormValues ) => { ( values: AddressFormValues ) => {
@ -86,7 +85,7 @@ const CustomerAddress = ( {
isExpanded={ editing } isExpanded={ editing }
/> />
), ),
[ billingAddress, addressFieldsConfig, editing ] [ billingAddress, addressFieldsConfig, editing, setEditing ]
); );
const renderAddressFormComponent = useCallback( const renderAddressFormComponent = useCallback(

View File

@ -47,6 +47,7 @@ const Block = ( {
billingAddress, billingAddress,
useShippingAsBilling, useShippingAsBilling,
setUseShippingAsBilling, setUseShippingAsBilling,
setEditingBillingAddress,
} = useCheckoutAddress(); } = useCheckoutAddress();
const { isEditor } = useEditorContext(); const { isEditor } = useEditorContext();
const isGuest = getSetting( 'currentUserId' ) === 0; const isGuest = getSetting( 'currentUserId' ) === 0;
@ -116,10 +117,6 @@ const Block = ( {
const noticeContext = useShippingAsBilling const noticeContext = useShippingAsBilling
? [ noticeContexts.SHIPPING_ADDRESS, noticeContexts.BILLING_ADDRESS ] ? [ noticeContexts.SHIPPING_ADDRESS, noticeContexts.BILLING_ADDRESS ]
: [ noticeContexts.SHIPPING_ADDRESS ]; : [ noticeContexts.SHIPPING_ADDRESS ];
const hasAddress = !! (
shippingAddress.address_1 &&
( shippingAddress.first_name || shippingAddress.last_name )
);
const { cartDataLoaded } = useSelect( ( select ) => { const { cartDataLoaded } = useSelect( ( select ) => {
const store = select( CART_STORE_KEY ); const store = select( CART_STORE_KEY );
@ -128,9 +125,6 @@ const Block = ( {
}; };
} ); } );
// Default editing state for CustomerAddress component comes from the current address and whether or not we're in the editor.
const defaultEditingAddress = isEditor || ! hasAddress;
return ( return (
<> <>
<StoreNoticesContainer context={ noticeContext } /> <StoreNoticesContainer context={ noticeContext } />
@ -138,7 +132,6 @@ const Block = ( {
{ cartDataLoaded ? ( { cartDataLoaded ? (
<CustomerAddress <CustomerAddress
addressFieldsConfig={ addressFieldsConfig } addressFieldsConfig={ addressFieldsConfig }
defaultEditing={ defaultEditingAddress }
/> />
) : null } ) : null }
</WrapperComponent> </WrapperComponent>
@ -151,6 +144,7 @@ const Block = ( {
if ( checked ) { if ( checked ) {
syncBillingWithShipping(); syncBillingWithShipping();
} else { } else {
setEditingBillingAddress( true );
clearBillingAddress( billingAddress ); clearBillingAddress( billingAddress );
} }
} } } }

View File

@ -1,7 +1,7 @@
/** /**
* External dependencies * External dependencies
*/ */
import { useState, useCallback, useEffect } from '@wordpress/element'; import { useCallback, useEffect } from '@wordpress/element';
import { Form } from '@woocommerce/base-components/cart-checkout'; import { Form } from '@woocommerce/base-components/cart-checkout';
import { useCheckoutAddress, useStoreEvents } from '@woocommerce/base-context'; import { useCheckoutAddress, useStoreEvents } from '@woocommerce/base-context';
import type { import type {
@ -20,19 +20,18 @@ import AddressCard from '../../address-card';
const CustomerAddress = ( { const CustomerAddress = ( {
addressFieldsConfig, addressFieldsConfig,
defaultEditing = false,
}: { }: {
addressFieldsConfig: FormFieldsConfig; addressFieldsConfig: FormFieldsConfig;
defaultEditing?: boolean;
} ) => { } ) => {
const { const {
shippingAddress, shippingAddress,
setShippingAddress, setShippingAddress,
setBillingAddress, setBillingAddress,
useShippingAsBilling, useShippingAsBilling,
editingShippingAddress: editing,
setEditingShippingAddress: setEditing,
} = useCheckoutAddress(); } = useCheckoutAddress();
const { dispatchCheckoutEvent } = useStoreEvents(); const { dispatchCheckoutEvent } = useStoreEvents();
const [ editing, setEditing ] = useState( defaultEditing );
// Forces editing state if store has errors. // Forces editing state if store has errors.
const { hasValidationErrors, invalidProps } = useSelect( ( select ) => { const { hasValidationErrors, invalidProps } = useSelect( ( select ) => {
@ -54,7 +53,7 @@ const CustomerAddress = ( {
if ( invalidProps.length > 0 && editing === false ) { if ( invalidProps.length > 0 && editing === false ) {
setEditing( true ); setEditing( true );
} }
}, [ editing, hasValidationErrors, invalidProps.length ] ); }, [ editing, hasValidationErrors, invalidProps.length, setEditing ] );
const onChangeAddress = useCallback( const onChangeAddress = useCallback(
( values: AddressFormValues ) => { ( values: AddressFormValues ) => {
@ -85,7 +84,7 @@ const CustomerAddress = ( {
isExpanded={ editing } isExpanded={ editing }
/> />
), ),
[ shippingAddress, addressFieldsConfig, editing ] [ shippingAddress, addressFieldsConfig, editing, setEditing ]
); );
const renderAddressFormComponent = useCallback( const renderAddressFormComponent = useCallback(

View File

@ -17,4 +17,6 @@ export const ACTION_TYPES = {
SET_REDIRECT_URL: 'SET_REDIRECT_URL', SET_REDIRECT_URL: 'SET_REDIRECT_URL',
SET_SHOULD_CREATE_ACCOUNT: 'SET_SHOULD_CREATE_ACCOUNT', SET_SHOULD_CREATE_ACCOUNT: 'SET_SHOULD_CREATE_ACCOUNT',
SET_USE_SHIPPING_AS_BILLING: 'SET_USE_SHIPPING_AS_BILLING', SET_USE_SHIPPING_AS_BILLING: 'SET_USE_SHIPPING_AS_BILLING',
SET_EDITING_BILLING_ADDRESS: 'SET_EDITING_BILLING_ADDRESS',
SET_EDITING_SHIPPING_ADDRESS: 'SET_EDITING_SHIPPING_ADDRESS',
} as const; } as const;

View File

@ -118,6 +118,30 @@ export const __internalSetUseShippingAsBilling = (
useShippingAsBilling, useShippingAsBilling,
} ); } );
/**
* Set whether the billing address is being edited
*
* @param isEditing True if the billing address is being edited, false otherwise
*/
export const setEditingBillingAddress = ( isEditing: boolean ) => {
return {
type: types.SET_EDITING_BILLING_ADDRESS,
isEditing,
};
};
/**
* Set whether the shipping address is being edited
*
* @param isEditing True if the shipping address is being edited, false otherwise
*/
export const setEditingShippingAddress = ( isEditing: boolean ) => {
return {
type: types.SET_EDITING_SHIPPING_ADDRESS,
isEditing,
};
};
/** /**
* Whether an account should be created for the user while checking out * Whether an account should be created for the user while checking out
* *
@ -182,6 +206,8 @@ export type CheckoutAction =
| typeof __internalSetCustomerId | typeof __internalSetCustomerId
| typeof __internalSetCustomerPassword | typeof __internalSetCustomerPassword
| typeof __internalSetUseShippingAsBilling | typeof __internalSetUseShippingAsBilling
| typeof setEditingBillingAddress
| typeof setEditingShippingAddress
| typeof __internalSetShouldCreateAccount | typeof __internalSetShouldCreateAccount
| typeof __internalSetOrderNotes | typeof __internalSetOrderNotes
| typeof setPrefersCollection | typeof setPrefersCollection

View File

@ -23,8 +23,28 @@ export type CheckoutState = {
shouldCreateAccount: boolean; // Should a user account be created? shouldCreateAccount: boolean; // Should a user account be created?
status: STATUS; // Status of the checkout status: STATUS; // Status of the checkout
useShippingAsBilling: boolean; // Should the billing form be hidden and inherit the shipping address? useShippingAsBilling: boolean; // Should the billing form be hidden and inherit the shipping address?
editingBillingAddress: boolean; // Is the billing address being edited?
editingShippingAddress: boolean; // Is the shipping address being edited?
}; };
// Default editing state for CustomerAddress component comes from the current address and whether or not we're in the editor.
const hasBillingAddress = !! (
checkoutData.billing_address.address_1 &&
( checkoutData.billing_address.first_name ||
checkoutData.billing_address.last_name )
);
const hasShippingAddress = !! (
checkoutData.shipping_address.address_1 &&
( checkoutData.shipping_address.first_name ||
checkoutData.shipping_address.last_name )
);
const billingMatchesShipping = isSameAddress(
checkoutData.billing_address,
checkoutData.shipping_address
);
export const defaultState: CheckoutState = { export const defaultState: CheckoutState = {
additionalFields: checkoutData.additional_fields || {}, additionalFields: checkoutData.additional_fields || {},
calculatingCount: 0, calculatingCount: 0,
@ -38,8 +58,7 @@ export const defaultState: CheckoutState = {
redirectUrl: '', redirectUrl: '',
shouldCreateAccount: false, shouldCreateAccount: false,
status: STATUS.IDLE, status: STATUS.IDLE,
useShippingAsBilling: isSameAddress( useShippingAsBilling: billingMatchesShipping,
checkoutData.billing_address, editingBillingAddress: ! hasBillingAddress,
checkoutData.shipping_address editingShippingAddress: ! hasShippingAddress,
),
}; };

View File

@ -130,6 +130,20 @@ const reducer = ( state = defaultState, action: CheckoutAction ) => {
} }
break; break;
case types.SET_EDITING_BILLING_ADDRESS:
newState = {
...state,
editingBillingAddress: action.isEditing,
};
break;
case types.SET_EDITING_SHIPPING_ADDRESS:
newState = {
...state,
editingShippingAddress: action.isEditing,
};
break;
case types.SET_SHOULD_CREATE_ACCOUNT: case types.SET_SHOULD_CREATE_ACCOUNT:
if ( if (
action.shouldCreateAccount !== undefined && action.shouldCreateAccount !== undefined &&

View File

@ -36,6 +36,14 @@ export const getUseShippingAsBilling = ( state: CheckoutState ) => {
return state.useShippingAsBilling; return state.useShippingAsBilling;
}; };
export const getEditingBillingAddress = ( state: CheckoutState ) => {
return state.editingBillingAddress;
};
export const getEditingShippingAddress = ( state: CheckoutState ) => {
return state.editingShippingAddress;
};
export const getExtensionData = ( state: CheckoutState ) => { export const getExtensionData = ( state: CheckoutState ) => {
return state.extensionData; return state.extensionData;
}; };

View File

@ -1,5 +1,7 @@
# Checkout Store (`wc/store/checkout`) <!-- omit in toc --> # Checkout Store (`wc/store/checkout`) <!-- omit in toc -->
<!-- markdownlint-disable MD024 -->
> 💡 What's the difference between the Cart Store and the Checkout Store? > 💡 What's the difference between the Cart Store and the Checkout Store?
> >
> The **Cart Store (`wc/store/cart`)** manages and retrieves data about the shopping cart, including items, customer data, and interactions like coupons. > The **Cart Store (`wc/store/cart`)** manages and retrieves data about the shopping cart, including items, customer data, and interactions like coupons.
@ -173,6 +175,36 @@ const store = select( CHECKOUT_STORE_KEY );
const useShippingAsBilling = store.getUseShippingAsBilling(); const useShippingAsBilling = store.getUseShippingAsBilling();
``` ```
### getEditingBillingAddress
Returns true if the billing address is being edited.
#### _Returns_ <!-- omit in toc -->
- `boolean`: True if the billing address is being edited.
#### _Example_ <!-- omit in toc -->
```js
const store = select( CHECKOUT_STORE_KEY );
const editingBillingAddress = store.getEditingBillingAddress();
```
### getEditingShippingAddress
Returns true if the shipping address is being edited.
#### _Returns_ <!-- omit in toc -->
- `boolean`: True if the shipping address is being edited.
#### _Example_ <!-- omit in toc -->
```js
const store = select( CHECKOUT_STORE_KEY );
const editingShippingAddress = store.getEditingShippingAddress();
```
### hasError ### hasError
Returns true if an error occurred, and false otherwise. Returns true if an error occurred, and false otherwise.
@ -293,7 +325,6 @@ const store = select( CHECKOUT_STORE_KEY );
const isCalculating = store.isCalculating(); const isCalculating = store.isCalculating();
``` ```
### prefersCollection ### prefersCollection
Returns true if the customer prefers to collect their order, and false otherwise. Returns true if the customer prefers to collect their order, and false otherwise.
@ -326,6 +357,36 @@ const store = dispatch( CHECKOUT_STORE_KEY );
store.setPrefersCollection( true ); store.setPrefersCollection( true );
``` ```
### setEditingBillingAddress
Set the billing address to editing state or collapsed state. Note that if the address has invalid fields, it will not be set to collapsed state.
#### _Parameters_ <!-- omit in toc -->
- _isEditing_ `boolean`: True to set the billing address to editing state, false to set it to collapsed state.
#### _Example_ <!-- omit in toc -->
```js
const store = dispatch( CHECKOUT_STORE_KEY );
store.setEditingBillingAddress( true );
```
### setEditingShippingAddress
Set the shipping address to editing state or collapsed state. Note that if the address has invalid fields, it will not be set to collapsed state.
#### _Parameters_ <!-- omit in toc -->
- _isEditing_ `boolean`: True to set the shipping address to editing state, false to set it to collapsed state.
#### _Example_ <!-- omit in toc -->
```js
const store = dispatch( CHECKOUT_STORE_KEY );
store.setEditingShippingAddress( true );
```
<!-- FEEDBACK --> <!-- FEEDBACK -->
--- ---

View File

@ -16,7 +16,6 @@
.wc-block-components-panel__button { .wc-block-components-panel__button {
box-sizing: border-box; box-sizing: border-box;
height: auto; height: auto;
line-height: inherit;
margin-top: em(6px); margin-top: em(6px);
padding-right: #{24px + $gap-smaller}; padding-right: #{24px + $gap-smaller};
padding-top: em($gap-small - 6px); padding-top: em($gap-small - 6px);

View File

@ -0,0 +1,4 @@
Significance: patch
Type: enhancement
Move address card state management to data stores in Checkout block.