diff --git a/assets/js/blocks/products/all-products/edit.js b/assets/js/blocks/products/all-products/edit.tsx similarity index 56% rename from assets/js/blocks/products/all-products/edit.js rename to assets/js/blocks/products/all-products/edit.tsx index e7ba049b5e5..0db6f3b46e8 100644 --- a/assets/js/blocks/products/all-products/edit.js +++ b/assets/js/blocks/products/all-products/edit.tsx @@ -2,25 +2,23 @@ * External dependencies */ import { __ } from '@wordpress/i18n'; -import { createBlock } from '@wordpress/blocks'; +import { createBlock, BlockInstance } from '@wordpress/blocks'; import { BlockControls, InnerBlocks, InspectorControls, } from '@wordpress/block-editor'; -import { withDispatch, withSelect } from '@wordpress/data'; +import { useDispatch } from '@wordpress/data'; import { PanelBody, - withSpokenMessages, Placeholder, Button, ToolbarGroup, Disabled, Tip, } from '@wordpress/components'; -import { Component } from '@wordpress/element'; -import { compose } from '@wordpress/compose'; -import PropTypes from 'prop-types'; +import { useState, useEffect } from '@wordpress/element'; +import { useDebounce } from '@wordpress/compose'; import { Icon, grid } from '@wordpress/icons'; import GridLayoutControl from '@woocommerce/editor-components/grid-layout-control'; import { @@ -31,6 +29,7 @@ import { getBlockMap } from '@woocommerce/atomic-utils'; import { previewProducts } from '@woocommerce/resource-previews'; import { getSetting } from '@woocommerce/settings'; import { blocksConfig } from '@woocommerce/block-settings'; +import { speak } from '@wordpress/a11y'; /** * Internal dependencies @@ -48,51 +47,50 @@ import { getSharedContentControls, getSharedListControls } from '../edit'; import Block from './block'; import './editor.scss'; -/** - * Component to handle edit mode of "All Products". - */ -class Editor extends Component { - static propTypes = { - /** - * The attributes for this block. - */ - attributes: PropTypes.object.isRequired, - /** - * A callback to update attributes. - */ - setAttributes: PropTypes.func.isRequired, - /** - * From withSpokenMessages. - */ - debouncedSpeak: PropTypes.func.isRequired, - }; +type Attributes = { + columns: number; + rows: number; + alignButtons: boolean; + layoutConfig: [ string, object? ][]; +}; - state = { - isEditing: false, - innerBlocks: [], - }; +type Props = { + contentVisibility: number; + orderby: number; + isPreview: number; + clientId: string; + attributes: Attributes; + setAttributes: ( attributes: Record< string, unknown > ) => void; +}; - blockMap = getBlockMap( 'woocommerce/all-products' ); +export default function Edit( props: Props ): JSX.Element { + const [ isEditing, setIsEditing ] = useState< boolean >( false ); + const [ innerBlocks, setInnerBlocks ] = useState< BlockInstance[] >( [] ); + const blockMap = getBlockMap( 'woocommerce/all-products' ); - componentDidMount = () => { - const { block } = this.props; - this.setState( { innerBlocks: block.innerBlocks } ); - }; + const { clientId, attributes, setAttributes } = props; - getTitle = () => { + const { columns, rows, alignButtons, layoutConfig } = attributes; + + useEffect( () => { + setInnerBlocks( innerBlocks ); + }, [] ); + + const { replaceInnerBlocks } = useDispatch( 'core/block-editor' ); + const debouncedSpeak = useDebounce( speak ); + + const getTitle = (): string => { return __( 'All Products', 'woo-gutenberg-products-block' ); }; - getIcon = () => { + const getIcon = (): JSX.Element => { return ; }; - togglePreview = () => { - const { debouncedSpeak } = this.props; + const togglePreview = (): void => { + setIsEditing( ! isEditing ); - this.setState( { isEditing: ! this.state.isEditing } ); - - if ( ! this.state.isEditing ) { + if ( ! isEditing ) { debouncedSpeak( __( 'Showing All Products block preview.', @@ -102,10 +100,7 @@ class Editor extends Component { } }; - getInspectorControls = () => { - const { attributes, setAttributes } = this.props; - const { columns, rows, alignButtons } = attributes; - + const getInspectorControls = (): JSX.Element => { return ( { - const { isEditing } = this.state; - + const getBlockControls = (): JSX.Element => { return ( this.togglePreview(), + onClick: () => togglePreview(), isActive: isEditing, }, ] } @@ -161,46 +154,47 @@ class Editor extends Component { ); }; - renderEditMode = () => { + const renderEditMode = () => { const onDone = () => { - const { block, setAttributes } = this.props; setAttributes( { - layoutConfig: getProductLayoutConfig( block.innerBlocks ), + layoutConfig: getProductLayoutConfig( innerBlocks ), } ); - this.setState( { innerBlocks: block.innerBlocks } ); - this.togglePreview(); + setInnerBlocks( innerBlocks ); + togglePreview(); }; const onCancel = () => { - const { block, replaceInnerBlocks } = this.props; - const { innerBlocks } = this.state; - replaceInnerBlocks( block.clientId, innerBlocks, false ); - this.togglePreview(); + replaceInnerBlocks( clientId, innerBlocks, false ); + togglePreview(); }; const onReset = () => { - const { block, replaceInnerBlocks } = this.props; - const newBlocks = []; - DEFAULT_PRODUCT_LIST_LAYOUT.map( ( [ name, attributes ] ) => { - newBlocks.push( createBlock( name, attributes ) ); + const newBlocks: BlockInstance[] = []; + DEFAULT_PRODUCT_LIST_LAYOUT.map( ( [ name, blockAttributes ] ) => { + newBlocks.push( createBlock( name, blockAttributes ) ); return true; } ); - replaceInnerBlocks( block.clientId, newBlocks, false ); - this.setState( { innerBlocks: block.innerBlocks } ); + replaceInnerBlocks( clientId, newBlocks, false ); + setInnerBlocks( innerBlocks ); }; - const InnerBlockProps = { - template: this.props.attributes.layoutConfig, + const InnerBlockProps: { + template: [ string, object? ][]; + templateLock: boolean; + allowedBlocks: Array< string >; + renderAppender?: undefined | boolean; + } = { + template: layoutConfig, templateLock: false, - allowedBlocks: Object.keys( this.blockMap ), + allowedBlocks: Object.keys( blockMap ), }; - if ( this.props.attributes.layoutConfig.length !== 0 ) { + if ( layoutConfig.length !== 0 ) { InnerBlockProps.renderAppender = false; } return ( - + { __( 'Display all products from your store as a grid.', 'woo-gutenberg-products-block' @@ -221,7 +215,9 @@ class Editor extends Component {
  • + { /* @ts-expect-error: `InnerBlocks` is a component that is typed in WordPress core*/ }
  • @@ -263,12 +259,10 @@ class Editor extends Component { ); }; - renderViewMode = () => { - const { attributes } = this.props; - const { layoutConfig } = attributes; + const renderViewMode = () => { const hasContent = layoutConfig && layoutConfig.length !== 0; - const blockTitle = this.getTitle(); - const blockIcon = this.getIcon(); + const blockTitle = getTitle(); + const blockIcon = getIcon(); if ( ! hasContent ) { return renderHiddenContentPlaceholder( blockTitle, blockIcon ); @@ -276,48 +270,29 @@ class Editor extends Component { return ( + { /* @ts-expect-error: `Block` is a component that is typed in WordPress core*/ } ); }; - render = () => { - const { attributes } = this.props; - const { isEditing } = this.state; - const blockTitle = this.getTitle(); - const blockIcon = this.getIcon(); + const blockTitle = getTitle(); + const blockIcon = getIcon(); - if ( blocksConfig.productCount === 0 ) { - return renderNoProductsPlaceholder( blockTitle, blockIcon ); - } + if ( blocksConfig.productCount === 0 ) { + return renderNoProductsPlaceholder( blockTitle, blockIcon ); + } - return ( -
    - { this.getBlockControls() } - { this.getInspectorControls() } - { isEditing ? this.renderEditMode() : this.renderViewMode() } -
    - ); - }; + return ( +
    + { getBlockControls() } + { getInspectorControls() } + { isEditing ? renderEditMode() : renderViewMode() } +
    + ); } - -export default compose( - withSpokenMessages, - withSelect( ( select, { clientId } ) => { - const { getBlock } = select( 'core/block-editor' ); - return { - block: getBlock( clientId ), - }; - } ), - withDispatch( ( dispatch ) => { - const { replaceInnerBlocks } = dispatch( 'core/block-editor' ); - return { - replaceInnerBlocks, - }; - } ) -)( Editor ); diff --git a/assets/js/blocks/products/all-products/index.js b/assets/js/blocks/products/all-products/index.js index 659c21724a9..fdf518361e5 100644 --- a/assets/js/blocks/products/all-products/index.js +++ b/assets/js/blocks/products/all-products/index.js @@ -10,8 +10,8 @@ import '@woocommerce/atomic-blocks'; */ import metadata from './block.json'; import deprecated from './deprecated'; -import edit from './edit'; -import save from './save'; +import Edit from './edit'; +import Save from './save'; import defaults from './defaults'; const { name } = metadata; @@ -26,9 +26,9 @@ const settings = { /> ), }, - edit, + edit: Edit, // Save the props to post content. - save, + save: Save, deprecated, defaults, }; diff --git a/assets/js/blocks/products/all-products/save.js b/assets/js/blocks/products/all-products/save.js index 108ae322d63..e9683b910ae 100644 --- a/assets/js/blocks/products/all-products/save.js +++ b/assets/js/blocks/products/all-products/save.js @@ -8,7 +8,7 @@ import { InnerBlocks } from '@wordpress/block-editor'; */ import { getBlockClassName } from '../utils.js'; -export default function save( { attributes } ) { +export default function Save( { attributes } ) { const dataAttributes = {}; Object.keys( attributes ) .sort() diff --git a/assets/js/blocks/products/base-utils.js b/assets/js/blocks/products/base-utils.ts similarity index 88% rename from assets/js/blocks/products/base-utils.js rename to assets/js/blocks/products/base-utils.ts index 236ae3e838f..53b36d6bde9 100644 --- a/assets/js/blocks/products/base-utils.js +++ b/assets/js/blocks/products/base-utils.ts @@ -2,6 +2,7 @@ * External dependencies */ import classnames from 'classnames'; +import { BlockInstance } from '@wordpress/blocks'; /** * Internal dependencies @@ -12,7 +13,7 @@ import { ImageSizing } from '../../atomic/blocks/product-elements/image/types'; /** * The default layout built from the default template. */ -export const DEFAULT_PRODUCT_LIST_LAYOUT = [ +export const DEFAULT_PRODUCT_LIST_LAYOUT: [ string, object? ][] = [ [ 'woocommerce/product-image', { imageSizing: ImageSizing.THUMBNAIL } ], [ 'woocommerce/product-title' ], [ 'woocommerce/product-price' ], @@ -25,7 +26,9 @@ export const DEFAULT_PRODUCT_LIST_LAYOUT = [ * * @param {Object[]} innerBlocks Inner block components. */ -export const getProductLayoutConfig = ( innerBlocks ) => { +export const getProductLayoutConfig = ( + innerBlocks: BlockInstance[] +): [ string, object? ][] => { if ( ! innerBlocks || innerBlocks.length === 0 ) { return []; } diff --git a/assets/js/previews/products.js b/assets/js/previews/products.ts similarity index 89% rename from assets/js/previews/products.js rename to assets/js/previews/products.ts index 441cf28af40..75ba459a5df 100644 --- a/assets/js/previews/products.js +++ b/assets/js/previews/products.ts @@ -3,13 +3,14 @@ */ import { __ } from '@wordpress/i18n'; import { WC_BLOCKS_IMAGE_URL } from '@woocommerce/block-settings'; +import { ProductResponseItem } from '@woocommerce/types'; const shortDescription = __( 'Fly your WordPress banner with this beauty! Deck out your office space or add it to your kids walls. This banner will spruce up any space it’s hung!', 'woo-gutenberg-products-block' ); -export const previewProducts = [ +export const previewProducts: Array< ProductResponseItem > = [ { id: 1, name: 'WordPress Pennant', @@ -33,7 +34,7 @@ export const previewProducts = [ sizes: '', }, ], - average_rating: 5, + average_rating: '5', categories: [ { id: 1, @@ -58,6 +59,10 @@ export const previewProducts = [ add_to_cart: { text: __( 'Add to cart', 'woo-gutenberg-products-block' ), description: __( 'Add to cart', 'woo-gutenberg-products-block' ), + url: '', + minimum: 1, + maximum: 99, + multiple_of: 1, }, has_options: false, is_purchasable: true,