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.