Skip to content
This repository has been archived by the owner on Feb 23, 2024. It is now read-only.

Introduce Additional Fields extensibility API. #12073

Open
wants to merge 46 commits into
base: trunk
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 33 commits
Commits
Show all changes
46 commits
Select commit Hold shift + click to select a range
e1e8306
move address to PHP side
senadir Nov 7, 2023
50c372a
support showing fields from server
senadir Nov 16, 2023
33a07c1
add placeholders to code
senadir Dec 5, 2023
6f90954
add data reading and saving functions
senadir Dec 5, 2023
48ee2dd
Add woocommerce_blocks_register_checkout_field function
opr Dec 5, 2023
88ee619
Add example code for registering a field
opr Dec 5, 2023
95cf895
Create register_checkout_field function
opr Dec 5, 2023
164f469
finish all code handling
senadir Dec 5, 2023
c09894c
fix all code errors
senadir Dec 5, 2023
9e70517
Require functions from Domain/Services
opr Dec 6, 2023
17a604a
fix how default address fields was used
senadir Dec 6, 2023
5140eaa
Update order address with additional fields
opr Dec 6, 2023
25f4394
Update default locale with fields without country limitation
opr Dec 6, 2023
d1fcd85
Update country locale with fields with country limitations
opr Dec 6, 2023
4ab27fd
Allow country_limitation to be specified when registering fields
opr Dec 6, 2023
8f0c8b4
persist user meta
senadir Dec 6, 2023
2f0c1b3
Remove country limitation specific code
opr Dec 7, 2023
42fbad6
add support for saving data in session
senadir Dec 7, 2023
a368f42
add todo to copy fields upon acount creation
senadir Dec 7, 2023
d323cd1
Update comment typo
opr Dec 7, 2023
443a133
Move address field fetching to order controller
opr Dec 7, 2023
fa8e4c8
Remove hardcoded test fields
opr Dec 7, 2023
d48c613
Ensure fields are added to correct place when registered
opr Dec 7, 2023
36f1460
Ensure fields are updated when changing custom value for first time
opr Dec 7, 2023
041d724
Do not create field registration function unless build is experimental
opr Dec 7, 2023
552b0f5
clean up code
senadir Dec 8, 2023
60fb0b6
clean up checkoutFields functions
senadir Dec 8, 2023
8ccaee8
rename get_fields
senadir Dec 8, 2023
3a02c06
handle review comments
senadir Dec 8, 2023
6c14f15
fix rebase error
senadir Dec 8, 2023
58d422b
fix unit test
senadir Dec 8, 2023
c416753
remove todo
senadir Dec 8, 2023
9d35d14
move fields defaults to jest
senadir Dec 8, 2023
856cf89
Ensure shipping address includes additional fields
opr Dec 8, 2023
6361137
Ensure billing address includes additional fields
opr Dec 8, 2023
3641977
Ensure default cart state includes additional fields
opr Dec 8, 2023
0fdfd6c
Revert "Ensure fields are updated when changing custom value for firs…
opr Dec 8, 2023
db497dc
fix default value for additional_fields key
senadir Dec 8, 2023
70bf9ef
Use default fields from constants and dont get setting again
opr Dec 8, 2023
5ada340
Ensure values have defaults if the config doesn't pass them
opr Dec 11, 2023
0ab9b3f
Show warning if a field is registered as hidden
opr Dec 11, 2023
704ce90
Fix lint error
opr Dec 11, 2023
9d25427
Ensure register field runs only when woocommerce_blocks_loaded fired
opr Dec 11, 2023
53694ad
only return registred fields in Store API schema
senadir Dec 11, 2023
f8567fb
move class calling to be inside classes
senadir Dec 11, 2023
b9aa65e
fix tests
senadir Dec 12, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -17,32 +17,22 @@ import {
import { useEffect, useMemo, useRef } from '@wordpress/element';
import { useInstanceId } from '@wordpress/compose';
import { useShallowEqual } from '@woocommerce/base-hooks';
import { defaultAddressFields } from '@woocommerce/settings';
import isShallowEqual from '@wordpress/is-shallow-equal';

/**
* Internal dependencies
*/
import {
AddressFormProps,
FieldType,
FieldConfig,
AddressFormFields,
} from './types';
import { AddressFormProps, FieldConfig, AddressFormFields } from './types';
import prepareAddressFields from './prepare-address-fields';
import validateShippingCountry from './validate-shipping-country';
import customValidationHandler from './custom-validation-handler';

const defaultFields = Object.keys(
defaultAddressFields
) as unknown as FieldType[];

/**
* Checkout address form.
*/
const AddressForm = ( {
id = '',
fields = defaultFields,
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Passing the whole fields as default no longer make sense, fields is now required.

fields,
fieldConfig = {} as FieldConfig,
onChange,
type = 'shipping',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import {
AddressField,
AddressFields,
CountryAddressFields,
defaultAddressFields,
defaultFields,
mikejolley marked this conversation as resolved.
Show resolved Hide resolved
KeyedAddressField,
LocaleSpecificAddressField,
} from '@woocommerce/settings';
Expand Down Expand Up @@ -114,7 +114,7 @@ const prepareAddressFields = (

return fields
.map( ( field ) => {
const defaultConfig = defaultAddressFields[ field ] || {};
const defaultConfig = defaultFields[ field ] || {};
const localeConfig = localeConfigs[ field ] || {};
const fieldConfig = fieldConfigs[ field ] || {};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { CheckoutProvider } from '@woocommerce/base-context';
import { useCheckoutAddress } from '@woocommerce/base-context/hooks';
import { ADDRESS_FIELDS_KEYS } from '@woocommerce/block-settings';

/**
* Internal dependencies
Expand Down Expand Up @@ -81,15 +82,14 @@ const inputAddress = async ( {

describe( 'AddressForm Component', () => {
const WrappedAddressForm = ( { type } ) => {
const { defaultAddressFields, setShippingAddress, shippingAddress } =
useCheckoutAddress();
const { setShippingAddress, shippingAddress } = useCheckoutAddress();

return (
<AddressForm
type={ type }
onChange={ setShippingAddress }
values={ shippingAddress }
fields={ Object.keys( defaultAddressFields ) }
fields={ ADDRESS_FIELDS_KEYS }
/>
);
};
Expand Down
6 changes: 3 additions & 3 deletions assets/js/base/context/hooks/use-checkout-address.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
* External dependencies
*/
import {
defaultAddressFields,
defaultFields,
AddressFields,
ShippingAddress,
BillingAddress,
Expand All @@ -26,7 +26,7 @@ interface CheckoutAddress {
setEmail: ( value: string ) => void;
useShippingAsBilling: boolean;
setUseShippingAsBilling: ( useShippingAsBilling: boolean ) => void;
defaultAddressFields: AddressFields;
defaultFields: AddressFields;
showShippingFields: boolean;
showBillingFields: boolean;
forcedBillingAddress: boolean;
Expand Down Expand Up @@ -74,7 +74,7 @@ export const useCheckoutAddress = (): CheckoutAddress => {
setShippingAddress,
setBillingAddress,
setEmail,
defaultAddressFields,
defaultFields,
useShippingAsBilling,
setUseShippingAsBilling: __internalSetUseShippingAsBilling,
needsShipping,
Expand Down
33 changes: 15 additions & 18 deletions assets/js/base/utils/address.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,16 +7,12 @@ import type {
CartResponseBillingAddress,
CartResponseShippingAddress,
} from '@woocommerce/types';
import {
AddressFields,
defaultAddressFields,
ShippingAddress,
BillingAddress,
} from '@woocommerce/settings';
import { ShippingAddress, BillingAddress } from '@woocommerce/settings';
import { decodeEntities } from '@wordpress/html-entities';
import {
SHIPPING_COUNTRIES,
SHIPPING_STATES,
ADDRESS_FIELDS_KEYS,
} from '@woocommerce/block-settings';

/**
Expand All @@ -26,10 +22,9 @@ export const isSameAddress = < T extends ShippingAddress | BillingAddress >(
address1: T,
address2: T
): boolean => {
return Object.keys( defaultAddressFields ).every(
( field: string ) =>
address1[ field as keyof T ] === address2[ field as keyof T ]
);
return Object.keys( ADDRESS_FIELDS_KEYS ).every( ( field: string ) => {
return address1[ field as keyof T ] === address2[ field as keyof T ];
} );
Comment on lines +25 to +27
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This would previously check against all fields, it's changed to only check address.

};

/**
Expand Down Expand Up @@ -94,10 +89,11 @@ export const emptyHiddenAddressFields = <
>(
address: T
): T => {
const fields = Object.keys(
defaultAddressFields
) as ( keyof AddressFields )[];
const addressFields = prepareAddressFields( fields, {}, address.country );
const addressFields = prepareAddressFields(
ADDRESS_FIELDS_KEYS,
{},
address.country
);
const newAddress = Object.assign( {}, address ) as T;

addressFields.forEach( ( { key = '', hidden = false } ) => {
Expand Down Expand Up @@ -160,10 +156,11 @@ export const isAddressComplete = (
if ( ! address.country ) {
return false;
}
const fields = Object.keys(
defaultAddressFields
) as ( keyof AddressFields )[];
const addressFields = prepareAddressFields( fields, {}, address.country );
const addressFields = prepareAddressFields(
ADDRESS_FIELDS_KEYS,
{},
address.country
);

return addressFields.every(
( { key = '', hidden = false, required = false } ) => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
} from '@woocommerce/settings';
import { useSelect } from '@wordpress/data';
import { VALIDATION_STORE_KEY } from '@woocommerce/block-data';
import { ADDRESS_FIELDS_KEYS } from '@woocommerce/block-settings';

/**
* Internal dependencies
Expand All @@ -26,7 +27,6 @@
defaultEditing?: boolean;
} ) => {
const {
defaultAddressFields,
billingAddress,
setShippingAddress,
setBillingAddress,
Expand Down Expand Up @@ -58,10 +58,6 @@
}
}, [ editing, hasValidationErrors, invalidProps.length ] );

const addressFieldKeys = Object.keys(
defaultAddressFields
) as ( keyof AddressFields )[];

const onChangeAddress = useCallback(
( values: Partial< BillingAddress > ) => {
setBillingAddress( values );
Expand Down Expand Up @@ -101,17 +97,16 @@
type="billing"
onChange={ onChangeAddress }
values={ billingAddress }
fields={ addressFieldKeys }
fields={ ADDRESS_FIELDS_KEYS }
fieldConfig={ addressFieldsConfig }
/>
</>
),
[
addressFieldKeys,
addressFieldsConfig,
billingAddress,
onChangeAddress,
]

Check failure on line 109 in assets/js/blocks/checkout/inner-blocks/checkout-billing-address-block/customer-address.tsx

View workflow job for this annotation

GitHub Actions / ESLint Report Analysis

assets/js/blocks/checkout/inner-blocks/checkout-billing-address-block/customer-address.tsx#L105-L109

[prettier/prettier] Replace `⏎↹↹↹addressFieldsConfig,⏎↹↹↹billingAddress,⏎↹↹↹onChangeAddress,⏎↹↹` with `·addressFieldsConfig,·billingAddress,·onChangeAddress·`
);

return (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import type {
} from '@woocommerce/settings';
import { useSelect } from '@wordpress/data';
import { VALIDATION_STORE_KEY } from '@woocommerce/block-data';
import { ADDRESS_FIELDS_KEYS } from '@woocommerce/block-settings';

/**
* Internal dependencies
Expand All @@ -26,7 +27,6 @@ const CustomerAddress = ( {
defaultEditing?: boolean;
} ) => {
const {
defaultAddressFields,
shippingAddress,
setShippingAddress,
setBillingAddress,
Expand Down Expand Up @@ -57,9 +57,6 @@ const CustomerAddress = ( {
}
}, [ editing, hasValidationErrors, invalidProps.length ] );

const addressFieldKeys = Object.keys(
defaultAddressFields
) as ( keyof AddressFields )[];
const onChangeAddress = useCallback(
( values: Partial< ShippingAddress > ) => {
setShippingAddress( values );
Expand Down Expand Up @@ -98,16 +95,11 @@ const CustomerAddress = ( {
type="shipping"
onChange={ onChangeAddress }
values={ shippingAddress }
fields={ addressFieldKeys }
fields={ ADDRESS_FIELDS_KEYS }
fieldConfig={ addressFieldsConfig }
/>
),
[
addressFieldKeys,
addressFieldsConfig,
onChangeAddress,
shippingAddress,
]
[ addressFieldsConfig, onChangeAddress, shippingAddress ]
);

return (
Expand Down
26 changes: 19 additions & 7 deletions assets/js/data/cart/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,8 @@ export const shippingAddressHasValidationErrors = () => {

export type BaseAddressKey =
| keyof CartBillingAddress
| keyof CartShippingAddress;
| keyof CartShippingAddress
| string; // string here because custom checkout fields can be added with arbitrary keys.

/**
* Normalizes address values before push.
Expand Down Expand Up @@ -82,12 +83,23 @@ export const getDirtyKeys = <
previousAddress
) as BaseAddressKey[];

return previousAddressKeys.filter( ( key: BaseAddressKey ) => {
return (
normalizeAddressProp( key, previousAddress[ key ] ) !==
normalizeAddressProp( key, address[ key ] )
);
} );
const addedKeys = Object.keys( address ).filter(
( key ) => ! previousAddressKeys.includes( key )
);

const removedKeys = previousAddressKeys.filter(
( key ) => ! Object.keys( address ).includes( key )
);

return previousAddressKeys
.filter( ( key: BaseAddressKey ) => {
return (
normalizeAddressProp( key, previousAddress[ key ] ) !==
normalizeAddressProp( key, address[ key ] )
);
} )
.concat( removedKeys )
.concat( addedKeys );
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you add an inline comment explaining why this is needed?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

cc @opr as I'm also not sure what this mean, but it helped get additional fields picked up and pushed.

};

/**
Expand Down
38 changes: 38 additions & 0 deletions assets/js/settings/blocks/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,12 @@ type CountryData = {
locale: Record< string, LocaleSpecificAddressField >;
};

type FieldsLocations = {
address: string[];
contact: string[];
additional: string[];
};

// Contains country names.
const countries = getSetting< Record< string, string > >( 'countries', {} );

Expand Down Expand Up @@ -111,3 +117,35 @@ export const COUNTRY_LOCALE = Object.fromEntries(
return [ countryCode, countryData[ countryCode ].locale || [] ];
} )
);

const defaultFieldsLocations: FieldsLocations = {
address: [
'first_name',
'last_name',
'company',
'address_1',
'address_2',
'city',
'postcode',
'country',
'state',
'phone',
],
contact: [ 'email' ],
additional: [],
};
Comment on lines +121 to +136
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This default alone isn't enough actually, because without field definition, Checkout would still not render right.


export const ADDRESS_FIELDS_KEYS = getSetting< FieldsLocations >(
'addressFieldsLocations',
defaultFieldsLocations
).address;

export const CONTACT_FIELDS_KEYS = getSetting< FieldsLocations >(
'addressFieldsLocations',
defaultFieldsLocations
).contact;

export const ADDITIONAL_FIELDS_KEYS = getSetting< FieldsLocations >(
'addressFieldsLocations',
defaultFieldsLocations
).additional;
Loading
Loading