Update onboarding form validation (https://github.com/woocommerce/woocommerce-admin/pull/2617)
* Update store details form validation * Add new form validation to business details step * Add new form validation to industry step * Add new form validation to product types step
This commit is contained in:
parent
de005798df
commit
60a1541e48
|
@ -27,12 +27,15 @@ class BusinessDetails extends Component {
|
|||
super();
|
||||
|
||||
this.state = {
|
||||
other_platform: '',
|
||||
product_count: '',
|
||||
selling_venues: '',
|
||||
extensions: {
|
||||
facebook: true,
|
||||
mailchimp: true,
|
||||
errors: {},
|
||||
fields: {
|
||||
other_platform: '',
|
||||
product_count: '',
|
||||
selling_venues: '',
|
||||
extensions: {
|
||||
facebook: true,
|
||||
mailchimp: true,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
|
@ -40,23 +43,28 @@ class BusinessDetails extends Component {
|
|||
}
|
||||
|
||||
async onContinue() {
|
||||
await this.validateForm();
|
||||
if ( Object.keys( this.state.errors ).length ) {
|
||||
return;
|
||||
}
|
||||
|
||||
const { addNotice, goToNextStep, isError, updateProfileItems } = this.props;
|
||||
const { other_platform, product_count, selling_venues } = this.state;
|
||||
const extensions = keys( pickBy( this.state.extensions ) );
|
||||
const { extensions, other_platform, product_count, selling_venues } = this.state.fields;
|
||||
const businessExtensions = keys( pickBy( extensions ) );
|
||||
|
||||
recordEvent( 'storeprofiler_store_business_details_continue', {
|
||||
product_number: product_count,
|
||||
already_selling: 'no' !== selling_venues,
|
||||
used_platform: other_platform,
|
||||
install_facebook: this.state.extensions.facebook,
|
||||
install_mailchimp: this.state.extensions.mailchimp,
|
||||
install_facebook: extensions.facebook,
|
||||
install_mailchimp: extensions.mailchimp,
|
||||
} );
|
||||
|
||||
await updateProfileItems( {
|
||||
other_platform,
|
||||
product_count,
|
||||
selling_venues,
|
||||
business_extensions: extensions,
|
||||
business_extensions: businessExtensions,
|
||||
} );
|
||||
|
||||
if ( ! isError ) {
|
||||
|
@ -69,17 +77,37 @@ class BusinessDetails extends Component {
|
|||
}
|
||||
}
|
||||
|
||||
isValidForm() {
|
||||
const { other_platform, product_count, selling_venues } = this.state;
|
||||
const other_platform_valid = [ 'other', 'brick-mortar-other' ].includes( selling_venues )
|
||||
? other_platform.length
|
||||
: true;
|
||||
validateField( name ) {
|
||||
const { errors, fields } = this.state;
|
||||
|
||||
if ( other_platform_valid && product_count.length && selling_venues.length ) {
|
||||
return true;
|
||||
switch ( name ) {
|
||||
case 'extensions':
|
||||
break;
|
||||
case 'other_platform':
|
||||
errors.other_platform =
|
||||
[ 'other', 'brick-mortar-other' ].includes( fields.selling_venues ) &&
|
||||
! fields.other_platform.length
|
||||
? __( 'This field is required', 'woocommerce-admin' )
|
||||
: null;
|
||||
break;
|
||||
default:
|
||||
errors[ name ] = ! fields[ name ].length
|
||||
? __( 'This field is required', 'woocommerce-admin' )
|
||||
: null;
|
||||
break;
|
||||
}
|
||||
|
||||
return false;
|
||||
this.setState( { errors: pickBy( errors ) } );
|
||||
}
|
||||
|
||||
updateValue( name, value ) {
|
||||
const fields = { ...this.state.fields, [ name ]: value };
|
||||
this.setState( { fields }, () => this.validateField( name ) );
|
||||
}
|
||||
|
||||
async validateForm() {
|
||||
const { fields } = this.state;
|
||||
Object.keys( fields ).forEach( fieldName => this.validateField( fieldName ) );
|
||||
}
|
||||
|
||||
getNumberRangeString( min, max = false ) {
|
||||
|
@ -98,18 +126,23 @@ class BusinessDetails extends Component {
|
|||
}
|
||||
|
||||
setDefaultValue( key, options ) {
|
||||
if ( ! this.state[ key ].length ) {
|
||||
this.setState( { [ key ]: options[ 0 ].value } );
|
||||
const { fields } = this.state;
|
||||
|
||||
if ( ! fields[ key ].length ) {
|
||||
this.setState( { fields: { ...fields, [ key ]: options[ 0 ].value } }, () =>
|
||||
this.validateField( key )
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
onExtensionChange( extension ) {
|
||||
this.setState( {
|
||||
extensions: {
|
||||
...this.state.extensions,
|
||||
[ extension ]: ! this.state.extensions[ extension ],
|
||||
},
|
||||
} );
|
||||
const { fields } = this.state;
|
||||
const extensions = {
|
||||
...fields.extensions,
|
||||
[ extension ]: ! fields.extensions[ extension ],
|
||||
};
|
||||
|
||||
this.setState( { fields: { ...fields, extensions } } );
|
||||
}
|
||||
|
||||
renderBusinessExtensionHelpText() {
|
||||
|
@ -118,7 +151,7 @@ class BusinessDetails extends Component {
|
|||
mailchimp: __( 'Mailchimp for WooCommerce', 'woocommerce-admin' ),
|
||||
};
|
||||
|
||||
const extensions = keys( pickBy( this.state.extensions ) );
|
||||
const extensions = keys( pickBy( this.state.fields.extensions ) );
|
||||
if ( 0 === extensions.length ) {
|
||||
return null;
|
||||
}
|
||||
|
@ -171,7 +204,7 @@ class BusinessDetails extends Component {
|
|||
</div>
|
||||
<div className="woocommerce-profile-wizard__benefit-toggle">
|
||||
<FormToggle
|
||||
checked={ this.state.extensions[ benefit.slug ] }
|
||||
checked={ this.state.fields.extensions[ benefit.slug ] }
|
||||
onChange={ () => this.onExtensionChange( benefit.slug ) }
|
||||
className="woocommerce-profile-wizard__toggle"
|
||||
/>
|
||||
|
@ -183,7 +216,8 @@ class BusinessDetails extends Component {
|
|||
}
|
||||
|
||||
render() {
|
||||
const { other_platform, product_count, selling_venues } = this.state;
|
||||
const { errors, fields } = this.state;
|
||||
const { other_platform, product_count, selling_venues } = fields;
|
||||
|
||||
const productCountOptions = [
|
||||
{
|
||||
|
@ -250,7 +284,7 @@ class BusinessDetails extends Component {
|
|||
];
|
||||
|
||||
// Show extensions when the currently selling elsewhere checkbox has been answered.
|
||||
const showExtensions = '' !== this.state.selling_venues;
|
||||
const showExtensions = '' !== selling_venues;
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
|
@ -262,29 +296,35 @@ class BusinessDetails extends Component {
|
|||
<Card className="woocommerce-profile-wizard__product-types-card">
|
||||
<SelectControl
|
||||
label={ __( 'How many products will you add?', 'woocommerce-admin' ) }
|
||||
onChange={ value => this.setState( { product_count: value } ) }
|
||||
onChange={ value => this.updateValue( 'product_count', value ) }
|
||||
onFocus={ this.setDefaultValue.bind( this, 'product_count', productCountOptions ) }
|
||||
options={ productCountOptions }
|
||||
value={ product_count }
|
||||
help={ errors.product_count }
|
||||
className={ errors.product_count ? 'has-error' : null }
|
||||
required
|
||||
/>
|
||||
|
||||
<SelectControl
|
||||
label={ __( 'Currently selling elsewhere?', 'woocommerce-admin' ) }
|
||||
onChange={ value => this.setState( { selling_venues: value } ) }
|
||||
onChange={ value => this.updateValue( 'selling_venues', value ) }
|
||||
onFocus={ this.setDefaultValue.bind( this, 'selling_venues', sellingVenueOptions ) }
|
||||
options={ sellingVenueOptions }
|
||||
value={ selling_venues }
|
||||
help={ errors.selling_venues }
|
||||
className={ errors.selling_venues ? 'has-error' : null }
|
||||
required
|
||||
/>
|
||||
|
||||
{ [ 'other', 'brick-mortar-other' ].includes( selling_venues ) && (
|
||||
<SelectControl
|
||||
label={ __( 'Which platform is the store using?', 'woocommerce-admin' ) }
|
||||
onChange={ value => this.setState( { other_platform: value } ) }
|
||||
onChange={ value => this.updateValue( 'other_platform', value ) }
|
||||
onFocus={ this.setDefaultValue.bind( this, 'other_platform', otherPlatformOptions ) }
|
||||
options={ otherPlatformOptions }
|
||||
value={ other_platform }
|
||||
help={ errors.other_platform }
|
||||
className={ errors.other_platform ? 'has-error' : null }
|
||||
required
|
||||
/>
|
||||
) }
|
||||
|
@ -295,7 +335,6 @@ class BusinessDetails extends Component {
|
|||
isPrimary
|
||||
className="woocommerce-profile-wizard__continue"
|
||||
onClick={ this.onContinue }
|
||||
disabled={ ! this.isValidForm() }
|
||||
>
|
||||
{ __( 'Continue', 'woocommerce-admin' ) }
|
||||
</Button>
|
||||
|
|
|
@ -20,6 +20,7 @@ class Industry extends Component {
|
|||
constructor() {
|
||||
super();
|
||||
this.state = {
|
||||
error: null,
|
||||
selected: [],
|
||||
};
|
||||
this.onContinue = this.onContinue.bind( this );
|
||||
|
@ -27,6 +28,11 @@ class Industry extends Component {
|
|||
}
|
||||
|
||||
async onContinue() {
|
||||
await this.validateField();
|
||||
if ( this.state.error ) {
|
||||
return;
|
||||
}
|
||||
|
||||
const { addNotice, goToNextStep, isError, updateProfileItems } = this.props;
|
||||
|
||||
recordEvent( 'storeprofiler_store_industry_continue', { store_industry: this.state.selected } );
|
||||
|
@ -42,27 +48,37 @@ class Industry extends Component {
|
|||
}
|
||||
}
|
||||
|
||||
async validateField() {
|
||||
const error = this.state.selected.length
|
||||
? null
|
||||
: __( 'Please select at least one industry', 'woocommerce-admin' );
|
||||
this.setState( { error } );
|
||||
}
|
||||
|
||||
onChange( slug ) {
|
||||
this.setState( state => {
|
||||
if ( includes( state.selected, slug ) ) {
|
||||
this.setState(
|
||||
state => {
|
||||
if ( includes( state.selected, slug ) ) {
|
||||
return {
|
||||
selected:
|
||||
filter( state.selected, value => {
|
||||
return value !== slug;
|
||||
} ) || [],
|
||||
};
|
||||
}
|
||||
const newSelected = state.selected;
|
||||
newSelected.push( slug );
|
||||
return {
|
||||
selected:
|
||||
filter( state.selected, value => {
|
||||
return value !== slug;
|
||||
} ) || [],
|
||||
selected: newSelected,
|
||||
};
|
||||
}
|
||||
const newSelected = state.selected;
|
||||
newSelected.push( slug );
|
||||
return {
|
||||
selected: newSelected,
|
||||
};
|
||||
} );
|
||||
},
|
||||
() => this.validateField()
|
||||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
const { industries } = wcSettings.onboarding;
|
||||
const { selected } = this.state;
|
||||
const { error } = this.state;
|
||||
return (
|
||||
<Fragment>
|
||||
<H className="woocommerce-profile-wizard__header-title">
|
||||
|
@ -82,13 +98,12 @@ class Industry extends Component {
|
|||
/>
|
||||
);
|
||||
} ) }
|
||||
{ error && <span className="woocommerce-profile-wizard__error">{ error }</span> }
|
||||
</div>
|
||||
|
||||
{ selected.length > 0 && (
|
||||
<Button isPrimary onClick={ this.onContinue }>
|
||||
{ __( 'Continue', 'woocommerce-admin' ) }
|
||||
</Button>
|
||||
) }
|
||||
<Button isPrimary onClick={ this.onContinue }>
|
||||
{ __( 'Continue', 'woocommerce-admin' ) }
|
||||
</Button>
|
||||
</Card>
|
||||
</Fragment>
|
||||
);
|
||||
|
|
|
@ -22,6 +22,7 @@ class ProductTypes extends Component {
|
|||
super();
|
||||
|
||||
this.state = {
|
||||
error: null,
|
||||
selected: [],
|
||||
};
|
||||
|
||||
|
@ -29,7 +30,19 @@ class ProductTypes extends Component {
|
|||
this.onChange = this.onChange.bind( this );
|
||||
}
|
||||
|
||||
async validateField() {
|
||||
const error = this.state.selected.length
|
||||
? null
|
||||
: __( 'Please select at least one product type', 'woocommerce-admin' );
|
||||
this.setState( { error } );
|
||||
}
|
||||
|
||||
async onContinue() {
|
||||
await this.validateField();
|
||||
if ( this.state.error ) {
|
||||
return;
|
||||
}
|
||||
|
||||
const { addNotice, goToNextStep, isError, updateProfileItems } = this.props;
|
||||
|
||||
recordEvent( 'storeprofiler_store_product_type_continue', {
|
||||
|
@ -48,21 +61,24 @@ class ProductTypes extends Component {
|
|||
}
|
||||
|
||||
onChange( slug ) {
|
||||
this.setState( state => {
|
||||
if ( includes( state.selected, slug ) ) {
|
||||
this.setState(
|
||||
state => {
|
||||
if ( includes( state.selected, slug ) ) {
|
||||
return {
|
||||
selected:
|
||||
filter( state.selected, value => {
|
||||
return value !== slug;
|
||||
} ) || [],
|
||||
};
|
||||
}
|
||||
const newSelected = state.selected;
|
||||
newSelected.push( slug );
|
||||
return {
|
||||
selected:
|
||||
filter( state.selected, value => {
|
||||
return value !== slug;
|
||||
} ) || [],
|
||||
selected: newSelected,
|
||||
};
|
||||
}
|
||||
const newSelected = state.selected;
|
||||
newSelected.push( slug );
|
||||
return {
|
||||
selected: newSelected,
|
||||
};
|
||||
} );
|
||||
},
|
||||
() => this.validateField()
|
||||
);
|
||||
}
|
||||
|
||||
onLearnMore( slug ) {
|
||||
|
@ -71,7 +87,7 @@ class ProductTypes extends Component {
|
|||
|
||||
render() {
|
||||
const { productTypes } = wcSettings.onboarding;
|
||||
const { selected } = this.state;
|
||||
const { error } = this.state;
|
||||
return (
|
||||
<Fragment>
|
||||
<H className="woocommerce-profile-wizard__header-title">
|
||||
|
@ -111,17 +127,16 @@ class ProductTypes extends Component {
|
|||
/>
|
||||
);
|
||||
} ) }
|
||||
{ error && <span className="woocommerce-profile-wizard__error">{ error }</span> }
|
||||
</div>
|
||||
|
||||
{ selected.length > 0 && (
|
||||
<Button
|
||||
isPrimary
|
||||
className="woocommerce-profile-wizard__continue"
|
||||
onClick={ this.onContinue }
|
||||
>
|
||||
{ __( 'Continue', 'woocommerce-admin' ) }
|
||||
</Button>
|
||||
) }
|
||||
<Button
|
||||
isPrimary
|
||||
className="woocommerce-profile-wizard__continue"
|
||||
onClick={ this.onContinue }
|
||||
>
|
||||
{ __( 'Continue', 'woocommerce-admin' ) }
|
||||
</Button>
|
||||
</Card>
|
||||
</Fragment>
|
||||
);
|
||||
|
|
|
@ -7,6 +7,7 @@ import { Button, SelectControl, TextControl } from 'newspack-components';
|
|||
import { Component, Fragment } from '@wordpress/element';
|
||||
import { compose } from '@wordpress/compose';
|
||||
import { decodeEntities } from '@wordpress/html-entities';
|
||||
import { pickBy } from 'lodash';
|
||||
import { withDispatch } from '@wordpress/data';
|
||||
import { recordEvent } from 'lib/tracks';
|
||||
|
||||
|
@ -21,15 +22,19 @@ class StoreDetails extends Component {
|
|||
super( ...arguments );
|
||||
|
||||
this.state = {
|
||||
addressLine1: '',
|
||||
addressLine2: '',
|
||||
city: '',
|
||||
countryState: '',
|
||||
countryStateOptions: [],
|
||||
postCode: '',
|
||||
errors: {},
|
||||
fields: {
|
||||
addressLine1: '',
|
||||
addressLine2: '',
|
||||
city: '',
|
||||
countryState: '',
|
||||
postCode: '',
|
||||
},
|
||||
};
|
||||
|
||||
this.onContinue = this.onContinue.bind( this );
|
||||
this.updateValue = this.updateValue.bind( this );
|
||||
}
|
||||
|
||||
componentWillMount() {
|
||||
|
@ -37,23 +42,51 @@ class StoreDetails extends Component {
|
|||
this.setState( { countryStateOptions } );
|
||||
}
|
||||
|
||||
isValidForm() {
|
||||
const { addressLine1, city, countryState, postCode } = this.state;
|
||||
validateField( name ) {
|
||||
const { errors, fields } = this.state;
|
||||
|
||||
if ( addressLine1.length && city.length && countryState.length && postCode.length ) {
|
||||
return true;
|
||||
switch ( name ) {
|
||||
case 'addressLine1':
|
||||
errors.addressLine1 = fields.addressLine1.length
|
||||
? null
|
||||
: __( 'Please add an address', 'woocommerce-admin' );
|
||||
break;
|
||||
case 'countryState':
|
||||
errors.countryState = fields.countryState.length
|
||||
? null
|
||||
: __( 'Please select a country and state', 'woocommerce-admin' );
|
||||
break;
|
||||
case 'city':
|
||||
errors.city = fields.city.length ? null : __( 'Please add a city', 'woocommerce-admin' );
|
||||
break;
|
||||
case 'postCode':
|
||||
errors.postCode = fields.postCode.length
|
||||
? null
|
||||
: __( 'Please add a post code', 'woocommerce-admin' );
|
||||
break;
|
||||
}
|
||||
|
||||
return false;
|
||||
this.setState( { errors: pickBy( errors ) } );
|
||||
}
|
||||
|
||||
updateValue( name, value ) {
|
||||
const fields = { ...this.state.fields, [ name ]: value };
|
||||
this.setState( { fields }, () => this.validateField( name ) );
|
||||
}
|
||||
|
||||
async validateForm() {
|
||||
const { fields } = this.state;
|
||||
Object.keys( fields ).forEach( fieldName => this.validateField( fieldName ) );
|
||||
}
|
||||
|
||||
async onContinue() {
|
||||
if ( ! this.isValidForm() ) {
|
||||
await this.validateForm();
|
||||
if ( Object.keys( this.state.errors ).length ) {
|
||||
return;
|
||||
}
|
||||
|
||||
const { addNotice, goToNextStep, isError, updateSettings } = this.props;
|
||||
const { addressLine1, addressLine2, city, countryState, postCode } = this.state;
|
||||
const { addressLine1, addressLine2, city, countryState, postCode } = this.state.fields;
|
||||
|
||||
recordEvent( 'storeprofiler_store_details_continue', {
|
||||
store_country: countryState.split( ':' )[ 0 ],
|
||||
|
@ -110,14 +143,8 @@ class StoreDetails extends Component {
|
|||
}
|
||||
|
||||
render() {
|
||||
const {
|
||||
addressLine1,
|
||||
addressLine2,
|
||||
city,
|
||||
countryState,
|
||||
countryStateOptions,
|
||||
postCode,
|
||||
} = this.state;
|
||||
const { countryStateOptions, errors, fields } = this.state;
|
||||
const { addressLine1, addressLine2, city, countryState, postCode } = fields;
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
|
@ -131,41 +158,51 @@ class StoreDetails extends Component {
|
|||
<Card>
|
||||
<TextControl
|
||||
label={ __( 'Address line 1', 'woocommerce-admin' ) }
|
||||
onChange={ value => this.setState( { addressLine1: value } ) }
|
||||
onChange={ value => this.updateValue( 'addressLine1', value ) }
|
||||
required
|
||||
value={ addressLine1 }
|
||||
help={ errors.addressLine1 }
|
||||
className={ errors.addressLine1 ? 'has-error' : null }
|
||||
/>
|
||||
|
||||
<TextControl
|
||||
label={ __( 'Address line 2 (optional)', 'woocommerce-admin' ) }
|
||||
onChange={ value => this.setState( { addressLine2: value } ) }
|
||||
onChange={ value => this.updateValue( 'addressLine2', value ) }
|
||||
required
|
||||
value={ addressLine2 }
|
||||
help={ errors.addressLine2 }
|
||||
className={ errors.addressLine2 ? 'has-error' : null }
|
||||
/>
|
||||
|
||||
<SelectControl
|
||||
label={ __( 'Country / State', 'woocommerce-admin' ) }
|
||||
onChange={ value => this.setState( { countryState: value } ) }
|
||||
onChange={ value => this.updateValue( 'countryState', value ) }
|
||||
options={ countryStateOptions }
|
||||
value={ countryState }
|
||||
required
|
||||
help={ errors.countryState }
|
||||
className={ errors.countryState ? 'has-error' : null }
|
||||
/>
|
||||
|
||||
<TextControl
|
||||
label={ __( 'City', 'woocommerce-admin' ) }
|
||||
onChange={ value => this.setState( { city: value } ) }
|
||||
onChange={ value => this.updateValue( 'city', value ) }
|
||||
required
|
||||
value={ city }
|
||||
help={ errors.city }
|
||||
className={ errors.city ? 'has-error' : null }
|
||||
/>
|
||||
|
||||
<TextControl
|
||||
label={ __( 'Post code', 'woocommerce-admin' ) }
|
||||
onChange={ value => this.setState( { postCode: value } ) }
|
||||
onChange={ value => this.updateValue( 'postCode', value ) }
|
||||
required
|
||||
value={ postCode }
|
||||
help={ errors.postCode }
|
||||
className={ errors.postCode ? 'has-error' : null }
|
||||
/>
|
||||
|
||||
<Button isPrimary onClick={ this.onContinue } disabled={ ! this.isValidForm() }>
|
||||
<Button isPrimary onClick={ this.onContinue }>
|
||||
{ __( 'Continue', 'woocommerce-admin' ) }
|
||||
</Button>
|
||||
</Card>
|
||||
|
|
|
@ -31,10 +31,6 @@
|
|||
min-width: 310px;
|
||||
max-width: 100%;
|
||||
justify-content: center;
|
||||
|
||||
&:disabled {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -154,6 +150,29 @@
|
|||
margin-top: $gap;
|
||||
margin-bottom: $gap;
|
||||
}
|
||||
|
||||
.components-base-control.has-error {
|
||||
margin-bottom: $gap * 2 !important;
|
||||
border-color: $muriel-red-500;
|
||||
|
||||
.components-base-control__help {
|
||||
top: 100%;
|
||||
position: absolute;
|
||||
margin-top: $gap-smallest;
|
||||
font-size: 12px;
|
||||
font-style: normal;
|
||||
color: $muriel-red-500;
|
||||
}
|
||||
}
|
||||
|
||||
.woocommerce-profile-wizard__error {
|
||||
display: block;
|
||||
margin-top: $gap;
|
||||
margin-bottom: $gap;
|
||||
font-size: 12px;
|
||||
color: $muriel-red-500;
|
||||
}
|
||||
|
||||
.muriel-input-text {
|
||||
&.empty {
|
||||
.components-base-control__label {
|
||||
|
|
Loading…
Reference in New Issue