/** * External dependencies */ import { __ } from '@wordpress/i18n'; import { Component } from '@wordpress/element'; import { compose } from '@wordpress/compose'; import { Button, Card, CardBody, CardFooter, TabPanel, __experimentalText as Text, FlexItem, CheckboxControl, } from '@wordpress/components'; import { withDispatch, withSelect } from '@wordpress/data'; import { SelectControl, Form, TextControl } from '@woocommerce/components'; import { ONBOARDING_STORE_NAME, PLUGINS_STORE_NAME, SETTINGS_STORE_NAME, } from '@woocommerce/data'; import { getSetting } from '@woocommerce/settings'; import { recordEvent } from '@woocommerce/tracks'; import classnames from 'classnames'; /** * Internal dependencies */ import { CurrencyContext } from '~/lib/currency-context'; import { createNoticesFromResponse } from '~/lib/notices'; import { platformOptions } from '../../data/platform-options'; import { employeeOptions } from '../../data/employee-options'; import { sellingVenueOptions } from '../../data/selling-venue-options'; import { getRevenueOptions } from '../../data/revenue-options'; import { getProductCountOptions } from '../../data/product-options'; import { SelectiveExtensionsBundle } from './selective-extensions-bundle'; import { getPluginSlug, getPluginTrackKey } from '~/utils'; import './style.scss'; const BUSINESS_DETAILS_TAB_NAME = 'business-details'; const BUSINESS_FEATURES_TAB_NAME = 'business-features'; export const filterBusinessExtensions = ( extensionInstallationOptions, alreadyActivatedExtensions = [] ) => { return ( Object.keys( extensionInstallationOptions ) .filter( ( key ) => extensionInstallationOptions[ key ] && key !== 'install_extensions' && ! alreadyActivatedExtensions.includes( key ) ) .map( getPluginSlug ) // remove duplicate .filter( ( item, index, arr ) => arr.indexOf( item ) === index ) ); }; const timeFrames = [ { name: '0-2s', max: 2 }, { name: '2-5s', max: 5 }, { name: '5-10s', max: 10 }, { name: '10-15s', max: 15 }, { name: '15-20s', max: 20 }, { name: '20-30s', max: 30 }, { name: '30-60s', max: 60 }, { name: '>60s' }, ]; function getTimeFrame( timeInMs ) { for ( const timeFrame of timeFrames ) { if ( ! timeFrame.max ) { return timeFrame.name; } if ( timeInMs < timeFrame.max * 1000 ) { return timeFrame.name; } } } export const prepareExtensionTrackingData = ( extensionInstallationOptions ) => { const installedExtensions = {}; for ( let [ fieldKey, value ] of Object.entries( extensionInstallationOptions ) ) { fieldKey = getPluginSlug( fieldKey ); const key = getPluginTrackKey( fieldKey ); if ( fieldKey !== 'install_extensions' && ! installedExtensions[ `install_${ key }` ] ) { installedExtensions[ `install_${ key }` ] = value; } } return installedExtensions; }; export const prepareExtensionTrackingInstallationData = ( extensionInstallationOptions, installationData ) => { const installed = []; const data = {}; for ( let [ fieldKey ] of Object.entries( extensionInstallationOptions ) ) { fieldKey = getPluginSlug( fieldKey ); const key = getPluginTrackKey( fieldKey ); if ( installationData && installationData.data && installationData.data.install_time && installationData.data.install_time[ fieldKey ] ) { installed.push( key ); data[ `install_time_${ key }` ] = getTimeFrame( installationData.data.install_time[ fieldKey ] ); } } data.installed_extensions = installed; data.activated_extensions = installationData && installationData.data && installationData.data.activated ? installationData.data.activated : []; return data; }; export const isSellingElsewhere = ( selectedOption ) => [ 'other', 'brick-mortar', 'brick-mortar-other', 'other-woocommerce', ].includes( selectedOption ); export const isSellingOtherPlatformInPerson = ( selectedOption ) => [ 'other', 'brick-mortar-other' ].includes( selectedOption ); class BusinessDetails extends Component { constructor( props ) { super(); this.state = { isPopoverVisible: false, isValid: false, currentTab: 'business-details', savedValues: null, }; this.onContinue = this.onContinue.bind( this ); this.validate = this.validate.bind( this ); props.trackStepValueChanges( props.step.key, { ...( this.state.savedValues || props.initialValues ) }, this.savedValues || props.initialValues, this.persistProfileItems.bind( this ) ); } async onContinue( extensionInstallationOptions, installableExtensionsData ) { const { createNotice, goToNextStep, installAndActivatePlugins, } = this.props; const alreadyActivatedExtensions = installableExtensionsData.reduce( ( actExtensions, bundle ) => { const activated = bundle.plugins .filter( ( plugin ) => plugin.is_activated ) .map( ( plugin ) => plugin.key ); return [ ...actExtensions, ...activated ]; }, [] ); const businessExtensions = filterBusinessExtensions( extensionInstallationOptions, alreadyActivatedExtensions ); const installedExtensions = prepareExtensionTrackingData( extensionInstallationOptions ); recordEvent( 'storeprofiler_store_business_features_continue', { all_extensions_installed: Object.values( extensionInstallationOptions ).every( ( val ) => val ), ...installedExtensions, } ); const promises = [ this.persistProfileItems( { business_extensions: [ ...businessExtensions, ...alreadyActivatedExtensions, ], } ), ]; if ( businessExtensions.length ) { const installationStartTime = window.performance.now(); promises.push( installAndActivatePlugins( businessExtensions ) .then( ( response ) => { const totalInstallationTime = window.performance.now() - installationStartTime; const installedExtensionsData = prepareExtensionTrackingInstallationData( extensionInstallationOptions, response ); recordEvent( 'storeprofiler_store_business_features_installed_and_activated', { success: true, total_time: getTimeFrame( totalInstallationTime ), ...installedExtensionsData, } ); createNoticesFromResponse( response ); } ) .catch( ( error ) => { recordEvent( 'storeprofiler_store_business_features_installed_and_activated', { success: false, failed_extensions: Object.keys( error.data || {} ).map( ( key ) => getPluginTrackKey( key ) ), } ); createNoticesFromResponse( error ); throw new Error(); } ) ); } Promise.all( promises ) .then( () => { goToNextStep( { step: BUSINESS_FEATURES_TAB_NAME } ); } ) .catch( () => { createNotice( 'error', __( 'There was a problem updating your business details', 'woocommerce' ) ); } ); } async persistProfileItems( additions = {} ) { const { updateProfileItems, createNotice } = this.props; const { number_employees: numberEmployees, other_platform: otherPlatform, other_platform_name: otherPlatformName, product_count: productCount, revenue, selling_venues: sellingVenues, setup_client: isSetupClient, } = this.state.savedValues; const updates = { number_employees: numberEmployees, other_platform: otherPlatform, other_platform_name: otherPlatform === 'other' ? otherPlatformName : '', product_count: productCount, revenue, selling_venues: sellingVenues, setup_client: isSetupClient, ...additions, }; // Remove possible empty values like `revenue` and `other_platform`. const finalUpdates = Object.entries( updates ).reduce( ( acc, [ key, val ] ) => { if ( val !== '' ) { acc[ key ] = val; } return acc; }, {} ); return updateProfileItems( finalUpdates ).catch( () => { createNotice( 'error', __( 'There was a problem updating your business details', 'woocommerce' ) ); } ); } validate( values ) { const errors = {}; if ( ! values.product_count.length ) { errors.product_count = __( 'This field is required', 'woocommerce' ); } if ( ! values.selling_venues.length ) { errors.selling_venues = __( 'This field is required', 'woocommerce' ); } if ( ! values.other_platform.length && isSellingOtherPlatformInPerson( values.selling_venues ) ) { errors.other_platform = __( 'This field is required', 'woocommerce' ); } if ( ! values.other_platform_name.trim().length && values.other_platform === 'other' && isSellingOtherPlatformInPerson( values.selling_venues ) ) { errors.other_platform_name = __( 'This field is required', 'woocommerce' ); } if ( ! values.number_employees.length && isSellingElsewhere( values.selling_venues ) ) { errors.number_employees = __( 'This field is required', 'woocommerce' ); } if ( ! values.revenue.length && isSellingElsewhere( values.selling_venues ) ) { errors.revenue = __( 'This field is required', 'woocommerce' ); } if ( Object.keys( errors ).length === 0 ) { this.setState( { isValid: true } ); } return errors; } trackBusinessDetailsStep( { number_employees: numberEmployees, other_platform: otherPlatform, other_platform_name: otherPlatformName, product_count: productCount, selling_venues: sellingVenues, revenue, setup_client: isSetupClient, } ) { const { getCurrencyConfig } = this.context; recordEvent( 'storeprofiler_store_business_details_continue_variant', { number_employees: numberEmployees, already_selling: sellingVenues, currency: getCurrencyConfig().code, product_number: productCount, revenue, used_platform: otherPlatform, used_platform_name: otherPlatformName, setup_client: isSetupClient, } ); recordEvent( 'storeprofiler_step_complete', { step: BUSINESS_DETAILS_TAB_NAME, wc_version: getSetting( 'wcVersion' ), } ); } getSelectControlProps( getInputProps, name = '' ) { const { className, ...props } = getInputProps( name ); return { ...props, className: classnames( `woocommerce-profile-wizard__${ name.replace( /\_/g, '-' ) }`, className ), }; } renderBusinessDetailsStep() { const { goToNextStep, isInstallingActivating, hasInstallActivateError, } = this.props; const { formatAmount, getCurrencyConfig } = this.context; const productCountOptions = getProductCountOptions( getCurrencyConfig() ); return (
); } renderFreeFeaturesStep() { const { isInstallingActivating, settings, profileItems } = this.props; const country = settings.woocommerce_default_country ? settings.woocommerce_default_country : null; return ( <>