Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Shipping improvements - Phase 3 #2757

Merged
merged 29 commits into from
Jan 13, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
315f77d
Remove the `hideTaxRates` prop from the `SetupFreeListings` component…
eason9487 Dec 20, 2024
630d1f3
Remove the tax rate setting from the `SetupFreeListings` component an…
eason9487 Dec 20, 2024
ad50a3c
Remove the validation error message from `TaxRate` component and disa…
eason9487 Jan 2, 2025
14e7070
Adjust `TaxRate` component to render children within its section wrap…
eason9487 Jan 2, 2025
e14ab9f
Add a new component to save and sync the tax rate setup individually.
eason9487 Jan 2, 2025
3e59f95
Add the tax rate setup to the Settings page.
eason9487 Jan 2, 2025
fdd48bd
Remove unused `ConditionalSection` component.
eason9487 Jan 2, 2025
1eda2d7
Update E2E tests.
eason9487 Jan 3, 2025
1132178
Update the description for a test case in `tests/e2e/specs/settings/s…
eason9487 Jan 6, 2025
fdae02a
Add the confirmation notification after the tax rate setting is succe…
eason9487 Jan 6, 2025
7166ec6
Merge pull request #2749 from woocommerce/update/move-tax-setting
eason9487 Jan 6, 2025
b47496c
Rename `js/src/pages/edit-free-listings` to `js/src/pages/shipping`.
eason9487 Jan 3, 2025
f86fcc7
Convert the `Shipping` from subpage component to page component.
eason9487 Jan 3, 2025
4c37856
Move the use of `Hero` out from `SetupFreeListings` component and mo…
eason9487 Jan 3, 2025
af87514
Lift the `Hero`, stepper wrappers, and submit button within `SetupFre…
eason9487 Jan 3, 2025
bccc6c4
Remove the edit button of free listings from the dashboard page.
eason9487 Jan 6, 2025
43d27ec
Add the dedicated shipping settings page.
eason9487 Jan 6, 2025
1fc72e0
Update E2E tests.
eason9487 Jan 6, 2025
49159c6
Merge pull request #2751 from woocommerce/update/dedicated-shipping-s…
eason9487 Jan 7, 2025
4c02429
Clean up the leftover parameters used to support Woo Navigation
eason9487 Jan 7, 2025
0cd9186
Merge pull request #2753 from woocommerce/dev/clean-up-woo-nav-leftover
eason9487 Jan 8, 2025
6a2b8e4
Merge branch 'develop' into feature/shipping-improvements
eason9487 Jan 8, 2025
f6df78c
Add a new prop to `SetupFreeListings` to determine whether to submit …
eason9487 Jan 9, 2025
5b50d43
Add the `ConfirmSaveModal` component.
eason9487 Jan 9, 2025
2f3308c
Pop up the confirmation prompt on the shipping page before saving cha…
eason9487 Jan 9, 2025
c530806
Update E2E tests.
eason9487 Jan 9, 2025
e2684ff
Merge pull request #2756 from woocommerce/tweak/confirmation-before-s…
eason9487 Jan 13, 2025
d05d56e
Merge branch 'develop' into feature/shipping-improvements
eason9487 Jan 13, 2025
6dd960b
Update `src/Tracking/README.md`.
eason9487 Jan 13, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading