From 547b771cdf20dedd3b401da4d262e5ebdbd19e7b Mon Sep 17 00:00:00 2001 From: Joshua T Flowers Date: Mon, 26 Aug 2019 13:49:04 +0800 Subject: [PATCH] Onboarding: Add tax task. (https://github.com/woocommerce/woocommerce-admin/pull/2830) --- .../profile-wizard/steps/store-details.js | 3 +- .../client/dashboard/task-list/index.js | 4 +- .../client/dashboard/task-list/style.scss | 57 +++ .../task-list/tasks/shipping/index.js | 24 +- .../task-list/tasks/shipping/rates.js | 4 +- .../tasks/{shipping => steps}/connect.js | 0 .../tasks/{shipping => steps}/location.js | 4 +- .../{shipping/labels.js => steps/plugins.js} | 36 +- .../client/dashboard/task-list/tasks/tax.js | 351 ++++++++++++++++++ .../client/dashboard/utils.js | 15 + .../src/Features/OnboardingTasks.php | 20 +- 11 files changed, 489 insertions(+), 29 deletions(-) rename plugins/woocommerce-admin/client/dashboard/task-list/tasks/{shipping => steps}/connect.js (100%) rename plugins/woocommerce-admin/client/dashboard/task-list/tasks/{shipping => steps}/location.js (95%) rename plugins/woocommerce-admin/client/dashboard/task-list/tasks/{shipping/labels.js => steps/plugins.js} (82%) create mode 100644 plugins/woocommerce-admin/client/dashboard/task-list/tasks/tax.js create mode 100644 plugins/woocommerce-admin/client/dashboard/utils.js diff --git a/plugins/woocommerce-admin/client/dashboard/profile-wizard/steps/store-details.js b/plugins/woocommerce-admin/client/dashboard/profile-wizard/steps/store-details.js index 06b34c697b9..36b0044bb3a 100644 --- a/plugins/woocommerce-admin/client/dashboard/profile-wizard/steps/store-details.js +++ b/plugins/woocommerce-admin/client/dashboard/profile-wizard/steps/store-details.js @@ -12,6 +12,7 @@ import { recordEvent } from 'lib/tracks'; /** * Internal depdencies */ +import { getCountryCode } from 'dashboard/utils'; import { H, Card, Form } from '@woocommerce/components'; import withSelect from 'wc-api/with-select'; import { @@ -46,7 +47,7 @@ class StoreDetails extends Component { } = this.props; recordEvent( 'storeprofiler_store_details_continue', { - store_country: values.countryState.split( ':' )[ 0 ], + store_country: getCountryCode( values.countryState ), setup_client: values.isClient, } ); diff --git a/plugins/woocommerce-admin/client/dashboard/task-list/index.js b/plugins/woocommerce-admin/client/dashboard/task-list/index.js index 7b4dca76f44..72cdc211885 100644 --- a/plugins/woocommerce-admin/client/dashboard/task-list/index.js +++ b/plugins/woocommerce-admin/client/dashboard/task-list/index.js @@ -20,6 +20,7 @@ import './style.scss'; import Connect from './tasks/connect'; import Products from './tasks/products'; import Shipping from './tasks/shipping'; +import Tax from './tasks/tax'; import withSelect from 'wc-api/with-select'; class TaskDashboard extends Component { @@ -106,7 +107,8 @@ class TaskDashboard extends Component { ), before: account_balance, after: chevron_right, - onClick: noop, + onClick: () => updateQueryString( { task: 'tax' } ), + container: , visible: true, }, { diff --git a/plugins/woocommerce-admin/client/dashboard/task-list/style.scss b/plugins/woocommerce-admin/client/dashboard/task-list/style.scss index 5d484ba832a..8e1a7f55e64 100644 --- a/plugins/woocommerce-admin/client/dashboard/task-list/style.scss +++ b/plugins/woocommerce-admin/client/dashboard/task-list/style.scss @@ -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; + } +} diff --git a/plugins/woocommerce-admin/client/dashboard/task-list/tasks/shipping/index.js b/plugins/woocommerce-admin/client/dashboard/task-list/tasks/shipping/index.js index 22fadf6aa62..467668460b0 100644 --- a/plugins/woocommerce-admin/client/dashboard/task-list/tasks/shipping/index.js +++ b/plugins/woocommerce-admin/client/dashboard/task-list/tasks/shipping/index.js @@ -18,9 +18,10 @@ import { getHistory, getNewPath } from '@woocommerce/navigation'; /** * Internal dependencies */ -import Connect from './connect'; -import StoreLocation from './location'; -import ShippingLabels from './labels'; +import Connect from '../steps/connect'; +import { getCountryCode } from 'dashboard/utils'; +import Plugins from '../steps/plugins'; +import StoreLocation from '../steps/location'; import ShippingRates from './rates'; import withSelect from 'wc-api/with-select'; @@ -146,7 +147,7 @@ class Shipping extends Component { key: 'store_location', label: __( 'Set store location', 'woocommerce-admin' ), description: __( 'The address from which your business operates', 'woocommerce-admin' ), - content: , + content: , visible: true, }, { @@ -159,7 +160,7 @@ class Shipping extends Component { content: ( ), @@ -173,7 +174,13 @@ class Shipping extends Component { 'Post Office by printing your shipping labels at home', 'woocommerce-admin' ), - content: , + content: ( + getHistory().push( getNewPath( {}, '/', {} ) ) } + { ...this.props } + /> + ), visible: [ 'US', 'GB', 'CA', 'AU' ].includes( countryCode ), }, { @@ -218,9 +225,8 @@ export default compose( const isSettingsError = Boolean( getSettingsError( 'general' ) ); const isSettingsRequesting = isGetSettingsRequesting( 'general' ); - const countryCode = settings.woocommerce_default_country - ? settings.woocommerce_default_country.split( ':' )[ 0 ] - : null; + const countryCode = getCountryCode( settings.woocommerce_default_country ); + const countries = ( wcSettings.dataEndpoints && wcSettings.dataEndpoints.countries ) || []; const country = countryCode ? countries.find( c => c.code === countryCode ) : null; const countryName = country ? country.name : null; diff --git a/plugins/woocommerce-admin/client/dashboard/task-list/tasks/shipping/rates.js b/plugins/woocommerce-admin/client/dashboard/task-list/tasks/shipping/rates.js index fb69b32b136..f87e0122a3a 100644 --- a/plugins/woocommerce-admin/client/dashboard/task-list/tasks/shipping/rates.js +++ b/plugins/woocommerce-admin/client/dashboard/task-list/tasks/shipping/rates.js @@ -77,7 +77,7 @@ class ShippingRates extends Component { createNotice( 'success', __( 'Your shipping rates have been updated.', 'woocommerce-admin' ) ); - this.props.completeStep(); + this.props.onComplete(); } renderInputPrefix() { @@ -225,7 +225,7 @@ ShippingRates.propTypes = { /** * Function used to mark the step complete. */ - completeStep: PropTypes.func.isRequired, + onComplete: PropTypes.func.isRequired, /** * Function to create a transient notice in the store. */ diff --git a/plugins/woocommerce-admin/client/dashboard/task-list/tasks/shipping/connect.js b/plugins/woocommerce-admin/client/dashboard/task-list/tasks/steps/connect.js similarity index 100% rename from plugins/woocommerce-admin/client/dashboard/task-list/tasks/shipping/connect.js rename to plugins/woocommerce-admin/client/dashboard/task-list/tasks/steps/connect.js diff --git a/plugins/woocommerce-admin/client/dashboard/task-list/tasks/shipping/location.js b/plugins/woocommerce-admin/client/dashboard/task-list/tasks/steps/location.js similarity index 95% rename from plugins/woocommerce-admin/client/dashboard/task-list/tasks/shipping/location.js rename to plugins/woocommerce-admin/client/dashboard/task-list/tasks/steps/location.js index f050fe488c5..34ceff6c27a 100644 --- a/plugins/woocommerce-admin/client/dashboard/task-list/tasks/shipping/location.js +++ b/plugins/woocommerce-admin/client/dashboard/task-list/tasks/steps/location.js @@ -26,7 +26,7 @@ export default class StoreLocation extends Component { } async onSubmit( values ) { - const { completeStep, createNotice, isSettingsError, updateSettings } = this.props; + const { onComplete, createNotice, isSettingsError, updateSettings } = this.props; await updateSettings( { general: { @@ -39,7 +39,7 @@ export default class StoreLocation extends Component { } ); if ( ! isSettingsError ) { - completeStep(); + onComplete(); } else { createNotice( 'error', diff --git a/plugins/woocommerce-admin/client/dashboard/task-list/tasks/shipping/labels.js b/plugins/woocommerce-admin/client/dashboard/task-list/tasks/steps/plugins.js similarity index 82% rename from plugins/woocommerce-admin/client/dashboard/task-list/tasks/shipping/labels.js rename to plugins/woocommerce-admin/client/dashboard/task-list/tasks/steps/plugins.js index c5cacbc7b3a..49a8508703d 100644 --- a/plugins/woocommerce-admin/client/dashboard/task-list/tasks/shipping/labels.js +++ b/plugins/woocommerce-admin/client/dashboard/task-list/tasks/steps/plugins.js @@ -7,13 +7,9 @@ import { Button } from 'newspack-components'; import { Component, Fragment } from '@wordpress/element'; import { compose } from '@wordpress/compose'; import { difference } from 'lodash'; +import PropTypes from 'prop-types'; import { withDispatch } from '@wordpress/data'; -/** - * WooCommerce dependencies - */ -import { getHistory, getNewPath } from '@woocommerce/navigation'; - /** * Internal dependencies */ @@ -21,18 +17,19 @@ import withSelect from 'wc-api/with-select'; const plugins = [ 'jetpack', 'woocommerce-services' ]; -class ShippingLabels extends Component { +class Plugins extends Component { constructor() { super( ...arguments ); this.installAndActivatePlugins = this.installAndActivatePlugins.bind( this ); + this.skipInstaller = this.skipInstaller.bind( this ); } componentDidUpdate( prevProps ) { const { activatedPlugins, activatePlugins, - completeStep, + onComplete, createNotice, errors, installedPlugins, @@ -56,7 +53,7 @@ class ShippingLabels extends Component { 'success', __( 'Plugins were successfully installed and activated.', 'woocommerce-admin' ) ); - completeStep(); + onComplete(); } } @@ -73,11 +70,11 @@ class ShippingLabels extends Component { } skipInstaller() { - getHistory().push( getNewPath( {}, '/', {} ) ); + this.props.onSkip(); } render() { - const { hasErrors, isRequesting } = this.props; + const { hasErrors, isRequesting, skipText } = this.props; return hasErrors ? ( - + ); } } +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( withSelect( select => { const { @@ -139,4 +151,4 @@ export default compose( installPlugins, }; } ) -)( ShippingLabels ); +)( Plugins ); diff --git a/plugins/woocommerce-admin/client/dashboard/task-list/tasks/tax.js b/plugins/woocommerce-admin/client/dashboard/task-list/tasks/tax.js new file mode 100644 index 00000000000..b57d9a9efd5 --- /dev/null +++ b/plugins/woocommerce-admin/client/dashboard/task-list/tasks/tax.js @@ -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§ion=standard' ); + } + } else { + createNotice( + 'error', + __( 'There was a problem updating your tax settings.', 'woocommerce-admin' ) + ); + } + } + + getAutomatedTaxStepContent() { + const { automatedTaxEnabled } = this.state; + const { isTaxSettingsRequesting } = this.props; + + return ( + +
+ autorenew +
+ + this.setState( { automatedTaxEnabled: ! automatedTaxEnabled } ) } + /> +
+
+ +
+ ); + } + + 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: ( + + ), + 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: ( + + ( window.location.href = getAdminLink( + 'admin.php?page=wc-settings&tab=tax§ion=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: , + 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: ( + + ), + visible: ! this.isSupportedCountry(), + }, + ]; + + return filter( steps, step => step.visible ); + } + + render() { + const { isPending, stepIndex } = this.state; + const { isGeneralSettingsRequesting, isTaxSettingsRequesting } = this.props; + + return ( +
+ + { null !== stepIndex ? ( + + ) : ( +
+ + 🎊 + + + { __( 'Good news!', 'woocommerce-admin' ) } + +

+ { interpolateComponents( { + mixedString: __( + '{{strong}}Jetpack{{/strong}} and {{strong}}WooCommerce Services{{/strong}} ' + + 'can automate your sales tax calculations for you.', + 'woocommerce-admin' + ), + components: { + strong: , + }, + } ) } +

+ + +
+ ) } +
+
+ ); + } +} + +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 ); diff --git a/plugins/woocommerce-admin/client/dashboard/utils.js b/plugins/woocommerce-admin/client/dashboard/utils.js new file mode 100644 index 00000000000..56977134d86 --- /dev/null +++ b/plugins/woocommerce-admin/client/dashboard/utils.js @@ -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 ]; +} diff --git a/plugins/woocommerce-admin/src/Features/OnboardingTasks.php b/plugins/woocommerce-admin/src/Features/OnboardingTasks.php index 6d53039c357..c3d1c6824d3 100644 --- a/plugins/woocommerce-admin/src/Features/OnboardingTasks.php +++ b/plugins/woocommerce-admin/src/Features/OnboardingTasks.php @@ -71,8 +71,9 @@ class OnboardingTasks { set_transient( self::TASKS_TRANSIENT, $tasks, DAY_IN_SECONDS ); } - $settings['onboarding']['tasks'] = $tasks; - $settings['onboarding']['shippingZonesCount'] = count( \WC_Shipping_Zones::get_zones() ); + $settings['onboarding']['automatedTaxSupportedCountries'] = self::get_automated_tax_supported_countries(); + $settings['onboarding']['tasks'] = $tasks; + $settings['onboarding']['shippingZonesCount'] = count( \WC_Shipping_Zones::get_zones() ); return $settings; } @@ -129,4 +130,19 @@ class OnboardingTasks { 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; + } }