diff --git a/packages/js/product-editor/changelog/dev-46768_add_context_to_errors b/packages/js/product-editor/changelog/dev-46768_add_context_to_errors new file mode 100644 index 00000000000..5b4f15eb7da --- /dev/null +++ b/packages/js/product-editor/changelog/dev-46768_add_context_to_errors @@ -0,0 +1,4 @@ +Significance: minor +Type: dev + +Add context to errors #49242 diff --git a/packages/js/product-editor/src/blocks/generic/number/edit.tsx b/packages/js/product-editor/src/blocks/generic/number/edit.tsx index 7063a2e4378..2e17702b5ba 100644 --- a/packages/js/product-editor/src/blocks/generic/number/edit.tsx +++ b/packages/js/product-editor/src/blocks/generic/number/edit.tsx @@ -17,6 +17,7 @@ import { useProductEdits } from '../../../hooks/use-product-edits'; export function Edit( { attributes, + clientId, context: { postType }, }: ProductEditorBlockEditProps< NumberBlockAttributes > ) { const blockProps = useWooBlockProps( attributes ); @@ -47,31 +48,40 @@ export function Edit( { value && parseFloat( value ) < min ) { - return sprintf( - // translators: %d is the minimum value of the number input. - __( - 'Value must be greater than or equal to %d', - 'woocommerce' + return { + message: sprintf( + // translators: %d is the minimum value of the number input. + __( + 'Value must be greater than or equal to %d', + 'woocommerce' + ), + min ), - min - ); + context: clientId, + }; } if ( typeof max === 'number' && value && parseFloat( value ) > max ) { - return sprintf( - // translators: %d is the maximum value of the number input. - __( - 'Value must be less than or equal to %d', - 'woocommerce' + return { + message: sprintf( + // translators: %d is the minimum value of the number input. + __( + 'Value must be less than or equal to %d', + 'woocommerce' + ), + min ), - max - ); + context: clientId, + }; } if ( required && ! value ) { - return __( 'This field is required.', 'woocommerce' ); + return { + message: __( 'This field is required.', 'woocommerce' ), + context: clientId, + }; } }, [ value ] diff --git a/packages/js/product-editor/src/blocks/generic/text/edit.tsx b/packages/js/product-editor/src/blocks/generic/text/edit.tsx index e1f75ce8170..f8a85cd9f31 100644 --- a/packages/js/product-editor/src/blocks/generic/text/edit.tsx +++ b/packages/js/product-editor/src/blocks/generic/text/edit.tsx @@ -20,6 +20,7 @@ import { TextBlockAttributes } from './types'; export function Edit( { attributes, + clientId, context: { postType }, }: ProductEditorBlockEditProps< TextBlockAttributes > ) { const blockProps = useWooBlockProps( attributes ); @@ -127,7 +128,10 @@ export function Edit( { input.setCustomValidity( customErrorMessage ); if ( ! input.validity.valid ) { - return input.validationMessage; + return { + message: customErrorMessage, + context: clientId, + }; } }, [ type, required, pattern, minLength, maxLength, min, max ] diff --git a/packages/js/product-editor/src/blocks/product-fields/inventory-email/edit.tsx b/packages/js/product-editor/src/blocks/product-fields/inventory-email/edit.tsx index ff03ea6f8eb..a5ae7497603 100644 --- a/packages/js/product-editor/src/blocks/product-fields/inventory-email/edit.tsx +++ b/packages/js/product-editor/src/blocks/product-fields/inventory-email/edit.tsx @@ -52,10 +52,13 @@ export function Edit( { `low_stock_amount-${ clientId }`, async function stockQuantityValidator() { if ( lowStockAmount && lowStockAmount < 0 ) { - return __( - 'This field must be a positive number.', - 'woocommerce' - ); + return { + message: __( + 'This field must be a positive number.', + 'woocommerce' + ), + context: clientId, + }; } }, [ lowStockAmount ] diff --git a/packages/js/product-editor/src/blocks/product-fields/inventory-quantity/edit.tsx b/packages/js/product-editor/src/blocks/product-fields/inventory-quantity/edit.tsx index ec17bac3693..7c3b5dde4a2 100644 --- a/packages/js/product-editor/src/blocks/product-fields/inventory-quantity/edit.tsx +++ b/packages/js/product-editor/src/blocks/product-fields/inventory-quantity/edit.tsx @@ -52,10 +52,13 @@ export function Edit( { `stock_quantity-${ clientId }`, async function stockQuantityValidator() { if ( manageStock && stockQuantity && stockQuantity < 0 ) { - return __( - 'Stock quantity must be a positive number.', - 'woocommerce' - ); + return { + message: __( + 'Stock quantity must be a positive number.', + 'woocommerce' + ), + context: clientId, + }; } }, [ manageStock, stockQuantity ] diff --git a/packages/js/product-editor/src/blocks/product-fields/name/edit.tsx b/packages/js/product-editor/src/blocks/product-fields/name/edit.tsx index 71691001d4b..5a6c555499c 100644 --- a/packages/js/product-editor/src/blocks/product-fields/name/edit.tsx +++ b/packages/js/product-editor/src/blocks/product-fields/name/edit.tsx @@ -83,14 +83,20 @@ export function NameBlockEdit( { 'name', async function nameValidator() { if ( ! name || name === AUTO_DRAFT_NAME ) { - return __( 'Product name is required.', 'woocommerce' ); + return { + message: __( 'Product name is required.', 'woocommerce' ), + context: clientId, + }; } if ( name.length > 120 ) { - return __( - 'Please enter a product name shorter than 120 characters.', - 'woocommerce' - ); + return { + message: __( + 'Please enter a product name shorter than 120 characters.', + 'woocommerce' + ), + context: clientId, + }; } }, [ name ] diff --git a/packages/js/product-editor/src/blocks/product-fields/regular-price/edit.tsx b/packages/js/product-editor/src/blocks/product-fields/regular-price/edit.tsx index 0089135f397..7743236ae5e 100644 --- a/packages/js/product-editor/src/blocks/product-fields/regular-price/edit.tsx +++ b/packages/js/product-editor/src/blocks/product-fields/regular-price/edit.tsx @@ -67,23 +67,35 @@ export function Edit( { const listPrice = Number.parseFloat( regularPrice ); if ( listPrice ) { if ( listPrice < 0 ) { - return __( - 'Regular price must be greater than or equals to zero.', - 'woocommerce' - ); + return { + message: __( + 'Regular price must be greater than or equals to zero.', + 'woocommerce' + ), + context: clientId, + }; } if ( salePrice && listPrice <= Number.parseFloat( salePrice ) ) { - return __( - 'Regular price must be greater than the sale price.', - 'woocommerce' - ); + return { + message: __( + 'Regular price must be greater than the sale price.', + 'woocommerce' + ), + context: clientId, + }; } } else if ( isRequired ) { - /* translators: label of required field. */ - return sprintf( __( '%s is required.', 'woocommerce' ), label ); + return { + message: sprintf( + /* translators: label of required field. */ + __( '%s is required.', 'woocommerce' ), + label + ), + context: clientId, + }; } }, [ regularPrice, salePrice ] diff --git a/packages/js/product-editor/src/blocks/product-fields/sale-price/edit.tsx b/packages/js/product-editor/src/blocks/product-fields/sale-price/edit.tsx index 0e23cda14bf..305a3e905e7 100644 --- a/packages/js/product-editor/src/blocks/product-fields/sale-price/edit.tsx +++ b/packages/js/product-editor/src/blocks/product-fields/sale-price/edit.tsx @@ -59,20 +59,26 @@ export function Edit( { async function salePriceValidator() { if ( salePrice ) { if ( Number.parseFloat( salePrice ) < 0 ) { - return __( - 'Sale price must be greater than or equals to zero.', - 'woocommerce' - ); + return { + message: __( + 'Sale price must be greater than or equals to zero.', + 'woocommerce' + ), + context: clientId, + }; } const listPrice = Number.parseFloat( regularPrice ); if ( ! listPrice || listPrice <= Number.parseFloat( salePrice ) ) { - return __( - 'Sale price must be lower than the regular price.', - 'woocommerce' - ); + return { + message: __( + 'Sale price must be lower than the regular price.', + 'woocommerce' + ), + context: clientId, + }; } } }, diff --git a/packages/js/product-editor/src/blocks/product-fields/schedule-sale/edit.tsx b/packages/js/product-editor/src/blocks/product-fields/schedule-sale/edit.tsx index 92880696252..0c13e1adca2 100644 --- a/packages/js/product-editor/src/blocks/product-fields/schedule-sale/edit.tsx +++ b/packages/js/product-editor/src/blocks/product-fields/schedule-sale/edit.tsx @@ -100,14 +100,23 @@ export function Edit( { async function dateOnSaleFromValidator() { if ( showScheduleSale && dateOnSaleFromGmt ) { if ( ! _dateOnSaleFrom.isValid() ) { - return __( 'Please enter a valid date.', 'woocommerce' ); + return { + message: __( + 'Please enter a valid date.', + 'woocommerce' + ), + context: clientId, + }; } if ( _dateOnSaleFrom.isAfter( _dateOnSaleTo ) ) { - return __( - 'The start date of the sale must be before the end date.', - 'woocommerce' - ); + return { + message: __( + 'The start date of the sale must be before the end date.', + 'woocommerce' + ), + context: clientId, + }; } } }, @@ -123,14 +132,23 @@ export function Edit( { async function dateOnSaleToValidator() { if ( showScheduleSale && dateOnSaleToGmt ) { if ( ! _dateOnSaleTo.isValid() ) { - return __( 'Please enter a valid date.', 'woocommerce' ); + return { + message: __( + 'Please enter a valid date.', + 'woocommerce' + ), + context: clientId, + }; } if ( _dateOnSaleTo.isBefore( _dateOnSaleFrom ) ) { - return __( - 'The end date of the sale must be after the start date.', - 'woocommerce' - ); + return { + message: __( + 'The end date of the sale must be after the start date.', + 'woocommerce' + ), + context: clientId, + }; } } }, diff --git a/packages/js/product-editor/src/blocks/product-fields/shipping-dimensions/edit.tsx b/packages/js/product-editor/src/blocks/product-fields/shipping-dimensions/edit.tsx index 041f40ba721..76a606a70dc 100644 --- a/packages/js/product-editor/src/blocks/product-fields/shipping-dimensions/edit.tsx +++ b/packages/js/product-editor/src/blocks/product-fields/shipping-dimensions/edit.tsx @@ -96,7 +96,13 @@ export function Edit( { `dimensions_width-${ clientId }`, async function dimensionsWidthValidator() { if ( dimensions?.width && +dimensions.width <= 0 ) { - return __( 'Width must be greater than zero.', 'woocommerce' ); + return { + message: __( + 'Width must be greater than zero.', + 'woocommerce' + ), + context: clientId, + }; } }, [ dimensions?.width ] @@ -110,7 +116,13 @@ export function Edit( { `dimensions_length-${ clientId }`, async function dimensionsLengthValidator() { if ( dimensions?.length && +dimensions.length <= 0 ) { - return __( 'Length must be greater than zero.', 'woocommerce' ); + return { + message: __( + 'Length must be greater than zero.', + 'woocommerce' + ), + context: clientId, + }; } }, [ dimensions?.length ] @@ -124,7 +136,13 @@ export function Edit( { `dimensions_height-${ clientId }`, async function dimensionsHeightValidator() { if ( dimensions?.height && +dimensions.height <= 0 ) { - return __( 'Height must be greater than zero.', 'woocommerce' ); + return { + message: __( + 'Height must be greater than zero.', + 'woocommerce' + ), + context: clientId, + }; } }, [ dimensions?.height ] @@ -138,7 +156,13 @@ export function Edit( { `weight-${ clientId }`, async function weightValidator() { if ( weight && +weight <= 0 ) { - return __( 'Weight must be greater than zero.', 'woocommerce' ); + return { + message: __( + 'Weight must be greater than zero.', + 'woocommerce' + ), + context: clientId, + }; } }, [ weight ] diff --git a/packages/js/product-editor/src/blocks/product-fields/variation-items/edit.tsx b/packages/js/product-editor/src/blocks/product-fields/variation-items/edit.tsx index a6f77557cf4..68a83893219 100644 --- a/packages/js/product-editor/src/blocks/product-fields/variation-items/edit.tsx +++ b/packages/js/product-editor/src/blocks/product-fields/variation-items/edit.tsx @@ -32,6 +32,7 @@ import { EmptyState } from '../../../components/empty-state'; export function Edit( { attributes, + clientId, context: { isInSelectedTab }, }: ProductEditorBlockEditProps< VariationOptionsBlockAttributes > ) { const noticeDimissed = useRef( false ); @@ -125,10 +126,13 @@ export function Edit( { }, } ); } - return __( - 'Set variation prices before adding this product.', - 'woocommerce' - ); + return { + message: __( + 'Set variation prices before adding this product.', + 'woocommerce' + ), + context: clientId, + }; } }, [ totalCountWithoutPrice ] diff --git a/packages/js/product-editor/src/contexts/validation-context/types.ts b/packages/js/product-editor/src/contexts/validation-context/types.ts index 6bfce6e40a2..df04c7646ff 100644 --- a/packages/js/product-editor/src/contexts/validation-context/types.ts +++ b/packages/js/product-editor/src/contexts/validation-context/types.ts @@ -24,7 +24,9 @@ export type ValidationProviderProps = { productId: number; }; -export type ValidationError = string | undefined; +export type ValidationError = + | { message?: string; context?: string; validatorId?: string } + | undefined; export type ValidationErrors = Record< string, ValidationError >; export type ValidatorRegistration< T > = { diff --git a/packages/js/product-editor/src/contexts/validation-context/use-validation.ts b/packages/js/product-editor/src/contexts/validation-context/use-validation.ts index 6621d9e8672..c60db5c46dd 100644 --- a/packages/js/product-editor/src/contexts/validation-context/use-validation.ts +++ b/packages/js/product-editor/src/contexts/validation-context/use-validation.ts @@ -31,7 +31,7 @@ export function useValidation< T >( return { ref, - error: context.errors[ validatorId ], + error: context.errors[ validatorId ]?.message, isValidating, async validate( newData?: Record< string, unknown > ) { setIsValidating( true ); diff --git a/packages/js/product-editor/src/contexts/validation-context/validation-provider.tsx b/packages/js/product-editor/src/contexts/validation-context/validation-provider.tsx index de6e2bad218..b1e9f95a8e8 100644 --- a/packages/js/product-editor/src/contexts/validation-context/validation-provider.tsx +++ b/packages/js/product-editor/src/contexts/validation-context/validation-provider.tsx @@ -9,6 +9,7 @@ import { createElement, useRef, useState } from '@wordpress/element'; * Internal dependencies */ import { + ValidationError, ValidationErrors, ValidationProviderProps, Validator, @@ -64,11 +65,13 @@ export function ValidationProvider< T >( { const result = validator( initialValue, newData ); return result.then( ( error ) => { + const errorWithValidatorId: ValidationError = + error !== undefined ? { validatorId, ...error } : undefined; setErrors( ( currentErrors ) => ( { ...currentErrors, - [ validatorId ]: error, + [ validatorId ]: errorWithValidatorId, } ) ); - return error; + return errorWithValidatorId; } ); } diff --git a/packages/js/product-editor/src/hooks/use-product-manager/use-product-manager.ts b/packages/js/product-editor/src/hooks/use-product-manager/use-product-manager.ts index 6bc0ec00fc6..e37412e987d 100644 --- a/packages/js/product-editor/src/hooks/use-product-manager/use-product-manager.ts +++ b/packages/js/product-editor/src/hooks/use-product-manager/use-product-manager.ts @@ -18,21 +18,21 @@ export function errorHandler( error: WPError, productStatus: ProductStatus ) { return error; } + const errorObj = Object.values( error ).find( + ( value ) => value !== undefined + ) as WPError | undefined; + if ( 'variations' in error && error.variations ) { return { + ...errorObj, code: 'variable_product_no_variation_prices', - message: error.variations, }; } - const errorMessage = Object.values( error ).find( - ( value ) => value !== undefined - ) as string | undefined; - - if ( errorMessage !== undefined ) { + if ( errorObj !== undefined ) { return { + ...errorObj, code: 'product_form_field_error', - message: errorMessage, }; }