From 42cc482ebcb1b9854a3b3af4231835be91098380 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maikel=20David=20P=C3=A9rez=20G=C3=B3mez?= Date: Fri, 14 Apr 2023 22:44:28 -0400 Subject: [PATCH] Create shipping fee field block and initial shipping section (#37642) * Setting up the Fees & dimensions section * Create product shipping fee block * Register product shipping fee block * Add changelog files * Fix php linter errors * Add reusable radio field and move the radio block to the blocks folder * Remove manually set block className because is autogenerated base on the block name --- .../js/product-editor/changelog/add-37406 | 4 + .../{components => blocks}/radio/block.json | 3 +- .../product-editor/src/blocks/radio/edit.tsx | 35 ++++ .../product-editor/src/blocks/radio/index.ts | 26 +++ .../product-editor/src/blocks/radio/types.ts | 11 ++ .../src/blocks/shipping-fee/block.json | 25 +++ .../src/blocks/shipping-fee/edit.tsx | 184 ++++++++++++++++++ .../src/blocks/shipping-fee/editor.scss | 11 ++ .../src/blocks/shipping-fee/index.ts | 29 +++ .../src/blocks/shipping-fee/types.ts | 8 + .../src/components/editor/init-blocks.ts | 4 +- .../src/components/radio-field/index.ts | 2 + .../components/radio-field/radio-field.tsx | 41 ++++ .../src/components/radio-field/style.scss | 26 +++ .../src/components/radio-field/types.ts | 9 + .../src/components/radio/edit.tsx | 46 ----- .../src/components/radio/editor.scss | 26 --- .../src/components/radio/index.ts | 17 -- .../src/components/section/block.json | 3 +- packages/js/product-editor/src/style.scss | 3 +- plugins/woocommerce/changelog/add-37406 | 4 + .../includes/class-wc-post-types.php | 16 +- 22 files changed, 437 insertions(+), 96 deletions(-) create mode 100644 packages/js/product-editor/changelog/add-37406 rename packages/js/product-editor/src/{components => blocks}/radio/block.json (94%) create mode 100644 packages/js/product-editor/src/blocks/radio/edit.tsx create mode 100644 packages/js/product-editor/src/blocks/radio/index.ts create mode 100644 packages/js/product-editor/src/blocks/radio/types.ts create mode 100644 packages/js/product-editor/src/blocks/shipping-fee/block.json create mode 100644 packages/js/product-editor/src/blocks/shipping-fee/edit.tsx create mode 100644 packages/js/product-editor/src/blocks/shipping-fee/editor.scss create mode 100644 packages/js/product-editor/src/blocks/shipping-fee/index.ts create mode 100644 packages/js/product-editor/src/blocks/shipping-fee/types.ts create mode 100644 packages/js/product-editor/src/components/radio-field/index.ts create mode 100644 packages/js/product-editor/src/components/radio-field/radio-field.tsx create mode 100644 packages/js/product-editor/src/components/radio-field/style.scss create mode 100644 packages/js/product-editor/src/components/radio-field/types.ts delete mode 100644 packages/js/product-editor/src/components/radio/edit.tsx delete mode 100644 packages/js/product-editor/src/components/radio/editor.scss delete mode 100644 packages/js/product-editor/src/components/radio/index.ts create mode 100644 plugins/woocommerce/changelog/add-37406 diff --git a/packages/js/product-editor/changelog/add-37406 b/packages/js/product-editor/changelog/add-37406 new file mode 100644 index 00000000000..92839987096 --- /dev/null +++ b/packages/js/product-editor/changelog/add-37406 @@ -0,0 +1,4 @@ +Significance: minor +Type: add + +Add product shipping fee block diff --git a/packages/js/product-editor/src/components/radio/block.json b/packages/js/product-editor/src/blocks/radio/block.json similarity index 94% rename from packages/js/product-editor/src/components/radio/block.json rename to packages/js/product-editor/src/blocks/radio/block.json index 33dec4fcf11..c675ee232b6 100644 --- a/packages/js/product-editor/src/components/radio/block.json +++ b/packages/js/product-editor/src/blocks/radio/block.json @@ -34,6 +34,5 @@ "inserter": false, "lock": false, "__experimentalToolbar": false - }, - "editorStyle": "file:./editor.css" + } } diff --git a/packages/js/product-editor/src/blocks/radio/edit.tsx b/packages/js/product-editor/src/blocks/radio/edit.tsx new file mode 100644 index 00000000000..7213cda4084 --- /dev/null +++ b/packages/js/product-editor/src/blocks/radio/edit.tsx @@ -0,0 +1,35 @@ +/** + * External dependencies + */ +import { createElement } from '@wordpress/element'; +import { BlockEditProps } from '@wordpress/blocks'; +import { useBlockProps } from '@wordpress/block-editor'; +import { useEntityProp } from '@wordpress/core-data'; + +/** + * Internal dependencies + */ +import { RadioField } from '../../components/radio-field'; +import { RadioBlockAttributes } from './types'; + +export function Edit( { attributes }: BlockEditProps< RadioBlockAttributes > ) { + const blockProps = useBlockProps(); + const { description, options, property, title } = attributes; + const [ value, setValue ] = useEntityProp< string >( + 'postType', + 'product', + property + ); + + return ( +
+ setValue( selected || '' ) } + /> +
+ ); +} diff --git a/packages/js/product-editor/src/blocks/radio/index.ts b/packages/js/product-editor/src/blocks/radio/index.ts new file mode 100644 index 00000000000..701e003d2b5 --- /dev/null +++ b/packages/js/product-editor/src/blocks/radio/index.ts @@ -0,0 +1,26 @@ +/** + * External dependencies + */ +import { BlockConfiguration } from '@wordpress/blocks'; + +/** + * Internal dependencies + */ +import { initBlock } from '../../utils/init-blocks'; +import blockConfiguration from './block.json'; +import { Edit } from './edit'; +import { RadioBlockAttributes } from './types'; + +const { name, ...metadata } = + blockConfiguration as BlockConfiguration< RadioBlockAttributes >; + +export { metadata, name }; + +export const settings: Partial< BlockConfiguration< RadioBlockAttributes > > = { + example: {}, + edit: Edit, +}; + +export function init() { + return initBlock( { name, metadata, settings } ); +} diff --git a/packages/js/product-editor/src/blocks/radio/types.ts b/packages/js/product-editor/src/blocks/radio/types.ts new file mode 100644 index 00000000000..6833ed8f9c8 --- /dev/null +++ b/packages/js/product-editor/src/blocks/radio/types.ts @@ -0,0 +1,11 @@ +/** + * External dependencies + */ +import { BlockAttributes } from '@wordpress/blocks'; + +export interface RadioBlockAttributes extends BlockAttributes { + title: string; + description: string; + property: string; + options: []; +} diff --git a/packages/js/product-editor/src/blocks/shipping-fee/block.json b/packages/js/product-editor/src/blocks/shipping-fee/block.json new file mode 100644 index 00000000000..b6af327a19e --- /dev/null +++ b/packages/js/product-editor/src/blocks/shipping-fee/block.json @@ -0,0 +1,25 @@ +{ + "$schema": "https://schemas.wp.org/trunk/block.json", + "apiVersion": 2, + "name": "woocommerce/product-shipping-fee-fields", + "title": "Product shipping fee fields", + "category": "woocommerce", + "description": "The product shipping fee fields.", + "keywords": [ "products", "shipping", "fee" ], + "textdomain": "default", + "attributes": { + "title": { + "type": "string", + "__experimentalRole": "content" + } + }, + "supports": { + "align": false, + "html": false, + "multiple": true, + "reusable": false, + "inserter": false, + "lock": false + }, + "editorStyle": "file:./editor.css" +} diff --git a/packages/js/product-editor/src/blocks/shipping-fee/edit.tsx b/packages/js/product-editor/src/blocks/shipping-fee/edit.tsx new file mode 100644 index 00000000000..e9b61190ac5 --- /dev/null +++ b/packages/js/product-editor/src/blocks/shipping-fee/edit.tsx @@ -0,0 +1,184 @@ +/** + * External dependencies + */ +import { BlockEditProps } from '@wordpress/blocks'; +import { Link } from '@woocommerce/components'; +import { + EXPERIMENTAL_PRODUCT_SHIPPING_CLASSES_STORE_NAME, + ProductShippingClass, +} from '@woocommerce/data'; +import { getNewPath } from '@woocommerce/navigation'; +import { recordEvent } from '@woocommerce/tracks'; +import { useBlockProps } from '@wordpress/block-editor'; +import { BaseControl, SelectControl } from '@wordpress/components'; +import { useInstanceId } from '@wordpress/compose'; +import { useEntityProp } from '@wordpress/core-data'; +import { useSelect } from '@wordpress/data'; +import { + Fragment, + createElement, + createInterpolateElement, + useEffect, + useState, +} from '@wordpress/element'; +import { __ } from '@wordpress/i18n'; +import classNames from 'classnames'; + +/** + * Internal dependencies + */ +import { ShippingFeeBlockAttributes } from './types'; +import { useValidation } from '../../hooks/use-validation'; +import { RadioField } from '../../components/radio-field'; + +const FOLLOW_CLASS_OPTION_VALUE = 'follow_class'; +const FREE_SHIPPING_OPTION_VALUE = 'free_shipping'; + +const options = [ + { + label: __( 'Follow class', 'woocommerce' ), + value: FOLLOW_CLASS_OPTION_VALUE, + }, + { + label: __( 'Free shipping', 'woocommerce' ), + value: FREE_SHIPPING_OPTION_VALUE, + }, +]; + +export function Edit( { + attributes, +}: BlockEditProps< ShippingFeeBlockAttributes > ) { + const { title } = attributes; + + const blockProps = useBlockProps(); + + const [ option, setOption ] = useState< string >( + FREE_SHIPPING_OPTION_VALUE + ); + + const [ shippingClass, setShippingClass ] = useEntityProp< string >( + 'postType', + 'product', + 'shipping_class' + ); + + const { shippingClasses } = useSelect( ( select ) => { + const { getProductShippingClasses } = select( + EXPERIMENTAL_PRODUCT_SHIPPING_CLASSES_STORE_NAME + ); + return { + shippingClasses: + getProductShippingClasses< ProductShippingClass[] >() ?? [], + }; + }, [] ); + + const shippingClassControlId = useInstanceId( BaseControl ) as string; + + const isShippingClassValid = useValidation( + 'product/shipping_class', + function shippingClassValidator() { + if ( option === FOLLOW_CLASS_OPTION_VALUE && ! shippingClass ) { + return false; + } + return true; + } + ); + + function handleOptionChange( value: string ) { + setOption( value ); + + if ( value === FOLLOW_CLASS_OPTION_VALUE ) { + const [ firstShippingClass ] = shippingClasses; + setShippingClass( firstShippingClass?.slug ?? '' ); + } else { + setShippingClass( '' ); + } + } + + useEffect( () => { + if ( shippingClass === '' ) { + setOption( FREE_SHIPPING_OPTION_VALUE ); + } else if ( shippingClass ) { + setOption( FOLLOW_CLASS_OPTION_VALUE ); + } + }, [ shippingClass ] ); + + return ( +
+
+
+ +
+
+ + { option === FOLLOW_CLASS_OPTION_VALUE && ( +
+
+ global settings.', + 'woocommerce' + ), + { + Link: ( + { + recordEvent( + 'product_shipping_global_settings_link_click' + ); + } } + > + + + ), + } + ) + : __( + 'The shipping class is required.', + 'woocommerce' + ) + } + > + { shippingClasses.map( ( { slug, name } ) => ( + + ) ) } + +
+ +
+
+ ) } +
+ ); +} diff --git a/packages/js/product-editor/src/blocks/shipping-fee/editor.scss b/packages/js/product-editor/src/blocks/shipping-fee/editor.scss new file mode 100644 index 00000000000..938548f73c7 --- /dev/null +++ b/packages/js/product-editor/src/blocks/shipping-fee/editor.scss @@ -0,0 +1,11 @@ +.wp-block-woocommerce-product-shipping-fee-fields { + .has-error { + .components-base-control .components-input-control__backdrop { + border-color: $studio-red-50; + } + + .components-base-control__help { + color: $studio-red-50; + } + } +} diff --git a/packages/js/product-editor/src/blocks/shipping-fee/index.ts b/packages/js/product-editor/src/blocks/shipping-fee/index.ts new file mode 100644 index 00000000000..a40803eedfd --- /dev/null +++ b/packages/js/product-editor/src/blocks/shipping-fee/index.ts @@ -0,0 +1,29 @@ +/** + * External dependencies + */ +import { createElement } from '@wordpress/element'; +import { BlockConfiguration } from '@wordpress/blocks'; + +/** + * Internal dependencies + */ +import { initBlock } from '../../utils/init-blocks'; +import blockConfiguration from './block.json'; +import { Edit } from './edit'; +import { ShippingFeeBlockAttributes } from './types'; + +const { name, ...metadata } = + blockConfiguration as BlockConfiguration< ShippingFeeBlockAttributes >; + +export { metadata, name }; + +export const settings: Partial< + BlockConfiguration< ShippingFeeBlockAttributes > +> = { + example: {}, + edit: Edit, +}; + +export function init() { + return initBlock( { name, metadata, settings } ); +} diff --git a/packages/js/product-editor/src/blocks/shipping-fee/types.ts b/packages/js/product-editor/src/blocks/shipping-fee/types.ts new file mode 100644 index 00000000000..90877619db6 --- /dev/null +++ b/packages/js/product-editor/src/blocks/shipping-fee/types.ts @@ -0,0 +1,8 @@ +/** + * External dependencies + */ +import { BlockAttributes } from '@wordpress/blocks'; + +export interface ShippingFeeBlockAttributes extends BlockAttributes { + title: string; +} diff --git a/packages/js/product-editor/src/components/editor/init-blocks.ts b/packages/js/product-editor/src/components/editor/init-blocks.ts index 920c9a7eae6..13467b55dd7 100644 --- a/packages/js/product-editor/src/components/editor/init-blocks.ts +++ b/packages/js/product-editor/src/components/editor/init-blocks.ts @@ -14,7 +14,7 @@ import { */ import { init as initImages } from '../images'; import { init as initName } from '../details-name-block'; -import { init as initRadio } from '../radio'; +import { init as initRadio } from '../../blocks/radio'; import { init as initSummary } from '../details-summary-block'; import { init as initSection } from '../section'; import { init as initTab } from '../tab'; @@ -27,6 +27,7 @@ import { init as initConditional } from '../../blocks/conditional'; import { init as initLowStockQty } from '../../blocks/inventory-email'; import { init as initCheckbox } from '../../blocks/checkbox'; import { init as initShippingDimensions } from '../../blocks/shipping-dimensions'; +import { init as initShippingFee } from '../../blocks/shipping-fee'; export const initBlocks = () => { const coreBlocks = __experimentalGetCoreBlocks(); @@ -52,4 +53,5 @@ export const initBlocks = () => { initLowStockQty(); initCheckbox(); initShippingDimensions(); + initShippingFee(); }; diff --git a/packages/js/product-editor/src/components/radio-field/index.ts b/packages/js/product-editor/src/components/radio-field/index.ts new file mode 100644 index 00000000000..a7c6224836d --- /dev/null +++ b/packages/js/product-editor/src/components/radio-field/index.ts @@ -0,0 +1,2 @@ +export * from './radio-field'; +export * from './types'; diff --git a/packages/js/product-editor/src/components/radio-field/radio-field.tsx b/packages/js/product-editor/src/components/radio-field/radio-field.tsx new file mode 100644 index 00000000000..77bda8efee9 --- /dev/null +++ b/packages/js/product-editor/src/components/radio-field/radio-field.tsx @@ -0,0 +1,41 @@ +/** + * External dependencies + */ +import { createElement, Fragment } from '@wordpress/element'; +import { RadioControl } from '@wordpress/components'; +import classNames from 'classnames'; + +/** + * Internal dependencies + */ +import { sanitizeHTML } from '../../utils/sanitize-html'; +import { RadioFieldProps } from './types'; + +export function RadioField< T = string >( { + title, + description, + className, + ...props +}: RadioFieldProps< T > ) { + return ( + + + { title } + + { description && ( + + ) } + + } + /> + ); +} diff --git a/packages/js/product-editor/src/components/radio-field/style.scss b/packages/js/product-editor/src/components/radio-field/style.scss new file mode 100644 index 00000000000..fe7ad92004d --- /dev/null +++ b/packages/js/product-editor/src/components/radio-field/style.scss @@ -0,0 +1,26 @@ +.woocommerce-radio-field { + .components-base-control__label { + text-transform: none; + font-weight: 400; + > span { + display: block; + line-height: 1.5; + } + margin-bottom: $gap-large; + } + + &__title { + font-size: 16px; + font-weight: 600; + color: #1e1e1e; + } + + &__description { + font-size: 13px; + color: #1e1e1e; + } + + .components-base-control__field > .components-v-stack { + gap: $gap; + } +} diff --git a/packages/js/product-editor/src/components/radio-field/types.ts b/packages/js/product-editor/src/components/radio-field/types.ts new file mode 100644 index 00000000000..f652960843b --- /dev/null +++ b/packages/js/product-editor/src/components/radio-field/types.ts @@ -0,0 +1,9 @@ +/** + * External dependencies + */ +import { RadioControl } from '@wordpress/components'; + +export type RadioFieldProps< T > = Omit< RadioControl.Props< T >, 'label' > & { + title: string; + description?: string; +}; diff --git a/packages/js/product-editor/src/components/radio/edit.tsx b/packages/js/product-editor/src/components/radio/edit.tsx deleted file mode 100644 index 77644752595..00000000000 --- a/packages/js/product-editor/src/components/radio/edit.tsx +++ /dev/null @@ -1,46 +0,0 @@ -/** - * External dependencies - */ -import { createElement, Fragment } from '@wordpress/element'; -import type { BlockAttributes } from '@wordpress/blocks'; -import { RadioControl } from '@wordpress/components'; -import { useBlockProps } from '@wordpress/block-editor'; -import { useEntityProp } from '@wordpress/core-data'; - -/** - * Internal dependencies - */ -import { sanitizeHTML } from '../../utils/sanitize-html'; - -export function Edit( { attributes }: { attributes: BlockAttributes } ) { - const blockProps = useBlockProps(); - const { description, options, property, title } = attributes; - const [ value, setValue ] = useEntityProp< string >( - 'postType', - 'product', - property - ); - - return ( -
- - - { title } - - - - } - selected={ value } - options={ options } - onChange={ ( selected ) => setValue( selected || '' ) } - /> -
- ); -} diff --git a/packages/js/product-editor/src/components/radio/editor.scss b/packages/js/product-editor/src/components/radio/editor.scss deleted file mode 100644 index 185c801248e..00000000000 --- a/packages/js/product-editor/src/components/radio/editor.scss +++ /dev/null @@ -1,26 +0,0 @@ -.wp-block-woocommerce-product-radio { - .components-base-control__label { - text-transform: none; - font-weight: 400; - > span { - display: block; - line-height: 1.5; - } - margin-bottom: $gap-large; - } - - &__title { - font-size: 16px; - font-weight: 600; - color: #1e1e1e; - } - - &__description { - font-size: 13px; - color: #1e1e1e; - } - - .components-base-control__field > .components-v-stack { - gap: $gap; - } -} diff --git a/packages/js/product-editor/src/components/radio/index.ts b/packages/js/product-editor/src/components/radio/index.ts deleted file mode 100644 index 15144b2b28d..00000000000 --- a/packages/js/product-editor/src/components/radio/index.ts +++ /dev/null @@ -1,17 +0,0 @@ -/** - * Internal dependencies - */ -import initBlock from '../../utils/init-block'; -import metadata from './block.json'; -import { Edit } from './edit'; - -const { name } = metadata; - -export { metadata, name }; - -export const settings = { - example: {}, - edit: Edit, -}; - -export const init = () => initBlock( { name, metadata, settings } ); diff --git a/packages/js/product-editor/src/components/section/block.json b/packages/js/product-editor/src/components/section/block.json index cde81d65f95..e851d66d1f2 100644 --- a/packages/js/product-editor/src/components/section/block.json +++ b/packages/js/product-editor/src/components/section/block.json @@ -12,7 +12,8 @@ "type": "string" }, "description": { - "type": "string" + "type": "string", + "__experimentalRole": "content" }, "icon": { "type": "object" diff --git a/packages/js/product-editor/src/style.scss b/packages/js/product-editor/src/style.scss index f5c15da6041..4c6f67e6d9a 100644 --- a/packages/js/product-editor/src/style.scss +++ b/packages/js/product-editor/src/style.scss @@ -1,6 +1,7 @@ @import 'blocks/schedule-sale/editor.scss'; @import 'blocks/track-inventory/editor.scss'; @import 'blocks/shipping-dimensions/editor.scss'; +@import 'blocks/shipping-fee/editor.scss'; @import 'components/editor/style.scss'; @import 'components/product-section-layout/style.scss'; @import 'components/edit-product-link-modal/style.scss'; @@ -9,8 +10,8 @@ @import 'components/header/style.scss'; @import 'components/images/editor.scss'; @import 'components/block-editor/style.scss'; -@import 'components/radio/editor.scss'; @import 'blocks/checkbox/editor.scss'; +@import 'components/radio-field/style.scss'; @import 'components/section/editor.scss'; @import 'components/tab/editor.scss'; @import 'components/tabs/style.scss'; diff --git a/plugins/woocommerce/changelog/add-37406 b/plugins/woocommerce/changelog/add-37406 new file mode 100644 index 00000000000..cddd48af695 --- /dev/null +++ b/plugins/woocommerce/changelog/add-37406 @@ -0,0 +1,4 @@ +Significance: minor +Type: add + +Add product shipping fee block to blocks template definition diff --git a/plugins/woocommerce/includes/class-wc-post-types.php b/plugins/woocommerce/includes/class-wc-post-types.php index 63d498a4115..2c9e509fcb9 100644 --- a/plugins/woocommerce/includes/class-wc-post-types.php +++ b/plugins/woocommerce/includes/class-wc-post-types.php @@ -687,12 +687,24 @@ class WC_Post_Types { array( 'woocommerce/product-section', array( - 'title' => __( 'Shipping', 'woocommerce' ), - 'icon' => array( + 'title' => __( 'Fees & dimensions', 'woocommerce' ), + 'description' => sprintf( + /* translators: %1$s: How to get started? link opening tag. %2$s: How to get started? link closing tag.*/ + __( 'Set up shipping costs and enter dimensions used for accurate rate calculations. %1$sHow to get started?%2$s.', 'woocommerce' ), + '', + '' + ), + 'icon' => array( 'src' => '', ), ), array( + array( + 'woocommerce/product-shipping-fee-fields', + array( + 'title' => __( 'Shipping fee', 'woocommerce' ), + ), + ), array( 'woocommerce/product-shipping-dimensions-fields', ),