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 */}
-
-
-
-
-
- }
- onClick={onOpen}
- >
-
-
-
-
- }
- onClick={() => setSortOpen(true)}
- >
- {formatMessage(
- {
- id: 'product_list.button.sort_by',
- defaultMessage: 'Sort By: {sortOption}'
- },
- {
- sortOption: selectedSortingOptionLabel?.label
- }
- )}
-
-
-
-
-
-
-
-
-
- {/* Body */}
-
-
- ]
- : undefined
- }
- isLoading={filtersLoading}
- toggleFilter={toggleFilter}
- filters={productSearchResult?.refinements}
- excludedFilters={['cgid']}
- selectedFilters={searchParams.refine}
- />
+
+
+
+
+
+
+
+
+ {/* Filter Button for Mobile */}
+
+
+
+
+
+ }
+ onClick={onOpen}
+ >
+
+
+
+
+ }
+ onClick={() => setSortOpen(true)}
+ >
+ {formatMessage(
+ {
+ id: 'product_list.button.sort_by',
+ defaultMessage: 'Sort By: {sortOption}'
+ },
+ {
+ sortOption: selectedSortingOptionLabel?.label
+ }
+ )}
+
+
-
-
- {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"
},