woocommerce/plugins/woocommerce-admin/client/profile-wizard/steps/business-details.js

950 lines
23 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* External dependencies
*/
import { __, _n, _x, sprintf } from '@wordpress/i18n';
import { Component, Fragment } from '@wordpress/element';
import { compose } from '@wordpress/compose';
import {
Button,
CheckboxControl,
FormToggle,
Popover,
} from '@wordpress/components';
import interpolateComponents from 'interpolate-components';
import { withDispatch, withSelect } from '@wordpress/data';
import { keys, get, pickBy } from 'lodash';
import {
H,
Card,
SelectControl,
Form,
TextControl,
} from '@woocommerce/components';
import { formatValue } from '@woocommerce/number';
import { getSetting } from '@woocommerce/wc-admin-settings';
import {
ONBOARDING_STORE_NAME,
PLUGINS_STORE_NAME,
pluginNames,
SETTINGS_STORE_NAME,
} from '@woocommerce/data';
import { recordEvent } from '@woocommerce/tracks';
/**
* Internal dependencies
*/
import { getCountryCode, getCurrencyRegion } from '../../dashboard/utils';
import { CurrencyContext } from '../../lib/currency-context';
import { createNoticesFromResponse } from '../../lib/notices';
const wcAdminAssetUrl = getSetting( 'wcAdminAssetUrl', '' );
class BusinessDetails extends Component {
constructor( props ) {
super();
const settings = get( props, 'settings', {} );
const profileItems = get( props, 'profileItems', {} );
const industrySlugs = get( profileItems, 'industry', [] ).map(
( industry ) => industry.slug
);
const businessExtensions = get(
profileItems,
'business_extensions',
false
);
this.state = {
isPopoverVisible: false,
};
this.initialValues = {
other_platform: profileItems.other_platform || '',
other_platform_name: profileItems.other_platform_name || '',
product_count: profileItems.product_count || '',
selling_venues: profileItems.selling_venues || '',
revenue: profileItems.revenue || '',
'facebook-for-woocommerce': businessExtensions
? businessExtensions.includes( 'facebook-for-woocommerce' )
: true,
'mailchimp-for-woocommerce': businessExtensions
? businessExtensions.includes( 'mailchimp-for-woocommerce' )
: true,
'kliken-marketing-for-google': businessExtensions
? businessExtensions.includes( 'kliken-marketing-for-google' )
: true,
install_extensions: true,
};
this.extensions = [
'facebook-for-woocommerce',
'mailchimp-for-woocommerce',
'kliken-marketing-for-google',
];
this.bundleInstall =
getCountryCode( settings.woocommerce_default_country ) === 'US' &&
( industrySlugs.includes( 'fashion-apparel-accessories' ) ||
industrySlugs.includes( 'health-beauty' ) );
this.onContinue = this.onContinue.bind( this );
this.validate = this.validate.bind( this );
this.getNumberRangeString = this.getNumberRangeString.bind( this );
this.numberFormat = this.numberFormat.bind( this );
}
async onContinue( values ) {
const {
createNotice,
goToNextStep,
installAndActivatePlugins,
updateProfileItems,
} = this.props;
const {
install_extensions: installExtensions,
other_platform: otherPlatform,
other_platform_name: otherPlatformName,
product_count: productCount,
revenue,
selling_venues: sellingVenues,
} = values;
const businessExtensions = this.getBusinessExtensions( values );
const { getCurrencyConfig } = this.context;
recordEvent( 'storeprofiler_store_business_details_continue', {
product_number: productCount,
already_selling: sellingVenues,
currency: getCurrencyConfig().code,
revenue,
used_platform: otherPlatform,
used_platform_name: otherPlatformName,
install_woocommerce_services: businessExtensions.includes(
'facebook-for-woocommerce'
),
install_jetpack: businessExtensions.includes( 'jetpack' ),
install_facebook: businessExtensions.includes(
'facebook-for-woocommerce'
),
install_mailchimp: businessExtensions.includes(
'mailchimp-for-woocommerce'
),
install_google_ads: businessExtensions.includes(
'kliken-marketing-for-google'
),
install_extensions: installExtensions,
bundle_install: this.bundleInstall,
} );
const _updates = {
other_platform: otherPlatform,
other_platform_name:
otherPlatform === 'other' ? otherPlatformName : '',
product_count: productCount,
revenue,
selling_venues: sellingVenues,
business_extensions: businessExtensions,
};
// Remove possible empty values like `revenue` and `other_platform`.
const updates = {};
Object.keys( _updates ).forEach( ( key ) => {
if ( _updates[ key ] !== '' ) {
updates[ key ] = _updates[ key ];
}
} );
const promises = [
updateProfileItems( updates ).catch( () => {
throw new Error();
} ),
];
if ( businessExtensions.length ) {
promises.push(
installAndActivatePlugins( businessExtensions )
.then( ( response ) => {
createNoticesFromResponse( response );
} )
.catch( ( error ) => {
createNoticesFromResponse( error );
throw new Error();
} )
);
}
Promise.all( promises )
.then( () => {
goToNextStep();
} )
.catch( () => {
createNotice(
'error',
__(
'There was a problem updating your business details.',
'woocommerce-admin'
)
);
} );
}
validate( values ) {
const errors = {};
if ( ! values.product_count.length ) {
errors.product_count = __(
'This field is required',
'woocommerce-admin'
);
}
if ( ! values.selling_venues.length ) {
errors.selling_venues = __(
'This field is required',
'woocommerce-admin'
);
}
if (
! values.other_platform.length &&
[ 'other', 'brick-mortar-other' ].includes( values.selling_venues )
) {
errors.other_platform = __(
'This field is required',
'woocommerce-admin'
);
}
if (
! values.other_platform_name &&
values.other_platform === 'other' &&
[ 'other', 'brick-mortar-other' ].includes( values.selling_venues )
) {
errors.other_platform_name = __(
'This field is required',
'woocommerce-admin'
);
}
if (
! values.revenue.length &&
[
'other',
'brick-mortar',
'brick-mortar-other',
'other-woocommerce',
].includes( values.selling_venues )
) {
errors.revenue = __(
'This field is required',
'woocommerce-admin'
);
}
return errors;
}
getBusinessExtensions( values ) {
if ( this.bundleInstall ) {
return values.install_extensions
? [
'jetpack',
'woocommerce-services',
'woocommerce-payments',
...this.extensions,
]
: [];
}
if ( values.selling_venues === '' ) {
return [];
}
return keys( pickBy( values ) ).filter( ( name ) =>
this.extensions.includes( name )
);
}
convertCurrency( value ) {
const region = getCurrencyRegion(
this.props.settings.woocommerce_default_country
);
if ( region === 'US' ) {
return value;
}
// These are rough exchange rates from USD. Precision is not paramount.
// The keys here should match the keys in `getCurrencyData`.
const exchangeRates = {
US: 1,
EU: 0.9,
IN: 71.24,
GB: 0.76,
BR: 4.19,
VN: 23172.5,
ID: 14031.0,
BD: 84.87,
PK: 154.8,
RU: 63.74,
TR: 5.75,
MX: 19.37,
CA: 1.32,
};
const exchangeRate = exchangeRates[ region ] || exchangeRates.US;
const digits = exchangeRate.toString().split( '.' )[ 0 ].length;
const multiplier = Math.pow( 10, 2 + digits );
return Math.round( ( value * exchangeRate ) / multiplier ) * multiplier;
}
numberFormat( value ) {
const { getCurrencyConfig } = this.context;
return formatValue( getCurrencyConfig(), 'number', value );
}
getNumberRangeString( min, max = false, format = this.numberFormat ) {
if ( ! max ) {
return sprintf(
_x(
'%s+',
'store product count or revenue',
'woocommerce-admin'
),
format( min )
);
}
return sprintf(
_x(
'%1$s - %2$s',
'store product count or revenue range',
'woocommerce-admin'
),
format( min ),
format( max )
);
}
renderBusinessExtensionHelpText( values ) {
const { isInstallingActivating } = this.props;
const extensions = this.getBusinessExtensions( values );
if ( extensions.length === 0 ) {
return null;
}
const extensionsList = extensions
.map( ( extension ) => {
return pluginNames[ extension ];
} )
.join( ', ' );
if ( isInstallingActivating ) {
return (
<p>
{ sprintf(
_n(
'Installing the following plugin: %s',
'Installing the following plugins: %s',
extensions.length,
'woocommerce-admin'
),
extensionsList
) }
</p>
);
}
const accountRequiredText = this.bundleInstall
? __(
'User accounts are required to use these features.',
'woocommerce-admin'
)
: '';
return (
<Fragment>
<p>
{ sprintf(
_n(
'The following plugin will be installed for free: %s. %s',
'The following plugins will be installed for free: %s. %s',
extensions.length,
'woocommerce-admin'
),
extensionsList,
accountRequiredText
) }
</p>
</Fragment>
);
}
renderBusinessExtensions( values, getInputProps ) {
// Show extensions when the currently selling elsewhere checkbox has been answered.
if ( values.selling_venues === '' ) {
return null;
}
const extensionBenefits = [
{
slug: 'facebook-for-woocommerce',
title: __( 'Market on Facebook', 'woocommerce-admin' ),
icon: 'onboarding/fb-woocommerce.png',
description: __(
'Grow your business by targeting the right people and driving sales with Facebook.',
'woocommerce-admin'
),
},
{
slug: 'mailchimp-for-woocommerce',
title: __(
'Contact customers with Mailchimp',
'woocommerce-admin'
),
icon: 'onboarding/mailchimp.png',
description: __(
'Send targeted campaigns, recover abandoned carts and much more with Mailchimp.',
'woocommerce-admin'
),
},
{
slug: 'kliken-marketing-for-google',
title: __( 'Drive sales with Google Ads', 'woocommerce-admin' ),
icon: 'onboarding/g-shopping.png',
description: __(
'Get in front of new customers on Google and secure $150 in ads credit with Klikens integration.',
'woocommerce-admin'
),
},
];
return (
<Fragment>
{ extensionBenefits.map( ( benefit ) => (
<div
className="woocommerce-profile-wizard__benefit"
key={ benefit.title }
>
<div className="woocommerce-profile-wizard__business-extension">
<img
src={ wcAdminAssetUrl + benefit.icon }
alt=""
/>
</div>
<div className="woocommerce-profile-wizard__benefit-content">
<H className="woocommerce-profile-wizard__benefit-title">
{ benefit.title }
</H>
<p>{ benefit.description }</p>
</div>
<div className="woocommerce-profile-wizard__benefit-toggle">
<FormToggle
checked={ values[ benefit.slug ] }
{ ...getInputProps( benefit.slug ) }
/>
</div>
</div>
) ) }
</Fragment>
);
}
renderBusinessExtensionsBundle( values, getInputProps ) {
const { isPopoverVisible } = this.state;
return (
<div className="woocommerce-business-extensions">
<label htmlFor="woocommerce-business-extensions__checkbox">
<CheckboxControl
id="woocommerce-business-extensions__checkbox"
{ ...getInputProps( 'install_extensions' ) }
/>
<span className="woocommerce-business-extensions__label-text">
{ interpolateComponents( {
mixedString: __(
'Install recommended {{strong}}free{{/strong}} business features',
'woocommerce-admin'
),
components: {
strong: <strong />,
},
} ) }
<span className="woocommerce-business-extensions__label-subtext">
{ __( 'Requires an account', 'woocommerce-admin' ) }
</span>
</span>
</label>
<div className="woocommerce-business-extensions__popover-wrapper">
<Button
isTertiary
label={ __(
'Learn more about recommended free business features',
'woocommerce-admin'
) }
onClick={ () => {
recordEvent(
'storeprofiler_store_business_details_popover'
);
this.setState( { isPopoverVisible: true } );
} }
>
<i
className="material-icons-outlined"
aria-hidden="true"
>
info
</i>
</Button>
{ isPopoverVisible && (
<Popover
className="woocommerce-business-extensions__popover"
focusOnMount="container"
position="top center"
onClose={ () =>
this.setState( { isPopoverVisible: false } )
}
>
<div className="woocommerce-business-extensions__benefits">
<div className="woocommerce-business-extensions__benefit">
<i
className="material-icons-outlined"
aria-hidden="true"
>
check
</i>
{ __(
'Manage your store on the go with the WooCommerce mobile app',
'woocommerce-admin'
) }
</div>
<div className="woocommerce-business-extensions__benefit">
<i
className="material-icons-outlined"
aria-hidden="true"
>
check
</i>
{ __(
'Accept credit cards with WooCommerce Payments',
'woocommerce-admin'
) }
</div>
<div className="woocommerce-business-extensions__benefit">
<i
className="material-icons-outlined"
aria-hidden="true"
>
check
</i>
{ __(
'Speed & security enhancements',
'woocommerce-admin'
) }
</div>
<div className="woocommerce-business-extensions__benefit">
<i
className="material-icons-outlined"
aria-hidden="true"
>
check
</i>
{ __(
'Automatic sales taxes',
'woocommerce-admin'
) }
</div>
<div className="woocommerce-business-extensions__benefit">
<i
className="material-icons-outlined"
aria-hidden="true"
>
check
</i>
{ __(
'Market on Facebook',
'woocommerce-admin'
) }
</div>
<div className="woocommerce-business-extensions__benefit">
<i
className="material-icons-outlined"
aria-hidden="true"
>
check
</i>
{ __(
'Contact customers with Mailchimp',
'woocommerce-admin'
) }
</div>
<div className="woocommerce-business-extensions__benefit">
<i
className="material-icons-outlined"
aria-hidden="true"
>
check
</i>
{ __(
'Drive sales with Google Ads',
'woocommerce-admin'
) }
</div>
<div className="woocommerce-business-extensions__benefit">
<i
className="material-icons-outlined"
aria-hidden="true"
>
check
</i>
{ __(
'Print shipping labels at home',
'woocommerce-admin'
) }
</div>
</div>
</Popover>
) }
</div>
</div>
);
}
render() {
const {
goToNextStep,
isInstallingActivating,
hasInstallActivateError,
} = this.props;
const { formatAmount } = this.context;
const productCountOptions = [
{
key: '0',
label: __(
"I don't have any products yet.",
'woocommerce-admin'
),
},
{
key: '1-10',
label: this.getNumberRangeString( 1, 10 ),
},
{
key: '11-100',
label: this.getNumberRangeString( 11, 100 ),
},
{
key: '101-1000',
label: this.getNumberRangeString( 101, 1000 ),
},
{
key: '1000+',
label: this.getNumberRangeString( 1000 ),
},
];
const revenueOptions = [
{
key: 'none',
label: sprintf(
/* translators: %s: $0 revenue amount */
__( "%s (I'm just getting started)", 'woocommerce-admin' ),
formatAmount( 0 )
),
},
{
key: 'up-to-2500',
label: sprintf(
/* translators: %s: A given revenue amount, e.g., $2500 */
__( 'Up to %s', 'woocommerce-admin' ),
formatAmount( this.convertCurrency( 2500 ) )
),
},
{
key: '2500-10000',
label: this.getNumberRangeString(
this.convertCurrency( 2500 ),
this.convertCurrency( 10000 ),
formatAmount
),
},
{
key: '10000-50000',
label: this.getNumberRangeString(
this.convertCurrency( 10000 ),
this.convertCurrency( 50000 ),
formatAmount
),
},
{
key: '50000-250000',
label: this.getNumberRangeString(
this.convertCurrency( 50000 ),
this.convertCurrency( 250000 ),
formatAmount
),
},
{
key: 'more-than-250000',
label: sprintf(
/* translators: %s: A given revenue amount, e.g., $250000 */
__( 'More than %s', 'woocommerce-admin' ),
formatAmount( this.convertCurrency( 250000 ) )
),
},
];
const sellingVenueOptions = [
{
key: 'no',
label: __( 'No', 'woocommerce-admin' ),
},
{
key: 'other',
label: __( 'Yes, on another platform', 'woocommerce-admin' ),
},
{
key: 'other-woocommerce',
label: __(
'Yes, I own a different store powered by WooCommerce',
'woocommerce-admin'
),
},
{
key: 'brick-mortar',
label: __(
'Yes, in person at physical stores and/or events',
'woocommerce-admin'
),
},
{
key: 'brick-mortar-other',
label: __(
'Yes, on another platform and in person at physical stores and/or events',
'woocommerce-admin'
),
},
];
const otherPlatformOptions = [
{
key: 'shopify',
label: __( 'Shopify', 'woocommerce-admin' ),
},
{
key: 'bigcommerce',
label: __( 'BigCommerce', 'woocommerce-admin' ),
},
{
key: 'magento',
label: __( 'Magento', 'woocommerce-admin' ),
},
{
key: 'wix',
label: __( 'Wix', 'woocommerce-admin' ),
},
{
key: 'amazon',
label: __( 'Amazon', 'woocommerce-admin' ),
},
{
key: 'ebay',
label: __( 'eBay', 'woocommerce-admin' ),
},
{
key: 'etsy',
label: __( 'Etsy', 'woocommerce-admin' ),
},
{
key: 'squarespace',
label: __( 'Squarespace', 'woocommerce-admin' ),
},
{
key: 'other',
label: __( 'Other', 'woocommerce-admin' ),
},
];
return (
<Form
initialValues={ this.initialValues }
onSubmitCallback={ this.onContinue }
validate={ this.validate }
>
{ ( { getInputProps, handleSubmit, values, isValidForm } ) => {
return (
<Fragment>
<H className="woocommerce-profile-wizard__header-title">
{ __(
'Tell us about your business',
'woocommerce-admin'
) }
</H>
<H className="woocommerce-profile-wizard__header-subtitle">
{ __(
"We'd love to know if you are just getting started or you already have a business in place.",
'woocommerce-admin'
) }
</H>
<Card>
<Fragment>
<SelectControl
label={ __(
'How many products do you plan to display?',
'woocommerce-admin'
) }
options={ productCountOptions }
required
{ ...getInputProps( 'product_count' ) }
/>
<SelectControl
label={ __(
'Currently selling elsewhere?',
'woocommerce-admin'
) }
options={ sellingVenueOptions }
required
{ ...getInputProps( 'selling_venues' ) }
/>
{ [
'other',
'brick-mortar',
'brick-mortar-other',
'other-woocommerce',
].includes( values.selling_venues ) && (
<SelectControl
label={ __(
"What's your current annual revenue?",
'woocommerce-admin'
) }
options={ revenueOptions }
required
{ ...getInputProps( 'revenue' ) }
/>
) }
{ [
'other',
'brick-mortar-other',
].includes( values.selling_venues ) && (
<Fragment>
<div className="business-competitors">
<SelectControl
label={ __(
'Which platform is the store using?',
'woocommerce-admin'
) }
options={
otherPlatformOptions
}
required
{ ...getInputProps(
'other_platform'
) }
/>
{ values.other_platform ===
'other' && (
<TextControl
label={ __(
'What is the platform name?',
'woocommerce-admin'
) }
required
{ ...getInputProps(
'other_platform_name'
) }
/>
) }
</div>
</Fragment>
) }
{ this.bundleInstall
? this.renderBusinessExtensionsBundle(
values,
getInputProps
)
: this.renderBusinessExtensions(
values,
getInputProps
) }
<div className="woocommerce-profile-wizard__card-actions">
<Button
isPrimary
onClick={ handleSubmit }
disabled={ ! isValidForm }
isBusy={ isInstallingActivating }
>
{ ! hasInstallActivateError
? __(
'Continue',
'woocommerce-admin'
)
: __(
'Retry',
'woocommerce-admin'
) }
</Button>
{ hasInstallActivateError && (
<Button
onClick={ () => goToNextStep() }
>
{ __(
'Continue without installing',
'woocommerce-admin'
) }
</Button>
) }
</div>
</Fragment>
</Card>
{ this.renderBusinessExtensionHelpText( values ) }
</Fragment>
);
} }
</Form>
);
}
}
BusinessDetails.contextType = CurrencyContext;
export default compose(
withSelect( ( select ) => {
const { getSettings, getSettingsError } = select( SETTINGS_STORE_NAME );
const { getProfileItems, getOnboardingError } = select(
ONBOARDING_STORE_NAME
);
const { getPluginsError, isPluginsRequesting } = select(
PLUGINS_STORE_NAME
);
const { general: settings = {} } = getSettings( 'general' );
return {
hasInstallActivateError:
getPluginsError( 'installPlugins' ) ||
getPluginsError( 'activatePlugins' ),
isError: Boolean( getOnboardingError( 'updateProfileItems' ) ),
profileItems: getProfileItems(),
isSettingsError: Boolean( getSettingsError( 'general' ) ),
settings,
isInstallingActivating:
isPluginsRequesting( 'installPlugins' ) ||
isPluginsRequesting( 'activatePlugins' ) ||
isPluginsRequesting( 'getJetpackConnectUrl' ),
};
} ),
withDispatch( ( dispatch ) => {
const { updateProfileItems } = dispatch( ONBOARDING_STORE_NAME );
const { installAndActivatePlugins } = dispatch( PLUGINS_STORE_NAME );
const { createNotice } = dispatch( 'core/notices' );
return {
createNotice,
installAndActivatePlugins,
updateProfileItems,
};
} )
)( BusinessDetails );