diff --git a/packages/template-retail-react-app/app/pages/product-list/index.jsx b/packages/template-retail-react-app/app/pages/product-list/index.jsx index 8704a28283..9b4f20ccf4 100644 --- a/packages/template-retail-react-app/app/pages/product-list/index.jsx +++ b/packages/template-retail-react-app/app/pages/product-list/index.jsx @@ -59,6 +59,7 @@ import EmptySearchResults from '@salesforce/retail-react-app/app/pages/product-l import PageHeader from '@salesforce/retail-react-app/app/pages/product-list/partials/page-header' import AbovePageHeader from '@salesforce/retail-react-app/app/pages/product-list/partials/above-page-header' import PageDesignerPromotionalBanner from '@salesforce/retail-react-app/app/pages/product-list/partials/page-designer-promotional-banner' +import StoreInventoryFilter from '@salesforce/retail-react-app/app/pages/product-list/partials/inventory-filter' // Icons import {FilterIcon, ChevronDownIcon} from '@salesforce/retail-react-app/app/components/icons' @@ -426,225 +427,240 @@ const ProductList = (props) => { return })} - {showNoResults ? ( - - ) : ( - <> - - - - {/* Header */} - - - - - - - - - - - - + <> + + + + {/* Header */} + + + + - {/* Filter Button for Mobile */} - - - - - - - - - - - - - - - - - - {/* Body */} - - - ] - : undefined - } - isLoading={filtersLoading} - toggleFilter={toggleFilter} - filters={productSearchResult?.refinements} - excludedFilters={['cgid']} - selectedFilters={searchParams.refine} - /> + + + + + + + + + {/* Filter Button for Mobile */} + + + + + + + + + + - - - {isHydrated() && - ((isRefetching && !isFetched) || !productSearchResult) - ? new Array(searchParams.limit) - .fill(0) - .map((value, index) => ( - - )) - : productSearchResult?.hits?.map((productSearchItem) => { - const productId = productSearchItem.productId - const isInWishlist = - !!wishlist?.customerProductListItems?.find( - (item) => item.productId === productId - ) + + + + + - return ( - { - if (searchQuery) { - einstein.sendClickSearch( - searchQuery, - productSearchItem - ) - } else if (category) { - einstein.sendClickCategory( - category, - productSearchItem - ) + {/* Body */} + + + , + + ] + : [ + + ] + } + isLoading={filtersLoading} + toggleFilter={toggleFilter} + filters={productSearchResult?.refinements} + excludedFilters={['cgid']} + selectedFilters={searchParams.refine} + /> + + + {showNoResults ? ( + + ) : ( + <> + + {isHydrated() && + ((isRefetching && !isFetched) || !productSearchResult) + ? new Array(searchParams.limit) + .fill(0) + .map((value, index) => ( + + )) + : productSearchResult?.hits?.map((productSearchItem) => { + const productId = productSearchItem.productId + const isInWishlist = + !!wishlist?.customerProductListItems?.find( + (item) => item.productId === productId + ) + + return ( + { - const action = toBeFavourite - ? addItemToWishlist - : removeItemFromWishlist - return action(productSearchItem) - }} - dynamicImageProps={{ - widths: [ - '50vw', - '50vw', - '20vw', - '20vw', - '25vw' - ] - }} - /> - ) - })} - - {/* Footer */} - - - - {/* - Our design doesn't call for a page size select. Show this element if you want - to add one to your design. - */} - - - - - - )} + + + {/* + Our design doesn't call for a page size select. Show this element if you want + to add one to your design. + */} + + + + )} + + + {/* Modal for filter options on mobile */} { key="itemsBefore" category={category} onSelect={onClose} + />, + + ] + : [ + ] - : undefined } excludedFilters={['cgid']} /> diff --git a/packages/template-retail-react-app/app/pages/product-list/index.test.js b/packages/template-retail-react-app/app/pages/product-list/index.test.js index 185a85b908..263e916f4d 100644 --- a/packages/template-retail-react-app/app/pages/product-list/index.test.js +++ b/packages/template-retail-react-app/app/pages/product-list/index.test.js @@ -324,3 +324,15 @@ test('should filter out refinements in the disallow list', async () => { expect(screen.getByText('Price')).toBeInTheDocument() }) }) +test('should display Store Inventory Filter component', async () => { + window.history.pushState({}, 'ProductList', '/uk/en-GB/category/mens-clothing-jackets') + renderWithProviders(, { + wrapperProps: {siteAlias: 'uk', locale: {id: 'en-GB'}} + }) + + // Wait for the page to load + expect(await screen.findByTestId('sf-product-list-page')).toBeInTheDocument() + + // Check that the Store Inventory Filter component is present + expect(await screen.findByTestId('sf-store-inventory-filter')).toBeInTheDocument() +}) diff --git a/packages/template-retail-react-app/app/pages/product-list/partials/inventory-filter.jsx b/packages/template-retail-react-app/app/pages/product-list/partials/inventory-filter.jsx new file mode 100644 index 0000000000..bc6eb4a5c3 --- /dev/null +++ b/packages/template-retail-react-app/app/pages/product-list/partials/inventory-filter.jsx @@ -0,0 +1,153 @@ +/* + * Copyright (c) 2025, Salesforce, Inc. + * All rights reserved. + * SPDX-License-Identifier: BSD-3-Clause + * For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/BSD-3-Clause + */ + +import React, {useEffect, useState} from 'react' +import {useIntl, FormattedMessage} from 'react-intl' +import PropTypes from 'prop-types' +import { + Heading, + Checkbox, + Stack, + Text, + useDisclosure +} from '@salesforce/retail-react-app/app/components/shared/ui' +import StoreLocatorModal from '@salesforce/retail-react-app/app/components/store-locator-modal' +import useMultiSite from '@salesforce/retail-react-app/app/hooks/use-multi-site' +import {getSelectedStoreData} from '@salesforce/retail-react-app/app/utils/store-locator-utils' + +const StoreInventoryFilter = ({toggleFilter, selectedFilters}) => { + const [selectedStore, setSelectedStore] = useState(null) + const {isOpen, onOpen, onClose} = useDisclosure() + const {site} = useMultiSite() + const {formatMessage} = useIntl() + + const isChecked = selectedFilters?.ilids !== undefined + + useEffect(() => { + const storeInfo = getSelectedStoreData(site?.id) + + if (storeInfo?.name && storeInfo?.inventoryId) { + setSelectedStore(storeInfo) + } + }, [site?.id]) + + const handleCheckboxChange = (e) => { + // If no store is selected or no inventoryId, open store locator + if (!selectedStore?.inventoryId) { + e.preventDefault() + onOpen() + return + } + + // Normal checkbox behavior when store is selected + const checked = e.target.checked + toggleFilter({value: selectedStore.inventoryId}, 'ilids', !checked, false) + } + + // Always open store locator when store name text is clicked + const handleStoreNameClick = (e) => { + e.stopPropagation() + e.preventDefault() + onOpen() + } + + const handleStoreLocatorClose = () => { + const storeInfo = getSelectedStoreData(site?.id) + + if (storeInfo?.name && storeInfo?.inventoryId) { + setSelectedStore(storeInfo) + + // Apply the filter when a store is selected from the locator + toggleFilter({value: storeInfo.inventoryId}, 'ilids', false, false) + } + + onClose() + } + + const storeLinkText = + selectedStore?.name || + formatMessage({ + defaultMessage: 'Select Store', + id: 'store_inventory_filter.action.select_store' + }) + + return ( + <> + + + + + + + {storeLinkText} + + ) + }} + /> + + + + + + ) +} + +StoreInventoryFilter.propTypes = { + toggleFilter: PropTypes.func.isRequired, + selectedFilters: PropTypes.object +} + +export default StoreInventoryFilter diff --git a/packages/template-retail-react-app/app/pages/product-list/partials/inventory-filter.test.js b/packages/template-retail-react-app/app/pages/product-list/partials/inventory-filter.test.js new file mode 100644 index 0000000000..886356ca67 --- /dev/null +++ b/packages/template-retail-react-app/app/pages/product-list/partials/inventory-filter.test.js @@ -0,0 +1,166 @@ +/* + * Copyright (c) 2025, Salesforce, Inc. + * All rights reserved. + * SPDX-License-Identifier: BSD-3-Clause + * For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/BSD-3-Clause + */ + +import React from 'react' +import {screen, waitFor} from '@testing-library/react' +import userEvent from '@testing-library/user-event' +import {renderWithProviders} from '@salesforce/retail-react-app/app/utils/test-utils' +import StoreInventoryFilter from '@salesforce/retail-react-app/app/pages/product-list/partials/inventory-filter' +import {getSelectedStoreData} from '@salesforce/retail-react-app/app/utils/store-locator-utils' + +jest.mock('@salesforce/retail-react-app/app/utils/store-locator-utils', () => ({ + getSelectedStoreData: jest.fn() +})) + +jest.mock('@salesforce/retail-react-app/app/components/store-locator-modal', () => { + // eslint-disable-next-line react/prop-types + function MockStoreLocatorModal({isOpen, onClose}) { + return isOpen ? ( +
+ +
+ ) : null + } + + return MockStoreLocatorModal +}) + +const mockToggleFilter = jest.fn() + +const defaultProps = { + toggleFilter: mockToggleFilter, + selectedFilters: {} +} + +const mockStoreData = { + id: 'store-123', + name: 'Test Store Location', + inventoryId: 'inv-456' +} + +describe('StoreInventoryFilter', () => { + beforeEach(() => { + jest.clearAllMocks() + localStorage.clear() + getSelectedStoreData.mockReturnValue(null) + }) + + test('renders component with default state', async () => { + renderWithProviders() + + expect(screen.getByTestId('sf-store-inventory-filter')).toBeInTheDocument() + expect(screen.getByText('Shop by Availability')).toBeInTheDocument() + expect(screen.getByText('Select Store')).toBeInTheDocument() + expect(screen.getByRole('checkbox')).not.toBeChecked() + }) + + test('displays selected store name when store data exists', async () => { + getSelectedStoreData.mockReturnValue(mockStoreData) + + renderWithProviders() + + await waitFor(() => { + expect(screen.getByText('Test Store Location')).toBeInTheDocument() + }) + }) + + test('shows checkbox as checked when ilids filter is selected', () => { + const propsWithFilter = { + ...defaultProps, + selectedFilters: {ilids: 'inv-456'} + } + + renderWithProviders() + + expect(screen.getByRole('checkbox')).toBeChecked() + }) + + test('opens store locator modal when checkbox clicked without selected store', async () => { + const user = userEvent.setup() + renderWithProviders() + + const checkbox = screen.getByRole('checkbox') + await user.click(checkbox) + + expect(screen.getByTestId('store-locator-modal')).toBeInTheDocument() + }) + + test('opens store locator modal when store name is clicked', async () => { + const user = userEvent.setup() + getSelectedStoreData.mockReturnValue(mockStoreData) + + renderWithProviders() + + await waitFor(() => { + expect(screen.getByText('Test Store Location')).toBeInTheDocument() + }) + + await user.click(screen.getByText('Test Store Location')) + + expect(screen.getByTestId('store-locator-modal')).toBeInTheDocument() + }) + + test('calls toggleFilter when checkbox is changed with selected store', async () => { + const user = userEvent.setup() + getSelectedStoreData.mockReturnValue(mockStoreData) + + renderWithProviders() + + await waitFor(() => { + expect(screen.getByText('Test Store Location')).toBeInTheDocument() + }) + + const checkbox = screen.getByRole('checkbox') + await user.click(checkbox) + + expect(mockToggleFilter).toHaveBeenCalledWith({value: 'inv-456'}, 'ilids', false, false) + }) + + test('calls toggleFilter to remove filter when checkbox is unchecked', async () => { + const user = userEvent.setup() + getSelectedStoreData.mockReturnValue(mockStoreData) + + const propsWithFilter = { + ...defaultProps, + selectedFilters: {ilids: 'inv-456'} + } + + renderWithProviders() + + await waitFor(() => { + expect(screen.getByText('Test Store Location')).toBeInTheDocument() + }) + + const checkbox = screen.getByRole('checkbox') + expect(checkbox).toBeChecked() + + await user.click(checkbox) + + expect(mockToggleFilter).toHaveBeenCalledWith({value: 'inv-456'}, 'ilids', true, false) + }) + + test('applies filter when store is selected from locator modal', async () => { + const user = userEvent.setup() + // Initially no store + getSelectedStoreData.mockReturnValue(null) + + renderWithProviders() + + // Click checkbox to open modal + await user.click(screen.getByRole('checkbox')) + expect(screen.getByTestId('store-locator-modal')).toBeInTheDocument() + + // Simulate store selection by changing the mock return value + getSelectedStoreData.mockReturnValue(mockStoreData) + + // Close modal + await user.click(screen.getByText('Close Modal')) + + // Should have called toggleFilter to apply the filter + expect(mockToggleFilter).toHaveBeenCalledWith({value: 'inv-456'}, 'ilids', false, false) + }) +}) diff --git a/packages/template-retail-react-app/app/pages/product-list/partials/selected-refinements.jsx b/packages/template-retail-react-app/app/pages/product-list/partials/selected-refinements.jsx index 8528a6e32c..d3bad8bb59 100644 --- a/packages/template-retail-react-app/app/pages/product-list/partials/selected-refinements.jsx +++ b/packages/template-retail-react-app/app/pages/product-list/partials/selected-refinements.jsx @@ -11,22 +11,46 @@ import PropTypes from 'prop-types' import {Box, Button, Wrap, WrapItem} from '@salesforce/retail-react-app/app/components/shared/ui' import {CloseIcon} from '@salesforce/retail-react-app/app/components/icons' import {REMOVE_FILTER} from '@salesforce/retail-react-app/app/pages/product-list/partials/refinements-utils' +import useMultiSite from '@salesforce/retail-react-app/app/hooks/use-multi-site' +import {getSelectedStoreData} from '@salesforce/retail-react-app/app/utils/store-locator-utils' const SelectedRefinements = ({toggleFilter, selectedFilterValues, filters, handleReset}) => { const {formatMessage} = useIntl() + const {site} = useMultiSite() const priceFilterValues = filters?.find((filter) => filter.attributeId === 'price') - let selectedFilters = [] for (const key in selectedFilterValues) { const filters = selectedFilterValues[key].split('|') filters?.forEach((filter) => { + let uiLabel = filter + + if (key === 'price') { + uiLabel = + priceFilterValues?.values?.find((priceFilter) => priceFilter.value === filter) + ?.label || filter + } else if (key === 'ilids') { + // Fallback text for in stock selected filter + uiLabel = formatMessage({ + id: 'selected_refinements.filter.in_stock', + defaultMessage: 'In Stock' + }) + + const storeInfo = getSelectedStoreData(site?.id) + if (storeInfo?.inventoryId === filter && storeInfo?.name) { + uiLabel = formatMessage( + { + id: 'store_inventory_filter.checkbox.label', + defaultMessage: 'In Stock at {storeName}' + }, + { + storeName: storeInfo.name + } + ) + } + } + const selected = { - uiLabel: - key === 'price' - ? priceFilterValues?.values?.find( - (priceFilter) => priceFilter.value === filter - )?.label - : filter, + uiLabel, value: key, apiLabel: filter } diff --git a/packages/template-retail-react-app/app/static/translations/compiled/en-GB.json b/packages/template-retail-react-app/app/static/translations/compiled/en-GB.json index 352d183530..884358383f 100644 --- a/packages/template-retail-react-app/app/static/translations/compiled/en-GB.json +++ b/packages/template-retail-react-app/app/static/translations/compiled/en-GB.json @@ -3141,6 +3141,12 @@ "value": "Clear All" } ], + "selected_refinements.filter.in_stock": [ + { + "type": 0, + "value": "In Stock" + } + ], "shipping_address.button.continue_to_shipping": [ { "type": 0, @@ -3289,6 +3295,60 @@ "value": " to proceed." } ], + "store_inventory_filter.action.change_store": [ + { + "type": 0, + "value": "Change Store" + } + ], + "store_inventory_filter.action.select_store": [ + { + "type": 0, + "value": "Select Store" + } + ], + "store_inventory_filter.action.select_store_link": [ + { + "type": 0, + "value": "Select a Store" + } + ], + "store_inventory_filter.checkbox.assistive_msg": [ + { + "type": 0, + "value": "Filter Products by Store Availability at " + }, + { + "type": 1, + "value": "storeName" + } + ], + "store_inventory_filter.checkbox.label": [ + { + "type": 0, + "value": "In Stock at " + }, + { + "type": 1, + "value": "storeName" + } + ], + "store_inventory_filter.heading.shop_by_availability": [ + { + "type": 0, + "value": "Shop by Availability" + } + ], + "store_inventory_filter.link.assistive_msg": [ + { + "type": 0, + "value": "Open Store Locator to " + }, + { + "type": 1, + "value": "action" + } + ], "store_locator.action.find": [ { "type": 0, diff --git a/packages/template-retail-react-app/app/static/translations/compiled/en-US.json b/packages/template-retail-react-app/app/static/translations/compiled/en-US.json index 352d183530..884358383f 100644 --- a/packages/template-retail-react-app/app/static/translations/compiled/en-US.json +++ b/packages/template-retail-react-app/app/static/translations/compiled/en-US.json @@ -3141,6 +3141,12 @@ "value": "Clear All" } ], + "selected_refinements.filter.in_stock": [ + { + "type": 0, + "value": "In Stock" + } + ], "shipping_address.button.continue_to_shipping": [ { "type": 0, @@ -3289,6 +3295,60 @@ "value": " to proceed." } ], + "store_inventory_filter.action.change_store": [ + { + "type": 0, + "value": "Change Store" + } + ], + "store_inventory_filter.action.select_store": [ + { + "type": 0, + "value": "Select Store" + } + ], + "store_inventory_filter.action.select_store_link": [ + { + "type": 0, + "value": "Select a Store" + } + ], + "store_inventory_filter.checkbox.assistive_msg": [ + { + "type": 0, + "value": "Filter Products by Store Availability at " + }, + { + "type": 1, + "value": "storeName" + } + ], + "store_inventory_filter.checkbox.label": [ + { + "type": 0, + "value": "In Stock at " + }, + { + "type": 1, + "value": "storeName" + } + ], + "store_inventory_filter.heading.shop_by_availability": [ + { + "type": 0, + "value": "Shop by Availability" + } + ], + "store_inventory_filter.link.assistive_msg": [ + { + "type": 0, + "value": "Open Store Locator to " + }, + { + "type": 1, + "value": "action" + } + ], "store_locator.action.find": [ { "type": 0, diff --git a/packages/template-retail-react-app/app/static/translations/compiled/en-XA.json b/packages/template-retail-react-app/app/static/translations/compiled/en-XA.json index 75acacecc2..47cc0e3664 100644 --- a/packages/template-retail-react-app/app/static/translations/compiled/en-XA.json +++ b/packages/template-retail-react-app/app/static/translations/compiled/en-XA.json @@ -6685,6 +6685,20 @@ "value": "]" } ], + "selected_refinements.filter.in_stock": [ + { + "type": 0, + "value": "[" + }, + { + "type": 0, + "value": "Īƞ Şŧǿǿƈķ" + }, + { + "type": 0, + "value": "]" + } + ], "shipping_address.button.continue_to_shipping": [ { "type": 0, @@ -7001,6 +7015,116 @@ "value": "]" } ], + "store_inventory_filter.action.change_store": [ + { + "type": 0, + "value": "[" + }, + { + "type": 0, + "value": "Ƈħȧȧƞɠḗḗ Şŧǿǿřḗḗ" + }, + { + "type": 0, + "value": "]" + } + ], + "store_inventory_filter.action.select_store": [ + { + "type": 0, + "value": "[" + }, + { + "type": 0, + "value": "Şḗḗŀḗḗƈŧ Şŧǿǿřḗḗ" + }, + { + "type": 0, + "value": "]" + } + ], + "store_inventory_filter.action.select_store_link": [ + { + "type": 0, + "value": "[" + }, + { + "type": 0, + "value": "Şḗḗŀḗḗƈŧ ȧȧ Şŧǿǿřḗḗ" + }, + { + "type": 0, + "value": "]" + } + ], + "store_inventory_filter.checkbox.assistive_msg": [ + { + "type": 0, + "value": "[" + }, + { + "type": 0, + "value": "Ƒīŀŧḗḗř Ƥřǿǿḓŭŭƈŧş ƀẏ Şŧǿǿřḗḗ Ȧṽȧȧīŀȧȧƀīŀīŧẏ ȧȧŧ " + }, + { + "type": 1, + "value": "storeName" + }, + { + "type": 0, + "value": "]" + } + ], + "store_inventory_filter.checkbox.label": [ + { + "type": 0, + "value": "[" + }, + { + "type": 0, + "value": "Īƞ Şŧǿǿƈķ ȧȧŧ " + }, + { + "type": 1, + "value": "storeName" + }, + { + "type": 0, + "value": "]" + } + ], + "store_inventory_filter.heading.shop_by_availability": [ + { + "type": 0, + "value": "[" + }, + { + "type": 0, + "value": "Şħǿǿƥ ƀẏ Ȧṽȧȧīŀȧȧƀīŀīŧẏ" + }, + { + "type": 0, + "value": "]" + } + ], + "store_inventory_filter.link.assistive_msg": [ + { + "type": 0, + "value": "[" + }, + { + "type": 0, + "value": "Ǿƥḗḗƞ Şŧǿǿřḗḗ Ŀǿǿƈȧȧŧǿǿř ŧǿǿ " + }, + { + "type": 1, + "value": "action" + }, + { + "type": 0, + "value": "]" + } + ], "store_locator.action.find": [ { "type": 0, diff --git a/packages/template-retail-react-app/app/utils/store-locator-utils.js b/packages/template-retail-react-app/app/utils/store-locator-utils.js new file mode 100644 index 0000000000..67307be988 --- /dev/null +++ b/packages/template-retail-react-app/app/utils/store-locator-utils.js @@ -0,0 +1,21 @@ +/* + * Copyright (c) 2025, Salesforce, Inc. + * All rights reserved. + * SPDX-License-Identifier: BSD-3-Clause + * For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/BSD-3-Clause + */ +export const getSelectedStoreData = (siteId) => { + // Handle SSR and localStorage errors + if (typeof window === 'undefined') { + return null + } + + try { + const storeInfoKey = `store_${siteId}` + const storeInfo = JSON.parse(window.localStorage.getItem(storeInfoKey) || 'null') + return storeInfo + } catch (error) { + console.debug('Failed to access localStorage:', error) + return null + } +} diff --git a/packages/template-retail-react-app/translations/en-GB.json b/packages/template-retail-react-app/translations/en-GB.json index c6e5c939e7..c3c64629fe 100644 --- a/packages/template-retail-react-app/translations/en-GB.json +++ b/packages/template-retail-react-app/translations/en-GB.json @@ -1343,6 +1343,9 @@ "selected_refinements.action.clear_all": { "defaultMessage": "Clear All" }, + "selected_refinements.filter.in_stock": { + "defaultMessage": "In Stock" + }, "shipping_address.button.continue_to_shipping": { "defaultMessage": "Continue to Shipping Method" }, @@ -1406,6 +1409,27 @@ "social_login_redirect.message.redirect_link": { "defaultMessage": "If you are not automatically redirected, click this link to proceed." }, + "store_inventory_filter.action.change_store": { + "defaultMessage": "Change Store" + }, + "store_inventory_filter.action.select_store": { + "defaultMessage": "Select Store" + }, + "store_inventory_filter.action.select_store_link": { + "defaultMessage": "Select a Store" + }, + "store_inventory_filter.checkbox.assistive_msg": { + "defaultMessage": "Filter Products by Store Availability at {storeName}" + }, + "store_inventory_filter.checkbox.label": { + "defaultMessage": "In Stock at {storeName}" + }, + "store_inventory_filter.heading.shop_by_availability": { + "defaultMessage": "Shop by Availability" + }, + "store_inventory_filter.link.assistive_msg": { + "defaultMessage": "Open Store Locator to {action}" + }, "store_locator.action.find": { "defaultMessage": "Find" }, diff --git a/packages/template-retail-react-app/translations/en-US.json b/packages/template-retail-react-app/translations/en-US.json index c6e5c939e7..c3c64629fe 100644 --- a/packages/template-retail-react-app/translations/en-US.json +++ b/packages/template-retail-react-app/translations/en-US.json @@ -1343,6 +1343,9 @@ "selected_refinements.action.clear_all": { "defaultMessage": "Clear All" }, + "selected_refinements.filter.in_stock": { + "defaultMessage": "In Stock" + }, "shipping_address.button.continue_to_shipping": { "defaultMessage": "Continue to Shipping Method" }, @@ -1406,6 +1409,27 @@ "social_login_redirect.message.redirect_link": { "defaultMessage": "If you are not automatically redirected, click this link to proceed." }, + "store_inventory_filter.action.change_store": { + "defaultMessage": "Change Store" + }, + "store_inventory_filter.action.select_store": { + "defaultMessage": "Select Store" + }, + "store_inventory_filter.action.select_store_link": { + "defaultMessage": "Select a Store" + }, + "store_inventory_filter.checkbox.assistive_msg": { + "defaultMessage": "Filter Products by Store Availability at {storeName}" + }, + "store_inventory_filter.checkbox.label": { + "defaultMessage": "In Stock at {storeName}" + }, + "store_inventory_filter.heading.shop_by_availability": { + "defaultMessage": "Shop by Availability" + }, + "store_inventory_filter.link.assistive_msg": { + "defaultMessage": "Open Store Locator to {action}" + }, "store_locator.action.find": { "defaultMessage": "Find" },