diff --git a/plugins/woocommerce-blocks/assets/js/base/components/cart-checkout/shipping-location/index.tsx b/plugins/woocommerce-blocks/assets/js/base/components/cart-checkout/shipping-location/index.tsx index b57fc0985d1..4f0c4f0b5b0 100644 --- a/plugins/woocommerce-blocks/assets/js/base/components/cart-checkout/shipping-location/index.tsx +++ b/plugins/woocommerce-blocks/assets/js/base/components/cart-checkout/shipping-location/index.tsx @@ -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; } diff --git a/plugins/woocommerce-blocks/assets/js/base/components/cart-checkout/totals/shipping/calculator-button.tsx b/plugins/woocommerce-blocks/assets/js/base/components/cart-checkout/totals/shipping/calculator-button.tsx index 9607ab9fd7f..887f34dbe31 100644 --- a/plugins/woocommerce-blocks/assets/js/base/components/cart-checkout/totals/shipping/calculator-button.tsx +++ b/plugins/woocommerce-blocks/assets/js/base/components/cart-checkout/totals/shipping/calculator-button.tsx @@ -15,15 +15,20 @@ export const CalculatorButton = ( { setIsShippingCalculatorOpen, }: CalculatorButtonProps ): JSX.Element => { return ( - + ); }; diff --git a/plugins/woocommerce-blocks/assets/js/base/components/cart-checkout/totals/shipping/index.tsx b/plugins/woocommerce-blocks/assets/js/base/components/cart-checkout/totals/shipping/index.tsx index 06febfcafd9..5b2fade1a9e 100644 --- a/plugins/woocommerce-blocks/assets/js/base/components/cart-checkout/totals/shipping/index.tsx +++ b/plugins/woocommerce-blocks/assets/js/base/components/cart-checkout/totals/shipping/index.tsx @@ -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 @@ -67,6 +68,8 @@ export const TotalsShipping = ( { } ); + const addressComplete = isAddressComplete( shippingAddress ); + return (
- ) + hasRates && cartHasCalculatedShipping + ? totalShippingValue + : // if address is not complete, display the link to add an address. + ! addressComplete && ( + + ) } description={ - hasRates && cartHasCalculatedShipping ? ( + // If address is complete, display the shipping address. + ( hasRates && cartHasCalculatedShipping ) || + addressComplete ? ( <> ) }
diff --git a/plugins/woocommerce-blocks/assets/js/base/components/cart-checkout/totals/shipping/shipping-address.tsx b/plugins/woocommerce-blocks/assets/js/base/components/cart-checkout/totals/shipping/shipping-address.tsx index 0df38408184..c2be8f15954 100644 --- a/plugins/woocommerce-blocks/assets/js/base/components/cart-checkout/totals/shipping/shipping-address.tsx +++ b/plugins/woocommerce-blocks/assets/js/base/components/cart-checkout/totals/shipping/shipping-address.tsx @@ -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 @@ -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 ( <> - + { showCalculator && ( diff --git a/plugins/woocommerce-blocks/assets/js/base/components/cart-checkout/totals/shipping/shipping-rate-selector.tsx b/plugins/woocommerce-blocks/assets/js/base/components/cart-checkout/totals/shipping/shipping-rate-selector.tsx index 78fcf12a674..a6bbe6dcdad 100644 --- a/plugins/woocommerce-blocks/assets/js/base/components/cart-checkout/totals/shipping/shipping-rate-selector.tsx +++ b/plugins/woocommerce-blocks/assets/js/base/components/cart-checkout/totals/shipping/shipping-rate-selector.tsx @@ -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'; /** @@ -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' ) @@ -31,18 +31,13 @@ export const ShippingRateSelector = ( { - { __( - 'No shipping options were found.', - 'woo-gutenberg-products-block' - ) } - + <> + { isAddressComplete && + __( + 'There are no shipping options available. Please check your shipping address.', + 'woo-gutenberg-products-block' + ) } + } shippingRates={ shippingRates } isLoadingRates={ isLoadingRates } diff --git a/plugins/woocommerce-blocks/assets/js/base/components/cart-checkout/totals/shipping/style.scss b/plugins/woocommerce-blocks/assets/js/base/components/cart-checkout/totals/shipping/style.scss index 9562503f8bf..5e9cf3b2b20 100644 --- a/plugins/woocommerce-blocks/assets/js/base/components/cart-checkout/totals/shipping/style.scss +++ b/plugins/woocommerce-blocks/assets/js/base/components/cart-checkout/totals/shipping/style.scss @@ -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(); diff --git a/plugins/woocommerce-blocks/assets/js/base/components/cart-checkout/totals/shipping/test/index.tsx b/plugins/woocommerce-blocks/assets/js/base/components/cart-checkout/totals/shipping/test/index.tsx new file mode 100644 index 00000000000..daa43f4a967 --- /dev/null +++ b/plugins/woocommerce-blocks/assets/js/base/components/cart-checkout/totals/shipping/test/index.tsx @@ -0,0 +1,282 @@ +/** + * External dependencies + */ +import { screen, render } from '@testing-library/react'; +import { SlotFillProvider } from '@woocommerce/blocks-checkout'; +import { previewCart as mockPreviewCart } from '@woocommerce/resource-previews'; +import * as wpData from '@wordpress/data'; +import * as baseContextHooks from '@woocommerce/base-context/hooks'; + +/** + * Internal dependencies + */ +import { TotalsShipping } from '../index'; + +jest.mock( '@wordpress/data', () => ( { + __esModule: true, + ...jest.requireActual( '@wordpress/data' ), + useSelect: jest.fn(), +} ) ); + +wpData.useSelect.mockImplementation( () => { + return { prefersCollection: false }; +} ); + +const shippingAddress = { + first_name: 'John', + last_name: 'Doe', + company: 'Company', + address_1: '409 Main Street', + address_2: 'Apt 1', + city: 'London', + postcode: 'W1T 4JG', + country: 'GB', + state: '', + email: 'john.doe@company', + phone: '+1234567890', +}; + +jest.mock( '@woocommerce/base-context/hooks', () => { + return { + __esModule: true, + ...jest.requireActual( '@woocommerce/base-context/hooks' ), + useShippingData: jest.fn(), + useStoreCart: jest.fn(), + }; +} ); +baseContextHooks.useShippingData.mockReturnValue( { + needsShipping: true, + shippingRates: [ + { + package_id: 0, + name: 'Shipping method', + destination: { + address_1: '', + address_2: '', + city: '', + state: '', + postcode: '', + country: '', + }, + items: [ + { + key: 'fb0c0a746719a7596f296344b80cb2b6', + name: 'Hoodie - Blue, Yes', + quantity: 1, + }, + { + key: '1f0e3dad99908345f7439f8ffabdffc4', + name: 'Beanie', + quantity: 1, + }, + ], + shipping_rates: [ + { + rate_id: 'flat_rate:1', + name: 'Flat rate', + description: '', + delivery_time: '', + price: '500', + taxes: '0', + instance_id: 1, + method_id: 'flat_rate', + meta_data: [ + { + key: 'Items', + value: 'Hoodie - Blue, Yes × 1, Beanie × 1', + }, + ], + selected: false, + currency_code: 'USD', + currency_symbol: '$', + currency_minor_unit: 2, + currency_decimal_separator: '.', + currency_thousand_separator: ',', + currency_prefix: '$', + currency_suffix: '', + }, + { + rate_id: 'local_pickup:2', + name: 'Local pickup', + description: '', + delivery_time: '', + price: '0', + taxes: '0', + instance_id: 2, + method_id: 'local_pickup', + meta_data: [ + { + key: 'Items', + value: 'Hoodie - Blue, Yes × 1, Beanie × 1', + }, + ], + selected: false, + currency_code: 'USD', + currency_symbol: '$', + currency_minor_unit: 2, + currency_decimal_separator: '.', + currency_thousand_separator: ',', + currency_prefix: '$', + currency_suffix: '', + }, + { + rate_id: 'free_shipping:5', + name: 'Free shipping', + description: '', + delivery_time: '', + price: '0', + taxes: '0', + instance_id: 5, + method_id: 'free_shipping', + meta_data: [ + { + key: 'Items', + value: 'Hoodie - Blue, Yes × 1, Beanie × 1', + }, + ], + selected: true, + currency_code: 'USD', + currency_symbol: '$', + currency_minor_unit: 2, + currency_decimal_separator: '.', + currency_thousand_separator: ',', + currency_prefix: '$', + currency_suffix: '', + }, + ], + }, + ], +} ); +baseContextHooks.useStoreCart.mockReturnValue( { + cartItems: mockPreviewCart.items, + cartTotals: [ mockPreviewCart.totals ], + cartCoupons: mockPreviewCart.coupons, + cartFees: mockPreviewCart.fees, + cartNeedsShipping: mockPreviewCart.needs_shipping, + shippingRates: [], + shippingAddress, + billingAddress: mockPreviewCart.billing_address, + cartHasCalculatedShipping: mockPreviewCart.has_calculated_shipping, + isLoadingRates: false, +} ); + +describe( 'TotalsShipping', () => { + it( 'should show correct calculator button label if address is complete', () => { + render( + + + + ); + expect( + screen.getByText( + 'Shipping to W1T 4JG, London, United Kingdom (UK)' + ) + ).toBeInTheDocument(); + expect( screen.getByText( 'Change address' ) ).toBeInTheDocument(); + } ); + it( 'should show correct calculator button label if address is incomplete', () => { + baseContextHooks.useStoreCart.mockReturnValue( { + cartItems: mockPreviewCart.items, + cartTotals: [ mockPreviewCart.totals ], + cartCoupons: mockPreviewCart.coupons, + cartFees: mockPreviewCart.fees, + cartNeedsShipping: mockPreviewCart.needs_shipping, + shippingRates: [], + shippingAddress: { + ...shippingAddress, + city: '', + country: '', + postcode: '', + }, + billingAddress: mockPreviewCart.billing_address, + cartHasCalculatedShipping: mockPreviewCart.has_calculated_shipping, + isLoadingRates: false, + } ); + render( + + + + ); + expect( + screen.queryByText( 'Change address' ) + ).not.toBeInTheDocument(); + expect( + screen.getByText( 'Add an address for shipping options' ) + ).toBeInTheDocument(); + } ); + it( 'does not show the calculator button when default rates are available and no address has been entered', () => { + baseContextHooks.useStoreCart.mockReturnValue( { + cartItems: mockPreviewCart.items, + cartTotals: [ mockPreviewCart.totals ], + cartCoupons: mockPreviewCart.coupons, + cartFees: mockPreviewCart.fees, + cartNeedsShipping: mockPreviewCart.needs_shipping, + shippingRates: mockPreviewCart.shipping_rates, + shippingAddress: { + ...shippingAddress, + city: '', + country: '', + postcode: '', + }, + billingAddress: mockPreviewCart.billing_address, + cartHasCalculatedShipping: mockPreviewCart.has_calculated_shipping, + isLoadingRates: false, + } ); + render( + + + + ); + expect( + screen.queryByText( 'Change address' ) + ).not.toBeInTheDocument(); + expect( + screen.queryByText( 'Add an address for shipping options' ) + ).not.toBeInTheDocument(); + } ); +} ); diff --git a/plugins/woocommerce-blocks/assets/js/base/components/cart-checkout/totals/shipping/test/shipping-placeholder.tsx b/plugins/woocommerce-blocks/assets/js/base/components/cart-checkout/totals/shipping/test/shipping-placeholder.tsx new file mode 100644 index 00000000000..469bb35d61a --- /dev/null +++ b/plugins/woocommerce-blocks/assets/js/base/components/cart-checkout/totals/shipping/test/shipping-placeholder.tsx @@ -0,0 +1,36 @@ +/** + * External dependencies + */ +import { screen, render } from '@testing-library/react'; + +/** + * Internal dependencies + */ +import ShippingPlaceholder from '../shipping-placeholder'; + +describe( 'ShippingPlaceholder', () => { + it( 'should show correct text if showCalculator is false', () => { + const { rerender } = render( + + ); + expect( + screen.getByText( 'No shipping options available' ) + ).toBeInTheDocument(); + rerender( + + ); + expect( + screen.getByText( 'Calculated during checkout' ) + ).toBeInTheDocument(); + } ); +} ); diff --git a/plugins/woocommerce-blocks/assets/js/base/utils/address.ts b/plugins/woocommerce-blocks/assets/js/base/utils/address.ts index e3738fb614b..f894db86997 100644 --- a/plugins/woocommerce-blocks/assets/js/base/utils/address.ts +++ b/plugins/woocommerce-blocks/assets/js/base/utils/address.ts @@ -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. @@ -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 = ( + 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; +}; diff --git a/plugins/woocommerce-blocks/assets/js/base/utils/test/address.ts b/plugins/woocommerce-blocks/assets/js/base/utils/test/address.ts index 8f454ad2058..27d31eef5b0 100644 --- a/plugins/woocommerce-blocks/assets/js/base/utils/test/address.ts +++ b/plugins/woocommerce-blocks/assets/js/base/utils/test/address.ts @@ -1,7 +1,11 @@ /** * External dependencies */ -import { emptyHiddenAddressFields } from '@woocommerce/base-utils'; +import { + emptyHiddenAddressFields, + isAddressComplete, + formatShippingAddress, +} from '@woocommerce/base-utils'; describe( 'emptyHiddenAddressFields', () => { it( "Removes state from an address where the country doesn't use states", () => { @@ -22,3 +26,101 @@ describe( 'emptyHiddenAddressFields', () => { expect( filteredAddress ).toHaveProperty( 'state', '' ); } ); } ); + +describe( 'isAddressComplete', () => { + it( 'correctly checks empty addresses', () => { + const address = { + first_name: '', + last_name: '', + company: '', + address_1: '', + address_2: '', + city: '', + postcode: '', + country: '', + state: '', + email: '', + phone: '', + }; + expect( isAddressComplete( address ) ).toBe( false ); + } ); + + it( 'correctly checks incomplete addresses', () => { + const address = { + first_name: 'John', + last_name: 'Doe', + company: 'Company', + address_1: '409 Main Street', + address_2: 'Apt 1', + city: '', + postcode: '', + country: '', + state: '', + email: 'john.doe@company', + phone: '+1234567890', + }; + expect( isAddressComplete( address ) ).toBe( false ); + + address.city = 'London'; + expect( isAddressComplete( address ) ).toBe( false ); + + address.postcode = 'W1T 4JG'; + address.country = 'GB'; + expect( isAddressComplete( address ) ).toBe( true ); + } ); + + it( 'correctly checks complete addresses', () => { + const address = { + first_name: 'John', + last_name: 'Doe', + company: 'Company', + address_1: '409 Main Street', + address_2: 'Apt 1', + city: 'London', + postcode: 'W1T 4JG', + country: 'GB', + state: '', + email: 'john.doe@company', + phone: '+1234567890', + }; + expect( isAddressComplete( address ) ).toBe( true ); + } ); +} ); + +describe( 'formatShippingAddress', () => { + it( 'returns null if address is empty', () => { + const address = { + first_name: '', + last_name: '', + company: '', + address_1: '', + address_2: '', + city: '', + postcode: '', + country: '', + state: '', + email: '', + phone: '', + }; + expect( formatShippingAddress( address ) ).toBe( null ); + } ); + + it( 'correctly returns the formatted address', () => { + const address = { + first_name: 'John', + last_name: 'Doe', + company: 'Company', + address_1: '409 Main Street', + address_2: 'Apt 1', + city: 'London', + postcode: 'W1T 4JG', + country: 'GB', + state: '', + email: 'john.doe@company', + phone: '+1234567890', + }; + expect( formatShippingAddress( address ) ).toBe( + 'W1T 4JG, London, United Kingdom (UK)' + ); + } ); +} ); diff --git a/plugins/woocommerce-blocks/assets/js/blocks/checkout/inner-blocks/checkout-order-summary-shipping/block.tsx b/plugins/woocommerce-blocks/assets/js/blocks/checkout/inner-blocks/checkout-order-summary-shipping/block.tsx index 84fd6e36b67..7ca7678c2b1 100644 --- a/plugins/woocommerce-blocks/assets/js/blocks/checkout/inner-blocks/checkout-order-summary-shipping/block.tsx +++ b/plugins/woocommerce-blocks/assets/js/blocks/checkout/inner-blocks/checkout-order-summary-shipping/block.tsx @@ -26,6 +26,7 @@ const Block = ( { showRateSelector={ false } values={ cartTotals } currency={ totalsCurrency } + isCheckout={ true } /> ); diff --git a/plugins/woocommerce-blocks/assets/js/blocks/checkout/inner-blocks/checkout-shipping-methods-block/block.tsx b/plugins/woocommerce-blocks/assets/js/blocks/checkout/inner-blocks/checkout-shipping-methods-block/block.tsx index 89e67fb766e..86f26557eed 100644 --- a/plugins/woocommerce-blocks/assets/js/blocks/checkout/inner-blocks/checkout-shipping-methods-block/block.tsx +++ b/plugins/woocommerce-blocks/assets/js/blocks/checkout/inner-blocks/checkout-shipping-methods-block/block.tsx @@ -7,22 +7,21 @@ import { ShippingRatesControl } from '@woocommerce/base-components/cart-checkout import { getShippingRatesPackageCount, hasCollectableRate, + isAddressComplete, } from '@woocommerce/base-utils'; import { getCurrencyFromPriceResponse } from '@woocommerce/price-format'; import FormattedMonetaryAmount from '@woocommerce/base-components/formatted-monetary-amount'; import { useEditorContext, noticeContexts } from '@woocommerce/base-context'; import { StoreNoticesContainer } from '@woocommerce/blocks-checkout'; import { decodeEntities } from '@wordpress/html-entities'; -import { Notice } from 'wordpress-components'; -import classnames from 'classnames'; import { getSetting } from '@woocommerce/settings'; import type { PackageRateOption, CartShippingPackageShippingRate, } from '@woocommerce/types'; -import type { ReactElement } from 'react'; -import { useSelect } from '@wordpress/data'; import { CART_STORE_KEY } from '@woocommerce/block-data'; +import { useSelect } from '@wordpress/data'; +import type { ReactElement } from 'react'; /** * Internal dependencies @@ -87,11 +86,16 @@ const Block = ( { } ) : shippingRates; + const shippingAddress = useSelect( ( select ) => { + return select( CART_STORE_KEY ).getCustomerData()?.shippingAddress; + } ); + if ( ! needsShipping ) { return null; } - const shippingAddressIsComplete = ! shippingAddressHasValidationErrors(); + const shippingAddressHasErrors = ! shippingAddressHasValidationErrors(); + const addressComplete = isAddressComplete( shippingAddress ); const shippingRatesPackageCount = getShippingRatesPackageCount( shippingRates ); @@ -99,7 +103,7 @@ const Block = ( { if ( ( ! hasCalculatedShipping && ! shippingRatesPackageCount ) || ( shippingCostRequiresAddress && - ( ! shippingAddressPushed || ! shippingAddressIsComplete ) ) + ( ! shippingAddressPushed || ! shippingAddressHasErrors ) ) ) { return (

@@ -121,18 +125,17 @@ const Block = ( { ) : ( - { __( - 'There are no shipping options available. Please check your shipping address.', - 'woo-gutenberg-products-block' - ) } - + <> + { addressComplete + ? __( + 'There are no shipping options available. Please check your shipping address.', + 'woo-gutenberg-products-block' + ) + : __( + 'Add a shipping address to view shipping options.', + 'woo-gutenberg-products-block' + ) } + } renderOption={ renderShippingRatesControlOption } collapsible={ false }