Price section - Small refactor and style fix (#34558)
* Fix modal * Add method sanitizePrice to `useProductHelper` # Conflicts: # plugins/woocommerce-admin/client/products/sections/pricing-section.tsx # Conflicts: # plugins/woocommerce-admin/client/products/sections/pricing-section.tsx * Add changelog * Add sales validation * Add sale price validation * Fix regularPriceProps * Set list price when it's empty * Fix Sale price validation * Check empty regular_price * Small refactor to use-product-helper * Remove method `maybeSetRegularPrice` Co-authored-by: Fernando Marichal <contacto@fernandomarichal.com>
This commit is contained in:
parent
b50f651129
commit
dadb1d6a87
|
@ -42,6 +42,11 @@
|
|||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
.woocommerce-enriched-label__help-wrapper {
|
||||
.components-popover {
|
||||
margin-top: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.woocommerce-edit-product {
|
||||
|
|
|
@ -34,5 +34,18 @@ export const validate = (
|
|||
'woocommerce'
|
||||
);
|
||||
}
|
||||
|
||||
if (
|
||||
values.sale_price &&
|
||||
( ! values.regular_price ||
|
||||
parseFloat( values.sale_price ) >=
|
||||
parseFloat( values?.regular_price ) )
|
||||
) {
|
||||
errors.sale_price = __(
|
||||
'Sale price cannot be equal to or higher than list price.',
|
||||
'woocommerce'
|
||||
);
|
||||
}
|
||||
|
||||
return errors;
|
||||
};
|
||||
|
|
|
@ -8,9 +8,11 @@ import { recordEvent } from '@woocommerce/tracks';
|
|||
import { useContext } from '@wordpress/element';
|
||||
import { useSelect } from '@wordpress/data';
|
||||
import interpolateComponents from '@automattic/interpolate-components';
|
||||
import classnames from 'classnames';
|
||||
import {
|
||||
// @ts-expect-error `__experimentalInputControl` does exist.
|
||||
__experimentalInputControl as InputControl,
|
||||
BaseControl,
|
||||
} from '@wordpress/components';
|
||||
|
||||
/**
|
||||
|
@ -21,13 +23,11 @@ import { ProductSectionLayout } from '../layout/product-section-layout';
|
|||
import { getInputControlProps } from './utils';
|
||||
import { ADMIN_URL } from '../../utils/admin-settings';
|
||||
import { CurrencyContext } from '../../lib/currency-context';
|
||||
import {
|
||||
NUMBERS_AND_DECIMAL_SEPARATOR,
|
||||
ONLY_ONE_DECIMAL_SEPARATOR,
|
||||
} from '../constants';
|
||||
import { useProductHelper } from '../use-product-helper';
|
||||
|
||||
export const PricingSection: React.FC = () => {
|
||||
const { getInputProps, setValue } = useFormContext< Product >();
|
||||
const { sanitizePrice } = useProductHelper();
|
||||
const { isResolving: isTaxSettingsResolving, taxSettings } = useSelect(
|
||||
( select ) => {
|
||||
const { getSettings, hasFinishedResolution } =
|
||||
|
@ -43,25 +43,6 @@ export const PricingSection: React.FC = () => {
|
|||
const pricesIncludeTax =
|
||||
taxSettings.woocommerce_prices_include_tax === 'yes';
|
||||
const context = useContext( CurrencyContext );
|
||||
const { getCurrencyConfig } = context;
|
||||
const { decimalSeparator } = getCurrencyConfig();
|
||||
const sanitizeAndSetPrice = ( name: string, value: string ) => {
|
||||
// Build regex to strip out everything except digits, decimal point and minus sign.
|
||||
const regex = new RegExp(
|
||||
NUMBERS_AND_DECIMAL_SEPARATOR.replace( '%s', decimalSeparator ),
|
||||
'g'
|
||||
);
|
||||
const decimalRegex = new RegExp(
|
||||
ONLY_ONE_DECIMAL_SEPARATOR.replaceAll( '%s', decimalSeparator ),
|
||||
'g'
|
||||
);
|
||||
const cleanValue = value
|
||||
.replace( regex, '' )
|
||||
.replace( decimalRegex, '' )
|
||||
.replace( decimalSeparator, '.' );
|
||||
setValue( name, cleanValue );
|
||||
return cleanValue;
|
||||
};
|
||||
|
||||
const taxIncludedInPriceText = __(
|
||||
'Per your {{link}}store settings{{/link}}, tax is {{strong}}included{{/strong}} in the price.',
|
||||
|
@ -105,6 +86,11 @@ export const PricingSection: React.FC = () => {
|
|||
},
|
||||
} );
|
||||
|
||||
const salePriceProps = getInputControlProps( {
|
||||
...getInputProps( 'sale_price' ),
|
||||
context,
|
||||
} );
|
||||
|
||||
return (
|
||||
<ProductSectionLayout
|
||||
title={ __( 'Pricing', 'woocommerce' ) }
|
||||
|
@ -138,9 +124,10 @@ export const PricingSection: React.FC = () => {
|
|||
...getInputProps( 'regular_price' ),
|
||||
context,
|
||||
} ) }
|
||||
onChange={ ( value: string ) =>
|
||||
sanitizeAndSetPrice( 'regular_price', value )
|
||||
}
|
||||
onChange={ ( value: string ) => {
|
||||
const sanitizedValue = sanitizePrice( value );
|
||||
setValue( 'regular_price', sanitizedValue );
|
||||
} }
|
||||
/>
|
||||
{ ! isTaxSettingsResolving && (
|
||||
<span className="woocommerce-product-form__secondary-text">
|
||||
|
@ -149,19 +136,32 @@ export const PricingSection: React.FC = () => {
|
|||
) }
|
||||
</div>
|
||||
|
||||
<div className="woocommerce-product-form__custom-label-input">
|
||||
<InputControl
|
||||
label={ salePriceTitle }
|
||||
id="sale_price"
|
||||
placeholder={ __( '8.59', 'woocommerce' ) }
|
||||
{ ...getInputControlProps( {
|
||||
...getInputProps( 'sale_price' ),
|
||||
context,
|
||||
} ) }
|
||||
onChange={ ( value: string ) =>
|
||||
sanitizeAndSetPrice( 'sale_price', value )
|
||||
<div
|
||||
className={ classnames(
|
||||
'woocommerce-product-form__custom-label-input',
|
||||
{
|
||||
'has-error': salePriceProps?.help !== '',
|
||||
}
|
||||
/>
|
||||
) }
|
||||
>
|
||||
<BaseControl
|
||||
id="sale_price"
|
||||
help={
|
||||
salePriceProps && salePriceProps.help
|
||||
? salePriceProps.help
|
||||
: ''
|
||||
}
|
||||
>
|
||||
<InputControl
|
||||
label={ salePriceTitle }
|
||||
placeholder={ __( '8.59', 'woocommerce' ) }
|
||||
{ ...salePriceProps }
|
||||
onChange={ ( value: string ) => {
|
||||
const sanitizedValue = sanitizePrice( value );
|
||||
setValue( 'sale_price', sanitizedValue );
|
||||
} }
|
||||
/>
|
||||
</BaseControl>
|
||||
</div>
|
||||
</ProductSectionLayout>
|
||||
);
|
||||
|
|
|
@ -426,6 +426,8 @@ describe( 'Validations', () => {
|
|||
'Please enter a price with one monetary decimal point without thousand separators and currency symbols.';
|
||||
const salePriceErrorMessage =
|
||||
'Please enter a price with one monetary decimal point without thousand separators and currency symbols.';
|
||||
const highSalePriceErrorMessage =
|
||||
'Sale price cannot be equal to or higher than list price.';
|
||||
const productWithoutName: Partial< Product > = {
|
||||
name: '',
|
||||
};
|
||||
|
@ -443,16 +445,24 @@ describe( 'Validations', () => {
|
|||
};
|
||||
const productSalePriceWithText: Partial< Product > = {
|
||||
name: 'My Product',
|
||||
regular_price: '201',
|
||||
sale_price: 'text',
|
||||
};
|
||||
const productSalePriceWithNotAllowedCharacters: Partial< Product > = {
|
||||
name: 'My Product',
|
||||
regular_price: '201',
|
||||
sale_price: '%&@#¢∞¬÷200',
|
||||
};
|
||||
const productSalePriceWithSpaces: Partial< Product > = {
|
||||
name: 'My Product',
|
||||
regular_price: '201',
|
||||
sale_price: '2 0 0',
|
||||
};
|
||||
const productSalePriceHigherThanRegular: Partial< Product > = {
|
||||
name: 'My Product',
|
||||
regular_price: '201',
|
||||
sale_price: '202',
|
||||
};
|
||||
const validProduct: Partial< Product > = {
|
||||
name: 'My Product',
|
||||
regular_price: '200',
|
||||
|
@ -482,6 +492,9 @@ describe( 'Validations', () => {
|
|||
expect( validate( productSalePriceWithSpaces ) ).toEqual( {
|
||||
sale_price: salePriceErrorMessage,
|
||||
} );
|
||||
expect( validate( productSalePriceHigherThanRegular ) ).toEqual( {
|
||||
sale_price: highSalePriceErrorMessage,
|
||||
} );
|
||||
expect( validate( validProduct ) ).toEqual( {} );
|
||||
} );
|
||||
} );
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
*/
|
||||
import { __ } from '@wordpress/i18n';
|
||||
import { useDispatch } from '@wordpress/data';
|
||||
import { useCallback, useState } from '@wordpress/element';
|
||||
import { useCallback, useContext, useState } from '@wordpress/element';
|
||||
import {
|
||||
Product,
|
||||
ProductsStoreActions,
|
||||
|
@ -15,6 +15,15 @@ import {
|
|||
import { recordEvent } from '@woocommerce/tracks';
|
||||
import { navigateTo } from '@woocommerce/navigation';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import { CurrencyContext } from '../lib/currency-context';
|
||||
import {
|
||||
NUMBERS_AND_DECIMAL_SEPARATOR,
|
||||
ONLY_ONE_DECIMAL_SEPARATOR,
|
||||
} from './constants';
|
||||
|
||||
function removeReadonlyProperties(
|
||||
product: Product
|
||||
): Omit< Product, ReadOnlyProperties > {
|
||||
|
@ -48,6 +57,7 @@ export function useProductHelper() {
|
|||
draft: false,
|
||||
publish: false,
|
||||
} );
|
||||
const context = useContext( CurrencyContext );
|
||||
|
||||
/**
|
||||
* Create product with status.
|
||||
|
@ -248,11 +258,40 @@ export function useProductHelper() {
|
|||
[]
|
||||
);
|
||||
|
||||
/**
|
||||
* Sanitizes a price.
|
||||
*
|
||||
* @param {string} price the price that will be sanitized.
|
||||
* @return {string} sanitized price.
|
||||
*/
|
||||
const sanitizePrice = useCallback(
|
||||
( price: string ) => {
|
||||
const { getCurrencyConfig } = context;
|
||||
const { decimalSeparator } = getCurrencyConfig();
|
||||
// Build regex to strip out everything except digits, decimal point and minus sign.
|
||||
const regex = new RegExp(
|
||||
NUMBERS_AND_DECIMAL_SEPARATOR.replace( '%s', decimalSeparator ),
|
||||
'g'
|
||||
);
|
||||
const decimalRegex = new RegExp(
|
||||
ONLY_ONE_DECIMAL_SEPARATOR.replaceAll( '%s', decimalSeparator ),
|
||||
'g'
|
||||
);
|
||||
const cleanValue = price
|
||||
.replace( regex, '' )
|
||||
.replace( decimalRegex, '' )
|
||||
.replace( decimalSeparator, '.' );
|
||||
return cleanValue;
|
||||
},
|
||||
[ context ]
|
||||
);
|
||||
|
||||
return {
|
||||
createProductWithStatus,
|
||||
updateProductWithStatus,
|
||||
copyProductWithStatus,
|
||||
deleteProductAndRedirect,
|
||||
sanitizePrice,
|
||||
isUpdatingDraft: updating.draft,
|
||||
isUpdatingPublished: updating.publish,
|
||||
isDeleting,
|
||||
|
|
|
@ -0,0 +1,4 @@
|
|||
Significance: minor
|
||||
Type: update
|
||||
|
||||
Price section - Small refactor and style fix
|
Loading…
Reference in New Issue