diff --git a/packages/js/components/changelog/add-36419-mvp-pricing-slot-fill b/packages/js/components/changelog/add-36419-mvp-pricing-slot-fill new file mode 100644 index 00000000000..015aaf6ddab --- /dev/null +++ b/packages/js/components/changelog/add-36419-mvp-pricing-slot-fill @@ -0,0 +1,4 @@ +Significance: patch +Type: fix + +Altering styles to correctly target fields within slot fills on product editor. diff --git a/packages/js/components/src/product-section-layout/style.scss b/packages/js/components/src/product-section-layout/style.scss index acd31a35894..64753f99ecd 100644 --- a/packages/js/components/src/product-section-layout/style.scss +++ b/packages/js/components/src/product-section-layout/style.scss @@ -11,9 +11,9 @@ &__body { padding: $gap-large; - > .components-base-control, - > .components-dropdown, - > .woocommerce-rich-text-editor { + .components-base-control, + .components-dropdown, + .woocommerce-rich-text-editor { &:not(:first-child):not(.components-radio-control) { margin-top: $gap-large - $gap-smaller; margin-bottom: 0; diff --git a/plugins/woocommerce-admin/client/products/fills/constants.ts b/plugins/woocommerce-admin/client/products/fills/constants.ts index c02e3feb90c..f55290dd385 100644 --- a/plugins/woocommerce-admin/client/products/fills/constants.ts +++ b/plugins/woocommerce-admin/client/products/fills/constants.ts @@ -1,5 +1,5 @@ -export const PRODUCT_DETAILS_SLUG = 'product-details'; - +export const PRICING_SECTION_BASIC_ID = 'pricing/basic'; +export const PRICING_SECTION_TAXES_ID = 'pricing/taxes'; export const DETAILS_SECTION_ID = 'general/details'; export const INVENTORY_SECTION_ID = 'inventory/inventory'; export const INVENTORY_SECTION_ADVANCED_ID = 'inventory/advanced'; @@ -11,5 +11,7 @@ export const SHIPPING_SECTION_DIMENSIONS_ID = 'shipping/dimensions'; export const TAB_INVENTORY_ID = 'tab/inventory'; export const TAB_GENERAL_ID = 'tab/general'; export const TAB_SHIPPING_ID = 'tab/shipping'; +export const TAB_PRICING_ID = 'tab/pricing'; export const PLUGIN_ID = 'woocommerce'; +export const PRODUCT_DETAILS_SLUG = 'product-details'; diff --git a/plugins/woocommerce-admin/client/products/fills/index.ts b/plugins/woocommerce-admin/client/products/fills/index.ts index de7aec1f4ca..00952fc20ef 100644 --- a/plugins/woocommerce-admin/client/products/fills/index.ts +++ b/plugins/woocommerce-admin/client/products/fills/index.ts @@ -8,3 +8,4 @@ export * from './details-section/details-section-fills'; export * from './images-section/images-section-fills'; export * from './attributes-section/attributes-section-fills'; export * from './inventory-section/inventory-section-fills'; +export * from './pricing-section/pricing-section-fills'; diff --git a/plugins/woocommerce-admin/client/products/fills/pricing-section/index.ts b/plugins/woocommerce-admin/client/products/fills/pricing-section/index.ts new file mode 100644 index 00000000000..8fa0169c0ff --- /dev/null +++ b/plugins/woocommerce-admin/client/products/fills/pricing-section/index.ts @@ -0,0 +1,4 @@ +export * from './pricing-field-list'; +export * from './pricing-field-sale'; +export * from './pricing-field-taxes-charge'; +export * from './pricing-field-taxes-class'; diff --git a/plugins/woocommerce-admin/client/products/fills/pricing-section/pricing-field-list.tsx b/plugins/woocommerce-admin/client/products/fills/pricing-section/pricing-field-list.tsx new file mode 100644 index 00000000000..67f5c971df6 --- /dev/null +++ b/plugins/woocommerce-admin/client/products/fills/pricing-section/pricing-field-list.tsx @@ -0,0 +1,116 @@ +/** + * External dependencies + */ +import { __ } from '@wordpress/i18n'; +import { useFormContext, Link } from '@woocommerce/components'; +import { recordEvent } from '@woocommerce/tracks'; +import { useContext } from '@wordpress/element'; +import { Product, SETTINGS_STORE_NAME } from '@woocommerce/data'; +import { useSelect } from '@wordpress/data'; +import interpolateComponents from '@automattic/interpolate-components'; +import { + BaseControl, + // @ts-expect-error `__experimentalInputControl` does exist. + __experimentalInputControl as InputControl, +} from '@wordpress/components'; + +/** + * Internal dependencies + */ +import { CurrencyInputProps } from './pricing-section-fills'; +import { formatCurrencyDisplayValue } from '../../sections/utils'; +import { CurrencyContext } from '../../../lib/currency-context'; +import { ADMIN_URL } from '~/utils/admin-settings'; + +type PricingListFieldProps = { + currencyInputProps: CurrencyInputProps; +}; + +export const PricingListField: React.FC< PricingListFieldProps > = ( { + currencyInputProps, +} ) => { + const { getInputProps } = useFormContext< Product >(); + const context = useContext( CurrencyContext ); + const { getCurrencyConfig, formatAmount } = context; + const currencyConfig = getCurrencyConfig(); + + const { isResolving: isTaxSettingsResolving, taxSettings } = useSelect( + ( select ) => { + const { getSettings, hasFinishedResolution } = + select( SETTINGS_STORE_NAME ); + return { + isResolving: ! hasFinishedResolution( 'getSettings', [ + 'tax', + ] ), + taxSettings: getSettings( 'tax' ).tax || {}, + taxesEnabled: + getSettings( 'general' )?.general + ?.woocommerce_calc_taxes === 'yes', + }; + } + ); + + const regularPriceProps = getInputProps( + 'regular_price', + currencyInputProps + ); + + const taxIncludedInPriceText = __( + 'Per your {{link}}store settings{{/link}}, tax is {{strong}}included{{/strong}} in the price.', + 'woocommerce' + ); + const taxNotIncludedInPriceText = __( + 'Per your {{link}}store settings{{/link}}, tax is {{strong}}not included{{/strong}} in the price.', + 'woocommerce' + ); + const pricesIncludeTax = + taxSettings.woocommerce_prices_include_tax === 'yes'; + + const taxSettingsElement = interpolateComponents( { + mixedString: pricesIncludeTax + ? taxIncludedInPriceText + : taxNotIncludedInPriceText, + components: { + link: ( + { + recordEvent( + 'product_pricing_list_price_help_tax_settings_click' + ); + } } + > + <> + + ), + strong: , + }, + } ); + + return ( + <> + + + + { ! isTaxSettingsResolving && ( + + { taxSettingsElement } + + ) } + + ); +}; diff --git a/plugins/woocommerce-admin/client/products/fills/pricing-section/pricing-field-sale.tsx b/plugins/woocommerce-admin/client/products/fills/pricing-section/pricing-field-sale.tsx new file mode 100644 index 00000000000..4cd274c2743 --- /dev/null +++ b/plugins/woocommerce-admin/client/products/fills/pricing-section/pricing-field-sale.tsx @@ -0,0 +1,213 @@ +/** + * External dependencies + */ +import { __ } from '@wordpress/i18n'; +import { + useFormContext, + Link, + __experimentalTooltip as Tooltip, + DateTimePickerControl, +} from '@woocommerce/components'; +import { recordEvent } from '@woocommerce/tracks'; +import { useContext, useState, useEffect } from '@wordpress/element'; +import { Product, OPTIONS_STORE_NAME } from '@woocommerce/data'; +import { useSelect } from '@wordpress/data'; +import interpolateComponents from '@automattic/interpolate-components'; +import { format as formatDate } from '@wordpress/date'; +import moment from 'moment'; +import { + BaseControl, + // @ts-expect-error `__experimentalInputControl` does exist. + __experimentalInputControl as InputControl, + ToggleControl, +} from '@wordpress/components'; + +/** + * Internal dependencies + */ +import { CurrencyInputProps } from './pricing-section-fills'; +import { formatCurrencyDisplayValue } from '../../sections/utils'; +import { CurrencyContext } from '../../../lib/currency-context'; + +type PricingListFieldProps = { + currencyInputProps: CurrencyInputProps; +}; + +const PRODUCT_SCHEDULED_SALE_SLUG = 'product-scheduled-sale'; + +export const PricingSaleField: React.FC< PricingListFieldProps > = ( { + currencyInputProps, +} ) => { + const { getInputProps, values, setValues } = useFormContext< Product >(); + + const { dateFormat, timeFormat } = useSelect( ( select ) => { + const { getOption } = select( OPTIONS_STORE_NAME ); + return { + dateFormat: ( getOption( 'date_format' ) as string ) || 'F j, Y', + timeFormat: ( getOption( 'time_format' ) as string ) || 'H:i', + }; + } ); + + const context = useContext( CurrencyContext ); + const { getCurrencyConfig, formatAmount } = context; + const currencyConfig = getCurrencyConfig(); + + const [ showSaleSchedule, setShowSaleSchedule ] = useState( false ); + const [ userToggledSaleSchedule, setUserToggledSaleSchedule ] = + useState( false ); + const [ autoToggledSaleSchedule, setAutoToggledSaleSchedule ] = + useState( false ); + + useEffect( () => { + if ( userToggledSaleSchedule || autoToggledSaleSchedule ) { + return; + } + + const hasDateOnSaleFrom = + typeof values.date_on_sale_from_gmt === 'string' && + values.date_on_sale_from_gmt.length > 0; + const hasDateOnSaleTo = + typeof values.date_on_sale_to_gmt === 'string' && + values.date_on_sale_to_gmt.length > 0; + + const hasSaleSchedule = hasDateOnSaleFrom || hasDateOnSaleTo; + + if ( hasSaleSchedule ) { + setAutoToggledSaleSchedule( true ); + setShowSaleSchedule( true ); + } + }, [ userToggledSaleSchedule, autoToggledSaleSchedule, values ] ); + + const salePriceProps = getInputProps( 'sale_price', currencyInputProps ); + + const dateTimePickerProps = { + className: 'woocommerce-product__date-time-picker', + isDateOnlyPicker: true, + dateTimeFormat: dateFormat, + }; + + const onSaleScheduleToggleChange = ( value: boolean ) => { + recordEvent( 'product_pricing_schedule_sale_toggle_click', { + enabled: value, + } ); + + setUserToggledSaleSchedule( true ); + setShowSaleSchedule( value ); + + if ( value ) { + setValues( { + date_on_sale_from_gmt: moment().startOf( 'day' ).toISOString(), + date_on_sale_to_gmt: null, + } as Product ); + } else { + setValues( { + date_on_sale_from_gmt: null, + date_on_sale_to_gmt: null, + } as Product ); + } + }; + + return ( + <> + + + + + + { __( 'Schedule sale', 'woocommerce' ) } + + { formatDate( + timeFormat, + moment().startOf( 'day' ) + ) } + + ), + endTime: ( + + { formatDate( + timeFormat, + moment().endOf( 'day' ) + ) } + + ), + moreLink: ( + + recordEvent( + 'add_product_learn_more', + { + category: + PRODUCT_SCHEDULED_SALE_SLUG, + } + ) + } + > + { __( + 'Learn more', + 'woocommerce' + ) } + + ), + }, + } ) } + /> + + } + checked={ showSaleSchedule } + onChange={ onSaleScheduleToggleChange } + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore disabled prop exists + disabled={ ! ( values.sale_price?.length > 0 ) } + /> + + { showSaleSchedule && ( + <> + + + + + ) } + + ); +}; diff --git a/plugins/woocommerce-admin/client/products/fills/pricing-section/pricing-field-taxes-charge.tsx b/plugins/woocommerce-admin/client/products/fills/pricing-section/pricing-field-taxes-charge.tsx new file mode 100644 index 00000000000..cd7f28c05da --- /dev/null +++ b/plugins/woocommerce-admin/client/products/fills/pricing-section/pricing-field-taxes-charge.tsx @@ -0,0 +1,40 @@ +/** + * External dependencies + */ +import { __ } from '@wordpress/i18n'; +import { useFormContext } from '@woocommerce/components'; +import { Product } from '@woocommerce/data'; +import { RadioControl } from '@wordpress/components'; + +export const PricingTaxesChargeField = () => { + const { getInputProps } = useFormContext< Product >(); + + const taxStatusProps = getInputProps( 'tax_status' ); + // These properties cause issues with the RadioControl component. + // A fix to form upstream would help if we can identify what type of input is used. + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + delete taxStatusProps.checked; + delete taxStatusProps.value; + + return ( + + ); +}; diff --git a/plugins/woocommerce-admin/client/products/fills/pricing-section/pricing-field-taxes-class.tsx b/plugins/woocommerce-admin/client/products/fills/pricing-section/pricing-field-taxes-class.tsx new file mode 100644 index 00000000000..3dc6f675298 --- /dev/null +++ b/plugins/woocommerce-admin/client/products/fills/pricing-section/pricing-field-taxes-class.tsx @@ -0,0 +1,87 @@ +/** + * External dependencies + */ +import { __ } from '@wordpress/i18n'; +import { + useFormContext, + CollapsibleContent, + Link, +} from '@woocommerce/components'; +import { + Product, + EXPERIMENTAL_TAX_CLASSES_STORE_NAME, + TaxClass, +} from '@woocommerce/data'; +import { useSelect } from '@wordpress/data'; +import { RadioControl } from '@wordpress/components'; +import interpolateComponents from '@automattic/interpolate-components'; + +/** + * Internal dependencies + */ +import { STANDARD_RATE_TAX_CLASS_SLUG } from '../../constants'; + +export const PricingTaxesClassField = () => { + const { getInputProps } = useFormContext< Product >(); + + const { isResolving: isTaxClassesResolving, taxClasses } = useSelect( + ( select ) => { + const { hasFinishedResolution, getTaxClasses } = select( + EXPERIMENTAL_TAX_CLASSES_STORE_NAME + ); + return { + isResolving: ! hasFinishedResolution( 'getTaxClasses' ), + taxClasses: getTaxClasses< TaxClass[] >(), + }; + } + ); + + const taxClassProps = getInputProps( 'tax_class' ); + // These properties cause issues with the RadioControl component. + // A fix to form upstream would help if we can identify what type of input is used. + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + delete taxClassProps.checked; + delete taxClassProps.value; + + return ( + + { ! isTaxClassesResolving && taxClasses.length > 0 && ( + + { __( 'Tax class', 'woocommerce' ) } + + { interpolateComponents( { + mixedString: __( + 'Apply a tax rate if this product qualifies for tax reduction or exemption. {{link}}Learn more{{/link}}', + 'woocommerce' + ), + components: { + link: ( + + <> + + ), + }, + } ) } + + + } + options={ taxClasses.map( ( taxClass ) => ( { + label: taxClass.name, + value: + taxClass.slug === STANDARD_RATE_TAX_CLASS_SLUG + ? '' + : taxClass.slug, + } ) ) } + /> + ) } + + ); +}; diff --git a/plugins/woocommerce-admin/client/products/fills/pricing-section/pricing-section-fills.tsx b/plugins/woocommerce-admin/client/products/fills/pricing-section/pricing-section-fills.tsx new file mode 100644 index 00000000000..b2a909769d3 --- /dev/null +++ b/plugins/woocommerce-admin/client/products/fills/pricing-section/pricing-section-fills.tsx @@ -0,0 +1,187 @@ +/** + * External dependencies + */ +import { __ } from '@wordpress/i18n'; +import { + __experimentalWooProductSectionItem as WooProductSectionItem, + __experimentalWooProductFieldItem as WooProductFieldItem, + __experimentalProductSectionLayout as ProductSectionLayout, + Link, + useFormContext, +} from '@woocommerce/components'; +import { registerPlugin } from '@wordpress/plugins'; +import { recordEvent } from '@woocommerce/tracks'; +import { Product } from '@woocommerce/data'; +import { useContext } from '@wordpress/element'; +import { Card, CardBody } from '@wordpress/components'; + +/** + * Internal dependencies + */ +import { + PricingListField, + PricingSaleField, + PricingTaxesClassField, + PricingTaxesChargeField, +} from './index'; +import { useProductHelper } from '../../use-product-helper'; +import { + PRICING_SECTION_BASIC_ID, + PRICING_SECTION_TAXES_ID, + TAB_PRICING_ID, + PLUGIN_ID, +} from '../constants'; +import { CurrencyContext } from '../../../lib/currency-context'; + +import './pricing-section.scss'; + +export type CurrencyInputProps = { + prefix: string; + className: string; + sanitize: ( value: Product[ keyof Product ] ) => string; + onFocus: ( event: React.FocusEvent< HTMLInputElement > ) => void; + onKeyUp: ( event: React.KeyboardEvent< HTMLInputElement > ) => void; +}; + +const PricingSection = () => { + const { setValues, values } = useFormContext< Product >(); + const { sanitizePrice } = useProductHelper(); + + const context = useContext( CurrencyContext ); + const { getCurrencyConfig } = context; + const currencyConfig = getCurrencyConfig(); + + const currencyInputProps: CurrencyInputProps = { + prefix: currencyConfig.symbol, + className: 'half-width-field components-currency-control', + sanitize: ( value: Product[ keyof Product ] ) => { + return sanitizePrice( String( value ) ); + }, + onFocus( event: React.FocusEvent< HTMLInputElement > ) { + // In some browsers like safari .select() function inside + // the onFocus event doesn't work as expected because it + // conflicts with onClick the first time user click the + // input. Using setTimeout defers the text selection and + // avoid the unexpected behaviour. + setTimeout( + function deferSelection( element: HTMLInputElement ) { + element.select(); + }, + 0, + event.currentTarget + ); + }, + onKeyUp( event: React.KeyboardEvent< HTMLInputElement > ) { + const name = event.currentTarget.name as keyof Pick< + Product, + 'regular_price' | 'sale_price' + >; + const amount = Number.parseFloat( + sanitizePrice( values[ name ] || '0' ) + ); + const step = Number( event.currentTarget.step || '1' ); + if ( event.code === 'ArrowUp' ) { + setValues( { + [ name ]: String( amount + step ), + } as unknown as Product ); + } + if ( event.code === 'ArrowDown' ) { + setValues( { + [ name ]: String( amount - step ), + } as unknown as Product ); + } + }, + }; + + return ( + <> + + + + { __( + 'Set a competitive price, put the product on sale, and manage tax calculations.', + 'woocommerce' + ) } + + { + recordEvent( 'add_product_pricing_help' ); + } } + > + { __( + 'How to price your product: expert tips', + 'woocommerce' + ) } + + + } + > + + + + + + + + + + + + + + + + + + + + + + + + + + ); +}; + +registerPlugin( 'wc-admin-product-editor-pricing-section', { + // @ts-expect-error 'scope' does exist. @types/wordpress__plugins is outdated. + scope: 'woocommerce-product-editor', + render: () => , +} ); diff --git a/plugins/woocommerce-admin/client/products/sections/pricing-section.scss b/plugins/woocommerce-admin/client/products/fills/pricing-section/pricing-section.scss similarity index 100% rename from plugins/woocommerce-admin/client/products/sections/pricing-section.scss rename to plugins/woocommerce-admin/client/products/fills/pricing-section/pricing-section.scss diff --git a/plugins/woocommerce-admin/client/products/product-form.tsx b/plugins/woocommerce-admin/client/products/product-form.tsx index 374cf25fb3b..b00a3a1d2a5 100644 --- a/plugins/woocommerce-admin/client/products/product-form.tsx +++ b/plugins/woocommerce-admin/client/products/product-form.tsx @@ -16,7 +16,6 @@ import { Ref } from 'react'; */ import { ProductFormHeader } from './layout/product-form-header'; import { ProductFormLayout } from './layout/product-form-layout'; -import { PricingSection } from './sections/pricing-section'; import { ProductVariationsSection } from './sections/product-variations-section'; import { validate } from './product-validation'; import { OptionsSection } from './sections/options-section'; @@ -26,6 +25,7 @@ import { TAB_GENERAL_ID, TAB_SHIPPING_ID, TAB_INVENTORY_ID, + TAB_PRICING_ID, } from './fills/constants'; export const ProductForm: React.FC< { @@ -60,7 +60,9 @@ export const ProductForm: React.FC< { title="Pricing" disabled={ !! product?.variations?.length } > - + - + { - const { sanitizePrice } = useProductHelper(); - const { getInputProps, setValues, values } = useFormContext< Product >(); - const [ showSaleSchedule, setShowSaleSchedule ] = useState( false ); - const [ userToggledSaleSchedule, setUserToggledSaleSchedule ] = - useState( false ); - const [ autoToggledSaleSchedule, setAutoToggledSaleSchedule ] = - useState( false ); - const { - isResolving: isTaxSettingsResolving, - taxSettings, - taxesEnabled, - } = useSelect( ( select ) => { - const { getSettings, hasFinishedResolution } = - select( SETTINGS_STORE_NAME ); - return { - isResolving: ! hasFinishedResolution( 'getSettings', [ 'tax' ] ), - taxSettings: getSettings( 'tax' ).tax || {}, - taxesEnabled: - getSettings( 'general' )?.general?.woocommerce_calc_taxes === - 'yes', - }; - } ); - - const { isResolving: isTaxClassesResolving, taxClasses } = useSelect( - ( select ) => { - const { hasFinishedResolution, getTaxClasses } = select( - EXPERIMENTAL_TAX_CLASSES_STORE_NAME - ); - return { - isResolving: ! hasFinishedResolution( 'getTaxClasses' ), - taxClasses: getTaxClasses< TaxClass[] >(), - }; - } - ); - - const pricesIncludeTax = - taxSettings.woocommerce_prices_include_tax === 'yes'; - const context = useContext( CurrencyContext ); - const { getCurrencyConfig, formatAmount } = context; - const currencyConfig = getCurrencyConfig(); - - const taxIncludedInPriceText = __( - 'Per your {{link}}store settings{{/link}}, tax is {{strong}}included{{/strong}} in the price.', - 'woocommerce' - ); - const taxNotIncludedInPriceText = __( - 'Per your {{link}}store settings{{/link}}, tax is {{strong}}not included{{/strong}} in the price.', - 'woocommerce' - ); - - const { dateFormat, timeFormat } = useSelect( ( select ) => { - const { getOption } = select( OPTIONS_STORE_NAME ); - return { - dateFormat: ( getOption( 'date_format' ) as string ) || 'F j, Y', - timeFormat: ( getOption( 'time_format' ) as string ) || 'H:i', - }; - } ); - - const onSaleScheduleToggleChange = ( value: boolean ) => { - recordEvent( 'product_pricing_schedule_sale_toggle_click', { - enabled: value, - } ); - - setUserToggledSaleSchedule( true ); - setShowSaleSchedule( value ); - - if ( value ) { - setValues( { - date_on_sale_from_gmt: moment().startOf( 'day' ).toISOString(), - date_on_sale_to_gmt: null, - } as Product ); - } else { - setValues( { - date_on_sale_from_gmt: null, - date_on_sale_to_gmt: null, - } as Product ); - } - }; - - useEffect( () => { - if ( userToggledSaleSchedule || autoToggledSaleSchedule ) { - return; - } - - const hasDateOnSaleFrom = - typeof values.date_on_sale_from_gmt === 'string' && - values.date_on_sale_from_gmt.length > 0; - const hasDateOnSaleTo = - typeof values.date_on_sale_to_gmt === 'string' && - values.date_on_sale_to_gmt.length > 0; - - const hasSaleSchedule = hasDateOnSaleFrom || hasDateOnSaleTo; - - if ( hasSaleSchedule ) { - setAutoToggledSaleSchedule( true ); - setShowSaleSchedule( true ); - } - }, [ userToggledSaleSchedule, autoToggledSaleSchedule, values ] ); - - const taxSettingsElement = interpolateComponents( { - mixedString: pricesIncludeTax - ? taxIncludedInPriceText - : taxNotIncludedInPriceText, - components: { - link: ( - { - recordEvent( - 'product_pricing_list_price_help_tax_settings_click' - ); - } } - > - <> - - ), - strong: , - }, - } ); - - const currencyInputProps = { - prefix: currencyConfig.symbol, - className: 'half-width-field components-currency-control', - sanitize: ( value: Product[ keyof Product ] ) => { - return sanitizePrice( String( value ) ); - }, - onFocus( event: React.FocusEvent< HTMLInputElement > ) { - // In some browsers like safari .select() function inside - // the onFocus event doesn't work as expected because it - // conflicts with onClick the first time user click the - // input. Using setTimeout defers the text selection and - // avoid the unexpected behaviour. - setTimeout( - function deferSelection( element: HTMLInputElement ) { - element.select(); - }, - 0, - event.currentTarget - ); - }, - onKeyUp( event: React.KeyboardEvent< HTMLInputElement > ) { - const name = event.currentTarget.name as keyof Pick< - Product, - 'regular_price' | 'sale_price' - >; - const amount = Number.parseFloat( - sanitizePrice( values[ name ] || '0' ) - ); - const step = Number( event.currentTarget.step || '1' ); - if ( event.code === 'ArrowUp' ) { - setValues( { - [ name ]: String( amount + step ), - } as unknown as Product ); - } - if ( event.code === 'ArrowDown' ) { - setValues( { - [ name ]: String( amount - step ), - } as unknown as Product ); - } - }, - }; - const regularPriceProps = getInputProps( - 'regular_price', - currencyInputProps - ); - const salePriceProps = getInputProps( 'sale_price', currencyInputProps ); - - const dateTimePickerProps = { - className: 'woocommerce-product__date-time-picker', - isDateOnlyPicker: true, - dateTimeFormat: dateFormat, - }; - - const taxStatusProps = getInputProps( 'tax_status' ); - // These properties cause issues with the RadioControl component. - // A fix to form upstream would help if we can identify what type of input is used. - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore - delete taxStatusProps.checked; - delete taxStatusProps.value; - - const taxClassProps = getInputProps( 'tax_class' ); - // These properties cause issues with the RadioControl component. - // A fix to form upstream would help if we can identify what type of input is used. - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore - delete taxClassProps.checked; - delete taxClassProps.value; - - return ( - - - { __( - 'Set a competitive price, put the product on sale, and manage tax calculations.', - 'woocommerce' - ) } - - { - recordEvent( 'add_product_pricing_help' ); - } } - > - { __( - 'How to price your product: expert tips', - 'woocommerce' - ) } - - - } - > - - - - - - { ! isTaxSettingsResolving && ( - - { taxSettingsElement } - - ) } - - - - - - - { __( 'Schedule sale', 'woocommerce' ) } - - { formatDate( - timeFormat, - moment().startOf( - 'day' - ) - ) } - - ), - endTime: ( - - { formatDate( - timeFormat, - moment().endOf( 'day' ) - ) } - - ), - moreLink: ( - - recordEvent( - 'add_product_learn_more', - { - category: - PRODUCT_SCHEDULED_SALE_SLUG, - } - ) - } - > - { __( - 'Learn more', - 'woocommerce' - ) } - - ), - }, - } ) } - /> - - } - checked={ showSaleSchedule } - onChange={ onSaleScheduleToggleChange } - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore disabled prop exists - disabled={ ! ( values.sale_price?.length > 0 ) } - /> - - { showSaleSchedule && ( - <> - - - - - ) } - - - - { taxesEnabled && ( - - - - - - { ! isTaxClassesResolving && - taxClasses.length > 0 && ( - - - { __( - 'Tax class', - 'woocommerce' - ) } - - - { interpolateComponents( { - mixedString: __( - 'Apply a tax rate if this product qualifies for tax reduction or exemption. {{link}}Learn more{{/link}}', - 'woocommerce' - ), - components: { - link: ( - - <> - - ), - }, - } ) } - - - } - options={ taxClasses.map( - ( taxClass ) => ( { - label: taxClass.name, - value: - taxClass.slug === - STANDARD_RATE_TAX_CLASS_SLUG - ? '' - : taxClass.slug, - } ) - ) } - /> - ) } - - - - ) } - - ); -}; diff --git a/plugins/woocommerce/changelog/add-36419-mvp-pricing-slot-fill b/plugins/woocommerce/changelog/add-36419-mvp-pricing-slot-fill new file mode 100644 index 00000000000..618c236ff84 --- /dev/null +++ b/plugins/woocommerce/changelog/add-36419-mvp-pricing-slot-fill @@ -0,0 +1,4 @@ +Significance: minor +Type: update + +Migrating product editor pricing section to slot fills.