rebase from live enviornment
This commit is contained in:
@@ -0,0 +1,27 @@
|
||||
const has = ( obj: Record< string, unknown >, path: string[] ): boolean => {
|
||||
return (
|
||||
!! path &&
|
||||
!! path.reduce< unknown >(
|
||||
( prevObj, key ) =>
|
||||
typeof prevObj === 'object' && prevObj !== null
|
||||
? ( prevObj as Record< string, unknown > )[ key ]
|
||||
: undefined,
|
||||
obj
|
||||
)
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Utility for returning whether the given path exists in the state.
|
||||
*
|
||||
* @param {Object} state The state being checked
|
||||
* @param {Array} path The path to check
|
||||
*
|
||||
* @return {boolean} True means this exists in the state.
|
||||
*/
|
||||
export default function hasInState(
|
||||
state: Record< string, unknown >,
|
||||
path: string[]
|
||||
): boolean {
|
||||
return has( state, path );
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
export { default as hasInState } from './has-in-state';
|
||||
export { default as updateState } from './update-state';
|
||||
export * from './process-error-response';
|
||||
@@ -0,0 +1,159 @@
|
||||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { createNotice, DEFAULT_ERROR_MESSAGE } from '@woocommerce/base-utils';
|
||||
import { decodeEntities } from '@wordpress/html-entities';
|
||||
import {
|
||||
objectHasProp,
|
||||
ApiErrorResponse,
|
||||
isApiErrorResponse,
|
||||
} from '@woocommerce/types';
|
||||
import { noticeContexts } from '@woocommerce/base-context/event-emit/utils';
|
||||
|
||||
type ApiParamError = {
|
||||
param: string;
|
||||
id: string;
|
||||
code: string;
|
||||
message: string;
|
||||
};
|
||||
|
||||
/**
|
||||
* Flattens error details which are returned from the API when multiple params are not valid.
|
||||
*
|
||||
* - Codes will be prefixed with the param. For example, `invalid_email` becomes `billing_address_invalid_email`.
|
||||
* - Additional error messages will be flattened alongside the main error message.
|
||||
* - Supports 1 level of nesting.
|
||||
* - Decodes HTML entities in error messages.
|
||||
*/
|
||||
export const getErrorDetails = (
|
||||
response: ApiErrorResponse
|
||||
): ApiParamError[] => {
|
||||
const errorDetails = objectHasProp( response.data, 'details' )
|
||||
? Object.entries( response.data.details )
|
||||
: null;
|
||||
|
||||
if ( ! errorDetails ) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return errorDetails.reduce(
|
||||
(
|
||||
acc,
|
||||
[
|
||||
param,
|
||||
{ code, message, additional_errors: additionalErrors = [] },
|
||||
]
|
||||
) => {
|
||||
return [
|
||||
...acc,
|
||||
{
|
||||
param,
|
||||
id: `${ param }_${ code }`,
|
||||
code,
|
||||
message: decodeEntities( message ),
|
||||
},
|
||||
...( Array.isArray( additionalErrors )
|
||||
? additionalErrors.flatMap( ( additionalError ) => {
|
||||
if (
|
||||
! objectHasProp( additionalError, 'code' ) ||
|
||||
! objectHasProp( additionalError, 'message' )
|
||||
) {
|
||||
return [];
|
||||
}
|
||||
return [
|
||||
{
|
||||
param,
|
||||
id: `${ param }_${ additionalError.code }`,
|
||||
code: additionalError.code,
|
||||
message: decodeEntities(
|
||||
additionalError.message
|
||||
),
|
||||
},
|
||||
];
|
||||
} )
|
||||
: [] ),
|
||||
];
|
||||
},
|
||||
[] as ApiParamError[]
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Gets appropriate error context from error code.
|
||||
*/
|
||||
const getErrorContextFromCode = ( code: string ): string => {
|
||||
switch ( code ) {
|
||||
case 'woocommerce_rest_missing_email_address':
|
||||
case 'woocommerce_rest_invalid_email_address':
|
||||
return noticeContexts.CONTACT_INFORMATION;
|
||||
default:
|
||||
return noticeContexts.CART;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Gets appropriate error context from error param name.
|
||||
*/
|
||||
const getErrorContextFromParam = ( param: string ): string | undefined => {
|
||||
switch ( param ) {
|
||||
case 'invalid_email':
|
||||
return noticeContexts.CONTACT_INFORMATION;
|
||||
case 'billing_address':
|
||||
return noticeContexts.BILLING_ADDRESS;
|
||||
case 'shipping_address':
|
||||
return noticeContexts.SHIPPING_ADDRESS;
|
||||
default:
|
||||
return undefined;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Processes the response for an invalid param error, with response code rest_invalid_param.
|
||||
*/
|
||||
const processInvalidParamResponse = (
|
||||
response: ApiErrorResponse,
|
||||
context: string | undefined
|
||||
) => {
|
||||
const errorDetails = getErrorDetails( response );
|
||||
|
||||
errorDetails.forEach( ( { code, message, id, param } ) => {
|
||||
createNotice( 'error', message, {
|
||||
id,
|
||||
context:
|
||||
context ||
|
||||
getErrorContextFromParam( param ) ||
|
||||
getErrorContextFromCode( code ),
|
||||
} );
|
||||
} );
|
||||
};
|
||||
|
||||
/**
|
||||
* Takes an API response object and creates error notices to display to the customer.
|
||||
*
|
||||
* This is where we can handle specific error codes and display notices in specific contexts.
|
||||
*/
|
||||
export const processErrorResponse = (
|
||||
response: ApiErrorResponse | null,
|
||||
context?: string | undefined
|
||||
) => {
|
||||
if ( ! isApiErrorResponse( response ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ( response.code === 'rest_invalid_param' ) {
|
||||
return processInvalidParamResponse( response, context );
|
||||
}
|
||||
|
||||
let errorMessage =
|
||||
decodeEntities( response.message ) || DEFAULT_ERROR_MESSAGE;
|
||||
|
||||
// Replace the generic invalid JSON message with something more user friendly.
|
||||
if ( response.code === 'invalid_json' ) {
|
||||
errorMessage = DEFAULT_ERROR_MESSAGE;
|
||||
}
|
||||
|
||||
createNotice( 'error', errorMessage, {
|
||||
id: response.code,
|
||||
context: context || getErrorContextFromCode( response.code ),
|
||||
} );
|
||||
};
|
||||
@@ -0,0 +1,37 @@
|
||||
/**
|
||||
* Utility for updating nested state in the path that changed.
|
||||
*/
|
||||
function updateNested< T >( // The state being updated
|
||||
state: T,
|
||||
// The path being updated
|
||||
path: string[],
|
||||
// The value to update for the path
|
||||
value: unknown,
|
||||
// The current index in the path
|
||||
index = 0
|
||||
): T {
|
||||
const key = path[ index ] as keyof T;
|
||||
if ( index === path.length - 1 ) {
|
||||
return { ...state, [ key ]: value };
|
||||
}
|
||||
|
||||
const nextState = state[ key ] || {};
|
||||
return {
|
||||
...state,
|
||||
[ key ]: updateNested( nextState, path, value, index + 1 ),
|
||||
} as T;
|
||||
}
|
||||
|
||||
/**
|
||||
* Utility for updating state and only cloning objects in the path that changed.
|
||||
*/
|
||||
export default function updateState< T >(
|
||||
// The state being updated
|
||||
state: T,
|
||||
// The path being updated
|
||||
path: string[],
|
||||
// The value to update for the path
|
||||
value: unknown
|
||||
): T {
|
||||
return updateNested( state, path, value );
|
||||
}
|
||||
Reference in New Issue
Block a user