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:
Fernando Marichal 2022-09-15 10:56:33 -03:00 committed by GitHub
parent b50f651129
commit dadb1d6a87
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 113 additions and 39 deletions

View File

@ -42,6 +42,11 @@
margin-bottom: 0;
}
}
.woocommerce-enriched-label__help-wrapper {
.components-popover {
margin-top: 0;
}
}
}
.woocommerce-edit-product {

View File

@ -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;
};

View File

@ -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>
);

View File

@ -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( {} );
} );
} );

View File

@ -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,

View File

@ -0,0 +1,4 @@
Significance: minor
Type: update
Price section - Small refactor and style fix