diff --git a/packages/js/data/changelog/update-39453_simple_to_variable_product b/packages/js/data/changelog/update-39453_simple_to_variable_product new file mode 100644 index 00000000000..19ed596c31b --- /dev/null +++ b/packages/js/data/changelog/update-39453_simple_to_variable_product @@ -0,0 +1,4 @@ +Significance: patch +Type: update + +Add new user preference to UserPreferences type. diff --git a/packages/js/data/src/user/types.ts b/packages/js/data/src/user/types.ts index 0cd89db8d1d..32d524f1e59 100644 --- a/packages/js/data/src/user/types.ts +++ b/packages/js/data/src/user/types.ts @@ -26,6 +26,7 @@ export type UserPreferences = { }; taxes_report_columns?: string; variable_product_tour_shown?: string; + variable_product_block_tour_shown?: string; variations_report_columns?: string; }; diff --git a/packages/js/product-editor/changelog/update-39453_simple_to_variable_product b/packages/js/product-editor/changelog/update-39453_simple_to_variable_product new file mode 100644 index 00000000000..85e2bf77427 --- /dev/null +++ b/packages/js/product-editor/changelog/update-39453_simple_to_variable_product @@ -0,0 +1,4 @@ +Significance: minor +Type: add + +Add isInSelectedTab context to tab blocks for use in nested blocks. diff --git a/packages/js/product-editor/changelog/update-39453_simple_to_variable_product_2 b/packages/js/product-editor/changelog/update-39453_simple_to_variable_product_2 new file mode 100644 index 00000000000..c472d5a6b57 --- /dev/null +++ b/packages/js/product-editor/changelog/update-39453_simple_to_variable_product_2 @@ -0,0 +1,4 @@ +Significance: minor +Type: update + +Update variation options block to auto create variations upon options update. diff --git a/packages/js/product-editor/src/blocks/catalog-visibility/index.ts b/packages/js/product-editor/src/blocks/catalog-visibility/index.ts index 276ba069731..efbd7fe8dc2 100644 --- a/packages/js/product-editor/src/blocks/catalog-visibility/index.ts +++ b/packages/js/product-editor/src/blocks/catalog-visibility/index.ts @@ -6,7 +6,7 @@ import { BlockConfiguration } from '@wordpress/blocks'; /** * Internal dependencies */ -import { initBlock } from '../../utils/init-blocks'; +import { initBlock } from '../../utils/init-block'; import blockConfiguration from './block.json'; import { Edit } from './edit'; import { CatalogVisibilityBlockAttributes } from './types'; diff --git a/packages/js/product-editor/src/blocks/checkbox/index.ts b/packages/js/product-editor/src/blocks/checkbox/index.ts index 15144b2b28d..6b8110f1433 100644 --- a/packages/js/product-editor/src/blocks/checkbox/index.ts +++ b/packages/js/product-editor/src/blocks/checkbox/index.ts @@ -1,11 +1,16 @@ +/** + * External dependencies + */ +import { BlockConfiguration } from '@wordpress/blocks'; + /** * Internal dependencies */ -import initBlock from '../../utils/init-block'; -import metadata from './block.json'; +import { initBlock } from '../../utils/init-block'; +import blockConfiguration from './block.json'; import { Edit } from './edit'; -const { name } = metadata; +const { name, ...metadata } = blockConfiguration as BlockConfiguration; export { metadata, name }; diff --git a/packages/js/product-editor/src/blocks/description/index.ts b/packages/js/product-editor/src/blocks/description/index.ts index 15144b2b28d..6b8110f1433 100644 --- a/packages/js/product-editor/src/blocks/description/index.ts +++ b/packages/js/product-editor/src/blocks/description/index.ts @@ -1,11 +1,16 @@ +/** + * External dependencies + */ +import { BlockConfiguration } from '@wordpress/blocks'; + /** * Internal dependencies */ -import initBlock from '../../utils/init-block'; -import metadata from './block.json'; +import { initBlock } from '../../utils/init-block'; +import blockConfiguration from './block.json'; import { Edit } from './edit'; -const { name } = metadata; +const { name, ...metadata } = blockConfiguration as BlockConfiguration; export { metadata, name }; diff --git a/packages/js/product-editor/src/blocks/inventory-email/index.ts b/packages/js/product-editor/src/blocks/inventory-email/index.ts index f022db49254..d8ad7f199c5 100644 --- a/packages/js/product-editor/src/blocks/inventory-email/index.ts +++ b/packages/js/product-editor/src/blocks/inventory-email/index.ts @@ -7,7 +7,7 @@ import { BlockConfiguration } from '@wordpress/blocks'; /** * Internal dependencies */ -import { initBlock } from '../../utils/init-blocks'; +import { initBlock } from '../../utils/init-block'; import blockConfiguration from './block.json'; import { Edit } from './edit'; import { InventoryEmailBlockAttributes } from './types'; diff --git a/packages/js/product-editor/src/blocks/inventory-quantity/index.ts b/packages/js/product-editor/src/blocks/inventory-quantity/index.ts index d52ac90ea2f..1e756b0a120 100644 --- a/packages/js/product-editor/src/blocks/inventory-quantity/index.ts +++ b/packages/js/product-editor/src/blocks/inventory-quantity/index.ts @@ -7,7 +7,7 @@ import { BlockConfiguration } from '@wordpress/blocks'; /** * Internal dependencies */ -import { initBlock } from '../../utils/init-blocks'; +import { initBlock } from '../../utils/init-block'; import blockConfiguration from './block.json'; import { Edit } from './edit'; import { TrackInventoryBlockAttributes } from './types'; diff --git a/packages/js/product-editor/src/blocks/inventory-sku/index.ts b/packages/js/product-editor/src/blocks/inventory-sku/index.ts index 15144b2b28d..6b8110f1433 100644 --- a/packages/js/product-editor/src/blocks/inventory-sku/index.ts +++ b/packages/js/product-editor/src/blocks/inventory-sku/index.ts @@ -1,11 +1,16 @@ +/** + * External dependencies + */ +import { BlockConfiguration } from '@wordpress/blocks'; + /** * Internal dependencies */ -import initBlock from '../../utils/init-block'; -import metadata from './block.json'; +import { initBlock } from '../../utils/init-block'; +import blockConfiguration from './block.json'; import { Edit } from './edit'; -const { name } = metadata; +const { name, ...metadata } = blockConfiguration as BlockConfiguration; export { metadata, name }; diff --git a/packages/js/product-editor/src/blocks/password/index.ts b/packages/js/product-editor/src/blocks/password/index.ts index f2052d92a26..ecda64c9723 100644 --- a/packages/js/product-editor/src/blocks/password/index.ts +++ b/packages/js/product-editor/src/blocks/password/index.ts @@ -6,7 +6,7 @@ import { BlockConfiguration } from '@wordpress/blocks'; /** * Internal dependencies */ -import { initBlock } from '../../utils/init-blocks'; +import { initBlock } from '../../utils/init-block'; import blockConfiguration from './block.json'; import { Edit } from './edit'; import { RequirePasswordBlockAttributes } from './types'; diff --git a/packages/js/product-editor/src/blocks/pricing/index.ts b/packages/js/product-editor/src/blocks/pricing/index.ts index b8d779c809e..5d624f6f8ed 100644 --- a/packages/js/product-editor/src/blocks/pricing/index.ts +++ b/packages/js/product-editor/src/blocks/pricing/index.ts @@ -6,7 +6,7 @@ import { BlockConfiguration } from '@wordpress/blocks'; /** * Internal dependencies */ -import { initBlock } from '../../utils/init-blocks'; +import { initBlock } from '../../utils/init-block'; import blockConfiguration from './block.json'; import { Edit } from './edit'; import { PricingBlockAttributes } from './types'; diff --git a/packages/js/product-editor/src/blocks/radio/index.ts b/packages/js/product-editor/src/blocks/radio/index.ts index 701e003d2b5..49fa2e00137 100644 --- a/packages/js/product-editor/src/blocks/radio/index.ts +++ b/packages/js/product-editor/src/blocks/radio/index.ts @@ -6,7 +6,7 @@ import { BlockConfiguration } from '@wordpress/blocks'; /** * Internal dependencies */ -import { initBlock } from '../../utils/init-blocks'; +import { initBlock } from '../../utils/init-block'; import blockConfiguration from './block.json'; import { Edit } from './edit'; import { RadioBlockAttributes } from './types'; diff --git a/packages/js/product-editor/src/blocks/regular-price/index.ts b/packages/js/product-editor/src/blocks/regular-price/index.ts index f838788b632..c1651d35880 100644 --- a/packages/js/product-editor/src/blocks/regular-price/index.ts +++ b/packages/js/product-editor/src/blocks/regular-price/index.ts @@ -6,7 +6,7 @@ import { BlockConfiguration } from '@wordpress/blocks'; /** * Internal dependencies */ -import { initBlock } from '../../utils/init-blocks'; +import { initBlock } from '../../utils/init-block'; import blockConfiguration from './block.json'; import { Edit } from './edit'; import { SalePriceBlockAttributes } from './types'; diff --git a/packages/js/product-editor/src/blocks/sale-price/index.ts b/packages/js/product-editor/src/blocks/sale-price/index.ts index f838788b632..c1651d35880 100644 --- a/packages/js/product-editor/src/blocks/sale-price/index.ts +++ b/packages/js/product-editor/src/blocks/sale-price/index.ts @@ -6,7 +6,7 @@ import { BlockConfiguration } from '@wordpress/blocks'; /** * Internal dependencies */ -import { initBlock } from '../../utils/init-blocks'; +import { initBlock } from '../../utils/init-block'; import blockConfiguration from './block.json'; import { Edit } from './edit'; import { SalePriceBlockAttributes } from './types'; diff --git a/packages/js/product-editor/src/blocks/schedule-sale/index.ts b/packages/js/product-editor/src/blocks/schedule-sale/index.ts index e3893b6f2ac..1e5ea6faf5d 100644 --- a/packages/js/product-editor/src/blocks/schedule-sale/index.ts +++ b/packages/js/product-editor/src/blocks/schedule-sale/index.ts @@ -7,7 +7,7 @@ import { BlockConfiguration } from '@wordpress/blocks'; /** * Internal dependencies */ -import { initBlock } from '../../utils/init-blocks'; +import { initBlock } from '../../utils/init-block'; import blockConfiguration from './block.json'; import { Edit } from './edit'; import { ScheduleSalePricingBlockAttributes } from './types'; diff --git a/packages/js/product-editor/src/blocks/section/index.ts b/packages/js/product-editor/src/blocks/section/index.ts index 70224538e05..2fc3fe0fa23 100644 --- a/packages/js/product-editor/src/blocks/section/index.ts +++ b/packages/js/product-editor/src/blocks/section/index.ts @@ -7,7 +7,7 @@ import { BlockConfiguration } from '@wordpress/blocks'; /** * Internal dependencies */ -import { initBlock } from '../../utils/init-blocks'; +import { initBlock } from '../../utils/init-block'; import blockConfiguration from './block.json'; import { Edit } from './edit'; import { SectionBlockAttributes } from './types'; diff --git a/packages/js/product-editor/src/blocks/shipping-class/index.ts b/packages/js/product-editor/src/blocks/shipping-class/index.ts index 2f0d4ef3046..ae58594e10e 100644 --- a/packages/js/product-editor/src/blocks/shipping-class/index.ts +++ b/packages/js/product-editor/src/blocks/shipping-class/index.ts @@ -7,7 +7,7 @@ import { BlockConfiguration } from '@wordpress/blocks'; /** * Internal dependencies */ -import { initBlock } from '../../utils/init-blocks'; +import { initBlock } from '../../utils/init-block'; import blockConfiguration from './block.json'; import { Edit } from './edit'; import { ShippingClassBlockAttributes } from './types'; diff --git a/packages/js/product-editor/src/blocks/shipping-dimensions/index.ts b/packages/js/product-editor/src/blocks/shipping-dimensions/index.ts index f79c1241adf..25b3e065d70 100644 --- a/packages/js/product-editor/src/blocks/shipping-dimensions/index.ts +++ b/packages/js/product-editor/src/blocks/shipping-dimensions/index.ts @@ -7,7 +7,7 @@ import { BlockConfiguration } from '@wordpress/blocks'; /** * Internal dependencies */ -import { initBlock } from '../../utils/init-blocks'; +import { initBlock } from '../../utils/init-block'; import blockConfiguration from './block.json'; import { Edit } from './edit'; import { ShippingDimensionsBlockAttributes } from './types'; diff --git a/packages/js/product-editor/src/blocks/tab/block.json b/packages/js/product-editor/src/blocks/tab/block.json index 42ead1c7713..fc69d24c329 100644 --- a/packages/js/product-editor/src/blocks/tab/block.json +++ b/packages/js/product-editor/src/blocks/tab/block.json @@ -27,6 +27,9 @@ "lock": false, "__experimentalToolbar": false }, + "providesContext": { + "isInSelectedTab": "isSelected" + }, "usesContext": [ "selectedTab" ], "editorStyle": "file:./editor.css", "templateLock": "contentOnly" diff --git a/packages/js/product-editor/src/blocks/tab/edit.tsx b/packages/js/product-editor/src/blocks/tab/edit.tsx index ad351b61d55..86624c577a1 100644 --- a/packages/js/product-editor/src/blocks/tab/edit.tsx +++ b/packages/js/product-editor/src/blocks/tab/edit.tsx @@ -4,25 +4,35 @@ import { InnerBlocks, useBlockProps } from '@wordpress/block-editor'; import classnames from 'classnames'; import { createElement } from '@wordpress/element'; -import type { BlockAttributes } from '@wordpress/blocks'; +import type { BlockAttributes, BlockEditProps } from '@wordpress/blocks'; /** * Internal dependencies */ import { TabButton } from './tab-button'; +export interface TabBlockAttributes extends BlockAttributes { + id: string; + title: string; + order: number; + isSelected?: boolean; +} + export function Edit( { + setAttributes, attributes, context, -}: { - attributes: BlockAttributes; +}: BlockEditProps< TabBlockAttributes > & { context?: { selectedTab?: string | null; }; } ) { const blockProps = useBlockProps(); - const { id, title, order } = attributes; + const { id, title, order, isSelected: contextIsSelected } = attributes; const isSelected = context?.selectedTab === id; + if ( isSelected !== contextIsSelected ) { + setAttributes( { isSelected } ); + } const classes = classnames( 'wp-block-woocommerce-product-tab__content', { 'is-selected': isSelected, diff --git a/packages/js/product-editor/src/blocks/tab/index.ts b/packages/js/product-editor/src/blocks/tab/index.ts index 15144b2b28d..1c048cec59f 100644 --- a/packages/js/product-editor/src/blocks/tab/index.ts +++ b/packages/js/product-editor/src/blocks/tab/index.ts @@ -1,17 +1,25 @@ +/** + * External dependencies + */ +import { BlockConfiguration } from '@wordpress/blocks'; + /** * Internal dependencies */ -import initBlock from '../../utils/init-block'; -import metadata from './block.json'; -import { Edit } from './edit'; +import { initBlock } from '../../utils/init-block'; +import blockConfiguration from './block.json'; +import { Edit, TabBlockAttributes } from './edit'; -const { name } = metadata; +const { name, ...metadata } = + blockConfiguration as BlockConfiguration< TabBlockAttributes >; export { metadata, name }; -export const settings = { +export const settings: Partial< BlockConfiguration< TabBlockAttributes > > = { example: {}, edit: Edit, }; -export const init = () => initBlock( { name, metadata, settings } ); +export function init() { + initBlock( { name, metadata, settings } ); +} diff --git a/packages/js/product-editor/src/blocks/toggle/index.ts b/packages/js/product-editor/src/blocks/toggle/index.ts index d4c46ef9b1f..ca9896724dd 100644 --- a/packages/js/product-editor/src/blocks/toggle/index.ts +++ b/packages/js/product-editor/src/blocks/toggle/index.ts @@ -6,7 +6,7 @@ import { BlockConfiguration } from '@wordpress/blocks'; /** * Internal dependencies */ -import { initBlock } from '../../utils/init-blocks'; +import { initBlock } from '../../utils/init-block'; import blockConfiguration from './block.json'; import { Edit } from './edit'; import { ToggleBlockAttributes } from './types'; diff --git a/packages/js/product-editor/src/blocks/variation-items/block.json b/packages/js/product-editor/src/blocks/variation-items/block.json index 1be886724b0..f29fc947aee 100644 --- a/packages/js/product-editor/src/blocks/variation-items/block.json +++ b/packages/js/product-editor/src/blocks/variation-items/block.json @@ -22,5 +22,6 @@ "lock": false, "__experimentalToolbar": false }, + "usesContext": [ "isInSelectedTab" ], "editorStyle": "file:./editor.css" } diff --git a/packages/js/product-editor/src/blocks/variation-items/edit.tsx b/packages/js/product-editor/src/blocks/variation-items/edit.tsx index 0549c6e3c3a..50bed1e45d3 100644 --- a/packages/js/product-editor/src/blocks/variation-items/edit.tsx +++ b/packages/js/product-editor/src/blocks/variation-items/edit.tsx @@ -2,19 +2,29 @@ * External dependencies */ import { useBlockProps } from '@wordpress/block-editor'; +import { BlockEditProps } from '@wordpress/blocks'; import { createElement } from '@wordpress/element'; /** * Internal dependencies */ import { VariationsTable } from '../../components/variations-table'; +import { VariationOptionsBlockAttributes } from './types'; +import { VariableProductTour } from './variable-product-tour'; -export function Edit() { +export function Edit( { + context, +}: BlockEditProps< VariationOptionsBlockAttributes > & { + context?: { + isInSelectedTab?: boolean; + }; +} ) { const blockProps = useBlockProps(); return (
+ { context?.isInSelectedTab && }
); } diff --git a/packages/js/product-editor/src/blocks/variation-items/editor.scss b/packages/js/product-editor/src/blocks/variation-items/editor.scss index 7f71df3b059..e2b824187a2 100644 --- a/packages/js/product-editor/src/blocks/variation-items/editor.scss +++ b/packages/js/product-editor/src/blocks/variation-items/editor.scss @@ -1,2 +1,13 @@ .wp-block-woocommerce-product-variations-items-field { } + +.variation-items-product-tour { + .tour-kit-spotlight { + border-radius: $gap-smaller; + padding: $gap-large; + } + .tour-kit-frame__container, + .woocommerce-tour-kit-step { + border-radius: $gap-smaller; + } +} diff --git a/packages/js/product-editor/src/blocks/variation-items/index.ts b/packages/js/product-editor/src/blocks/variation-items/index.ts index 8ef0d0cfd76..18412dd99c7 100644 --- a/packages/js/product-editor/src/blocks/variation-items/index.ts +++ b/packages/js/product-editor/src/blocks/variation-items/index.ts @@ -6,7 +6,7 @@ import { BlockConfiguration } from '@wordpress/blocks'; /** * Internal dependencies */ -import { initBlock } from '../../utils/init-blocks'; +import { initBlock } from '../../utils/init-block'; import blockConfiguration from './block.json'; import { Edit } from './edit'; import { VariationOptionsBlockAttributes } from './types'; diff --git a/packages/js/product-editor/src/blocks/variation-items/variable-product-tour.tsx b/packages/js/product-editor/src/blocks/variation-items/variable-product-tour.tsx new file mode 100644 index 00000000000..15a01260669 --- /dev/null +++ b/packages/js/product-editor/src/blocks/variation-items/variable-product-tour.tsx @@ -0,0 +1,138 @@ +/** + * External dependencies + */ +import { createElement, useEffect, useRef, useState } from '@wordpress/element'; +import { __ } from '@wordpress/i18n'; +import { TourKit, TourKitTypes } from '@woocommerce/components'; +import { + EXPERIMENTAL_PRODUCT_VARIATIONS_STORE_NAME, + useUserPreferences, +} from '@woocommerce/data'; +import { recordEvent } from '@woocommerce/tracks'; +import { useSelect } from '@wordpress/data'; +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore No types for this exist yet. +// eslint-disable-next-line @woocommerce/dependency-group +import { useEntityId } from '@wordpress/core-data'; + +/** + * Internal dependencies + */ +import { DEFAULT_PER_PAGE_OPTION } from '../../constants'; + +export const VariableProductTour: React.FC = () => { + const [ isTourOpen, setIsTourOpen ] = useState( false ); + const productId = useEntityId( 'postType', 'product' ); + const prevTotalCount = useRef< undefined | number >(); + + const { totalCount } = useSelect( + ( select ) => { + const { getProductVariationsTotalCount } = select( + EXPERIMENTAL_PRODUCT_VARIATIONS_STORE_NAME + ); + const requestParams = { + product_id: productId, + page: 1, + per_page: DEFAULT_PER_PAGE_OPTION, + order: 'asc', + orderby: 'menu_order', + }; + return { + totalCount: + getProductVariationsTotalCount< number >( requestParams ), + }; + }, + [ productId ] + ); + + const { + updateUserPreferences, + variable_product_block_tour_shown: hasShownTour, + } = useUserPreferences(); + + const config: TourKitTypes.WooConfig = { + placement: 'top', + steps: [ + { + referenceElements: { + desktop: + '.wp-block-woocommerce-product-variation-items-field', + }, + focusElement: { + desktop: + '.wp-block-woocommerce-product-variation-items-field', + }, + meta: { + name: 'product-variations-2', + heading: __( + '⚡️ This product now has variations', + 'woocommerce' + ), + descriptions: { + desktop: __( + 'From now on, you’ll manage pricing, shipping, and inventory for each variation individually—just like any other product in your store.', + 'woocommerce' + ), + }, + primaryButton: { + text: __( 'Got it', 'woocommerce' ), + }, + }, + }, + ], + options: { + classNames: [ 'variation-items-product-tour' ], + // WooTourKit does not handle merging of default options properly, + // so we need to duplicate the effects options here. + effects: { + arrowIndicator: true, + spotlight: { + interactivity: { + enabled: true, + }, + }, + }, + callbacks: { + onStepViewOnce: () => { + recordEvent( 'variable_product_block_tour_shown', { + variable_count: totalCount, + } ); + }, + }, + popperModifiers: [ + { + name: 'offset', + options: { + // 24px for additional padding and 8px for arrow. + offset: [ 0, 32 ], + }, + }, + ], + }, + closeHandler: () => { + updateUserPreferences( { + variable_product_block_tour_shown: 'yes', + } ); + setIsTourOpen( false ); + + recordEvent( 'variable_product_block_tour_dismissed' ); + }, + }; + + useEffect( () => { + const isFirstVariation = + prevTotalCount.current !== totalCount && + totalCount > 0 && + prevTotalCount.current === 0; + prevTotalCount.current = totalCount; + if ( isFirstVariation && ! isTourOpen ) { + setIsTourOpen( true ); + } + }, [ totalCount ] ); + + if ( hasShownTour === 'yes' || ! isTourOpen ) { + return null; + } + + return ; +}; diff --git a/packages/js/product-editor/src/blocks/variation-options/edit.tsx b/packages/js/product-editor/src/blocks/variation-options/edit.tsx index 3b33bf8468a..543cafc5379 100644 --- a/packages/js/product-editor/src/blocks/variation-options/edit.tsx +++ b/packages/js/product-editor/src/blocks/variation-options/edit.tsx @@ -23,6 +23,7 @@ import { useProductAttributes, } from '../../hooks/use-product-attributes'; import { AttributeControl } from '../../components/attribute-control'; +import { useProductVariationsHelper } from '../../hooks/use-product-variations-helper'; function manageDefaultAttributes( values: EnhancedProductAttribute[] ) { return values.reduce< Product[ 'default_attributes' ] >( @@ -45,6 +46,7 @@ function manageDefaultAttributes( values: EnhancedProductAttribute[] ) { export function Edit() { const blockProps = useBlockProps(); + const { generateProductVariations } = useProductVariationsHelper(); const [ entityAttributes, setEntityAttributes ] = useEntityProp< ProductAttribute[] @@ -64,6 +66,7 @@ export function Edit() { onChange( values ) { setEntityAttributes( values ); setEntityDefaultAttributes( manageDefaultAttributes( values ) ); + generateProductVariations( values ); }, } ); diff --git a/packages/js/product-editor/src/blocks/variation-options/index.ts b/packages/js/product-editor/src/blocks/variation-options/index.ts index 888b5cc021d..ad48fe3aa9e 100644 --- a/packages/js/product-editor/src/blocks/variation-options/index.ts +++ b/packages/js/product-editor/src/blocks/variation-options/index.ts @@ -7,7 +7,7 @@ import { BlockConfiguration } from '@wordpress/blocks'; /** * Internal dependencies */ -import { initBlock } from '../../utils/init-blocks'; +import { initBlock } from '../../utils/init-block'; import blockConfiguration from './block.json'; import { Edit } from './edit'; import { VariationOptionsBlockAttributes } from './types'; diff --git a/packages/js/product-editor/src/blocks/variations/edit.tsx b/packages/js/product-editor/src/blocks/variations/edit.tsx index 9b6a40e27c0..7cc7853f655 100644 --- a/packages/js/product-editor/src/blocks/variations/edit.tsx +++ b/packages/js/product-editor/src/blocks/variations/edit.tsx @@ -34,6 +34,7 @@ import { useProductAttributes, } from '../../hooks/use-product-attributes'; import { getAttributeId } from '../../components/attribute-control/utils'; +import { useProductVariationsHelper } from '../../hooks/use-product-variations-helper'; function hasAttributesUsedForVariations( productAttributes: Product[ 'attributes' ] @@ -56,6 +57,7 @@ export function Edit( { }: BlockEditProps< VariationsBlockAttributes > ) { const { description } = attributes; + const { generateProductVariations } = useProductVariationsHelper(); const [ isNewModalVisible, setIsNewModalVisible ] = useState( false ); const [ productAttributes, setProductAttributes ] = useEntityProp< Product[ 'attributes' ] @@ -74,6 +76,7 @@ export function Edit( { setDefaultProductAttributes( getFirstOptionFromEachAttribute( values ) ); + generateProductVariations( values ); }, } ); diff --git a/packages/js/product-editor/src/blocks/variations/index.ts b/packages/js/product-editor/src/blocks/variations/index.ts index d08313a98b2..2dc2e6bbc84 100644 --- a/packages/js/product-editor/src/blocks/variations/index.ts +++ b/packages/js/product-editor/src/blocks/variations/index.ts @@ -7,7 +7,7 @@ import { BlockConfiguration } from '@wordpress/blocks'; /** * Internal dependencies */ -import { initBlock } from '../../utils/init-blocks'; +import { initBlock } from '../../utils/init-block'; import blockConfiguration from './block.json'; import { Edit } from './edit'; import { VariationsBlockAttributes } from './types'; diff --git a/packages/js/product-editor/src/components/tabs/test/tabs.spec.tsx b/packages/js/product-editor/src/components/tabs/test/tabs.spec.tsx index bcebdcfd431..b6948599b4a 100644 --- a/packages/js/product-editor/src/components/tabs/test/tabs.spec.tsx +++ b/packages/js/product-editor/src/components/tabs/test/tabs.spec.tsx @@ -24,6 +24,13 @@ jest.mock( '@woocommerce/navigation', () => ( { getQuery: jest.fn().mockReturnValue( {} ), } ) ); +const blockProps = { + setAttributes: () => {}, + className: '', + clientId: '', + isSelected: false, +}; + function MockTabs( { onChange = jest.fn() } ) { const [ selected, setSelected ] = useState< string | null >( null ); const mockContext = { @@ -39,15 +46,18 @@ function MockTabs( { onChange = jest.fn() } ) { } } /> diff --git a/packages/js/product-editor/src/components/variations-table/variations-table.tsx b/packages/js/product-editor/src/components/variations-table/variations-table.tsx index 4f16b59accf..c57d599e91c 100644 --- a/packages/js/product-editor/src/components/variations-table/variations-table.tsx +++ b/packages/js/product-editor/src/components/variations-table/variations-table.tsx @@ -5,6 +5,7 @@ import { __ } from '@wordpress/i18n'; import { Button, Spinner, Tooltip } from '@wordpress/components'; import { EXPERIMENTAL_PRODUCT_VARIATIONS_STORE_NAME, + ProductAttribute, ProductVariation, } from '@woocommerce/data'; import { @@ -23,7 +24,7 @@ import { CurrencyContext } from '@woocommerce/currency'; // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore No types for this exist yet. // eslint-disable-next-line @woocommerce/dependency-group -import { useEntityId } from '@wordpress/core-data'; +import { useEntityId, useEntityProp } from '@wordpress/core-data'; /** * Internal dependencies @@ -46,6 +47,16 @@ export function VariationsTable() { const [ isUpdating, setIsUpdating ] = useState< Record< string, boolean > >( {} ); + const [ entityAttributes ] = useEntityProp< ProductAttribute[] >( + 'postType', + 'product', + 'attributes' + ); + const variableAttributeTags = entityAttributes + .filter( ( attr ) => attr.variation ) + .map( ( attr ) => attr.options ) + .flat(); + const productId = useEntityId( 'postType', 'product' ); const context = useContext( CurrencyContext ); const { formatAmount } = context; @@ -114,34 +125,45 @@ export function VariationsTable() { { variations.map( ( variation ) => (
- { variation.attributes.map( ( attribute ) => { - const tag = ( - /* eslint-disable-next-line @typescript-eslint/ban-ts-comment */ - /* @ts-ignore Additional props are not required. */ - - ); + { variation.attributes + .filter( ( attribute ) => + variableAttributeTags.includes( + attribute.option + ) + ) + .map( ( attribute ) => { + const tag = ( + /* eslint-disable-next-line @typescript-eslint/ban-ts-comment */ + /* @ts-ignore Additional props are not required. */ + + ); - return attribute.option.length <= - PRODUCT_VARIATION_TITLE_LIMIT ? ( - tag - ) : ( - - { tag } - - ); - } ) } + return attribute.option.length <= + PRODUCT_VARIATION_TITLE_LIMIT ? ( + tag + ) : ( + + { tag } + + ); + } ) }
void; + onChange: ( attributes: ProductAttribute[] ) => void; productId?: number; }; @@ -79,11 +79,11 @@ export function useProductAttributes( { }; const getAugmentedAttributes = ( - atts: ProductAttribute[], + atts: EnhancedProductAttribute[], variation: boolean, startPosition: number - ) => { - return atts.map( ( attribute, index ) => ( { + ): ProductAttribute[] => { + return atts.map( ( { isDefault, terms, ...attribute }, index ) => ( { ...attribute, variation, position: startPosition + index, diff --git a/packages/js/product-editor/src/hooks/use-product-variations-helper.ts b/packages/js/product-editor/src/hooks/use-product-variations-helper.ts new file mode 100644 index 00000000000..e105654997d --- /dev/null +++ b/packages/js/product-editor/src/hooks/use-product-variations-helper.ts @@ -0,0 +1,77 @@ +/** + * External dependencies + */ +import { useDispatch } from '@wordpress/data'; +import { useEntityProp } from '@wordpress/core-data'; +import { useCallback, useState } from '@wordpress/element'; +import { + Product, + EXPERIMENTAL_PRODUCT_VARIATIONS_STORE_NAME, +} from '@woocommerce/data'; + +/** + * Internal dependencies + */ +import { EnhancedProductAttribute } from './use-product-attributes'; + +export function useProductVariationsHelper() { + const [ productId ] = useEntityProp< number >( + 'postType', + 'product', + 'id' + ); + const { saveEntityRecord } = useDispatch( 'core' ); + const { + generateProductVariations: _generateProductVariations, + invalidateResolutionForStoreSelector, + } = useDispatch( EXPERIMENTAL_PRODUCT_VARIATIONS_STORE_NAME ); + + const [ isGenerating, setIsGenerating ] = useState( false ); + + const generateProductVariations = useCallback( + async ( attributes: EnhancedProductAttribute[] ) => { + setIsGenerating( true ); + + const updateProductAttributes = async () => { + const hasVariableAttribute = attributes.some( + ( attr ) => attr.variation + ); + await saveEntityRecord< Promise< Product > >( + 'postType', + 'product', + { + id: productId, + type: hasVariableAttribute ? 'variable' : 'simple', + attributes, + } + ); + }; + + return updateProductAttributes() + .then( () => { + return _generateProductVariations< { count: number } >( { + product_id: productId, + } ); + } ) + .then( ( data ) => { + if ( data.count > 0 ) { + invalidateResolutionForStoreSelector( + 'getProductVariations' + ); + return invalidateResolutionForStoreSelector( + 'getProductVariationsTotalCount' + ); + } + } ) + .finally( () => { + setIsGenerating( false ); + } ); + }, + [] + ); + + return { + generateProductVariations, + isGenerating, + }; +} diff --git a/packages/js/product-editor/src/utils/index.ts b/packages/js/product-editor/src/utils/index.ts index a546a1a8653..bf3e695d1c6 100644 --- a/packages/js/product-editor/src/utils/index.ts +++ b/packages/js/product-editor/src/utils/index.ts @@ -22,7 +22,7 @@ import { isValidEmail } from './validate-email'; export * from './create-ordered-children'; export * from './sort-fills-by-order'; -export * from './init-blocks'; +export * from './init-block'; export * from './product-apifetch-middleware'; export * from './sift'; diff --git a/packages/js/product-editor/src/utils/init-block.ts b/packages/js/product-editor/src/utils/init-block.ts index 31ab31bd3c5..06404008120 100644 --- a/packages/js/product-editor/src/utils/init-block.ts +++ b/packages/js/product-editor/src/utils/init-block.ts @@ -2,38 +2,30 @@ * External dependencies */ import { + Block, BlockConfiguration, - BlockEditProps, registerBlockType, } from '@wordpress/blocks'; -import { ComponentType } from 'react'; -type BlockRepresentation = { - name: string; - metadata: BlockConfiguration; - settings: Partial< Omit< BlockConfiguration, 'edit' > > & { - readonly edit?: - | ComponentType< - BlockEditProps< object > & { - context?: Record< string, unknown >; - } - > - | undefined; - }; -}; +interface BlockRepresentation< T extends Record< string, object > > { + name?: string; + metadata: BlockConfiguration< T >; + settings: Partial< BlockConfiguration< T > >; +} /** * Function to register an individual block. * - * @param {Object} block The block to be registered. - * - * @return {WPBlockType|void} The block, if it has been successfully registered; - * otherwise `undefined`. + * @param block The block to be registered. + * @return The block, if it has been successfully registered; otherwise `undefined`. */ -export default function initBlock( block: BlockRepresentation ) { +export function initBlock< + // eslint-disable-next-line @typescript-eslint/no-explicit-any + T extends Record< string, any > = Record< string, any > +>( block: BlockRepresentation< T > ): Block< T > | undefined { if ( ! block ) { return; } const { metadata, settings, name } = block; - return registerBlockType( { name, ...metadata }, settings ); + return registerBlockType< T >( { name, ...metadata }, settings ); } diff --git a/packages/js/product-editor/src/utils/init-blocks.ts b/packages/js/product-editor/src/utils/init-blocks.ts deleted file mode 100644 index 06404008120..00000000000 --- a/packages/js/product-editor/src/utils/init-blocks.ts +++ /dev/null @@ -1,31 +0,0 @@ -/** - * External dependencies - */ -import { - Block, - BlockConfiguration, - registerBlockType, -} from '@wordpress/blocks'; - -interface BlockRepresentation< T extends Record< string, object > > { - name?: string; - metadata: BlockConfiguration< T >; - settings: Partial< BlockConfiguration< T > >; -} - -/** - * Function to register an individual block. - * - * @param block The block to be registered. - * @return The block, if it has been successfully registered; otherwise `undefined`. - */ -export function initBlock< - // eslint-disable-next-line @typescript-eslint/no-explicit-any - T extends Record< string, any > = Record< string, any > ->( block: BlockRepresentation< T > ): Block< T > | undefined { - if ( ! block ) { - return; - } - const { metadata, settings, name } = block; - return registerBlockType< T >( { name, ...metadata }, settings ); -} diff --git a/plugins/woocommerce-admin/client/products/fields/options/index.ts b/plugins/woocommerce-admin/client/products/fields/options/index.ts deleted file mode 100644 index 5f30ef383a5..00000000000 --- a/plugins/woocommerce-admin/client/products/fields/options/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './options'; diff --git a/plugins/woocommerce-admin/client/products/fields/options/options.tsx b/plugins/woocommerce-admin/client/products/fields/options/options.tsx deleted file mode 100644 index f4bd7ae1b98..00000000000 --- a/plugins/woocommerce-admin/client/products/fields/options/options.tsx +++ /dev/null @@ -1,79 +0,0 @@ -/** - * External dependencies - */ -import { __ } from '@wordpress/i18n'; -import { Product, ProductAttribute } from '@woocommerce/data'; -import { recordEvent } from '@woocommerce/tracks'; -import { useFormContext } from '@woocommerce/components'; -import { AttributeControl } from '@woocommerce/product-editor/src/components/attribute-control'; -import { useProductAttributes } from '@woocommerce/product-editor/src/hooks/use-product-attributes'; - -/** - * Internal dependencies - */ -import { useProductVariationsHelper } from '../../hooks/use-product-variations-helper'; - -type OptionsProps = { - value: ProductAttribute[]; - onChange: ( value: ProductAttribute[] ) => void; - productId?: number; -}; - -export const Options: React.FC< OptionsProps > = ( { - value, - onChange, - productId, -} ) => { - const { values } = useFormContext< Product >(); - const { generateProductVariations } = useProductVariationsHelper(); - - const { attributes, handleChange } = useProductAttributes( { - allAttributes: value, - isVariationAttributes: true, - onChange: ( newAttributes ) => { - onChange( newAttributes ); - generateProductVariations( { - ...values, - attributes: newAttributes, - } ); - }, - productId, - } ); - - return ( - { - recordEvent( 'product_add_options_modal_add_button_click' ); - } } - onChange={ handleChange } - onNewModalCancel={ () => { - recordEvent( 'product_add_options_modal_cancel_button_click' ); - } } - onNewModalOpen={ () => { - if ( ! attributes.length ) { - recordEvent( 'product_add_first_option_button_click' ); - return; - } - recordEvent( 'product_add_option_button' ); - } } - uiStrings={ { - emptyStateSubtitle: __( 'No options yet', 'woocommerce' ), - newAttributeListItemLabel: __( 'Add option', 'woocommerce' ), - newAttributeModalTitle: __( 'Add options', 'woocommerce' ), - globalAttributeHelperMessage: __( - `You can change the option's name in {{link}}Attributes{{/link}}.`, - 'woocommerce' - ), - } } - onRemove={ () => - recordEvent( - 'product_remove_option_confirmation_confirm_click' - ) - } - onRemoveCancel={ () => - recordEvent( 'product_remove_option_confirmation_cancel_click' ) - } - /> - ); -}; diff --git a/plugins/woocommerce-admin/client/products/fills/product-form-fills.tsx b/plugins/woocommerce-admin/client/products/fills/product-form-fills.tsx index 179650d0b33..fa1c59c2d0f 100644 --- a/plugins/woocommerce-admin/client/products/fills/product-form-fills.tsx +++ b/plugins/woocommerce-admin/client/products/fills/product-form-fills.tsx @@ -20,7 +20,6 @@ import { PricingSectionFills } from './pricing-section'; import { InventorySectionFills } from './inventory-section'; import { AttributesSectionFills } from './attributes-section'; import { ImagesSectionFills } from './images-section'; -import { OptionsSection } from '../sections/options-section'; import { ProductVariationsSection } from '../sections/product-variations-section'; import { TAB_GENERAL_ID, @@ -113,7 +112,6 @@ const Tabs = () => { tabProps={ tabPropData.options } > <> - diff --git a/plugins/woocommerce-admin/client/products/hooks/use-product-variations-helper.ts b/plugins/woocommerce-admin/client/products/hooks/use-product-variations-helper.ts deleted file mode 100644 index f999a67be3e..00000000000 --- a/plugins/woocommerce-admin/client/products/hooks/use-product-variations-helper.ts +++ /dev/null @@ -1,70 +0,0 @@ -/** - * External dependencies - */ -import { AUTO_DRAFT_NAME } from '@woocommerce/product-editor'; -import { useDispatch } from '@wordpress/data'; -import { useCallback, useState } from '@wordpress/element'; -import { - Product, - EXPERIMENTAL_PRODUCT_VARIATIONS_STORE_NAME, - PRODUCTS_STORE_NAME, -} from '@woocommerce/data'; -import { useFormContext } from '@woocommerce/components'; - -export function useProductVariationsHelper() { - const { - generateProductVariations: _generateProductVariations, - invalidateResolutionForStoreSelector, - } = useDispatch( EXPERIMENTAL_PRODUCT_VARIATIONS_STORE_NAME ); - const { createProduct, updateProduct } = useDispatch( PRODUCTS_STORE_NAME ); - const { resetForm } = useFormContext< Product >(); - - const [ isGenerating, setIsGenerating ] = useState( false ); - - const generateProductVariations = useCallback( - async ( product: Partial< Product > ) => { - setIsGenerating( true ); - - const createOrUpdateProduct = product.id - ? () => - updateProduct< Promise< Product > >( - product.id, - product - ) - : () => { - return createProduct< Promise< Product > >( { - ...product, - status: 'auto-draft', - name: product.name || AUTO_DRAFT_NAME, - } ); - }; - - return createOrUpdateProduct() - .then( ( createdOrUpdatedProduct ) => { - if ( ! product.id ) { - resetForm( { - ...createdOrUpdatedProduct, - name: product.name || '', - } ); - } - return _generateProductVariations( { - product_id: createdOrUpdatedProduct.id, - } ); - } ) - .then( () => { - return invalidateResolutionForStoreSelector( - 'getProductVariations' - ); - } ) - .finally( () => { - setIsGenerating( false ); - } ); - }, - [] - ); - - return { - generateProductVariations, - isGenerating, - }; -} diff --git a/plugins/woocommerce-admin/client/products/sections/options-section.scss b/plugins/woocommerce-admin/client/products/sections/options-section.scss deleted file mode 100644 index 8090c6112dd..00000000000 --- a/plugins/woocommerce-admin/client/products/sections/options-section.scss +++ /dev/null @@ -1,6 +0,0 @@ -.woocommerce-product-options-section.woocommerce-form-section { - .woocommerce-form-section__content { - padding: 0; - border: 0; - } -} diff --git a/plugins/woocommerce-admin/client/products/sections/options-section.tsx b/plugins/woocommerce-admin/client/products/sections/options-section.tsx deleted file mode 100644 index 821ffe9e9ab..00000000000 --- a/plugins/woocommerce-admin/client/products/sections/options-section.tsx +++ /dev/null @@ -1,55 +0,0 @@ -/** - * External dependencies - */ -import { __ } from '@wordpress/i18n'; -import { Link, useFormContext } from '@woocommerce/components'; -import { Product } from '@woocommerce/data'; -import { recordEvent } from '@woocommerce/tracks'; - -/** - * Internal dependencies - */ -import './options-section.scss'; -import { ProductSectionLayout } from '../layout/product-section-layout'; -import { Options } from '../fields/options'; - -export const OptionsSection: React.FC = () => { - const { - getInputProps, - values: { id: productId }, - } = useFormContext< Product >(); - - return ( - - - { __( - 'Add and manage options, such as size and color, for customers to choose on the product page.', - 'woocommerce' - ) } - - { - recordEvent( 'learn_more_about_options_help' ); - } } - > - { __( 'Learn more about options', 'woocommerce' ) } - - - } - > - - - ); -}; diff --git a/plugins/woocommerce/changelog/update-39453_simple_to_variable_product b/plugins/woocommerce/changelog/update-39453_simple_to_variable_product new file mode 100644 index 00000000000..61811ccbfca --- /dev/null +++ b/plugins/woocommerce/changelog/update-39453_simple_to_variable_product @@ -0,0 +1,4 @@ +Significance: minor +Type: dev + +Remove unused variation option components diff --git a/plugins/woocommerce/src/Admin/Features/ProductBlockEditor/Init.php b/plugins/woocommerce/src/Admin/Features/ProductBlockEditor/Init.php index c9852fe88b9..470054b98b7 100644 --- a/plugins/woocommerce/src/Admin/Features/ProductBlockEditor/Init.php +++ b/plugins/woocommerce/src/Admin/Features/ProductBlockEditor/Init.php @@ -46,6 +46,7 @@ class Init { add_action( 'admin_enqueue_scripts', array( $this, 'dequeue_conflicting_styles' ), 100 ); add_action( 'get_edit_post_link', array( $this, 'update_edit_product_link' ), 10, 2 ); } + add_filter( 'woocommerce_admin_get_user_data_fields', array( $this, 'add_user_data_fields' ) ); add_action( 'admin_enqueue_scripts', array( $this, 'enqueue_scripts' ) ); add_filter( 'woocommerce_register_post_type_product', array( $this, 'add_product_template' ) ); @@ -778,6 +779,21 @@ class Init { return $args; } + /** + * Adds fields so that we can store user preferences for the variations block. + * + * @param array $user_data_fields User data fields. + * @return array + */ + public function add_user_data_fields( $user_data_fields ) { + return array_merge( + $user_data_fields, + array( + 'variable_product_block_tour_shown', + ) + ); + } + /** * Sets the current screen to the block editor if a wc-admin page. */