Add additional information block for custom checkout fields (#43274)

Co-authored-by: Mike Jolley <mike.jolley@me.com>
Co-authored-by: github-actions <github-actions@github.com>
This commit is contained in:
Thomas Roberts 2024-01-10 06:20:06 -08:00 committed by GitHub
parent 5451d1ef23
commit d8331dc3f1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
20 changed files with 422 additions and 2 deletions

View File

@ -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 > {

View File

@ -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 (
<div
className={ classnames(
'is-loading',
attributes.className
) }
/>
);
},
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 ];
},
},
],
};

View File

@ -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,
},
},
};

View File

@ -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
}

View File

@ -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 (
<>
<StoreNoticesContainer
context={ noticeContexts.ADDITIONAL_INFORMATION }
/>
<Form
id="additional-information"
addressType="additional-information"
onChange={ onChangeForm }
values={ additionalFieldValues }
fields={ ADDITIONAL_FORM_KEYS }
/>
</>
);
};
export default Block;

View File

@ -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 (
<FormStepBlock
setAttributes={ setAttributes }
attributes={ attributes }
className={ classnames(
'wc-block-checkout__additional-information-fields',
attributes?.className
) }
>
<Block />
</FormStepBlock>
);
};
export const Save = (): JSX.Element => {
return <div { ...useBlockProps.save() } />;
};

View File

@ -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;
}
}

View File

@ -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 (
<FormStep
id="additional-information-fields"
disabled={ checkoutIsProcessing }
className={ classnames(
'wc-block-checkout__additional-information-fields',
className
) }
title={ title }
description={ description }
showStepNumber={ showStepNumber }
>
<Block />
{ children }
</FormStep>
);
};
export default withFilteredAttributes( attributes )( FrontendBlock );

View File

@ -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: (
<Icon
icon={ customPostType }
className="wc-block-editor-components-block-icon"
/>
),
},
edit: Edit,
save: Save,
} );

View File

@ -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;
}

View File

@ -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', {}, [] ],

View File

@ -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,

View File

@ -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';

View File

@ -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(

View File

@ -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;

View File

@ -32,6 +32,10 @@
<div class="wp-block-woocommerce-checkout-payment-block"></div>
<!-- /wp:woocommerce/checkout-payment-block -->
<!-- wp:woocommerce/checkout-additional-information-block -->
<div class="wp-block-woocommerce-checkout-additional-information-block"></div>
<!-- /wp:woocommerce/checkout-additional-information-block -->
<!-- wp:woocommerce/checkout-order-note-block -->
<div class="wp-block-woocommerce-checkout-order-note-block"></div>
<!-- /wp:woocommerce/checkout-order-note-block -->

View File

@ -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.

View File

@ -2589,6 +2589,10 @@ EOT;
<div class="wp-block-woocommerce-checkout-payment-block"></div>
<!-- /wp:woocommerce/checkout-payment-block -->
<!-- wp:woocommerce/checkout-additional-information-block -->
<div class="wp-block-woocommerce-checkout-additional-information-block"></div>
<!-- /wp:woocommerce/checkout-additional-information-block -->
<!-- wp:woocommerce/checkout-order-note-block -->
<div class="wp-block-woocommerce-checkout-order-note-block"></div>
<!-- /wp:woocommerce/checkout-order-note-block -->

View File

@ -169,7 +169,8 @@ class Checkout extends AbstractBlock {
<div data-block-name="woocommerce/checkout-shipping-address-block" class="wp-block-woocommerce-checkout-shipping-address-block"></div>
<div data-block-name="woocommerce/checkout-billing-address-block" class="wp-block-woocommerce-checkout-billing-address-block"></div>
<div data-block-name="woocommerce/checkout-shipping-methods-block" class="wp-block-woocommerce-checkout-shipping-methods-block"></div>
<div data-block-name="woocommerce/checkout-payment-block" class="wp-block-woocommerce-checkout-payment-block"></div>' .
<div data-block-name="woocommerce/checkout-payment-block" class="wp-block-woocommerce-checkout-payment-block"></div>
<div data-block-name="woocommerce/checkout-additional-information-block" class="wp-block-woocommerce-checkout-additional-information-block"></div>' .
( isset( $attributes['showOrderNotes'] ) && false === $attributes['showOrderNotes'] ? '' : '<div data-block-name="woocommerce/checkout-order-note-block" class="wp-block-woocommerce-checkout-order-note-block"></div>' ) .
( isset( $attributes['showPolicyLinks'] ) && false === $attributes['showPolicyLinks'] ? '' : '<div data-block-name="woocommerce/checkout-terms-block" class="wp-block-woocommerce-checkout-terms-block"></div>' ) .
'<div data-block-name="woocommerce/checkout-actions-block" class="wp-block-woocommerce-checkout-actions-block"></div>
@ -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 . '<div data-block-name="woocommerce/checkout-additional-information-block" class="wp-block-woocommerce-checkout-additional-information-block"></div>' . PHP_EOL . PHP_EOL;
$has_additional_information_regex = '/<div[^<]*?data-block-name="woocommerce\/checkout-additional-information-block"[^>]*?>/mi';
$has_additional_information_block = preg_match( $has_additional_information_regex, $content );
if ( ! $has_additional_information_block ) {
$payment_block_regex = '/<div[^<]*?data-block-name="woocommerce\/checkout-payment-block" class="wp-block-woocommerce-checkout-payment-block"[^>]*?><\/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',

View File

@ -0,0 +1,14 @@
<?php
namespace Automattic\WooCommerce\Blocks\BlockTypes;
/**
* CheckoutAdditionalInformationBlock class.
*/
class CheckoutAdditionalInformationBlock extends AbstractInnerBlock {
/**
* Block name.
*
* @var string
*/
protected $block_name = 'checkout-additional-information-block';
}