2023-05-30 07:05:38 +00:00
/ * *
* External dependencies
* /
import { __ } from '@wordpress/i18n' ;
2023-10-24 15:07:26 +00:00
import {
Button ,
TextControl ,
Notice ,
Spinner ,
CheckboxControl ,
} from '@wordpress/components' ;
2023-11-01 04:42:16 +00:00
import { FormInputValidation } from '@automattic/components' ;
2023-05-30 07:05:38 +00:00
import { SelectControl } from '@woocommerce/components' ;
import { Icon , chevronDown } from '@wordpress/icons' ;
import {
createInterpolateElement ,
useEffect ,
useState ,
} from '@wordpress/element' ;
import { findCountryOption , getCountry } from '@woocommerce/onboarding' ;
2023-06-19 04:58:16 +00:00
import { decodeEntities } from '@wordpress/html-entities' ;
2023-11-01 04:42:16 +00:00
import { z } from 'zod' ;
2024-05-31 03:49:36 +00:00
import clsx from 'clsx' ;
2023-06-19 04:58:16 +00:00
2023-04-24 02:08:24 +00:00
/ * *
* Internal dependencies
* /
2024-06-07 07:06:40 +00:00
import { CoreProfilerStateMachineContext } from '../index' ;
import { BusinessInfoEvent } from '../events' ;
2023-05-30 07:05:38 +00:00
import { CountryStateOption } from '../services/country' ;
import { Heading } from '../components/heading/heading' ;
import { Navigation } from '../components/navigation/navigation' ;
/** These are some store names that are known to be set by default and not likely to be used as actual names */
export const POSSIBLY_DEFAULT_STORE_NAMES = [
undefined ,
'woocommerce' ,
'Site Title' ,
'' ,
] ;
2023-05-30 23:45:10 +00:00
export type IndustryChoice = ( typeof industryChoices ) [ number ] [ 'key' ] ;
2023-05-30 07:05:38 +00:00
export const industryChoices = [
{
label : __ ( 'Clothing and accessories' , 'woocommerce' ) ,
key : 'clothing_and_accessories' as const ,
} ,
2024-05-20 15:10:53 +00:00
{
label : __ ( 'Food and drink' , 'woocommerce' ) ,
key : 'food_and_drink' as const ,
} ,
{
label : __ ( 'Electronics and computers' , 'woocommerce' ) ,
key : 'electronics_and_computers' as const ,
} ,
2023-05-30 07:05:38 +00:00
{
label : __ ( 'Health and beauty' , 'woocommerce' ) ,
key : 'health_and_beauty' as const ,
} ,
{
2024-05-20 15:10:53 +00:00
label : __ ( 'Education and learning' , 'woocommerce' ) ,
key : 'education_and_learning' as const ,
2023-05-30 07:05:38 +00:00
} ,
{
label : __ ( 'Home, furniture and garden' , 'woocommerce' ) ,
key : 'home_furniture_and_garden' as const ,
} ,
{
2024-05-20 15:10:53 +00:00
label : __ ( 'Arts and crafts' , 'woocommerce' ) ,
key : 'arts_and_crafts' as const ,
2023-05-30 07:05:38 +00:00
} ,
{
2024-05-20 15:10:53 +00:00
label : __ ( 'Sports and recreation' , 'woocommerce' ) ,
key : 'sports_and_recreation' as const ,
2023-05-30 07:05:38 +00:00
} ,
{
label : __ ( 'Other' , 'woocommerce' ) ,
key : 'other' as const ,
} ,
] ;
2023-04-24 02:08:24 +00:00
2023-05-30 23:45:10 +00:00
export type IndustryChoiceOption = ( typeof industryChoices ) [ number ] ;
2023-05-30 07:05:38 +00:00
export const selectIndustryMapping = {
im_just_starting_my_business : __ (
'What type of products or services do you plan to sell?' ,
'woocommerce'
) ,
im_already_selling : __ (
'Which industry is your business in?' ,
'woocommerce'
) ,
im_setting_up_a_store_for_a_client : __ (
"Which industry is your client's business in?" ,
'woocommerce'
) ,
} ;
2023-07-24 12:34:38 +00:00
export type BusinessInfoContextProps = Pick <
CoreProfilerStateMachineContext ,
'geolocatedLocation' | 'userProfile' | 'businessInfo' | 'countries'
> & {
onboardingProfile : Pick <
CoreProfilerStateMachineContext [ 'onboardingProfile' ] ,
2023-10-24 15:07:26 +00:00
| 'industry'
| 'business_choice'
| 'is_store_country_set'
| 'is_agree_marketing'
| 'store_email'
> ;
2023-11-21 10:50:39 +00:00
} & Partial < Pick < CoreProfilerStateMachineContext , 'currentUserEmail' > > ;
2023-07-24 12:34:38 +00:00
2023-04-24 02:08:24 +00:00
export const BusinessInfo = ( {
context ,
2023-05-30 07:05:38 +00:00
navigationProgress ,
2023-04-24 02:08:24 +00:00
sendEvent ,
} : {
2023-07-24 12:34:38 +00:00
context : BusinessInfoContextProps ;
2023-05-30 07:05:38 +00:00
navigationProgress : number ;
2023-04-24 02:08:24 +00:00
sendEvent : ( event : BusinessInfoEvent ) = > void ;
} ) = > {
2023-05-30 07:05:38 +00:00
const {
geolocatedLocation ,
userProfile : { businessChoice } ,
businessInfo ,
countries ,
onboardingProfile : {
2024-07-23 02:45:58 +00:00
is_store_country_set : isStoreCountrySet = false ,
industry : industryFromOnboardingProfile = [ ] ,
business_choice : businessChoiceFromOnboardingProfile = '' ,
is_agree_marketing : isOptInMarketingFromOnboardingProfile = false ,
store_email : storeEmailAddressFromOnboardingProfile = '' ,
} = { } ,
2023-10-24 15:07:26 +00:00
currentUserEmail ,
2023-05-30 07:05:38 +00:00
} = context ;
const [ storeName , setStoreName ] = useState (
businessInfo . storeName || ''
) ;
const [ storeCountry , setStoreCountry ] = useState < CountryStateOption > ( {
key : '' ,
label : '' ,
} ) ;
useEffect ( ( ) = > {
if ( isStoreCountrySet ) {
const previouslyStoredCountryOption = countries . find (
( country ) = > country . key === businessInfo . location
) ;
setStoreCountry (
previouslyStoredCountryOption || { key : '' , label : '' }
) ;
}
} , [ businessInfo . location , countries , isStoreCountrySet ] ) ;
const [ geolocationMatch , setGeolocationMatch ] = useState ( {
key : '' ,
label : '' ,
} ) ;
useEffect ( ( ) = > {
if ( geolocatedLocation ) {
const foundCountryOption = findCountryOption (
countries ,
geolocatedLocation
) ;
if ( foundCountryOption ) {
setGeolocationMatch ( foundCountryOption ) ;
if ( ! isStoreCountrySet ) {
setStoreCountry ( foundCountryOption ) ;
2023-04-24 02:08:24 +00:00
}
2023-05-30 07:05:38 +00:00
}
}
} , [ countries , isStoreCountrySet , geolocatedLocation ] ) ;
const geolocationOverruled =
geolocatedLocation &&
getCountry ( storeCountry . key ) !== getCountry ( geolocationMatch . key ) ;
const [ industry , setIndustry ] = useState <
IndustryChoiceOption | undefined
> (
industryFromOnboardingProfile
? industryChoices . find (
( choice ) = >
choice . key === industryFromOnboardingProfile [ 0 ]
)
: undefined
) ;
const selectCountryLabel = __ ( 'Select country/region' , 'woocommerce' ) ;
const selectIndustryQuestionLabel =
selectIndustryMapping [
2023-06-15 19:45:54 +00:00
businessChoice ||
businessChoiceFromOnboardingProfile ||
'im_just_starting_my_business'
2023-05-30 07:05:38 +00:00
] ;
const [ dismissedGeolocationNotice , setDismissedGeolocationNotice ] =
useState ( false ) ;
2023-06-19 05:33:28 +00:00
const [ hasSubmitted , setHasSubmitted ] = useState ( false ) ;
2023-11-01 06:07:47 +00:00
const [ isEmailInvalid , setIsEmailInvalid ] = useState ( false ) ;
2023-11-01 04:42:16 +00:00
2023-10-24 15:07:26 +00:00
const [ storeEmailAddress , setEmailAddress ] = useState (
storeEmailAddressFromOnboardingProfile || currentUserEmail || ''
) ;
const [ isOptInMarketing , setIsOptInMarketing ] = useState < boolean > (
isOptInMarketingFromOnboardingProfile || false
) ;
2023-11-01 04:42:16 +00:00
const [ doValidate , setDoValidate ] = useState ( false ) ;
useEffect ( ( ) = > {
2023-11-01 06:07:47 +00:00
if ( doValidate ) {
2023-11-01 04:42:16 +00:00
const parseEmail = z
. string ( )
. email ( )
. safeParse ( storeEmailAddress ) ;
2023-11-01 06:07:47 +00:00
setIsEmailInvalid ( isOptInMarketing && ! parseEmail . success ) ;
2023-11-01 04:42:16 +00:00
setDoValidate ( false ) ;
}
} , [ isOptInMarketing , doValidate , storeEmailAddress ] ) ;
2023-05-30 07:05:38 +00:00
return (
< div
className = "woocommerce-profiler-business-information"
data - testid = "core-profiler-business-information"
>
< Navigation percentage = { navigationProgress } / >
< div className = "woocommerce-profiler-page__content woocommerce-profiler-business-information__content" >
< Heading
className = "woocommerce-profiler__stepper-heading"
title = { __ (
'Tell us a bit about your store' ,
'woocommerce'
) }
subTitle = { __ (
"We'll use this information to help you set up payments, shipping, and taxes, as well as recommending the best theme for your store." ,
'woocommerce'
) }
/ >
< form
className = "woocommerce-profiler-business-information-form"
autoComplete = "off"
>
< TextControl
className = "woocommerce-profiler-business-info-store-name"
onChange = { ( value ) = > {
setStoreName ( value ) ;
} }
2023-06-19 04:58:16 +00:00
value = { decodeEntities ( storeName ) }
2023-05-30 07:05:38 +00:00
label = {
< >
{ __ (
'Give your store a name' ,
'woocommerce'
) }
< / >
}
placeholder = { __ (
'Ex. My awesome store' ,
'woocommerce'
) }
/ >
< p className = "woocommerce-profiler-question-subtext" >
{ __ (
"Don't worry — you can always change it later!" ,
'woocommerce'
) }
< / p >
< p className = "woocommerce-profiler-question-label" >
{ selectIndustryQuestionLabel }
< / p >
< SelectControl
className = "woocommerce-profiler-select-control__industry"
instanceId = { 1 }
placeholder = { __ (
'Select an industry' ,
'woocommerce'
) }
label = { __ ( 'Select an industry' , 'woocommerce' ) }
options = { industryChoices }
excludeSelectedOptions = { false }
help = { < Icon icon = { chevronDown } / > }
onChange = { (
2023-05-30 23:45:10 +00:00
results : Array <
( typeof industryChoices ) [ number ]
>
2023-05-30 07:05:38 +00:00
) = > {
if ( results . length ) {
setIndustry ( results [ 0 ] ) ;
}
} }
selected = { industry ? [ industry ] : [ ] }
showAllOnFocus
isSearchable
/ >
< p className = "woocommerce-profiler-question-label" >
{ __ ( 'Where is your store located?' , 'woocommerce' ) }
< span className = "woocommerce-profiler-question-required" >
{ '*' }
< / span >
< / p >
< SelectControl
className = "woocommerce-profiler-select-control__country"
instanceId = { 2 }
placeholder = { selectCountryLabel }
label = {
storeCountry . key === '' ? selectCountryLabel : ''
}
getSearchExpression = { ( query : string ) = > {
return new RegExp (
'(^' + query + '| — (' + query + '))' ,
'i'
) ;
} }
options = { countries }
excludeSelectedOptions = { false }
help = { < Icon icon = { chevronDown } / > }
onChange = { ( results : Array < CountryStateOption > ) = > {
if ( results . length ) {
setStoreCountry ( results [ 0 ] ) ;
}
} }
selected = { storeCountry ? [ storeCountry ] : [ ] }
showAllOnFocus
isSearchable
/ >
2024-06-13 04:42:30 +00:00
{ countries . length === 0 && (
< Notice
className = "woocommerce-profiler-select-control__country-error"
isDismissible = { false }
status = "error"
>
{ createInterpolateElement (
__ (
'Oops! We encountered a problem while fetching the list of countries to choose from. <retryButton/> or <skipButton/>' ,
'woocommerce'
) ,
{
retryButton : (
< Button
onClick = { ( ) = > {
sendEvent ( {
type : 'RETRY_PRE_BUSINESS_INFO' ,
} ) ;
} }
variant = "tertiary"
>
{ __ (
'Please try again' ,
'woocommerce'
) }
< / Button >
) ,
skipButton : (
< Button
onClick = { ( ) = > {
sendEvent ( {
type : 'SKIP_BUSINESS_INFO_STEP' ,
} ) ;
} }
variant = "tertiary"
>
{ __ (
'skip this step' ,
'woocommerce'
) }
< / Button >
) ,
}
) }
< / Notice >
) }
2023-05-30 07:05:38 +00:00
{ /* woocommerce-profiler-select-control__country-spacer exists purely because the select-control above has an unremovable and unstyleable div and that's preventing margin collapse */ }
< div className = "woocommerce-profiler-select-control__country-spacer" / >
{ geolocationOverruled && ! dismissedGeolocationNotice && (
< Notice
className = "woocommerce-profiler-geolocation-notice"
onRemove = { ( ) = >
setDismissedGeolocationNotice ( true )
}
status = "warning"
>
< p >
{ createInterpolateElement (
__ (
// translators: first tag is filled with the country name detected by geolocation, second tag is the country name selected by the user
"It looks like you're located in <geolocatedCountry></geolocatedCountry>. Are you sure you want to create a store in <selectedCountry></selectedCountry>?" ,
'woocommerce'
) ,
{
geolocatedCountry : (
< Button
className = "geolocation-notice-geolocated-country"
variant = "link"
onClick = { ( ) = >
setStoreCountry (
geolocationMatch
)
}
>
{
geolocatedLocation ? . country_long
}
< / Button >
) ,
selectedCountry : (
< span className = "geolocation-notice-selected-country" >
{ storeCountry . label }
< / span >
) ,
}
) }
< / p >
< p >
{ __ (
'Setting up your store in the wrong country may lead to the following issues: ' ,
'woocommerce'
) }
< / p >
< ul className = "woocommerce-profiler-geolocation-notice__list" >
< li >
{ __ (
'Tax and duty obligations' ,
'woocommerce'
) }
< / li >
< li >
{ __ ( 'Payment issues' , 'woocommerce' ) }
< / li >
< li >
{ __ ( 'Shipping issues' , 'woocommerce' ) }
< / li >
< / ul >
< / Notice >
) }
2023-11-21 10:50:39 +00:00
{
2023-10-24 15:07:26 +00:00
< >
< TextControl
2024-05-31 03:49:36 +00:00
className = { clsx (
2023-11-01 04:42:16 +00:00
'woocommerce-profiler-business-info-email-adddress' ,
2023-11-01 06:07:47 +00:00
{ 'is-error' : isEmailInvalid }
2023-11-01 04:42:16 +00:00
) }
2023-10-24 15:07:26 +00:00
onChange = { ( value ) = > {
2023-11-01 06:07:47 +00:00
if ( isEmailInvalid ) {
2023-11-01 04:42:16 +00:00
setDoValidate ( true ) ; // trigger validation as we want to feedback to the user as soon as it becomes valid
}
2023-10-24 15:07:26 +00:00
setEmailAddress ( value ) ;
} }
2023-11-01 04:42:16 +00:00
onBlur = { ( ) = > {
setDoValidate ( true ) ;
} }
2023-10-24 15:07:26 +00:00
value = { decodeEntities ( storeEmailAddress ) }
label = {
< >
{ __ (
'Your email address' ,
'woocommerce'
) }
{ isOptInMarketing && (
< span className = "woocommerce-profiler-question-required" >
{ '*' }
< / span >
) }
< / >
}
placeholder = { __ (
'wordpress@example.com' ,
'woocommerce'
) }
/ >
2023-11-01 06:07:47 +00:00
{ isEmailInvalid && (
2023-11-01 04:42:16 +00:00
< FormInputValidation
isError
text = { __ (
'This email is not valid.' ,
'woocommerce'
) }
/ >
) }
2023-10-24 15:07:26 +00:00
< CheckboxControl
className = "core-profiler__checkbox"
label = { __ (
'Opt-in to receive tips, discounts, and recommendations from the Woo team directly in your inbox.' ,
'woocommerce'
) }
checked = { isOptInMarketing }
2023-11-01 04:42:16 +00:00
onChange = { ( isChecked ) = > {
setIsOptInMarketing ( isChecked ) ;
2023-11-01 06:07:47 +00:00
setDoValidate ( true ) ;
2023-11-01 04:42:16 +00:00
} }
2023-10-24 15:07:26 +00:00
/ >
< / >
2023-11-21 10:50:39 +00:00
}
2023-05-30 07:05:38 +00:00
< / form >
< div className = "woocommerce-profiler-button-container" >
< Button
className = "woocommerce-profiler-button"
variant = "primary"
2023-11-21 10:50:39 +00:00
disabled = { ! storeCountry . key || isEmailInvalid }
2023-05-30 07:05:38 +00:00
onClick = { ( ) = > {
sendEvent ( {
type : 'BUSINESS_INFO_COMPLETED' ,
payload : {
storeName ,
industry : industry?.key ,
storeLocation : storeCountry.key ,
geolocationOverruled :
geolocationOverruled || false ,
2023-10-24 15:07:26 +00:00
isOptInMarketing ,
storeEmailAddress ,
2023-05-30 07:05:38 +00:00
} ,
} ) ;
2023-06-19 05:33:28 +00:00
setHasSubmitted ( true ) ;
2023-05-30 07:05:38 +00:00
} }
>
2023-06-19 05:33:28 +00:00
{ hasSubmitted ? (
< Spinner / >
) : (
__ ( 'Continue' , 'woocommerce' )
) }
2023-05-30 07:05:38 +00:00
< / Button >
< / div >
< / div >
< / div >
2023-04-24 02:08:24 +00:00
) ;
} ;