2019-08-21 05:58:47 +00:00
|
|
|
/**
|
|
|
|
* External dependencies
|
|
|
|
*/
|
|
|
|
import { __ } from '@wordpress/i18n';
|
|
|
|
import apiFetch from '@wordpress/api-fetch';
|
2020-08-24 13:20:57 +00:00
|
|
|
import { compose } from '@wordpress/compose';
|
|
|
|
import { withDispatch } from '@wordpress/data';
|
2019-08-21 05:58:47 +00:00
|
|
|
import { Component, Fragment } from '@wordpress/element';
|
2019-12-02 17:39:22 +00:00
|
|
|
import { Button, FormToggle } from '@wordpress/components';
|
2019-08-21 05:58:47 +00:00
|
|
|
import PropTypes from 'prop-types';
|
2020-01-28 16:54:39 +00:00
|
|
|
import { Flag, Form, TextControlWithAffixes } from '@woocommerce/components';
|
2020-08-24 13:20:57 +00:00
|
|
|
import { ONBOARDING_STORE_NAME } from '@woocommerce/data';
|
2020-08-20 04:59:52 +00:00
|
|
|
import { recordEvent } from '@woocommerce/tracks';
|
2021-02-17 22:54:02 +00:00
|
|
|
import { Icon, globe } from '@wordpress/icons';
|
2019-09-23 21:47:08 +00:00
|
|
|
|
2019-10-07 20:27:34 +00:00
|
|
|
/**
|
|
|
|
* Internal dependencies
|
|
|
|
*/
|
2020-08-13 02:05:22 +00:00
|
|
|
import { CurrencyContext } from '../../../lib/currency-context';
|
2019-08-21 05:58:47 +00:00
|
|
|
|
|
|
|
class ShippingRates extends Component {
|
|
|
|
constructor() {
|
|
|
|
super( ...arguments );
|
|
|
|
|
|
|
|
this.updateShippingZones = this.updateShippingZones.bind( this );
|
|
|
|
}
|
|
|
|
|
2020-03-23 15:25:02 +00:00
|
|
|
getShippingMethods( zone, type = null ) {
|
2020-04-08 15:19:38 +00:00
|
|
|
// Sometimes the wc/v3/shipping/zones response does not include a methods attribute, return early if so.
|
|
|
|
if ( ! zone || ! zone.methods || ! Array.isArray( zone.methods ) ) {
|
|
|
|
return [];
|
|
|
|
}
|
|
|
|
|
2020-03-23 15:25:02 +00:00
|
|
|
if ( ! type ) {
|
|
|
|
return zone.methods;
|
|
|
|
}
|
|
|
|
|
|
|
|
return zone.methods
|
|
|
|
? zone.methods.filter( ( method ) => method.method_id === type )
|
|
|
|
: [];
|
|
|
|
}
|
|
|
|
|
|
|
|
disableShippingMethods( zone, methods ) {
|
|
|
|
if ( ! methods.length ) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
methods.forEach( ( method ) => {
|
|
|
|
apiFetch( {
|
|
|
|
method: 'POST',
|
|
|
|
path: `/wc/v3/shipping/zones/${ zone.id }/methods/${ method.instance_id }`,
|
|
|
|
data: {
|
|
|
|
enabled: false,
|
|
|
|
},
|
|
|
|
} );
|
|
|
|
} );
|
|
|
|
}
|
|
|
|
|
2019-08-21 05:58:47 +00:00
|
|
|
async updateShippingZones( values ) {
|
2020-08-24 13:20:57 +00:00
|
|
|
const {
|
|
|
|
clearTaskStatusCache,
|
|
|
|
createNotice,
|
|
|
|
shippingZones,
|
|
|
|
} = this.props;
|
2019-08-21 05:58:47 +00:00
|
|
|
|
2019-10-07 20:27:34 +00:00
|
|
|
let restOfTheWorld = false;
|
|
|
|
let shippingCost = false;
|
2020-03-23 15:25:02 +00:00
|
|
|
shippingZones.forEach( ( zone ) => {
|
2020-02-14 02:23:21 +00:00
|
|
|
if ( zone.id === 0 ) {
|
|
|
|
restOfTheWorld =
|
2020-03-23 15:25:02 +00:00
|
|
|
zone.toggleable && values[ `${ zone.id }_enabled` ];
|
2019-10-07 20:27:34 +00:00
|
|
|
} else {
|
|
|
|
shippingCost =
|
2020-02-14 02:23:21 +00:00
|
|
|
values[ `${ zone.id }_rate` ] !== '' &&
|
|
|
|
parseFloat( values[ `${ zone.id }_rate` ] ) !==
|
|
|
|
parseFloat( 0 );
|
2019-10-07 20:27:34 +00:00
|
|
|
}
|
|
|
|
|
2020-03-23 15:25:02 +00:00
|
|
|
const shippingMethods = this.getShippingMethods( zone );
|
|
|
|
const methodType =
|
|
|
|
parseFloat( values[ `${ zone.id }_rate` ] ) === parseFloat( 0 )
|
|
|
|
? 'free_shipping'
|
|
|
|
: 'flat_rate';
|
|
|
|
const shippingMethod = this.getShippingMethods( zone, methodType )
|
|
|
|
.length
|
|
|
|
? this.getShippingMethods( zone, methodType )[ 0 ]
|
|
|
|
: null;
|
|
|
|
|
|
|
|
if ( zone.toggleable && ! values[ `${ zone.id }_enabled` ] ) {
|
|
|
|
// Disable any shipping methods that exist if toggled off.
|
|
|
|
this.disableShippingMethods( zone, shippingMethods );
|
2019-08-21 05:58:47 +00:00
|
|
|
return;
|
2020-03-23 15:25:02 +00:00
|
|
|
} else if ( shippingMethod ) {
|
|
|
|
// Disable all methods except the one being updated.
|
|
|
|
const methodsToDisable = shippingMethods.filter(
|
|
|
|
( method ) =>
|
|
|
|
method.instance_id !== shippingMethod.instance_id
|
|
|
|
);
|
|
|
|
this.disableShippingMethods( zone, methodsToDisable );
|
2019-08-21 05:58:47 +00:00
|
|
|
}
|
|
|
|
|
2020-03-23 15:25:02 +00:00
|
|
|
apiFetch( {
|
|
|
|
method: 'POST',
|
|
|
|
path: shippingMethod
|
|
|
|
? // Update the first existing method if one exists, otherwise create a new one.
|
|
|
|
`/wc/v3/shipping/zones/${ zone.id }/methods/${ shippingMethod.instance_id }`
|
|
|
|
: `/wc/v3/shipping/zones/${ zone.id }/methods`,
|
|
|
|
data: {
|
|
|
|
method_id: methodType,
|
|
|
|
enabled: true,
|
|
|
|
settings: { cost: values[ `${ zone.id }_rate` ] },
|
|
|
|
},
|
|
|
|
} );
|
2019-08-21 05:58:47 +00:00
|
|
|
} );
|
|
|
|
|
2019-10-07 20:27:34 +00:00
|
|
|
recordEvent( 'tasklist_shipping_set_costs', {
|
|
|
|
shipping_cost: shippingCost,
|
|
|
|
rest_world: restOfTheWorld,
|
|
|
|
} );
|
|
|
|
|
2020-08-24 13:20:57 +00:00
|
|
|
clearTaskStatusCache();
|
2019-08-21 05:58:47 +00:00
|
|
|
|
2020-02-14 02:23:21 +00:00
|
|
|
createNotice(
|
|
|
|
'success',
|
2021-02-10 23:57:51 +00:00
|
|
|
__( 'Your shipping rates have been updated', 'woocommerce-admin' )
|
2020-02-14 02:23:21 +00:00
|
|
|
);
|
2019-08-21 05:58:47 +00:00
|
|
|
|
2019-08-26 05:49:04 +00:00
|
|
|
this.props.onComplete();
|
2019-08-21 05:58:47 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
renderInputPrefix() {
|
2020-06-17 23:33:40 +00:00
|
|
|
const { symbolPosition, symbol } = this.context.getCurrencyConfig();
|
2020-02-14 02:23:21 +00:00
|
|
|
if ( symbolPosition.indexOf( 'right' ) === 0 ) {
|
2020-01-28 16:54:39 +00:00
|
|
|
return null;
|
2019-08-21 05:58:47 +00:00
|
|
|
}
|
2020-02-14 02:23:21 +00:00
|
|
|
return (
|
|
|
|
<span className="woocommerce-shipping-rate__control-prefix">
|
|
|
|
{ symbol }
|
|
|
|
</span>
|
|
|
|
);
|
2019-08-21 05:58:47 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
renderInputSuffix( rate ) {
|
2020-06-17 23:33:40 +00:00
|
|
|
const { symbolPosition, symbol } = this.context.getCurrencyConfig();
|
2020-02-14 02:23:21 +00:00
|
|
|
if ( symbolPosition.indexOf( 'right' ) === 0 ) {
|
|
|
|
return (
|
|
|
|
<span className="woocommerce-shipping-rate__control-suffix">
|
|
|
|
{ symbol }
|
|
|
|
</span>
|
|
|
|
);
|
2019-08-21 05:58:47 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return parseFloat( rate ) === parseFloat( 0 ) ? (
|
|
|
|
<span className="woocommerce-shipping-rate__control-suffix">
|
|
|
|
{ __( 'Free shipping', 'woocommerce-admin' ) }
|
|
|
|
</span>
|
|
|
|
) : null;
|
|
|
|
}
|
|
|
|
|
2020-01-28 16:54:39 +00:00
|
|
|
getFormattedRate( value ) {
|
2020-04-02 21:54:38 +00:00
|
|
|
const { formatDecimalString } = this.context;
|
|
|
|
const currencyString = formatDecimalString( value );
|
2020-01-28 16:54:39 +00:00
|
|
|
if ( ! value.length || ! currencyString.length ) {
|
2020-04-02 21:54:38 +00:00
|
|
|
return formatDecimalString( 0 );
|
2020-01-28 16:54:39 +00:00
|
|
|
}
|
|
|
|
|
2020-04-02 21:54:38 +00:00
|
|
|
return formatDecimalString( value );
|
2020-01-28 16:54:39 +00:00
|
|
|
}
|
|
|
|
|
2019-08-21 05:58:47 +00:00
|
|
|
getInitialValues() {
|
2020-04-02 21:54:38 +00:00
|
|
|
const { formatDecimalString } = this.context;
|
2019-08-21 05:58:47 +00:00
|
|
|
const values = {};
|
|
|
|
|
2020-02-14 02:23:21 +00:00
|
|
|
this.props.shippingZones.forEach( ( zone ) => {
|
2020-03-23 15:25:02 +00:00
|
|
|
const shippingMethods = this.getShippingMethods( zone );
|
|
|
|
const rate =
|
|
|
|
shippingMethods.length && shippingMethods[ 0 ].settings.cost
|
|
|
|
? this.getFormattedRate(
|
|
|
|
shippingMethods[ 0 ].settings.cost.value
|
2020-02-14 02:23:21 +00:00
|
|
|
)
|
2020-04-02 21:54:38 +00:00
|
|
|
: formatDecimalString( 0 );
|
2019-08-21 05:58:47 +00:00
|
|
|
values[ `${ zone.id }_rate` ] = rate;
|
|
|
|
|
2020-03-23 15:25:02 +00:00
|
|
|
if ( shippingMethods.length && shippingMethods[ 0 ].enabled ) {
|
2019-08-21 05:58:47 +00:00
|
|
|
values[ `${ zone.id }_enabled` ] = true;
|
|
|
|
} else {
|
|
|
|
values[ `${ zone.id }_enabled` ] = false;
|
|
|
|
}
|
|
|
|
} );
|
|
|
|
|
|
|
|
return values;
|
|
|
|
}
|
|
|
|
|
|
|
|
validate( values ) {
|
|
|
|
const errors = {};
|
|
|
|
|
2020-02-14 02:23:21 +00:00
|
|
|
const rates = Object.keys( values ).filter( ( field ) =>
|
|
|
|
field.endsWith( '_rate' )
|
|
|
|
);
|
2019-08-21 05:58:47 +00:00
|
|
|
|
2020-02-14 02:23:21 +00:00
|
|
|
rates.forEach( ( rate ) => {
|
2019-08-21 05:58:47 +00:00
|
|
|
if ( values[ rate ] < 0 ) {
|
2020-02-14 02:23:21 +00:00
|
|
|
errors[ rate ] = __(
|
|
|
|
'Shipping rates can not be negative numbers.',
|
|
|
|
'woocommerce-admin'
|
|
|
|
);
|
2019-08-21 05:58:47 +00:00
|
|
|
}
|
|
|
|
} );
|
|
|
|
|
|
|
|
return errors;
|
|
|
|
}
|
|
|
|
|
|
|
|
render() {
|
2019-12-10 19:54:51 +00:00
|
|
|
const { buttonText, shippingZones } = this.props;
|
2019-08-21 05:58:47 +00:00
|
|
|
|
|
|
|
if ( ! shippingZones.length ) {
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
|
|
|
return (
|
|
|
|
<Form
|
|
|
|
initialValues={ this.getInitialValues() }
|
2021-05-18 04:24:24 +00:00
|
|
|
onSubmit={ this.updateShippingZones }
|
2019-08-21 05:58:47 +00:00
|
|
|
validate={ this.validate }
|
|
|
|
>
|
2020-02-14 02:23:21 +00:00
|
|
|
{ ( {
|
|
|
|
getInputProps,
|
|
|
|
handleSubmit,
|
|
|
|
setTouched,
|
|
|
|
setValue,
|
|
|
|
values,
|
|
|
|
} ) => {
|
2019-08-21 05:58:47 +00:00
|
|
|
return (
|
|
|
|
<Fragment>
|
|
|
|
<div className="woocommerce-shipping-rates">
|
2020-02-14 02:23:21 +00:00
|
|
|
{ shippingZones.map( ( zone ) => (
|
|
|
|
<div
|
|
|
|
className="woocommerce-shipping-rate"
|
|
|
|
key={ zone.id }
|
|
|
|
>
|
2019-08-21 05:58:47 +00:00
|
|
|
<div className="woocommerce-shipping-rate__icon">
|
|
|
|
{ zone.locations ? (
|
2020-02-14 02:23:21 +00:00
|
|
|
zone.locations.map(
|
|
|
|
( location ) => (
|
|
|
|
<Flag
|
|
|
|
size={ 24 }
|
|
|
|
code={
|
|
|
|
location.code
|
|
|
|
}
|
|
|
|
key={
|
|
|
|
location.code
|
|
|
|
}
|
|
|
|
/>
|
|
|
|
)
|
|
|
|
)
|
2019-08-21 05:58:47 +00:00
|
|
|
) : (
|
|
|
|
// Icon used for zones without locations or "Rest of the world".
|
2021-02-17 22:54:02 +00:00
|
|
|
<Icon icon={ globe } />
|
2019-08-21 05:58:47 +00:00
|
|
|
) }
|
|
|
|
</div>
|
|
|
|
<div className="woocommerce-shipping-rate__main">
|
2020-06-12 07:48:49 +00:00
|
|
|
{ zone.toggleable ? (
|
|
|
|
<label
|
|
|
|
htmlFor={ `woocommerce-shipping-rate__toggle-${ zone.id }` }
|
|
|
|
className="woocommerce-shipping-rate__name"
|
|
|
|
>
|
|
|
|
{ zone.name }
|
2020-02-14 02:23:21 +00:00
|
|
|
<FormToggle
|
2020-06-12 07:48:49 +00:00
|
|
|
id={ `woocommerce-shipping-rate__toggle-${ zone.id }` }
|
2020-02-14 02:23:21 +00:00
|
|
|
{ ...getInputProps(
|
|
|
|
`${ zone.id }_enabled`
|
|
|
|
) }
|
|
|
|
/>
|
2020-06-12 07:48:49 +00:00
|
|
|
</label>
|
|
|
|
) : (
|
|
|
|
<div className="woocommerce-shipping-rate__name">
|
|
|
|
{ zone.name }
|
|
|
|
</div>
|
|
|
|
) }
|
2020-03-23 15:25:02 +00:00
|
|
|
{ ( ! zone.toggleable ||
|
2020-02-14 02:23:21 +00:00
|
|
|
values[
|
|
|
|
`${ zone.id }_enabled`
|
|
|
|
] ) && (
|
2020-01-28 16:54:39 +00:00
|
|
|
<TextControlWithAffixes
|
2020-02-14 02:23:21 +00:00
|
|
|
label={ __(
|
|
|
|
'Shipping cost',
|
|
|
|
'woocommerce-admin'
|
|
|
|
) }
|
2020-01-28 16:54:39 +00:00
|
|
|
required
|
2020-02-14 02:23:21 +00:00
|
|
|
{ ...getInputProps(
|
|
|
|
`${ zone.id }_rate`
|
|
|
|
) }
|
2020-01-28 16:54:39 +00:00
|
|
|
onBlur={ () => {
|
2020-02-14 02:23:21 +00:00
|
|
|
setTouched(
|
|
|
|
`${ zone.id }_rate`
|
|
|
|
);
|
2020-01-28 16:54:39 +00:00
|
|
|
setValue(
|
|
|
|
`${ zone.id }_rate`,
|
2020-02-14 02:23:21 +00:00
|
|
|
this.getFormattedRate(
|
|
|
|
values[
|
|
|
|
`${ zone.id }_rate`
|
|
|
|
]
|
|
|
|
)
|
2020-01-28 16:54:39 +00:00
|
|
|
);
|
|
|
|
} }
|
|
|
|
prefix={ this.renderInputPrefix() }
|
2020-02-14 02:23:21 +00:00
|
|
|
suffix={ this.renderInputSuffix(
|
|
|
|
values[
|
|
|
|
`${ zone.id }_rate`
|
|
|
|
]
|
|
|
|
) }
|
2020-01-28 16:54:39 +00:00
|
|
|
className="muriel-input-text woocommerce-shipping-rate__control-wrapper"
|
|
|
|
/>
|
2019-08-21 05:58:47 +00:00
|
|
|
) }
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
) ) }
|
|
|
|
</div>
|
|
|
|
|
|
|
|
<Button isPrimary onClick={ handleSubmit }>
|
2020-02-14 02:23:21 +00:00
|
|
|
{ buttonText ||
|
|
|
|
__( 'Update', 'woocommerce-admin' ) }
|
2019-08-21 05:58:47 +00:00
|
|
|
</Button>
|
|
|
|
</Fragment>
|
|
|
|
);
|
|
|
|
} }
|
|
|
|
</Form>
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
ShippingRates.propTypes = {
|
2019-12-10 19:54:51 +00:00
|
|
|
/**
|
|
|
|
* Text displayed on the primary button.
|
|
|
|
*/
|
|
|
|
buttonText: PropTypes.string,
|
2019-08-21 05:58:47 +00:00
|
|
|
/**
|
|
|
|
* Function used to mark the step complete.
|
|
|
|
*/
|
2019-08-26 05:49:04 +00:00
|
|
|
onComplete: PropTypes.func.isRequired,
|
2019-08-21 05:58:47 +00:00
|
|
|
/**
|
|
|
|
* Function to create a transient notice in the store.
|
|
|
|
*/
|
|
|
|
createNotice: PropTypes.func.isRequired,
|
|
|
|
/**
|
|
|
|
* Array of shipping zones returned from the WC REST API with added
|
|
|
|
* `methods` and `locations` properties appended from separate API calls.
|
|
|
|
*/
|
|
|
|
shippingZones: PropTypes.array,
|
|
|
|
};
|
|
|
|
|
|
|
|
ShippingRates.defaultProps = {
|
|
|
|
shippingZones: [],
|
|
|
|
};
|
|
|
|
|
2020-04-02 21:54:38 +00:00
|
|
|
ShippingRates.contextType = CurrencyContext;
|
|
|
|
|
2020-08-24 13:20:57 +00:00
|
|
|
export default compose(
|
|
|
|
withDispatch( ( dispatch ) => {
|
|
|
|
const { invalidateResolutionForStoreSelector } = dispatch(
|
|
|
|
ONBOARDING_STORE_NAME
|
|
|
|
);
|
|
|
|
|
|
|
|
return {
|
|
|
|
clearTaskStatusCache: () =>
|
|
|
|
invalidateResolutionForStoreSelector( 'getTasksStatus' ),
|
|
|
|
};
|
|
|
|
} )
|
|
|
|
)( ShippingRates );
|