This commit is contained in:
Joshua T Flowers 2019-08-26 13:49:04 +08:00 committed by GitHub
parent 822f18c2be
commit 547b771cdf
11 changed files with 489 additions and 29 deletions

View File

@ -12,6 +12,7 @@ import { recordEvent } from 'lib/tracks';
/** /**
* Internal depdencies * Internal depdencies
*/ */
import { getCountryCode } from 'dashboard/utils';
import { H, Card, Form } from '@woocommerce/components'; import { H, Card, Form } from '@woocommerce/components';
import withSelect from 'wc-api/with-select'; import withSelect from 'wc-api/with-select';
import { import {
@ -46,7 +47,7 @@ class StoreDetails extends Component {
} = this.props; } = this.props;
recordEvent( 'storeprofiler_store_details_continue', { recordEvent( 'storeprofiler_store_details_continue', {
store_country: values.countryState.split( ':' )[ 0 ], store_country: getCountryCode( values.countryState ),
setup_client: values.isClient, setup_client: values.isClient,
} ); } );

View File

@ -20,6 +20,7 @@ import './style.scss';
import Connect from './tasks/connect'; import Connect from './tasks/connect';
import Products from './tasks/products'; import Products from './tasks/products';
import Shipping from './tasks/shipping'; import Shipping from './tasks/shipping';
import Tax from './tasks/tax';
import withSelect from 'wc-api/with-select'; import withSelect from 'wc-api/with-select';
class TaskDashboard extends Component { class TaskDashboard extends Component {
@ -106,7 +107,8 @@ class TaskDashboard extends Component {
), ),
before: <i className="material-icons-outlined">account_balance</i>, before: <i className="material-icons-outlined">account_balance</i>,
after: <i className="material-icons-outlined">chevron_right</i>, after: <i className="material-icons-outlined">chevron_right</i>,
onClick: noop, onClick: () => updateQueryString( { task: 'tax' } ),
container: <Tax />,
visible: true, visible: true,
}, },
{ {

View File

@ -137,3 +137,60 @@
} }
} }
} }
.woocommerce-task-tax__automated-tax-control {
display: flex;
align-items: center;
margin-top: $gap;
i {
margin-left: $gap;
margin-right: $gap-large;
}
.woocommerce-task-tax__automated-tax-control-inner {
border-top: 1px solid $new-muriel-gray-5;
display: flex;
align-items: center;
flex: 1;
font-size: 16px;
padding-top: $gap;
padding-bottom: $gap;
}
.components-form-toggle {
margin-left: auto;
}
}
.woocommerce-task-tax__success {
display: flex;
flex-direction: column;
align-items: center;
padding: $gap-largest;
text-align: center;
.woocommerce-task-tax__success-icon {
font-size: 48px;
height: 48px;
align-items: center;
display: flex;
}
#woocommerce-task-tax__success-message {
font-size: 32px;
font-weight: 400;
}
.components-button.is-primary {
min-width: 328px;
@include breakpoint( '<782px' ) {
min-width: auto;
}
}
p {
margin-top: 0;
font-size: 16px;
}
}

View File

@ -18,9 +18,10 @@ import { getHistory, getNewPath } from '@woocommerce/navigation';
/** /**
* Internal dependencies * Internal dependencies
*/ */
import Connect from './connect'; import Connect from '../steps/connect';
import StoreLocation from './location'; import { getCountryCode } from 'dashboard/utils';
import ShippingLabels from './labels'; import Plugins from '../steps/plugins';
import StoreLocation from '../steps/location';
import ShippingRates from './rates'; import ShippingRates from './rates';
import withSelect from 'wc-api/with-select'; import withSelect from 'wc-api/with-select';
@ -146,7 +147,7 @@ class Shipping extends Component {
key: 'store_location', key: 'store_location',
label: __( 'Set store location', 'woocommerce-admin' ), label: __( 'Set store location', 'woocommerce-admin' ),
description: __( 'The address from which your business operates', 'woocommerce-admin' ), description: __( 'The address from which your business operates', 'woocommerce-admin' ),
content: <StoreLocation completeStep={ this.completeStep } { ...this.props } />, content: <StoreLocation onComplete={ this.completeStep } { ...this.props } />,
visible: true, visible: true,
}, },
{ {
@ -159,7 +160,7 @@ class Shipping extends Component {
content: ( content: (
<ShippingRates <ShippingRates
shippingZones={ this.state.shippingZones } shippingZones={ this.state.shippingZones }
completeStep={ this.completeStep } onComplete={ this.completeStep }
{ ...this.props } { ...this.props }
/> />
), ),
@ -173,7 +174,13 @@ class Shipping extends Component {
'Post Office by printing your shipping labels at home', 'Post Office by printing your shipping labels at home',
'woocommerce-admin' 'woocommerce-admin'
), ),
content: <ShippingLabels completeStep={ this.completeStep } { ...this.props } />, content: (
<Plugins
onComplete={ this.completeStep }
onSkip={ () => getHistory().push( getNewPath( {}, '/', {} ) ) }
{ ...this.props }
/>
),
visible: [ 'US', 'GB', 'CA', 'AU' ].includes( countryCode ), visible: [ 'US', 'GB', 'CA', 'AU' ].includes( countryCode ),
}, },
{ {
@ -218,9 +225,8 @@ export default compose(
const isSettingsError = Boolean( getSettingsError( 'general' ) ); const isSettingsError = Boolean( getSettingsError( 'general' ) );
const isSettingsRequesting = isGetSettingsRequesting( 'general' ); const isSettingsRequesting = isGetSettingsRequesting( 'general' );
const countryCode = settings.woocommerce_default_country const countryCode = getCountryCode( settings.woocommerce_default_country );
? settings.woocommerce_default_country.split( ':' )[ 0 ]
: null;
const countries = ( wcSettings.dataEndpoints && wcSettings.dataEndpoints.countries ) || []; const countries = ( wcSettings.dataEndpoints && wcSettings.dataEndpoints.countries ) || [];
const country = countryCode ? countries.find( c => c.code === countryCode ) : null; const country = countryCode ? countries.find( c => c.code === countryCode ) : null;
const countryName = country ? country.name : null; const countryName = country ? country.name : null;

View File

@ -77,7 +77,7 @@ class ShippingRates extends Component {
createNotice( 'success', __( 'Your shipping rates have been updated.', 'woocommerce-admin' ) ); createNotice( 'success', __( 'Your shipping rates have been updated.', 'woocommerce-admin' ) );
this.props.completeStep(); this.props.onComplete();
} }
renderInputPrefix() { renderInputPrefix() {
@ -225,7 +225,7 @@ ShippingRates.propTypes = {
/** /**
* Function used to mark the step complete. * Function used to mark the step complete.
*/ */
completeStep: PropTypes.func.isRequired, onComplete: PropTypes.func.isRequired,
/** /**
* Function to create a transient notice in the store. * Function to create a transient notice in the store.
*/ */

View File

@ -26,7 +26,7 @@ export default class StoreLocation extends Component {
} }
async onSubmit( values ) { async onSubmit( values ) {
const { completeStep, createNotice, isSettingsError, updateSettings } = this.props; const { onComplete, createNotice, isSettingsError, updateSettings } = this.props;
await updateSettings( { await updateSettings( {
general: { general: {
@ -39,7 +39,7 @@ export default class StoreLocation extends Component {
} ); } );
if ( ! isSettingsError ) { if ( ! isSettingsError ) {
completeStep(); onComplete();
} else { } else {
createNotice( createNotice(
'error', 'error',

View File

@ -7,13 +7,9 @@ import { Button } from 'newspack-components';
import { Component, Fragment } from '@wordpress/element'; import { Component, Fragment } from '@wordpress/element';
import { compose } from '@wordpress/compose'; import { compose } from '@wordpress/compose';
import { difference } from 'lodash'; import { difference } from 'lodash';
import PropTypes from 'prop-types';
import { withDispatch } from '@wordpress/data'; import { withDispatch } from '@wordpress/data';
/**
* WooCommerce dependencies
*/
import { getHistory, getNewPath } from '@woocommerce/navigation';
/** /**
* Internal dependencies * Internal dependencies
*/ */
@ -21,18 +17,19 @@ import withSelect from 'wc-api/with-select';
const plugins = [ 'jetpack', 'woocommerce-services' ]; const plugins = [ 'jetpack', 'woocommerce-services' ];
class ShippingLabels extends Component { class Plugins extends Component {
constructor() { constructor() {
super( ...arguments ); super( ...arguments );
this.installAndActivatePlugins = this.installAndActivatePlugins.bind( this ); this.installAndActivatePlugins = this.installAndActivatePlugins.bind( this );
this.skipInstaller = this.skipInstaller.bind( this );
} }
componentDidUpdate( prevProps ) { componentDidUpdate( prevProps ) {
const { const {
activatedPlugins, activatedPlugins,
activatePlugins, activatePlugins,
completeStep, onComplete,
createNotice, createNotice,
errors, errors,
installedPlugins, installedPlugins,
@ -56,7 +53,7 @@ class ShippingLabels extends Component {
'success', 'success',
__( 'Plugins were successfully installed and activated.', 'woocommerce-admin' ) __( 'Plugins were successfully installed and activated.', 'woocommerce-admin' )
); );
completeStep(); onComplete();
} }
} }
@ -73,11 +70,11 @@ class ShippingLabels extends Component {
} }
skipInstaller() { skipInstaller() {
getHistory().push( getNewPath( {}, '/', {} ) ); this.props.onSkip();
} }
render() { render() {
const { hasErrors, isRequesting } = this.props; const { hasErrors, isRequesting, skipText } = this.props;
return hasErrors ? ( return hasErrors ? (
<Button isPrimary onClick={ () => location.reload() }> <Button isPrimary onClick={ () => location.reload() }>
@ -88,12 +85,27 @@ class ShippingLabels extends Component {
<Button isBusy={ isRequesting } isPrimary onClick={ this.installAndActivatePlugins }> <Button isBusy={ isRequesting } isPrimary onClick={ this.installAndActivatePlugins }>
{ __( 'Install & enable', 'woocommerce-admin' ) } { __( 'Install & enable', 'woocommerce-admin' ) }
</Button> </Button>
<Button onClick={ this.skipInstaller }>{ __( 'No thanks', 'woocommerce-admin' ) }</Button> <Button onClick={ this.skipInstaller }>{ skipText || __( 'No thanks', 'woocommerce-admin' ) }</Button>
</Fragment> </Fragment>
); );
} }
} }
Plugins.propTypes = {
/**
* Called when the plugin installer is completed.
*/
onComplete: PropTypes.func.isRequired,
/**
* Called when the plugin installer is skipped.
*/
onSkip: PropTypes.func.isRequired,
/**
* Text used for the skip installer button.
*/
skipText: PropTypes.string,
};
export default compose( export default compose(
withSelect( select => { withSelect( select => {
const { const {
@ -139,4 +151,4 @@ export default compose(
installPlugins, installPlugins,
}; };
} ) } )
)( ShippingLabels ); )( Plugins );

View File

@ -0,0 +1,351 @@
/** @format */
/**
* External dependencies
*/
import { __ } from '@wordpress/i18n';
import { Button } from 'newspack-components';
import { Component, Fragment } from '@wordpress/element';
import { compose } from '@wordpress/compose';
import { filter } from 'lodash';
import { FormToggle } from '@wordpress/components';
import interpolateComponents from 'interpolate-components';
import { withDispatch } from '@wordpress/data';
/**
* WooCommerce dependencies
*/
import { Card, H, Stepper } from '@woocommerce/components';
import { getAdminLink, getHistory, getNewPath } from '@woocommerce/navigation';
/**
* Internal dependencies
*/
import Connect from './steps/connect';
import { getCountryCode } from 'dashboard/utils';
import Plugins from './steps/plugins';
import StoreLocation from './steps/location';
import withSelect from 'wc-api/with-select';
class Tax extends Component {
constructor() {
super( ...arguments );
this.initialState = {
isPending: false,
stepIndex: 0,
automatedTaxEnabled: true,
};
this.state = this.initialState;
this.completeStep = this.completeStep.bind( this );
this.updateAutomatedTax = this.updateAutomatedTax.bind( this );
}
componentDidMount() {
this.reset();
}
reset() {
this.setState( this.initialState );
}
componentDidUpdate( prevProps ) {
const { generalSettings, isJetpackConnected, pluginsToActivate, taxSettings } = this.props;
const {
woocommerce_store_address,
woocommerce_default_country,
woocommerce_store_postcode,
} = generalSettings;
const { stepIndex } = this.state;
const currentStep = this.getSteps()[ stepIndex ];
const currentStepKey = currentStep && currentStep.key;
const isCompleteAddress =
woocommerce_store_address && woocommerce_default_country && woocommerce_store_postcode;
// Show the success screen if all requirements are satisfied from the beginning.
if (
0 === stepIndex &&
( ! pluginsToActivate.length &&
isCompleteAddress &&
isJetpackConnected &&
this.isSupportedCountry() )
) {
/* eslint-disable react/no-did-update-set-state */
this.setState( { stepIndex: null } );
/* eslint-enable react/no-did-update-set-state */
return;
}
if ( 'store_location' === currentStepKey && isCompleteAddress ) {
this.completeStep();
}
if (
taxSettings.wc_connect_taxes_enabled &&
taxSettings.wc_connect_taxes_enabled !== prevProps.taxSettings.wc_connect_taxes_enabled
) {
/* eslint-disable react/no-did-update-set-state */
this.setState( {
automatedTaxEnabled: 'yes' === taxSettings.wc_connect_taxes_enabled ? true : false,
} );
/* eslint-enable react/no-did-update-set-state */
}
if ( 'connect' === currentStepKey && isJetpackConnected ) {
this.completeStep();
}
}
isSupportedCountry() {
const { countryCode } = this.props;
const { automatedTaxSupportedCountries } = wcSettings.onboarding;
return automatedTaxSupportedCountries.includes( countryCode );
}
completeStep() {
const { stepIndex } = this.state;
const steps = this.getSteps();
const nextStep = steps[ stepIndex + 1 ];
if ( nextStep ) {
this.setState( { stepIndex: stepIndex + 1 } );
} else {
getHistory().push( getNewPath( {}, '/', {} ) );
}
}
async updateAutomatedTax() {
const { createNotice, isTaxSettingsError, updateSettings } = this.props;
const { automatedTaxEnabled } = this.state;
await updateSettings( {
tax: {
wc_connect_taxes_enabled: automatedTaxEnabled ? 'yes' : 'no',
},
} );
if ( ! isTaxSettingsError ) {
createNotice( 'success', __( 'Your tax settings have been updated.', 'woocommerce-admin' ) );
if ( automatedTaxEnabled ) {
getHistory().push( getNewPath( {}, '/', {} ) );
} else {
window.location = getAdminLink( 'admin.php?page=wc-settings&tab=tax&section=standard' );
}
} else {
createNotice(
'error',
__( 'There was a problem updating your tax settings.', 'woocommerce-admin' )
);
}
}
getAutomatedTaxStepContent() {
const { automatedTaxEnabled } = this.state;
const { isTaxSettingsRequesting } = this.props;
return (
<Fragment>
<div className="woocommerce-task-tax__automated-tax-control">
<i className="material-icons-outlined">autorenew</i>
<div className="woocommerce-task-tax__automated-tax-control-inner">
<label
htmlFor="woocommerce-task-tax__automated-tax-control-input"
className="woocommerce-task-tax__automated-tax-control-label"
>
{ __( 'Automate sales tax calculations', 'woocommerce-adfmin' ) }
</label>
<FormToggle
id="woocommerce-task-tax__automated-tax-control-input"
checked={ automatedTaxEnabled }
onChange={ () => this.setState( { automatedTaxEnabled: ! automatedTaxEnabled } ) }
/>
</div>
</div>
<Button isPrimary onClick={ this.updateAutomatedTax } isBusy={ isTaxSettingsRequesting }>
{ __( 'Complete task', 'woocommerce-admin' ) }
</Button>
</Fragment>
);
}
getSteps() {
const { generalSettings, isGeneralSettingsRequesting } = this.props;
const steps = [
{
key: 'store_location',
label: __( 'Set store location', 'woocommerce-admin' ),
description: __( 'The address from which your business operates', 'woocommerce-admin' ),
content: (
<StoreLocation
{ ...this.props }
onComplete={ this.completeStep }
isSettingsRequesting={ isGeneralSettingsRequesting }
settings={ generalSettings }
/>
),
visible: true,
},
{
key: 'plugins',
label: __( 'Install Jetpack and WooCommerce Services', 'woocommerce-admin' ),
description: __(
'Jetpack and WooCommerce services allow you to automate sales tax calculations',
'woocommerce-admin'
),
content: (
<Plugins
onComplete={ this.completeStep }
onSkip={ () =>
( window.location.href = getAdminLink(
'admin.php?page=wc-settings&tab=tax&section=standard'
) )
}
skipText={ __( 'Set up tax rates manually', 'woocommerce-admin' ) }
/>
),
visible: this.isSupportedCountry(),
},
{
key: 'connect',
label: __( 'Connect your store', 'woocommerce-admin' ),
description: __(
'Connect your store to WordPress.com to enable automated sales tax calculations',
'woocommerce-admin'
),
content: <Connect { ...this.props } />,
visible: this.isSupportedCountry(),
},
{
key: 'automated_tax',
label: __( 'Enable automated tax calculations', 'woocommerce-admin' ),
description: __(
'Sales taxes will be calculated automatically when a customer checks out',
'woocommerce-admin'
),
content: this.getAutomatedTaxStepContent(),
visible: this.isSupportedCountry(),
},
{
key: 'manual_configuration',
label: __( 'Congifure tax rates', 'woocommerce-admin' ),
description: __(
'Head over to the tax rate settings screen to configure your tax rates',
'woocommerce-admin'
),
content: (
<Button
isPrimary
href={ getAdminLink( 'admin.php?page=wc-settings&tab=tax&section=standard' ) }
>
{ __( 'Configure', 'woocommerce-admin' ) }
</Button>
),
visible: ! this.isSupportedCountry(),
},
];
return filter( steps, step => step.visible );
}
render() {
const { isPending, stepIndex } = this.state;
const { isGeneralSettingsRequesting, isTaxSettingsRequesting } = this.props;
return (
<div className="woocommerce-task-tax">
<Card className="is-narrow">
{ null !== stepIndex ? (
<Stepper
isPending={ isPending || isGeneralSettingsRequesting || isTaxSettingsRequesting }
isVertical={ true }
currentStep={ this.getSteps()[ stepIndex ].key }
steps={ this.getSteps() }
/>
) : (
<div className="woocommerce-task-tax__success">
<span
className="woocommerce-task-tax__success-icon"
role="img"
aria-labelledby="woocommerce-task-tax__success-message"
>
🎊
</span>
<H id="woocommerce-task-tax__success-message">
{ __( 'Good news!', 'woocommerce-admin' ) }
</H>
<p>
{ interpolateComponents( {
mixedString: __(
'{{strong}}Jetpack{{/strong}} and {{strong}}WooCommerce Services{{/strong}} ' +
'can automate your sales tax calculations for you.',
'woocommerce-admin'
),
components: {
strong: <strong />,
},
} ) }
</p>
<Button
isPrimary
onClick={ () =>
this.setState( { automatedTaxEnabled: true }, this.updateAutomatedTax )
}
>
{ __( 'Yes please', 'woocommerce-admin' ) }
</Button>
<Button
onClick={ () =>
this.setState( { automatedTaxEnabled: false }, this.updateAutomatedTax )
}
>
{ __( "No thanks, I'll configure taxes manually", 'woocommerce-admin' ) }
</Button>
</div>
) }
</Card>
</div>
);
}
}
export default compose(
withSelect( select => {
const { getSettings, getSettingsError, isGetSettingsRequesting } = select( 'wc-api' );
const generalSettings = getSettings( 'general' );
const isGeneralSettingsError = Boolean( getSettingsError( 'general' ) );
const isGeneralSettingsRequesting = isGetSettingsRequesting( 'general' );
const taxSettings = getSettings( 'tax' );
const isTaxSettingsError = Boolean( getSettingsError( 'tax' ) );
const isTaxSettingsRequesting = isGetSettingsRequesting( 'tax' );
const countryCode = getCountryCode( generalSettings.woocommerce_default_country );
// @todo This value should be fetched and updated via the wc-api.
const isJetpackConnected = false;
// @todo This should check against a list of already activated plugins and should be
// revisited after https://github.com/woocommerce/woocommerce-admin/pull/2825 is merged.
const pluginsToActivate = [ 'jetpack', 'woocommerce-services' ];
return {
countryCode,
isGeneralSettingsError,
isGeneralSettingsRequesting,
generalSettings,
isTaxSettingsError,
isTaxSettingsRequesting,
taxSettings,
isJetpackConnected,
pluginsToActivate,
};
} ),
withDispatch( dispatch => {
const { createNotice } = dispatch( 'core/notices' );
const { updateSettings } = dispatch( 'wc-api' );
return {
createNotice,
updateSettings,
};
} )
)( Tax );

View File

@ -0,0 +1,15 @@
/**
* Gets the country code from a country:state value string.
*
* @format
* @param {string} countryState Country state string, e.g. US:GA.
* @return {string} Country string.
*/
export function getCountryCode( countryState ) {
if ( ! countryState ) {
return null;
}
return countryState.split( ':' )[ 0 ];
}

View File

@ -71,8 +71,9 @@ class OnboardingTasks {
set_transient( self::TASKS_TRANSIENT, $tasks, DAY_IN_SECONDS ); set_transient( self::TASKS_TRANSIENT, $tasks, DAY_IN_SECONDS );
} }
$settings['onboarding']['tasks'] = $tasks; $settings['onboarding']['automatedTaxSupportedCountries'] = self::get_automated_tax_supported_countries();
$settings['onboarding']['shippingZonesCount'] = count( \WC_Shipping_Zones::get_zones() ); $settings['onboarding']['tasks'] = $tasks;
$settings['onboarding']['shippingZonesCount'] = count( \WC_Shipping_Zones::get_zones() );
return $settings; return $settings;
} }
@ -129,4 +130,19 @@ class OnboardingTasks {
return false; return false;
} }
/**
* Get an array of countries that support automated tax.
*
* @return array
*/
public static function get_automated_tax_supported_countries() {
// https://developers.taxjar.com/api/reference/#countries .
$tax_supported_countries = array_merge(
array( 'US', 'CA', 'AU' ),
WC()->countries->get_european_union_countries()
);
return $tax_supported_countries;
}
} }