Add context to errors (#49242)

* Add context to errors

* Add changelog

* Fix product manager hook

* Add useBlocksHelper

# Conflicts:
#	packages/js/product-editor/src/blocks/product-fields/shipping-dimensions/edit.tsx

* Send clientId as context

* Add validatorId when validating

* Small refactor errorHandler

* Improve method
This commit is contained in:
Fernando Marichal 2024-07-13 10:39:13 -03:00 committed by GitHub
parent 9fa32b4f2b
commit 64ce7bd732
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
15 changed files with 175 additions and 76 deletions

View File

@ -0,0 +1,4 @@
Significance: minor
Type: dev
Add context to errors #49242

View File

@ -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(
return {
message: sprintf(
// translators: %d is the minimum value of the number input.
__(
'Value must be greater than or equal to %d',
'woocommerce'
),
min
);
),
context: clientId,
};
}
if (
typeof max === 'number' &&
value &&
parseFloat( value ) > max
) {
return sprintf(
// translators: %d is the maximum value of the number input.
return {
message: sprintf(
// translators: %d is the minimum value of the number input.
__(
'Value must be less than or equal to %d',
'woocommerce'
),
max
);
min
),
context: clientId,
};
}
if ( required && ! value ) {
return __( 'This field is required.', 'woocommerce' );
return {
message: __( 'This field is required.', 'woocommerce' ),
context: clientId,
};
}
},
[ value ]

View File

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

View File

@ -52,10 +52,13 @@ export function Edit( {
`low_stock_amount-${ clientId }`,
async function stockQuantityValidator() {
if ( lowStockAmount && lowStockAmount < 0 ) {
return __(
return {
message: __(
'This field must be a positive number.',
'woocommerce'
);
),
context: clientId,
};
}
},
[ lowStockAmount ]

View File

@ -52,10 +52,13 @@ export function Edit( {
`stock_quantity-${ clientId }`,
async function stockQuantityValidator() {
if ( manageStock && stockQuantity && stockQuantity < 0 ) {
return __(
return {
message: __(
'Stock quantity must be a positive number.',
'woocommerce'
);
),
context: clientId,
};
}
},
[ manageStock, stockQuantity ]

View File

@ -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 __(
return {
message: __(
'Please enter a product name shorter than 120 characters.',
'woocommerce'
);
),
context: clientId,
};
}
},
[ name ]

View File

@ -67,23 +67,35 @@ export function Edit( {
const listPrice = Number.parseFloat( regularPrice );
if ( listPrice ) {
if ( listPrice < 0 ) {
return __(
return {
message: __(
'Regular price must be greater than or equals to zero.',
'woocommerce'
);
),
context: clientId,
};
}
if (
salePrice &&
listPrice <= Number.parseFloat( salePrice )
) {
return __(
return {
message: __(
'Regular price must be greater than the sale price.',
'woocommerce'
);
),
context: clientId,
};
}
} else if ( isRequired ) {
return {
message: sprintf(
/* translators: label of required field. */
return sprintf( __( '%s is required.', 'woocommerce' ), label );
__( '%s is required.', 'woocommerce' ),
label
),
context: clientId,
};
}
},
[ regularPrice, salePrice ]

View File

@ -59,20 +59,26 @@ export function Edit( {
async function salePriceValidator() {
if ( salePrice ) {
if ( Number.parseFloat( salePrice ) < 0 ) {
return __(
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 __(
return {
message: __(
'Sale price must be lower than the regular price.',
'woocommerce'
);
),
context: clientId,
};
}
}
},

View File

@ -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 __(
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 __(
return {
message: __(
'The end date of the sale must be after the start date.',
'woocommerce'
);
),
context: clientId,
};
}
}
},

View File

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

View File

@ -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 __(
return {
message: __(
'Set variation prices before adding this product.',
'woocommerce'
);
),
context: clientId,
};
}
},
[ totalCountWithoutPrice ]

View File

@ -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 > = {

View File

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

View File

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

View File

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