From d8331dc3f1d0ba99767e27eef6eb5ebcf6e81e2a Mon Sep 17 00:00:00 2001 From: Thomas Roberts <5656702+opr@users.noreply.github.com> Date: Wed, 10 Jan 2024 06:20:06 -0800 Subject: [PATCH] Add additional information block for custom checkout fields (#43274) Co-authored-by: Mike Jolley Co-authored-by: github-actions --- .../js/base/context/event-emit/utils.ts | 1 + .../assets/js/blocks/checkout/index.tsx | 83 +++++++++++++++++++ .../attributes.tsx | 27 ++++++ .../block.json | 30 +++++++ .../block.tsx | 50 +++++++++++ .../edit.tsx | 47 +++++++++++ .../editor.scss | 12 +++ .../frontend.tsx | 56 +++++++++++++ .../index.tsx | 26 ++++++ .../style.scss | 29 +++++++ .../checkout-fields-block/edit.tsx | 1 + .../inner-blocks/component-metadata.ts | 2 + .../js/blocks/checkout/inner-blocks/index.tsx | 1 + .../inner-blocks/register-components.ts | 10 +++ .../js/settings/shared/default-fields.ts | 7 +- .../tests/e2e/bin/pages/checkout.html | 4 + .../43274-add-rebase-42081-additional-fields | 4 + .../woocommerce/includes/class-wc-install.php | 4 + .../src/Blocks/BlockTypes/Checkout.php | 16 +++- .../CheckoutAdditionalInformationBlock.php | 14 ++++ 20 files changed, 422 insertions(+), 2 deletions(-) create mode 100644 plugins/woocommerce-blocks/assets/js/blocks/checkout/inner-blocks/checkout-additional-information-block/attributes.tsx create mode 100644 plugins/woocommerce-blocks/assets/js/blocks/checkout/inner-blocks/checkout-additional-information-block/block.json create mode 100644 plugins/woocommerce-blocks/assets/js/blocks/checkout/inner-blocks/checkout-additional-information-block/block.tsx create mode 100644 plugins/woocommerce-blocks/assets/js/blocks/checkout/inner-blocks/checkout-additional-information-block/edit.tsx create mode 100644 plugins/woocommerce-blocks/assets/js/blocks/checkout/inner-blocks/checkout-additional-information-block/editor.scss create mode 100644 plugins/woocommerce-blocks/assets/js/blocks/checkout/inner-blocks/checkout-additional-information-block/frontend.tsx create mode 100644 plugins/woocommerce-blocks/assets/js/blocks/checkout/inner-blocks/checkout-additional-information-block/index.tsx create mode 100644 plugins/woocommerce-blocks/assets/js/blocks/checkout/inner-blocks/checkout-additional-information-block/style.scss create mode 100644 plugins/woocommerce/changelog/43274-add-rebase-42081-additional-fields create mode 100644 plugins/woocommerce/src/Blocks/BlockTypes/CheckoutAdditionalInformationBlock.php diff --git a/plugins/woocommerce-blocks/assets/js/base/context/event-emit/utils.ts b/plugins/woocommerce-blocks/assets/js/base/context/event-emit/utils.ts index f9749e2d018..2e0f9db0d16 100644 --- a/plugins/woocommerce-blocks/assets/js/base/context/event-emit/utils.ts +++ b/plugins/woocommerce-blocks/assets/js/base/context/event-emit/utils.ts @@ -35,6 +35,7 @@ export enum noticeContexts { BILLING_ADDRESS = 'wc/checkout/billing-address', SHIPPING_METHODS = 'wc/checkout/shipping-methods', CHECKOUT_ACTIONS = 'wc/checkout/checkout-actions', + ADDITIONAL_INFORMATION = 'wc/checkout/additional-information', } export interface ResponseType extends Record< string, unknown > { diff --git a/plugins/woocommerce-blocks/assets/js/blocks/checkout/index.tsx b/plugins/woocommerce-blocks/assets/js/blocks/checkout/index.tsx index 7503bfb8b5e..4b289d7ec4a 100644 --- a/plugins/woocommerce-blocks/assets/js/blocks/checkout/index.tsx +++ b/plugins/woocommerce-blocks/assets/js/blocks/checkout/index.tsx @@ -116,6 +116,11 @@ const settings = { {}, [] ), + createBlock( + 'woocommerce/checkout-additional-information-block', + {}, + [] + ), showOrderNotes ? createBlock( 'woocommerce/checkout-order-note-block', @@ -154,6 +159,84 @@ const settings = { ); }, }, + // Adds the additional information block. + { + save( { attributes }: { attributes: { className: string } } ) { + return ( +
+ ); + }, + isEligible: ( + _attributes: Record< string, unknown >, + innerBlocks: BlockInstance[] + ) => { + const checkoutFieldsBlock = innerBlocks.find( + ( block: { name: string } ) => + block.name === 'woocommerce/checkout-fields-block' + ); + + if ( ! checkoutFieldsBlock ) { + return false; + } + + // Top level block is the fields block, we then need to search within that for the additional information block. + return ! checkoutFieldsBlock.innerBlocks.some( + ( block: { name: string } ) => + block.name === + 'woocommerce/checkout-additional-information-block' + ); + }, + migrate: ( + attributes: Record< string, unknown >, + innerBlocks: BlockInstance[] + ) => { + const checkoutFieldsBlockIndex = innerBlocks.findIndex( + ( block: { name: string } ) => + block.name === 'woocommerce/checkout-fields-block' + ); + + if ( checkoutFieldsBlockIndex === -1 ) { + return false; + } + + const checkoutFieldsBlock = + innerBlocks[ checkoutFieldsBlockIndex ]; + + const insertIndex = checkoutFieldsBlock.innerBlocks.findIndex( + ( block: { name: string } ) => + block.name === + 'wp-block-woocommerce-checkout-payment-block' + ); + + if ( insertIndex === -1 ) { + return false; + } + + innerBlocks[ checkoutFieldsBlockIndex ] = + checkoutFieldsBlock.innerBlocks + .slice( 0, insertIndex ) + .concat( + createBlock( + 'woocommerce/checkout-additional-information-block', + {}, + [] + ) + ) + .concat( + innerBlocks.slice( + insertIndex + 1, + innerBlocks.length + ) + ); + + return [ attributes, innerBlocks ]; + }, + }, ], }; diff --git a/plugins/woocommerce-blocks/assets/js/blocks/checkout/inner-blocks/checkout-additional-information-block/attributes.tsx b/plugins/woocommerce-blocks/assets/js/blocks/checkout/inner-blocks/checkout-additional-information-block/attributes.tsx new file mode 100644 index 00000000000..b75c221be0b --- /dev/null +++ b/plugins/woocommerce-blocks/assets/js/blocks/checkout/inner-blocks/checkout-additional-information-block/attributes.tsx @@ -0,0 +1,27 @@ +/** + * External dependencies + */ +import { __ } from '@wordpress/i18n'; + +/** + * Internal dependencies + */ +import formStepAttributes from '../../form-step/attributes'; + +export default { + ...formStepAttributes( { + defaultTitle: __( 'Additional order information', 'woocommerce' ), + defaultDescription: '', + } ), + className: { + type: 'string', + default: '', + }, + lock: { + type: 'object', + default: { + move: true, + remove: true, + }, + }, +}; diff --git a/plugins/woocommerce-blocks/assets/js/blocks/checkout/inner-blocks/checkout-additional-information-block/block.json b/plugins/woocommerce-blocks/assets/js/blocks/checkout/inner-blocks/checkout-additional-information-block/block.json new file mode 100644 index 00000000000..34b6a6d668a --- /dev/null +++ b/plugins/woocommerce-blocks/assets/js/blocks/checkout/inner-blocks/checkout-additional-information-block/block.json @@ -0,0 +1,30 @@ +{ + "name": "woocommerce/checkout-additional-information-block", + "version": "1.0.0", + "title": "Additional information", + "description": "Render additional fields in the 'Additional information' location.", + "category": "woocommerce", + "supports": { + "align": false, + "html": false, + "multiple": false, + "reusable": false + }, + "attributes": { + "className": { + "type": "string", + "default": "" + }, + "lock": { + "type": "object", + "default": { + "remove": true, + "move": false + } + } + }, + "parent": [ "woocommerce/checkout-fields-block" ], + "textdomain": "woocommerce", + "$schema": "https://schemas.wp.org/trunk/block.json", + "apiVersion": 2 +} diff --git a/plugins/woocommerce-blocks/assets/js/blocks/checkout/inner-blocks/checkout-additional-information-block/block.tsx b/plugins/woocommerce-blocks/assets/js/blocks/checkout/inner-blocks/checkout-additional-information-block/block.tsx new file mode 100644 index 00000000000..4896e5efe1e --- /dev/null +++ b/plugins/woocommerce-blocks/assets/js/blocks/checkout/inner-blocks/checkout-additional-information-block/block.tsx @@ -0,0 +1,50 @@ +/** + * External dependencies + */ +import { noticeContexts } from '@woocommerce/base-context'; +import { StoreNoticesContainer } from '@woocommerce/blocks-components'; +import { useDispatch, useSelect } from '@wordpress/data'; +import { CHECKOUT_STORE_KEY } from '@woocommerce/block-data'; +import { ADDITIONAL_FORM_KEYS } from '@woocommerce/block-settings'; +import { Form } from '@woocommerce/base-components/cart-checkout'; +import type { FunctionComponent } from 'react'; + +const Block: FunctionComponent = () => { + const { additionalFields } = useSelect( ( select ) => { + const store = select( CHECKOUT_STORE_KEY ); + return { + additionalFields: store.getAdditionalFields(), + }; + } ); + + const { setAdditionalFields } = useDispatch( CHECKOUT_STORE_KEY ); + + const onChangeForm = ( additionalValues ) => { + setAdditionalFields( additionalValues ); + }; + + const additionalFieldValues = { + ...additionalFields, + }; + + if ( ADDITIONAL_FORM_KEYS.length === 0 ) { + return null; + } + + return ( + <> + +
+ + ); +}; + +export default Block; diff --git a/plugins/woocommerce-blocks/assets/js/blocks/checkout/inner-blocks/checkout-additional-information-block/edit.tsx b/plugins/woocommerce-blocks/assets/js/blocks/checkout/inner-blocks/checkout-additional-information-block/edit.tsx new file mode 100644 index 00000000000..a46244c1ed3 --- /dev/null +++ b/plugins/woocommerce-blocks/assets/js/blocks/checkout/inner-blocks/checkout-additional-information-block/edit.tsx @@ -0,0 +1,47 @@ +/** + * External dependencies + */ +import { useBlockProps } from '@wordpress/block-editor'; +import { FormStepBlock } from '@woocommerce/blocks/checkout/form-step'; +import classnames from 'classnames'; +import { ADDITIONAL_FORM_KEYS } from '@woocommerce/block-settings'; + +/** + * Internal dependencies + */ +import Block from './block'; +import './editor.scss'; + +export const Edit = ( { + attributes, + setAttributes, +}: { + attributes: { + title: string; + description: string; + showStepNumber: boolean; + className: string; + }; + setAttributes: ( attributes: Record< string, unknown > ) => void; +} ) => { + if ( ADDITIONAL_FORM_KEYS.length === 0 ) { + return null; + } + + return ( + + + + ); +}; + +export const Save = (): JSX.Element => { + return
; +}; diff --git a/plugins/woocommerce-blocks/assets/js/blocks/checkout/inner-blocks/checkout-additional-information-block/editor.scss b/plugins/woocommerce-blocks/assets/js/blocks/checkout/inner-blocks/checkout-additional-information-block/editor.scss new file mode 100644 index 00000000000..edda61aaa3d --- /dev/null +++ b/plugins/woocommerce-blocks/assets/js/blocks/checkout/inner-blocks/checkout-additional-information-block/editor.scss @@ -0,0 +1,12 @@ +// Adjust padding and margins in the editor to improve selected block outlines. +.wp-block-woocommerce-checkout-order-note-block { + margin-top: 20px; + margin-bottom: 20px; + padding-top: 4px; + padding-bottom: 4px; + + .wc-block-checkout__add-note { + margin-top: 0; + margin-bottom: 0; + } +} diff --git a/plugins/woocommerce-blocks/assets/js/blocks/checkout/inner-blocks/checkout-additional-information-block/frontend.tsx b/plugins/woocommerce-blocks/assets/js/blocks/checkout/inner-blocks/checkout-additional-information-block/frontend.tsx new file mode 100644 index 00000000000..aa7ba49d37e --- /dev/null +++ b/plugins/woocommerce-blocks/assets/js/blocks/checkout/inner-blocks/checkout-additional-information-block/frontend.tsx @@ -0,0 +1,56 @@ +/** + * External dependencies + */ +import classnames from 'classnames'; +import { FormStep } from '@woocommerce/blocks-components'; +import { ADDITIONAL_FORM_KEYS } from '@woocommerce/block-settings'; +import { useSelect } from '@wordpress/data'; +import { CHECKOUT_STORE_KEY } from '@woocommerce/block-data'; +import { withFilteredAttributes } from '@woocommerce/shared-hocs'; + +/** + * Internal dependencies + */ +import Block from './block'; +import attributes from './attributes'; + +const FrontendBlock = ( { + title, + description, + showStepNumber, + children, + className, +}: { + title: string; + description: string; + showStepNumber: boolean; + children: JSX.Element; + className?: string; +} ) => { + const checkoutIsProcessing = useSelect( ( select ) => + select( CHECKOUT_STORE_KEY ).isProcessing() + ); + + if ( ADDITIONAL_FORM_KEYS.length === 0 ) { + return null; + } + + return ( + + + { children } + + ); +}; + +export default withFilteredAttributes( attributes )( FrontendBlock ); diff --git a/plugins/woocommerce-blocks/assets/js/blocks/checkout/inner-blocks/checkout-additional-information-block/index.tsx b/plugins/woocommerce-blocks/assets/js/blocks/checkout/inner-blocks/checkout-additional-information-block/index.tsx new file mode 100644 index 00000000000..231a6574290 --- /dev/null +++ b/plugins/woocommerce-blocks/assets/js/blocks/checkout/inner-blocks/checkout-additional-information-block/index.tsx @@ -0,0 +1,26 @@ +/** + * External dependencies + */ +import { Icon, customPostType } from '@wordpress/icons'; +import { registerBlockType } from '@wordpress/blocks'; + +/** + * Internal dependencies + */ +import { Edit, Save } from './edit'; +import './style.scss'; +import attributes from './attributes'; + +registerBlockType( 'woocommerce/checkout-additional-information-block', { + attributes, + icon: { + src: ( + + ), + }, + edit: Edit, + save: Save, +} ); diff --git a/plugins/woocommerce-blocks/assets/js/blocks/checkout/inner-blocks/checkout-additional-information-block/style.scss b/plugins/woocommerce-blocks/assets/js/blocks/checkout/inner-blocks/checkout-additional-information-block/style.scss new file mode 100644 index 00000000000..5c122f330e6 --- /dev/null +++ b/plugins/woocommerce-blocks/assets/js/blocks/checkout/inner-blocks/checkout-additional-information-block/style.scss @@ -0,0 +1,29 @@ +.wc-block-checkout__add-note { + margin: em($gap-large) 0; +} + +.is-mobile, +.is-small, +.is-medium { + .wc-block-checkout__add-note { + border-bottom: 1px solid $universal-border-light; + margin-bottom: em($gap); + margin-top: em($gap); + padding: em($gap) 0; + } +} + +.wc-block-checkout__add-note .wc-block-components-textarea { + margin-top: $gap; + + &:focus { + background-color: #fff; + color: $input-text-active; + outline: 0; + box-shadow: 0 0 0 1px $input-border-gray; + } +} + +.wc-block-components-form .wc-block-checkout__order-notes.wc-block-components-checkout-step { + padding-left: 0; +} diff --git a/plugins/woocommerce-blocks/assets/js/blocks/checkout/inner-blocks/checkout-fields-block/edit.tsx b/plugins/woocommerce-blocks/assets/js/blocks/checkout/inner-blocks/checkout-fields-block/edit.tsx index 86738fdb13d..d7a5a1f9645 100644 --- a/plugins/woocommerce-blocks/assets/js/blocks/checkout/inner-blocks/checkout-fields-block/edit.tsx +++ b/plugins/woocommerce-blocks/assets/js/blocks/checkout/inner-blocks/checkout-fields-block/edit.tsx @@ -47,6 +47,7 @@ export const Edit = ( { [ 'woocommerce/checkout-billing-address-block', {}, [] ], [ 'woocommerce/checkout-shipping-methods-block', {}, [] ], [ 'woocommerce/checkout-payment-block', {}, [] ], + [ 'woocommerce/checkout-additional-information-block', {}, [] ], [ 'woocommerce/checkout-order-note-block', {}, [] ], [ 'woocommerce/checkout-terms-block', {}, [] ], [ 'woocommerce/checkout-actions-block', {}, [] ], diff --git a/plugins/woocommerce-blocks/assets/js/blocks/checkout/inner-blocks/component-metadata.ts b/plugins/woocommerce-blocks/assets/js/blocks/checkout/inner-blocks/component-metadata.ts index d117b7c3759..ad2f2f0350d 100644 --- a/plugins/woocommerce-blocks/assets/js/blocks/checkout/inner-blocks/component-metadata.ts +++ b/plugins/woocommerce-blocks/assets/js/blocks/checkout/inner-blocks/component-metadata.ts @@ -2,6 +2,7 @@ * Internal dependencies */ import CHECKOUT_ACTIONS from './checkout-actions-block/block.json'; +import CHECKOUT_ADDITIONAL_INFORMATION from './checkout-additional-information-block/block.json'; import CHECKOUT_BILLING_ADDRESS from './checkout-billing-address-block/block.json'; import CHECKOUT_CONTACT_INFORMATION from './checkout-contact-information-block/block.json'; import CHECKOUT_EXPRESS_PAYMENT from './checkout-express-payment-block/block.json'; @@ -25,6 +26,7 @@ import CHECKOUT_ORDER_SUMMARY_CART_ITEMS from './checkout-order-summary-cart-ite export default { CHECKOUT_ACTIONS, + CHECKOUT_ADDITIONAL_INFORMATION, CHECKOUT_BILLING_ADDRESS, CHECKOUT_CONTACT_INFORMATION, CHECKOUT_EXPRESS_PAYMENT, diff --git a/plugins/woocommerce-blocks/assets/js/blocks/checkout/inner-blocks/index.tsx b/plugins/woocommerce-blocks/assets/js/blocks/checkout/inner-blocks/index.tsx index c995b7ddb4e..df3fa59846c 100644 --- a/plugins/woocommerce-blocks/assets/js/blocks/checkout/inner-blocks/index.tsx +++ b/plugins/woocommerce-blocks/assets/js/blocks/checkout/inner-blocks/index.tsx @@ -8,6 +8,7 @@ import './checkout-terms-block'; import './checkout-contact-information-block'; import './checkout-billing-address-block'; import './checkout-actions-block'; +import './checkout-additional-information-block'; import './checkout-order-note-block'; import './checkout-order-summary-block'; import './checkout-payment-block'; diff --git a/plugins/woocommerce-blocks/assets/js/blocks/checkout/inner-blocks/register-components.ts b/plugins/woocommerce-blocks/assets/js/blocks/checkout/inner-blocks/register-components.ts index 0937dbea6b6..73e7b53d2d5 100644 --- a/plugins/woocommerce-blocks/assets/js/blocks/checkout/inner-blocks/register-components.ts +++ b/plugins/woocommerce-blocks/assets/js/blocks/checkout/inner-blocks/register-components.ts @@ -109,6 +109,16 @@ registerCheckoutBlock( { ), } ); +registerCheckoutBlock( { + metadata: metadata.CHECKOUT_ADDITIONAL_INFORMATION, + component: lazy( + () => + import( + /* webpackChunkName: "checkout-blocks/additional-information" */ './checkout-additional-information-block/frontend' + ) + ), +} ); + registerCheckoutBlock( { metadata: metadata.CHECKOUT_ORDER_NOTE, component: lazy( diff --git a/plugins/woocommerce-blocks/assets/js/settings/shared/default-fields.ts b/plugins/woocommerce-blocks/assets/js/settings/shared/default-fields.ts index b45c84bf40a..8447c2d260d 100644 --- a/plugins/woocommerce-blocks/assets/js/settings/shared/default-fields.ts +++ b/plugins/woocommerce-blocks/assets/js/settings/shared/default-fields.ts @@ -55,7 +55,12 @@ export type ContactForm = CoreContactForm & Record< string, FormField >; export type FormFields = AddressForm & ContactForm; export type AddressFormValues = Omit< ShippingAddress, 'email' >; export type ContactFormValues = { email: string }; -export type FormType = 'billing' | 'shipping' | 'contact'; +export type AdditionalInformationFormValues = Record< string, string >; +export type FormType = + | 'billing' + | 'shipping' + | 'contact' + | 'additional-information'; export interface CoreAddress { first_name: string; diff --git a/plugins/woocommerce-blocks/tests/e2e/bin/pages/checkout.html b/plugins/woocommerce-blocks/tests/e2e/bin/pages/checkout.html index fbb4cba9153..68baf602b8f 100644 --- a/plugins/woocommerce-blocks/tests/e2e/bin/pages/checkout.html +++ b/plugins/woocommerce-blocks/tests/e2e/bin/pages/checkout.html @@ -32,6 +32,10 @@
+ +
+ +
diff --git a/plugins/woocommerce/changelog/43274-add-rebase-42081-additional-fields b/plugins/woocommerce/changelog/43274-add-rebase-42081-additional-fields new file mode 100644 index 00000000000..28f5913dd2b --- /dev/null +++ b/plugins/woocommerce/changelog/43274-add-rebase-42081-additional-fields @@ -0,0 +1,4 @@ +Significance: patch +Type: add +Comment: Allow additional fields to be rendered at the end of the Checkout block. This is locked behind a feature flag, the fields won't show up until we remove the feature gate. + diff --git a/plugins/woocommerce/includes/class-wc-install.php b/plugins/woocommerce/includes/class-wc-install.php index a6ba15ddd25..fac2bdaaf80 100644 --- a/plugins/woocommerce/includes/class-wc-install.php +++ b/plugins/woocommerce/includes/class-wc-install.php @@ -2589,6 +2589,10 @@ EOT;
+ +
+ +
diff --git a/plugins/woocommerce/src/Blocks/BlockTypes/Checkout.php b/plugins/woocommerce/src/Blocks/BlockTypes/Checkout.php index 01a6f8359b9..2f394b6c5f2 100644 --- a/plugins/woocommerce/src/Blocks/BlockTypes/Checkout.php +++ b/plugins/woocommerce/src/Blocks/BlockTypes/Checkout.php @@ -169,7 +169,8 @@ class Checkout extends AbstractBlock {
-
' . +
+
' . ( isset( $attributes['showOrderNotes'] ) && false === $attributes['showOrderNotes'] ? '' : '
' ) . ( isset( $attributes['showPolicyLinks'] ) && false === $attributes['showPolicyLinks'] ? '' : '
' ) . '
@@ -217,6 +218,18 @@ class Checkout extends AbstractBlock { $content = preg_replace( $shipping_address_block_regex, $local_pickup_inner_blocks, $content ); } + /** + * Add the Additional Information block to checkouts missing it. + */ + $additional_information_inner_blocks = '$0' . PHP_EOL . PHP_EOL . '
' . PHP_EOL . PHP_EOL; + $has_additional_information_regex = '/]*?>/mi'; + $has_additional_information_block = preg_match( $has_additional_information_regex, $content ); + + if ( ! $has_additional_information_block ) { + $payment_block_regex = '/]*?><\/div>/mi'; + $content = preg_replace( $payment_block_regex, $additional_information_inner_blocks, $content ); + } + return $content; } @@ -480,6 +493,7 @@ class Checkout extends AbstractBlock { return [ 'Checkout', 'CheckoutActionsBlock', + 'CheckoutAdditionalInformationBlock', 'CheckoutBillingAddressBlock', 'CheckoutContactInformationBlock', 'CheckoutExpressPaymentBlock', diff --git a/plugins/woocommerce/src/Blocks/BlockTypes/CheckoutAdditionalInformationBlock.php b/plugins/woocommerce/src/Blocks/BlockTypes/CheckoutAdditionalInformationBlock.php new file mode 100644 index 00000000000..72f71c0e7c5 --- /dev/null +++ b/plugins/woocommerce/src/Blocks/BlockTypes/CheckoutAdditionalInformationBlock.php @@ -0,0 +1,14 @@ +