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

View File

@ -7,14 +7,12 @@ import {
useCheckoutAddress,
useEditorContext,
noticeContexts,
useShippingData,
} from '@woocommerce/base-context';
import Noninteractive from '@woocommerce/base-components/noninteractive';
import type { ShippingAddress, FormFieldsConfig } from '@woocommerce/settings';
import { StoreNoticesContainer } from '@woocommerce/blocks-components';
import { useSelect } from '@wordpress/data';
import { CART_STORE_KEY } from '@woocommerce/block-data';
import isShallowEqual from '@wordpress/is-shallow-equal';
/**
* Internal dependencies
@ -36,14 +34,9 @@ const Block = ( {
showPhoneField: boolean;
requirePhoneField: boolean;
} ): JSX.Element => {
const {
shippingAddress,
billingAddress,
setShippingAddress,
useBillingAsShipping,
} = useCheckoutAddress();
const { billingAddress, setShippingAddress, useBillingAsShipping } =
useCheckoutAddress();
const { isEditor } = useEditorContext();
const { needsShipping } = useShippingData();
// Syncs shipping address with billing address if "Force shipping to the customer billing address" is enabled.
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 (
<>
<StoreNoticesContainer context={ noticeContext } />
@ -121,7 +101,6 @@ const Block = ( {
{ cartDataLoaded ? (
<CustomerAddress
addressFieldsConfig={ addressFieldsConfig }
defaultEditing={ defaultEditingAddress }
/>
) : null }
</WrapperComponent>

View File

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

View File

@ -47,6 +47,7 @@ const Block = ( {
billingAddress,
useShippingAsBilling,
setUseShippingAsBilling,
setEditingBillingAddress,
} = useCheckoutAddress();
const { isEditor } = useEditorContext();
const isGuest = getSetting( 'currentUserId' ) === 0;
@ -116,10 +117,6 @@ const Block = ( {
const noticeContext = useShippingAsBilling
? [ noticeContexts.SHIPPING_ADDRESS, noticeContexts.BILLING_ADDRESS ]
: [ noticeContexts.SHIPPING_ADDRESS ];
const hasAddress = !! (
shippingAddress.address_1 &&
( shippingAddress.first_name || shippingAddress.last_name )
);
const { cartDataLoaded } = useSelect( ( select ) => {
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 (
<>
<StoreNoticesContainer context={ noticeContext } />
@ -138,7 +132,6 @@ const Block = ( {
{ cartDataLoaded ? (
<CustomerAddress
addressFieldsConfig={ addressFieldsConfig }
defaultEditing={ defaultEditingAddress }
/>
) : null }
</WrapperComponent>
@ -151,6 +144,7 @@ const Block = ( {
if ( checked ) {
syncBillingWithShipping();
} else {
setEditingBillingAddress( true );
clearBillingAddress( billingAddress );
}
} }

View File

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

View File

@ -17,4 +17,6 @@ export const ACTION_TYPES = {
SET_REDIRECT_URL: 'SET_REDIRECT_URL',
SET_SHOULD_CREATE_ACCOUNT: 'SET_SHOULD_CREATE_ACCOUNT',
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;

View File

@ -118,6 +118,30 @@ export const __internalSetUseShippingAsBilling = (
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
*
@ -182,6 +206,8 @@ export type CheckoutAction =
| typeof __internalSetCustomerId
| typeof __internalSetCustomerPassword
| typeof __internalSetUseShippingAsBilling
| typeof setEditingBillingAddress
| typeof setEditingShippingAddress
| typeof __internalSetShouldCreateAccount
| typeof __internalSetOrderNotes
| typeof setPrefersCollection

View File

@ -23,8 +23,28 @@ export type CheckoutState = {
shouldCreateAccount: boolean; // Should a user account be created?
status: STATUS; // Status of the checkout
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 = {
additionalFields: checkoutData.additional_fields || {},
calculatingCount: 0,
@ -38,8 +58,7 @@ export const defaultState: CheckoutState = {
redirectUrl: '',
shouldCreateAccount: false,
status: STATUS.IDLE,
useShippingAsBilling: isSameAddress(
checkoutData.billing_address,
checkoutData.shipping_address
),
useShippingAsBilling: billingMatchesShipping,
editingBillingAddress: ! hasBillingAddress,
editingShippingAddress: ! hasShippingAddress,
};

View File

@ -130,6 +130,20 @@ const reducer = ( state = defaultState, action: CheckoutAction ) => {
}
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:
if (
action.shouldCreateAccount !== undefined &&

View File

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

View File

@ -1,5 +1,7 @@
# Checkout Store (`wc/store/checkout`) <!-- omit in toc -->
<!-- markdownlint-disable MD024 -->
> 💡 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.
@ -173,6 +175,36 @@ const store = select( CHECKOUT_STORE_KEY );
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
Returns true if an error occurred, and false otherwise.
@ -293,7 +325,6 @@ const store = select( CHECKOUT_STORE_KEY );
const isCalculating = store.isCalculating();
```
### prefersCollection
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 );
```
### 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 -->
---

View File

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