2021-09-21 19:33:44 +00:00
/ * *
* External dependencies
* /
2023-02-20 13:32:14 +00:00
import { _ _ , sprintf } from '@wordpress/i18n' ;
2021-09-21 19:33:44 +00:00
import apiFetch from '@wordpress/api-fetch' ;
import { Component } from '@wordpress/element' ;
2023-02-20 13:32:14 +00:00
import { Button , Card , CardBody } from '@wordpress/components' ;
2021-09-21 19:33:44 +00:00
import { compose } from '@wordpress/compose' ;
2023-02-20 13:32:14 +00:00
import { filter } from 'lodash' ;
2022-02-21 02:34:25 +00:00
import interpolateComponents from '@automattic/interpolate-components' ;
2021-09-21 19:33:44 +00:00
import { withDispatch , withSelect } from '@wordpress/data' ;
import { Link , Stepper , Plugins } from '@woocommerce/components' ;
2022-01-06 12:53:30 +00:00
import { getAdminLink } from '@woocommerce/settings' ;
2021-09-21 19:33:44 +00:00
import { getHistory , getNewPath } from '@woocommerce/navigation' ;
2021-12-13 21:13:05 +00:00
import {
SETTINGS _STORE _NAME ,
ONBOARDING _STORE _NAME ,
PLUGINS _STORE _NAME ,
2022-03-01 12:33:41 +00:00
COUNTRIES _STORE _NAME ,
2021-12-13 21:13:05 +00:00
} from '@woocommerce/data' ;
2021-09-21 19:33:44 +00:00
import { recordEvent } from '@woocommerce/tracks' ;
import { registerPlugin } from '@wordpress/plugins' ;
import { WooOnboardingTask } from '@woocommerce/onboarding' ;
2022-06-29 07:40:42 +00:00
import { Text } from '@woocommerce/experimental' ;
2023-02-20 13:32:14 +00:00
import classNames from 'classnames' ;
2021-09-21 19:33:44 +00:00
/ * *
* Internal dependencies
* /
import Connect from '../../../dashboard/components/connect' ;
import { getCountryCode } from '../../../dashboard/utils' ;
import StoreLocation from '../steps/location' ;
import ShippingRates from './rates' ;
2023-02-20 13:32:14 +00:00
import { getShippingProviders } from './shipping-providers/shipping-providers' ;
2021-09-21 19:33:44 +00:00
import { createNoticesFromResponse } from '../../../lib/notices' ;
2021-10-05 18:20:28 +00:00
import './shipping.scss' ;
2021-09-21 19:33:44 +00:00
export class Shipping extends Component {
constructor ( props ) {
super ( props ) ;
this . initialState = {
isPending : false ,
step : 'store_location' ,
shippingZones : [ ] ,
} ;
// Cache active plugins to prevent removal mid-step.
this . activePlugins = props . activePlugins ;
this . state = this . initialState ;
this . completeStep = this . completeStep . bind ( this ) ;
2022-06-29 07:40:42 +00:00
this . shippingSmartDefaultsEnabled =
window . wcAdminFeatures &&
window . wcAdminFeatures [ 'shipping-smart-defaults' ] ;
2022-07-06 02:13:57 +00:00
this . storeLocationCompleted = false ;
2021-09-21 19:33:44 +00:00
}
componentDidMount ( ) {
this . reset ( ) ;
}
reset ( ) {
this . setState ( this . initialState ) ;
}
async fetchShippingZones ( ) {
const { countryCode , countryName } = this . props ;
// @todo The following fetches for shipping information should be moved into
// @woocommerce/data to make these methods and states more readily available.
const shippingZones = [ ] ;
const zones = await apiFetch ( { path : '/wc/v3/shipping/zones' } ) ;
let hasCountryZone = false ;
await Promise . all (
zones . map ( async ( zone ) => {
// "Rest of the world zone"
if ( zone . id === 0 ) {
zone . methods = await apiFetch ( {
path : ` /wc/v3/shipping/zones/ ${ zone . id } /methods ` ,
} ) ;
2022-03-30 09:00:04 +00:00
zone . name = _ _ ( 'Rest of the world' , 'woocommerce' ) ;
2021-09-21 19:33:44 +00:00
zone . toggleable = true ;
shippingZones . push ( zone ) ;
return ;
}
// Return any zone with a single location matching the country zone.
zone . locations = await apiFetch ( {
path : ` /wc/v3/shipping/zones/ ${ zone . id } /locations ` ,
} ) ;
const countryLocation = zone . locations . find (
( location ) => countryCode === location . code
) ;
if ( countryLocation ) {
zone . methods = await apiFetch ( {
path : ` /wc/v3/shipping/zones/ ${ zone . id } /methods ` ,
} ) ;
shippingZones . push ( zone ) ;
hasCountryZone = true ;
}
} )
) ;
// Create the default store country zone if it doesn't exist.
if ( ! hasCountryZone ) {
const zone = await apiFetch ( {
method : 'POST' ,
path : '/wc/v3/shipping/zones' ,
data : { name : countryName } ,
} ) ;
zone . locations = await apiFetch ( {
method : 'POST' ,
path : ` /wc/v3/shipping/zones/ ${ zone . id } /locations ` ,
data : [ { code : countryCode , type : 'country' } ] ,
} ) ;
shippingZones . push ( zone ) ;
}
shippingZones . reverse ( ) ;
this . setState ( { isPending : false , shippingZones } ) ;
}
componentDidUpdate ( prevProps , prevState ) {
2022-03-01 12:33:41 +00:00
const { countryCode , countryName , settings } = this . props ;
2021-09-21 19:33:44 +00:00
const {
woocommerce _store _address : storeAddress ,
woocommerce _default _country : defaultCountry ,
woocommerce _store _postcode : storePostCode ,
} = settings ;
const { step } = this . state ;
if (
step === 'rates' &&
( prevProps . countryCode !== countryCode ||
2022-03-01 12:33:41 +00:00
prevProps . countryName !== countryName ||
2021-09-21 19:33:44 +00:00
prevState . step !== 'rates' )
) {
2022-03-01 12:33:41 +00:00
this . setState ( { isPending : true } ) ;
if ( countryName ) {
this . fetchShippingZones ( ) ;
}
2021-09-21 19:33:44 +00:00
}
const isCompleteAddress = Boolean (
storeAddress && defaultCountry && storePostCode
) ;
if ( step === 'store_location' && isCompleteAddress ) {
2022-07-06 02:13:57 +00:00
if (
this . shippingSmartDefaultsEnabled &&
! this . storeLocationCompleted
) {
this . completeStep ( ) ;
this . storeLocationCompleted = true ;
} else if ( ! this . shippingSmartDefaultsEnabled ) {
this . completeStep ( ) ;
}
2021-09-21 19:33:44 +00:00
}
}
completeStep ( ) {
2021-10-01 19:53:22 +00:00
const { createNotice , onComplete } = this . props ;
2021-09-21 19:33:44 +00:00
const { step } = this . state ;
const steps = this . getSteps ( ) ;
const currentStepIndex = steps . findIndex ( ( s ) => s . key === step ) ;
const nextStep = steps [ currentStepIndex + 1 ] ;
if ( nextStep ) {
this . setState ( { step : nextStep . key } ) ;
} else {
createNotice (
'success' ,
_ _ (
"📦 Shipping is done! Don't worry, you can always change it later" ,
2022-03-30 09:00:04 +00:00
'woocommerce'
2021-09-21 19:33:44 +00:00
)
) ;
2021-10-01 19:53:22 +00:00
onComplete ( ) ;
2021-09-21 19:33:44 +00:00
}
}
getSteps ( ) {
2021-12-13 21:13:05 +00:00
const {
countryCode ,
createNotice ,
invalidateResolutionForStoreSelector ,
isJetpackConnected ,
onComplete ,
optimisticallyCompleteTask ,
settings ,
task ,
updateAndPersistSettingsForGroup ,
} = this . props ;
2023-02-20 13:32:14 +00:00
const pluginsToPromote = getShippingProviders ( this . props . countryCode ) ;
const pluginsToActivate = pluginsToPromote . map ( ( pluginToPromote ) => {
return pluginToPromote . slug ;
} ) ;
// Add jetpack to the list if the list includes woocommerce-services
if ( pluginsToActivate . includes ( 'woocommerce-services' ) ) {
pluginsToActivate . push ( 'jetpack' ) ;
}
const onShippingPluginInstalltionSkip = ( ) => {
recordEvent ( 'tasklist_shipping_label_printing' , {
install : false ,
plugins _to _activate : pluginsToActivate ,
} ) ;
getHistory ( ) . push ( getNewPath ( { } , '/' , { } ) ) ;
onComplete ( ) ;
} ;
const getSinglePluginDescription = ( name , url ) => {
return interpolateComponents ( {
mixedString : sprintf (
/* translators: %s = plugin name */
_ _ (
'Save time and money by printing your shipping labels right from your computer with %1$s. Try %2$s for free. {{link}}Learn more{{/link}}' ,
'woocommerce'
) ,
name ,
name
) ,
components : {
link : < Link href = { url } target = "_blank" type = "external" / > ,
} ,
} ) ;
} ;
2021-09-21 19:33:44 +00:00
const requiresJetpackConnection =
! isJetpackConnected && countryCode === 'US' ;
2022-06-29 07:40:42 +00:00
let steps = [
2021-09-21 19:33:44 +00:00
{
key : 'store_location' ,
2022-03-30 09:00:04 +00:00
label : _ _ ( 'Set store location' , 'woocommerce' ) ,
2021-09-21 19:33:44 +00:00
description : _ _ (
'The address from which your business operates' ,
2022-03-30 09:00:04 +00:00
'woocommerce'
2021-09-21 19:33:44 +00:00
) ,
content : (
< StoreLocation
2021-12-13 21:13:05 +00:00
createNotice = { createNotice }
updateAndPersistSettingsForGroup = {
updateAndPersistSettingsForGroup
}
settings = { settings }
2021-09-21 19:33:44 +00:00
onComplete = { ( values ) => {
const country = getCountryCode (
values . countryState
) ;
recordEvent ( 'tasklist_shipping_set_location' , {
country ,
} ) ;
2022-07-06 02:13:57 +00:00
2022-02-07 18:55:35 +00:00
// Don't need to trigger completeStep here as it's triggered by the address updates in the componentDidUpdate function.
2022-07-06 02:13:57 +00:00
if ( this . shippingSmartDefaultsEnabled ) {
this . completeStep ( ) ;
}
2021-09-21 19:33:44 +00:00
} }
/ >
) ,
visible : true ,
} ,
{
key : 'rates' ,
2022-03-30 09:00:04 +00:00
label : _ _ ( 'Set shipping costs' , 'woocommerce' ) ,
2021-09-21 19:33:44 +00:00
description : _ _ (
'Define how much customers pay to ship to different destinations' ,
2022-03-30 09:00:04 +00:00
'woocommerce'
2021-09-21 19:33:44 +00:00
) ,
content : (
< ShippingRates
buttonText = {
pluginsToActivate . length ||
requiresJetpackConnection
2022-03-30 09:00:04 +00:00
? _ _ ( 'Proceed' , 'woocommerce' )
: _ _ ( 'Complete task' , 'woocommerce' )
2021-09-21 19:33:44 +00:00
}
shippingZones = { this . state . shippingZones }
2021-12-13 21:13:05 +00:00
onComplete = { ( ) => {
const { id } = task ;
optimisticallyCompleteTask ( id ) ;
invalidateResolutionForStoreSelector ( ) ;
this . completeStep ( ) ;
} }
createNotice = { createNotice }
2021-09-21 19:33:44 +00:00
/ >
) ,
visible :
settings . woocommerce _ship _to _countries === 'disabled'
? false
: true ,
} ,
{
key : 'label_printing' ,
2022-03-30 09:00:04 +00:00
label : _ _ ( 'Enable shipping label printing' , 'woocommerce' ) ,
2021-09-21 19:33:44 +00:00
description : pluginsToActivate . includes (
'woocommerce-shipstation-integration'
)
? interpolateComponents ( {
mixedString : _ _ (
'We recommend using ShipStation to save time at the post office by printing your shipping ' +
'labels at home. Try ShipStation free for 30 days. {{link}}Learn more{{/link}}.' ,
2022-03-30 09:00:04 +00:00
'woocommerce'
2021-09-21 19:33:44 +00:00
) ,
components : {
link : (
< Link
href = "https://woocommerce.com/products/shipstation-integration?utm_medium=product"
target = "_blank"
type = "external"
/ >
) ,
} ,
} )
: _ _ (
'With WooCommerce Shipping you can save time ' +
'by printing your USPS and DHL Express shipping labels at home' ,
2022-03-30 09:00:04 +00:00
'woocommerce'
2021-09-21 19:33:44 +00:00
) ,
content : (
< Plugins
2023-02-20 13:32:14 +00:00
onComplete = { ( _plugins , response ) => {
2021-09-21 19:33:44 +00:00
createNoticesFromResponse ( response ) ;
recordEvent ( 'tasklist_shipping_label_printing' , {
install : true ,
plugins _to _activate : pluginsToActivate ,
} ) ;
this . completeStep ( ) ;
} }
onError = { ( errors , response ) =>
createNoticesFromResponse ( response )
}
onSkip = { ( ) => {
recordEvent ( 'tasklist_shipping_label_printing' , {
install : false ,
plugins _to _activate : pluginsToActivate ,
} ) ;
2023-02-20 13:32:14 +00:00
invalidateResolutionForStoreSelector ( ) ;
2021-09-21 19:33:44 +00:00
getHistory ( ) . push ( getNewPath ( { } , '/' , { } ) ) ;
2021-12-13 21:13:05 +00:00
onComplete ( ) ;
2021-09-21 19:33:44 +00:00
} }
pluginSlugs = { pluginsToActivate }
/ >
) ,
visible : pluginsToActivate . length ,
} ,
2023-02-20 13:32:14 +00:00
// Only needed for WooCommerce Shipping
2021-09-21 19:33:44 +00:00
{
key : 'connect' ,
2022-03-30 09:00:04 +00:00
label : _ _ ( 'Connect your store' , 'woocommerce' ) ,
2021-09-21 19:33:44 +00:00
description : _ _ (
'Connect your store to WordPress.com to enable label printing' ,
2022-03-30 09:00:04 +00:00
'woocommerce'
2021-09-21 19:33:44 +00:00
) ,
content : (
< Connect
redirectUrl = { getAdminLink (
'admin.php?page=wc-admin'
) }
completeStep = { this . completeStep }
onConnect = { ( ) => {
recordEvent ( 'tasklist_shipping_connect_store' ) ;
} }
/ >
) ,
visible : requiresJetpackConnection ,
} ,
] ;
2022-06-29 07:40:42 +00:00
// Override the step fields for the smart shipping defaults.
if ( this . shippingSmartDefaultsEnabled ) {
const agreementText = pluginsToActivate . includes (
'woocommerce-services'
)
? _ _ (
'By installing Jetpack and WooCommerce Shipping you agree to the {{link}}Terms of Service{{/link}}.' ,
'woocommerce'
)
: _ _ (
'By installing Jetpack you agree to the {{link}}Terms of Service{{/link}}.' ,
'woocommerce'
) ;
const shippingSmartDefaultsSteps = {
rates : {
label : _ _ ( 'Review your shipping options' , 'woocommerce' ) ,
description : _ _ (
'We recommend the following shipping options based on your location. You can manage your shipping options again at any time in WooCommerce Shipping settings.' ,
'woocommerce'
) ,
2022-07-06 02:13:57 +00:00
onClick :
this . state . step !== 'rates'
? ( ) => {
this . setState ( { step : 'rates' } ) ;
}
: undefined ,
2022-06-29 07:40:42 +00:00
content : (
< ShippingRates
buttonText = { _ _ (
'Save shipping options' ,
'woocommerce'
) }
shippingZones = { this . state . shippingZones }
onComplete = { ( ) => {
const { id } = task ;
optimisticallyCompleteTask ( id ) ;
invalidateResolutionForStoreSelector ( ) ;
this . completeStep ( ) ;
} }
createNotice = { createNotice }
/ >
) ,
} ,
label _printing : {
label : _ _ (
'Enable shipping label printing and discounted rates' ,
'woocommerce'
) ,
2023-02-20 13:32:14 +00:00
description :
pluginsToPromote . length === 1
? getSinglePluginDescription (
pluginsToPromote [ 0 ] . name ,
pluginsToPromote [ 0 ] . url
)
: _ _ (
'Save time and money by printing your shipping labels right from your computer with one of these shipping solutions.' ,
2022-06-29 07:40:42 +00:00
'woocommerce'
2023-02-20 13:32:14 +00:00
) ,
2022-06-29 07:40:42 +00:00
content : (
< >
2023-02-20 13:32:14 +00:00
{ pluginsToPromote . length === 1 ? (
pluginsToPromote [ 0 ] [
'single-partner-layout'
] ( )
2022-06-29 07:40:42 +00:00
) : (
2023-02-20 13:32:14 +00:00
< div className = "woocommerce-task-shipping-recommendation_plugins-install-container" >
{ pluginsToPromote . map (
( pluginToPromote ) => {
const pluginsForPartner = [
pluginToPromote ? . slug ,
pluginToPromote ? . dependencies ,
] . filter (
( element ) =>
element !== undefined
) ; // remove undefineds
return pluginToPromote [
'dual-partner-layout'
] ( {
children : (
< div className = "woocommerce-task-shipping-recommendations_plugins-buttons" >
< Plugins
onComplete = { (
response
) => {
createNoticesFromResponse (
response
) ;
recordEvent (
'tasklist_shipping_label_printing' ,
{
install : true ,
plugins _to _activate :
pluginsForPartner ,
}
) ;
invalidateResolutionForStoreSelector ( ) ;
this . completeStep ( ) ;
} }
onError = { (
errors ,
response
) =>
createNoticesFromResponse (
response
)
}
installText = { _ _ (
'Install and enable' ,
'woocommerce'
) }
learnMoreLink = {
pluginToPromote . url
}
onLearnMore = { ( ) => {
recordEvent (
'tasklist_shipping_label_printing_learn_more' ,
{
plugin : pluginToPromote . slug ,
}
) ;
} }
pluginSlugs = {
pluginsForPartner
}
installButtonVariant = {
'secondary'
}
/ >
< / d i v >
) ,
} ) ;
2022-06-29 07:40:42 +00:00
}
2023-02-20 13:32:14 +00:00
) }
< / d i v >
) }
{ pluginsToPromote . length === 1 &&
pluginsToPromote [ 0 ] . slug === undefined && ( // if it doesn't have a slug we just show a download button
< a
href = { pluginsToPromote [ 0 ] . url }
target = "_blank"
rel = "noreferrer"
>
< Button variant = "primary" >
{ _ _ ( 'Download' , 'woocommerce' ) }
< / B u t t o n >
< / a >
) }
{ pluginsToPromote . length === 1 &&
pluginsToPromote [ 0 ] . slug ? (
< Plugins
onComplete = { ( _plugins , response ) => {
createNoticesFromResponse ( response ) ;
recordEvent (
'tasklist_shipping_label_printing' ,
{
install : true ,
plugins _to _activate :
pluginsToActivate ,
}
) ;
invalidateResolutionForStoreSelector ( ) ;
this . completeStep ( ) ;
} }
onError = { ( errors , response ) =>
createNoticesFromResponse ( response )
}
onSkip = { onShippingPluginInstalltionSkip }
pluginSlugs = { pluginsToActivate }
installText = { _ _ (
'Install and enable' ,
'woocommerce'
) }
/ >
) : (
< Button
isTertiary
onClick = { onShippingPluginInstalltionSkip }
className = { classNames (
'woocommerce-task-shipping-recommendations_skip-button' ,
pluginsToPromote . length === 2
? 'dual'
: ''
) }
>
{ _ _ ( 'No Thanks' , 'woocommerce' ) }
< / B u t t o n >
) }
2022-06-29 07:40:42 +00:00
{ ! isJetpackConnected &&
pluginsToActivate . includes (
'woocommerce-services'
) && (
< Text
variant = "caption"
className = "woocommerce-task__caption"
size = "12"
lineHeight = "16px"
style = { { display : 'block' } }
>
{ interpolateComponents ( {
mixedString : agreementText ,
components : {
link : (
< Link
href = {
'https://wordpress.com/tos/'
}
target = "_blank"
type = "external"
>
< > < / >
< / L i n k >
) ,
} ,
} ) }
< / T e x t >
) }
< / >
) ,
} ,
store _location : {
label : _ _ ( 'Set your store location' , 'woocommerce' ) ,
description : _ _ (
'Add your store location to help us calculate shipping rates and the best shipping options for you. You can manage your store location again at any time in WooCommerce Settings General.' ,
'woocommerce'
) ,
2022-07-06 02:13:57 +00:00
onClick :
this . state . step !== 'store_location'
? ( ) => {
this . setState ( { step : 'store_location' } ) ;
}
: undefined ,
2022-06-29 07:40:42 +00:00
buttonText : _ _ ( 'Save store location' , 'woocommerce' ) ,
} ,
} ;
steps = steps . map ( ( step ) => {
if ( shippingSmartDefaultsSteps . hasOwnProperty ( step . key ) ) {
step = {
... step ,
... shippingSmartDefaultsSteps [ step . key ] ,
} ;
}
// Empty description field if it's not the current step.
if ( step . key !== this . state . step ) {
step . description = '' ;
}
return step ;
} ) ;
}
2021-09-21 19:33:44 +00:00
return filter ( steps , ( step ) => step . visible ) ;
}
render ( ) {
const { isPending , step } = this . state ;
const { isUpdateSettingsRequesting } = this . props ;
2022-06-29 07:40:42 +00:00
const steps = this . getSteps ( ) ;
2021-09-21 19:33:44 +00:00
return (
< div className = "woocommerce-task-shipping" >
< Card className = "woocommerce-task-card" >
< CardBody >
< Stepper
isPending = {
isPending || isUpdateSettingsRequesting
}
isVertical
currentStep = { step }
2022-06-29 07:40:42 +00:00
steps = { steps }
2021-09-21 19:33:44 +00:00
/ >
< / C a r d B o d y >
< / C a r d >
< / d i v >
) ;
}
}
const ShippingWrapper = compose (
withSelect ( ( select ) => {
2022-06-21 08:37:34 +00:00
const { getSettings , isUpdateSettingsRequesting } =
select ( SETTINGS _STORE _NAME ) ;
const { getActivePlugins , isJetpackConnected } =
select ( PLUGINS _STORE _NAME ) ;
2022-03-01 12:33:41 +00:00
const { getCountry } = select ( COUNTRIES _STORE _NAME ) ;
2021-09-21 19:33:44 +00:00
const { general : settings = { } } = getSettings ( 'general' ) ;
const countryCode = getCountryCode (
settings . woocommerce _default _country
) ;
2022-03-01 12:33:41 +00:00
const country = countryCode ? getCountry ( countryCode ) : null ;
2021-09-21 19:33:44 +00:00
const countryName = country ? country . name : null ;
const activePlugins = getActivePlugins ( ) ;
return {
countryCode ,
countryName ,
isUpdateSettingsRequesting : isUpdateSettingsRequesting ( 'general' ) ,
settings ,
activePlugins ,
isJetpackConnected : isJetpackConnected ( ) ,
} ;
} ) ,
withDispatch ( ( dispatch ) => {
const { createNotice } = dispatch ( 'core/notices' ) ;
2022-06-21 08:37:34 +00:00
const { updateAndPersistSettingsForGroup } =
dispatch ( SETTINGS _STORE _NAME ) ;
2021-12-13 21:13:05 +00:00
const {
invalidateResolutionForStoreSelector ,
optimisticallyCompleteTask ,
} = dispatch ( ONBOARDING _STORE _NAME ) ;
2021-09-21 19:33:44 +00:00
return {
createNotice ,
2021-12-13 21:13:05 +00:00
invalidateResolutionForStoreSelector ,
optimisticallyCompleteTask ,
2021-09-21 19:33:44 +00:00
updateAndPersistSettingsForGroup ,
} ;
} )
) ( Shipping ) ;
registerPlugin ( 'wc-admin-onboarding-task-shipping' , {
scope : 'woocommerce-tasks' ,
render : ( ) => (
< WooOnboardingTask id = "shipping" >
2021-12-13 21:13:05 +00:00
{ ( { onComplete , task } ) => {
return (
< ShippingWrapper onComplete = { onComplete } task = { task } / >
) ;
2021-10-01 19:53:22 +00:00
} }
2021-09-21 19:33:44 +00:00
< / W o o O n b o a r d i n g T a s k >
) ,
} ) ;