Skip to content

Commit

Permalink
Merge pull request #2757 from woocommerce/feature/shipping-improvements
Browse files Browse the repository at this point in the history
Shipping improvements - Phase 3
  • Loading branch information
eason9487 authored Jan 13, 2025
2 parents 23419b9 + 6dd960b commit d7ad271
Show file tree
Hide file tree
Showing 42 changed files with 720 additions and 540 deletions.
34 changes: 0 additions & 34 deletions js/src/components/conditional-section.js

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,6 @@ exports[`checkErrors Audience When the audience location option is an invalid va

exports[`checkErrors Audience When the audience location option is an invalid value or missing, should not pass 2`] = `"Please select a location option."`;

exports[`checkErrors For tax rate, if store country code or selected country codes include 'US' When the tax rate option is an invalid value or missing, should not pass 1`] = `"Please specify tax rate option."`;

exports[`checkErrors For tax rate, if store country code or selected country codes include 'US' When the tax rate option is an invalid value or missing, should not pass 2`] = `"Please specify tax rate option."`;

exports[`checkErrors For tax rate, if store country code or selected country codes include 'US' When the tax rate option is an invalid value or missing, should not pass 3`] = `"Please specify tax rate option."`;

exports[`checkErrors For tax rate, if store country code or selected country codes include 'US' When the tax rate option is an invalid value or missing, should not pass 4`] = `"Please specify tax rate option."`;

exports[`checkErrors Offer free shipping With flat shipping rate option When there are some non-free shipping rates, and offer free shipping is checked, and there is no minimum order amount for non-free shipping rates, should not pass 1`] = `"Please enter minimum order for free shipping."`;

exports[`checkErrors Shipping rates For flat type When there are any selected countries with shipping rates not set, should not pass 1`] = `"Please specify estimated shipping rates for all the countries, and the rate cannot be less than 0."`;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,8 @@ import { __ } from '@wordpress/i18n';
const validlocationSet = new Set( [ 'all', 'selected' ] );
const validShippingRateSet = new Set( [ 'automatic', 'flat', 'manual' ] );
const validShippingTimeSet = new Set( [ 'flat', 'manual' ] );
const validTaxRateSet = new Set( [ 'destination', 'manual' ] );

const checkErrors = (
values,
shippingTimes,
finalCountryCodes,
storeCountryCode,
hideTaxRates = false
) => {
const checkErrors = ( values, shippingTimes, finalCountryCodes ) => {
const errors = {};

// Check audience.
Expand Down Expand Up @@ -102,20 +95,6 @@ const checkErrors = (
);
}

/**
* Check tax rate (required for U.S. only).
*/
if (
! hideTaxRates &&
( storeCountryCode === 'US' || finalCountryCodes.includes( 'US' ) ) &&
! validTaxRateSet.has( values.tax_rate )
) {
errors.tax_rate = __(
'Please specify tax rate option.',
'google-listings-and-ads'
);
}

return errors;
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,6 @@ describe( 'checkErrors', () => {
countries: [ 'US', 'JP' ],
shipping_rate: 'flat',
shipping_time: 'flat',
tax_rate: 'manual',
shipping_country_rates: toRates( [ 'US', 10 ], [ 'JP', 30, 88 ] ),
offer_free_shipping: true,
};
Expand Down Expand Up @@ -436,72 +435,4 @@ describe( 'checkErrors', () => {
} );
} );
} );

describe( `For tax rate, if store country code or selected country codes include 'US'`, () => {
let codes;

beforeEach( () => {
codes = [ 'US' ];
} );

it( `When the tax rate option is an invalid value or missing, should not pass`, () => {
// Not set yet
let errors = checkErrors( defaultFormValues, [], codes );

expect( errors ).toHaveProperty( 'tax_rate' );
expect( errors.tax_rate ).toMatchSnapshot();

errors = checkErrors( defaultFormValues, [], [], 'US' );

expect( errors ).toHaveProperty( 'tax_rate' );
expect( errors.tax_rate ).toMatchSnapshot();

// Invalid value
errors = checkErrors(
{ ...defaultFormValues, tax_rate: true },
[],
codes
);

expect( errors ).toHaveProperty( 'tax_rate' );
expect( errors.tax_rate ).toMatchSnapshot();

// Invalid value
errors = checkErrors(
{ ...defaultFormValues, tax_rate: 'invalid' },
[],
codes
);

expect( errors ).toHaveProperty( 'tax_rate' );
expect( errors.tax_rate ).toMatchSnapshot();
} );

it( 'When the tax rate option is a valid value, should pass', () => {
// Selected destination
const destinationTaxRate = {
...defaultFormValues,
tax_rate: 'destination',
};

let errors = checkErrors( destinationTaxRate, [], codes );

expect( errors ).not.toHaveProperty( 'tax_rate' );

errors = checkErrors( destinationTaxRate, [], [], 'US' );

expect( errors ).not.toHaveProperty( 'tax_rate' );

// Selected manual
const manualTaxRate = { ...defaultFormValues, tax_rate: 'manual' };

errors = checkErrors( manualTaxRate, [], codes );

expect( errors ).not.toHaveProperty( 'tax_rate' );

errors = checkErrors( destinationTaxRate, [], [], 'US' );

expect( errors ).not.toHaveProperty( 'tax_rate' );
} );
} );
} );
Original file line number Diff line number Diff line change
@@ -1,76 +1,33 @@
/**
* External dependencies
*/
import { __ } from '@wordpress/i18n';

/**
* Internal dependencies
*/
import { useAdaptiveFormContext } from '~/components/adaptive-form';
import StepContent from '~/components/stepper/step-content';
import StepContentActions from '~/components/stepper/step-content-actions';
import StepContentFooter from '~/components/stepper/step-content-footer';
import TaxRate from '~/pages/settings/setup-tax-rate/tax-rate';
import useDisplayTaxRate from '~/pages/settings/setup-tax-rate/useDisplayTaxRate';
import ChooseAudienceSection from '~/components/free-listings/choose-audience-section';
import ShippingRateSection from '~/components/shipping-rate-section';
import ShippingTimeSection from '~/components/free-listings/configure-product-listings/shipping-time-section';
import AppButton from '~/components/app-button';
import ConditionalSection from '~/components/conditional-section';
import OrderValueConditionSection from '~/components/order-value-condition-section';
import isNonFreeShippingRate from '~/utils/isNonFreeShippingRate';

/**
* Form to configure free listigns.
*
* @param {Object} props React props.
* @param {string} [props.submitLabel="Complete setup"] Submit button label.
* @param {boolean} [props.hideTaxRates] Whether to hide tax rate section.
*/
const FormContent = ( {
submitLabel = __( 'Complete setup', 'google-listings-and-ads' ),
hideTaxRates,
} ) => {
const { values, isValidForm, handleSubmit, adapter } =
useAdaptiveFormContext();
const displayTaxRate = useDisplayTaxRate( adapter.audienceCountries );
const shouldDisplayTaxRate = ! hideTaxRates && displayTaxRate;
const FormContent = () => {
const { values } = useAdaptiveFormContext();

const shouldDisplayShippingTime = values.shipping_time === 'flat';
const shouldDisplayOrderValueCondition =
values.shipping_rate === 'flat' &&
values.shipping_country_rates.some( isNonFreeShippingRate );

const handleSubmitClick = ( event ) => {
if ( shouldDisplayTaxRate !== null && isValidForm ) {
return handleSubmit( event );
}

adapter.showValidation();
};

return (
<StepContent>
<>
<ChooseAudienceSection />
<ShippingRateSection />
{ shouldDisplayOrderValueCondition && (
<OrderValueConditionSection />
) }
{ shouldDisplayShippingTime && <ShippingTimeSection /> }
<ConditionalSection show={ shouldDisplayTaxRate }>
<TaxRate />
</ConditionalSection>
<StepContentFooter>
<StepContentActions>
<AppButton
isPrimary
loading={ adapter.isSubmitting }
onClick={ handleSubmitClick }
>
{ submitLabel }
</AppButton>
</StepContentActions>
</StepContentFooter>
</StepContent>
</>
);
};

Expand Down
66 changes: 44 additions & 22 deletions js/src/components/free-listings/setup-free-listings/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,15 @@
* External dependencies
*/
import { useRef } from '@wordpress/element';
import { createSlotFill } from '@wordpress/components';
import { Form } from '@woocommerce/components';
import { pick, noop } from 'lodash';

/**
* Internal dependencies
*/
import useStoreCountry from '~/hooks/useStoreCountry';
import AppSpinner from '~/components/app-spinner';
import Hero from '~/components/free-listings/configure-product-listings/hero';
import AppButton from '~/components/app-button';
import AdaptiveForm from '~/components/adaptive-form';
import ValidationErrors from '~/components/validation-errors';
import checkErrors from '~/components/free-listings/configure-product-listings/checkErrors';
Expand All @@ -32,7 +32,7 @@ const targetAudienceFields = [ 'locale', 'language', 'location', 'countries' ];
*
* If we are adding a new settings field, it should be added into this array.
*/
const settingsFieldNames = [ 'shipping_rate', 'shipping_time', 'tax_rate' ];
const settingsFieldNames = [ 'shipping_rate', 'shipping_time' ];

/**
* Get settings object from Form values.
Expand All @@ -53,9 +53,16 @@ const getSettings = ( values ) => {
return pick( values, settingsFieldNames );
};

const alwaysTrue = () => true;

const { Fill, Slot } = createSlotFill( 'gla/SetupFreeListings/SubmitButton' );

/**
* Setup step to configure free listings.
*
* Note that this component requires to specify the location where it wants to
* render its submit button via `<SetupFreeListings.SubmitButton />`.
*
* @param {Object} props
* @param {TargetAudienceData} props.targetAudience Target audience value data to be initialed the form, if not given AppSpinner will be rendered.
* @param {(targetAudience: TargetAudienceData) => Array<CountryCode>} props.resolveFinalCountries Callback for this component to resolve the given `targetAudience` to the final list of countries.
Expand All @@ -66,10 +73,9 @@ const getSettings = ( values ) => {
* @param {(newValue: Object) => void} [props.onShippingRatesChange] Callback called with new data once shipping rates are changed. Forwarded from {@link Form.Props.onChange}.
* @param {Array<ShippingTime>} props.shippingTimes Shipping times data, if not given AppSpinner will be rendered.
* @param {(newValue: Object) => void} [props.onShippingTimesChange] Callback called with new data once shipping times are changed. Forwarded from {@link Form.Props.onChange}.
* @param {() => boolean | Promise<boolean>} [props.onRequestSubmit] Callback called before the form is submitted. If it returns false, the form will not be submitted.
* @param {() => void} [props.onContinue] Callback called once continue button is clicked. Could be async. While it's being resolved the form would turn into a saving state.
* @param {string} [props.submitLabel] Submit button label, to be forwarded to `FormContent`.
* @param {JSX.Element} props.headerTitle Title in the header block of this setup.
* @param {boolean} [props.hideTaxRates=false] Whether to hide tax rate section, to be forwarded to `FormContent`.
* @param {string} props.submitLabel Submit button label.
*/
const SetupFreeListings = ( {
targetAudience,
Expand All @@ -81,13 +87,11 @@ const SetupFreeListings = ( {
onShippingRatesChange = noop,
shippingTimes,
onShippingTimesChange = noop,
onRequestSubmit = alwaysTrue,
onContinue = noop,
submitLabel,
headerTitle,
hideTaxRates = false,
} ) => {
const formRef = useRef();
const { code: storeCountryCode } = useStoreCountry();

if ( ! ( targetAudience && settings && shippingRates && shippingTimes ) ) {
return <AppSpinner />;
Expand All @@ -97,13 +101,7 @@ const SetupFreeListings = ( {
const countries = resolveFinalCountries( values );
const { shipping_country_times: shippingTimesData } = values;

return checkErrors(
values,
shippingTimesData,
countries,
storeCountryCode,
hideTaxRates
);
return checkErrors( values, shippingTimesData, countries );
};

const handleChange = ( change, values ) => {
Expand Down Expand Up @@ -208,7 +206,6 @@ const SetupFreeListings = ( {

return (
<div className="gla-setup-free-listings">
<Hero headerTitle={ headerTitle } />
<AdaptiveForm
ref={ formRef }
initialValues={ {
Expand All @@ -220,7 +217,6 @@ const SetupFreeListings = ( {
// These are the fields for settings.
shipping_rate: settings.shipping_rate,
shipping_time: settings.shipping_time,
tax_rate: settings.tax_rate,
// This is used in UI only, not used in API.
offer_free_shipping:
getOfferFreeShippingInitialValue( shippingRates ),
Expand All @@ -233,13 +229,39 @@ const SetupFreeListings = ( {
validate={ handleValidate }
onSubmit={ onContinue }
>
<FormContent
submitLabel={ submitLabel }
hideTaxRates={ hideTaxRates }
/>
{ ( formContext ) => {
const { isValidForm, handleSubmit, adapter } = formContext;
const handleSubmitClick = async ( event ) => {
if ( isValidForm ) {
if ( ! ( await onRequestSubmit() ) ) {
return;
}
return handleSubmit( event );
}

adapter.showValidation();
};

return (
<>
<FormContent />
<Fill>
<AppButton
isPrimary
loading={ adapter.isSubmitting }
onClick={ handleSubmitClick }
>
{ submitLabel }
</AppButton>
</Fill>
</>
);
} }
</AdaptiveForm>
</div>
);
};

SetupFreeListings.SubmitButton = Slot;

export default SetupFreeListings;
Loading

0 comments on commit d7ad271

Please sign in to comment.