Skip to content
This repository has been archived by the owner on Feb 23, 2024. It is now read-only.

Display the link to add the shipping address when shipping address is not available #8141

Merged
merged 34 commits into from
Mar 22, 2023
Merged
Show file tree
Hide file tree
Changes from 28 commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
52f902c
Remove hardcoded notices & change display shipping calculator condition
Jan 23, 2023
14bd6e1
Merge branch 'trunk' into add/8027-shipping-address-link
Jan 26, 2023
bfe1986
Fix the label of the calculator button and TypeScript
Jan 27, 2023
3c5870f
Merge branch 'trunk' into add/8027-shipping-address-link
Jan 30, 2023
0b0d618
Change the link style and fix editor issue
Jan 30, 2023
33f7c2d
Prevent default for the add address link
Feb 4, 2023
00d9526
Remove hardcoded notices & change display shipping calculator codition
Jan 23, 2023
0853d6c
Fix the label of the calculator button and TypeScript
Jan 27, 2023
e7b13d8
Change the link style and fix editor issue
Jan 30, 2023
140f9c2
Prevent default for the add address link
Feb 4, 2023
9cddc47
Add error notice when no shipping rates available for the given address
Feb 20, 2023
65ff5b1
Merge branch 'trunk' into add/8027-shipping-address-link
Feb 20, 2023
2957079
Merge remote-tracking branch 'origin/add/8027-shipping-address-link' …
Feb 20, 2023
c5242c6
Remove hasRates prop from shipping placeholder
Feb 20, 2023
6f1c528
Replace Notice with text message in Cart
Feb 21, 2023
c1c2d92
Merge branch 'trunk' into add/8027-shipping-address-link
Feb 21, 2023
c4dcdf1
Add margin-top to shipping rate selector on Cart block
Feb 21, 2023
a82b40a
Add unit cases for isAddressComplete and formatShippingAddress functions
Feb 24, 2023
9c120e0
Merge branch 'trunk' into add/8027-shipping-address-link
Feb 24, 2023
2d239ca
Update assets/js/base/utils/test/address.ts
Feb 27, 2023
cd63d77
Update assets/js/base/components/cart-checkout/totals/shipping/shippi…
Feb 27, 2023
fb385aa
Update assets/js/base/utils/test/address.ts
Feb 27, 2023
5bef454
Update assets/js/base/utils/address.ts
Feb 27, 2023
163eff1
Update assets/js/base/utils/address.ts
Feb 27, 2023
4525d35
Update assets/js/base/utils/test/address.ts
Feb 27, 2023
3d7e976
Update assets/js/base/utils/test/address.ts
Feb 27, 2023
45f9199
Fix typo in unit test data
Feb 28, 2023
d52f937
Remove postcode from isAddressComplete check
Feb 28, 2023
3b1e4c8
Add unit tests for shipping rate calculator (#8621)
Mar 13, 2023
c475033
Merge branch 'trunk' into add/8027-shipping-address-link
Mar 13, 2023
6fc2740
Merge branch 'trunk' into add/8027-shipping-address-link
Mar 16, 2023
7a13f4f
Fix linting error
Mar 16, 2023
1922c03
Display correct message in checkout when address shipping is complete
Mar 16, 2023
62378fb
Merge branch 'trunk' into add/8027-shipping-address-link
Mar 22, 2023
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
Original file line number Diff line number Diff line change
Expand Up @@ -2,56 +2,15 @@
* External dependencies
*/
import { __, sprintf } from '@wordpress/i18n';
import { ShippingAddress, getSetting } from '@woocommerce/settings';
import { decodeEntities } from '@wordpress/html-entities';

interface ShippingLocationProps {
address: ShippingAddress;
formattedLocation: string | null;
}

/**
* Shows a formatted shipping location.
*
* @param {Object} props Incoming props for the component.
* @param {Object} props.address Incoming address information.
*/
// Shows a formatted shipping location.
const ShippingLocation = ( {
address,
formattedLocation,
}: ShippingLocationProps ): JSX.Element | null => {
// we bail early if we don't have an address.
if ( Object.values( address ).length === 0 ) {
return null;
}
const shippingCountries = getSetting( 'shippingCountries', {} ) as Record<
string,
string
>;
const shippingStates = getSetting( 'shippingStates', {} ) as Record<
string,
Record< string, string >
>;
const formattedCountry =
typeof shippingCountries[ address.country ] === 'string'
? decodeEntities( shippingCountries[ address.country ] )
: '';

const formattedState =
typeof shippingStates[ address.country ] === 'object' &&
typeof shippingStates[ address.country ][ address.state ] === 'string'
? decodeEntities(
shippingStates[ address.country ][ address.state ]
)
: address.state;

const addressParts = [];

addressParts.push( address.postcode.toUpperCase() );
addressParts.push( address.city );
addressParts.push( formattedState );
addressParts.push( formattedCountry );

const formattedLocation = addressParts.filter( Boolean ).join( ', ' );

if ( ! formattedLocation ) {
return null;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,15 +15,20 @@ export const CalculatorButton = ( {
setIsShippingCalculatorOpen,
}: CalculatorButtonProps ): JSX.Element => {
return (
<button
className="wc-block-components-totals-shipping__change-address-button"
onClick={ () => {
<a
role="button"
href="#wc-block-components-shipping-calculator-address__link"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This causes the URL in the browser to update when clicking. It is unclear to me why we have to show a link instead of a button when rates are not available, what is the specific reason for showing different elements?

To avoid the URL change you can pass the event as a parameter of the onClick handler, and call .preventDefault() on it, like so:

onClick={ ( e ) => {
  e.preventDefault();
  setIsShippingCalculatorOpen( ! isShippingCalculatorOpen );
} }

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, I missed adding the e.preventDefault();. Thank you, I have updated the code.

It is unclear to me why we have to show a link instead of a button when rates are not available, what is the specific reason for showing different elements?

It's because of the styling of the elements. I kept the original button for the conditions where we are displaying the calculate link.
For others, I'm displaying the Add an address link to match the style with the coupon code link.

I tried using the link at both places but then the calculate link gets the theme color which was taking too much attention. So I decided to keep the original style and elements for it. Please let me know if there is a better way to tackle it.

className="wc-block-components-totals-shipping__change-address__link"
id="wc-block-components-totals-shipping__change-address__link"
onClick={ ( e ) => {
e.preventDefault();
setIsShippingCalculatorOpen( ! isShippingCalculatorOpen );
} }
aria-label={ label }
aria-expanded={ isShippingCalculatorOpen }
>
{ label }
</button>
</a>
);
};

Expand Down
37 changes: 22 additions & 15 deletions assets/js/base/components/cart-checkout/totals/shipping/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import type { Currency } from '@woocommerce/price-format';
import { ShippingVia } from '@woocommerce/base-components/cart-checkout/totals/shipping/shipping-via';
import { useSelect } from '@wordpress/data';
import { CHECKOUT_STORE_KEY } from '@woocommerce/block-data';
import { isAddressComplete } from '@woocommerce/base-utils';

/**
* Internal dependencies
Expand Down Expand Up @@ -67,6 +68,8 @@ export const TotalsShipping = ( {
}
);

const addressComplete = isAddressComplete( shippingAddress );

return (
<div
className={ classnames(
Expand All @@ -77,23 +80,26 @@ export const TotalsShipping = ( {
<TotalsItem
label={ __( 'Shipping', 'woo-gutenberg-products-block' ) }
value={
hasRates && cartHasCalculatedShipping ? (
totalShippingValue
) : (
<ShippingPlaceholder
showCalculator={ showCalculator }
isCheckout={ isCheckout }
isShippingCalculatorOpen={
isShippingCalculatorOpen
}
setIsShippingCalculatorOpen={
setIsShippingCalculatorOpen
}
/>
)
hasRates && cartHasCalculatedShipping
? totalShippingValue
: // if address is not complete, display the link to add an address.
! addressComplete && (
<ShippingPlaceholder
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@tarunvijwani, on the Checkout Block page, we would like to show the "ShippingPlaceholder" component when there are no shipping methods available (i.e., hasRates && cartHasCalculatedShipping). But, with this additional condition (i.e., ! addressComplete &&), the Shipping placeholder will never be displayed if the address is complete!

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Continuing the discussion on slack: p1679930401191499-slack-C8X6Q7XQU

showCalculator={ showCalculator }
isCheckout={ isCheckout }
isShippingCalculatorOpen={
isShippingCalculatorOpen
}
setIsShippingCalculatorOpen={
setIsShippingCalculatorOpen
}
/>
)
}
description={
hasRates && cartHasCalculatedShipping ? (
// If address is complete, display the shipping address.
( hasRates && cartHasCalculatedShipping ) ||
addressComplete ? (
<>
<ShippingVia
selectedShippingRates={ selectedShippingRates }
Expand Down Expand Up @@ -132,6 +138,7 @@ export const TotalsShipping = ( {
hasRates={ hasRates }
shippingRates={ shippingRates }
isLoadingRates={ isLoadingRates }
isAddressComplete={ addressComplete }
/>
) }
</div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,11 @@
*/
import { __ } from '@wordpress/i18n';
import { EnteredAddress } from '@woocommerce/settings';
import {
formatShippingAddress,
isAddressComplete,
} from '@woocommerce/base-utils';
import { useEditorContext } from '@woocommerce/base-context';

/**
* Internal dependencies
Expand All @@ -23,13 +28,21 @@ export const ShippingAddress = ( {
setIsShippingCalculatorOpen,
shippingAddress,
}: ShippingAddressProps ): JSX.Element | null => {
const addressComplete = isAddressComplete( shippingAddress );
const { isEditor } = useEditorContext();

// If the address is incomplete, and we're not in the editor, don't show anything.
if ( ! addressComplete && ! isEditor ) {
return null;
}
const formattedLocation = formatShippingAddress( shippingAddress );
return (
<>
<ShippingLocation address={ shippingAddress } />
<ShippingLocation formattedLocation={ formattedLocation } />
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice change, this will be helpful for #7997

{ showCalculator && (
<CalculatorButton
label={ __(
'(change address)',
'Change address',
'woo-gutenberg-products-block'
) }
isShippingCalculatorOpen={ isShippingCalculatorOpen }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,10 @@ export const ShippingPlaceholder = ( {

return (
<CalculatorButton
label={ __(
'Add an address for shipping options',
'woo-gutenberg-products-block'
) }
isShippingCalculatorOpen={ isShippingCalculatorOpen }
setIsShippingCalculatorOpen={ setIsShippingCalculatorOpen }
/>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,6 @@
* External dependencies
*/
import { __ } from '@wordpress/i18n';
import { Notice } from 'wordpress-components';
import classnames from 'classnames';
import type { CartResponseShippingRate } from '@woocommerce/types';

/**
Expand All @@ -15,12 +13,14 @@ export interface ShippingRateSelectorProps {
hasRates: boolean;
shippingRates: CartResponseShippingRate[];
isLoadingRates: boolean;
isAddressComplete: boolean;
}

export const ShippingRateSelector = ( {
hasRates,
shippingRates,
isLoadingRates,
isAddressComplete,
}: ShippingRateSelectorProps ): JSX.Element => {
const legend = hasRates
? __( 'Shipping options', 'woo-gutenberg-products-block' )
Expand All @@ -31,18 +31,13 @@ export const ShippingRateSelector = ( {
<ShippingRatesControl
className="wc-block-components-totals-shipping__options"
noResultsMessage={
<Notice
isDismissible={ false }
className={ classnames(
'wc-block-components-shipping-rates-control__no-results-notice',
'woocommerce-error'
) }
>
{ __(
'No shipping options were found.',
'woo-gutenberg-products-block'
) }
</Notice>
<>
{ isAddressComplete &&
__(
'There are no shipping options available. Please check your shipping address.',
'woo-gutenberg-products-block'
) }
</>
}
shippingRates={ shippingRates }
isLoadingRates={ isLoadingRates }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,16 @@
flex-basis: 100%;
text-align: left;
}
margin-top: ($gap-small);
}

.wc-block-components-shipping-rates-control__no-results-notice {
margin: 0 0 em($gap-small);
}

.wc-block-components-totals-shipping__change-address__link {
font-weight: normal;
}
.wc-block-components-totals-shipping__change-address-button {
@include link-button();

Expand Down
61 changes: 61 additions & 0 deletions assets/js/base/utils/address.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,9 @@ import {
defaultAddressFields,
ShippingAddress,
BillingAddress,
getSetting,
} from '@woocommerce/settings';
import { decodeEntities } from '@wordpress/html-entities';

/**
* Compare two addresses and see if they are the same.
Expand Down Expand Up @@ -100,3 +102,62 @@ export const emptyHiddenAddressFields = <

return newAddress;
};

/*
* Formats a shipping address for display.
*
* @param {Object} address The address to format.
* @return {string | null} The formatted address or null if no address is provided.
*/
export const formatShippingAddress = (
opr marked this conversation as resolved.
Show resolved Hide resolved
address: ShippingAddress | BillingAddress
): string | null => {
// We bail early if we don't have an address.
if ( Object.values( address ).length === 0 ) {
return null;
}
const shippingCountries = getSetting< Record< string, string > >(
'shippingCountries',
{}
);
const shippingStates = getSetting< Record< string, string > >(
'shippingStates',
{}
);
const formattedCountry =
typeof shippingCountries[ address.country ] === 'string'
? decodeEntities( shippingCountries[ address.country ] )
: '';

const formattedState =
typeof shippingStates[ address.country ] === 'object' &&
typeof shippingStates[ address.country ][ address.state ] === 'string'
? decodeEntities(
shippingStates[ address.country ][ address.state ]
)
: address.state;

const addressParts = [];

addressParts.push( address.postcode.toUpperCase() );
addressParts.push( address.city );
addressParts.push( formattedState );
addressParts.push( formattedCountry );

const formattedLocation = addressParts.filter( Boolean ).join( ', ' );

if ( ! formattedLocation ) {
return null;
}

return formattedLocation;
};

/**
* Returns true if the address has a city and country.
*/
export const isAddressComplete = (
address: ShippingAddress | BillingAddress
): boolean => {
return !! address.city && !! address.country;
opr marked this conversation as resolved.
Show resolved Hide resolved
};
Loading