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

@W-17443086 Allow store to be selected by Shopper #2187

Merged
merged 20 commits into from
Jan 22, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
05f2e55
add radio buttons for each store
hajinsuha1 Jan 7, 2025
1247e54
save store data in loca storage
hajinsuha1 Jan 7, 2025
6cf11ec
align radio button to store name
hajinsuha1 Jan 9, 2025
f42ed1c
keep selected store selected
hajinsuha1 Jan 9, 2025
48299f6
ensure the description of the store is described when radio button is…
hajinsuha1 Jan 9, 2025
469ec2c
add unit tests
hajinsuha1 Jan 9, 2025
8bd458e
lint
hajinsuha1 Jan 9, 2025
6728456
fix margin
hajinsuha1 Jan 10, 2025
5faed18
fix margin so that if there is a view more or isn't the bottom margin…
hajinsuha1 Jan 10, 2025
375e8ae
Merge branch 'feature-bopis' into W-17443086-select-store-from-store-…
hajinsuha1 Jan 10, 2025
868d129
fix bug with window being undefined in the store-locator page
hajinsuha1 Jan 10, 2025
abc3421
Merge branch 'W-17443086-select-store-from-store-locator-ui' of githu…
hajinsuha1 Jan 10, 2025
d73464d
simplif null checks in store-lists
hajinsuha1 Jan 15, 2025
abbb3b6
update changelog
hajinsuha1 Jan 16, 2025
61bd914
Merge branch 'feature-bopis' into W-17443086-select-store-from-store-…
hajinsuha1 Jan 16, 2025
a602253
use sx instead of style
hajinsuha1 Jan 17, 2025
fd8c67c
Merge branch 'W-17443086-select-store-from-store-locator-ui' of githu…
hajinsuha1 Jan 17, 2025
bdf378d
Merge branch 'develop' into W-17443086-select-store-from-store-locato…
hajinsuha1 Jan 17, 2025
e79f06e
Merge branch 'develop' into W-17443086-select-store-from-store-locato…
hajinsuha1 Jan 21, 2025
33a631f
change marginTop of radio button from 1.5px to 1px
hajinsuha1 Jan 21, 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
1 change: 1 addition & 0 deletions packages/template-retail-react-app/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
- [BUG] Fixed GET /shopper-context API calls being made without the usid [#2206](https://github.com/SalesforceCommerceCloud/pwa-kit/pull/2206)
- Update test data references to 2024, and unify to 01/2040 [#2196](https://github.com/SalesforceCommerceCloud/pwa-kit/pull/2197)
- Fixed failing checkout tests [#2195](https://github.com/SalesforceCommerceCloud/pwa-kit/pull/2195)
- Allow store to be selectable in StoreLocator [#2187](https://github.com/SalesforceCommerceCloud/pwa-kit/pull/2187)
- [BUG] Fixed "getCheckboxProps is not a function" when rendering checkout page in generated app.[#2140](https://github.com/SalesforceCommerceCloud/pwa-kit/pull/2140)
- Replace transfer basket call with merge basket on checkout [#2138](https://github.com/SalesforceCommerceCloud/pwa-kit/pull/2138)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
* For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/BSD-3-Clause
*/

import React from 'react'
Copy link
Collaborator

Choose a reason for hiding this comment

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

You're missing a CHANGELOG.md update associated with this PR

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Added! @bfeister question on the release process. Currently I have the base branch set to a feature branch feature-bopis. Does this make sense or should I just merge the change into develop?

This change does works on it's own, although it doesn't really do anything other than save the store info in local storage

Copy link
Collaborator

Choose a reason for hiding this comment

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

I think it's fine to merge into develop since it works on it's own. It'll help to avoid a "big bang" merge and make that diff smaller

import React, {useEffect, useState} from 'react'
import {useIntl} from 'react-intl'
import PropTypes from 'prop-types'

Expand All @@ -15,78 +15,113 @@ import {
AccordionButton,
AccordionIcon,
AccordionPanel,
Box
Box,
HStack,
Radio,
RadioGroup
} from '@salesforce/retail-react-app/app/components/shared/ui'

// Hooks
import useMultiSite from '@salesforce/retail-react-app/app/hooks/use-multi-site'

const StoresList = ({storesInfo}) => {
const intl = useIntl()
const {site} = useMultiSite()
const storeInfoKey = `store_${site.id}`
const [selectedStore, setSelectedStore] = useState('')

return storesInfo?.map((store, index) => {
return (
<AccordionItem key={index}>
<Box margin="10px">
{store.name ? <Box fontSize="lg">{store.name}</Box> : ''}
<Box fontSize="md" color="gray.600">
{store.address1}
</Box>
<Box fontSize="md" color="gray.600">
{store.city}, {store.stateCode ? store.stateCode : ''} {store.postalCode}
</Box>
{store.distance !== undefined ? (
<>
<br />
<Box fontSize="md" color="gray.600">
{store.distance} {store.distanceUnit}{' '}
{intl.formatMessage({
id: 'store_locator.description.away',
defaultMessage: 'away'
})}
</Box>
</>
) : (
''
)}
{store.phone !== undefined ? (
<>
<br />
<Box fontSize="md" color="gray.600">
{intl.formatMessage({
id: 'store_locator.description.phone',
defaultMessage: 'Phone:'
})}{' '}
{store.phone}
</Box>
</>
) : (
''
)}
{store?.storeHours ? (
<>
{' '}
<AccordionButton color="blue.700" style={{marginTop: '10px'}}>
<Box fontSize="lg">
{intl.formatMessage({
id: 'store_locator.action.viewMore',
defaultMessage: 'View More'
})}
</Box>
<AccordionIcon />
</AccordionButton>
<AccordionPanel mb={6} mt={4}>
<div
dangerouslySetInnerHTML={{
__html: store?.storeHours
}}
/>
</AccordionPanel>{' '}
</>
) : (
''
)}
</Box>
</AccordionItem>
useEffect(() => {
setSelectedStore(JSON.parse(window.localStorage.getItem(storeInfoKey))?.id || '')
}, [storeInfoKey])

const handleChange = (storeId) => {
setSelectedStore(storeId)
const store = storesInfo.find((store) => store.id === storeId)
window.localStorage.setItem(
storeInfoKey,
JSON.stringify({
id: storeId,
name: store.name || null,
inventoryId: store.inventoryId || null
})
)
})
}

return (
<RadioGroup onChange={handleChange} value={selectedStore}>
{storesInfo?.map((store, index) => {
return (
<AccordionItem key={index}>
<HStack align="flex-start" mt="16px" mb="16px">
<Radio
value={store.id}
mt="1px"
aria-describedby={`store-info-${store.id}`}
></Radio>
<Box id={`store-info-${store.id}`}>
{store.name && <Box fontSize="lg">{store.name}</Box>}
<Box fontSize="md" color="gray.600">
{store.address1}
</Box>
<Box fontSize="md" color="gray.600">
{store.city}, {store.stateCode ? store.stateCode : ''}{' '}
{store.postalCode}
</Box>
{store.distance !== undefined && (
<>
<br />
<Box fontSize="md" color="gray.600">
{store.distance} {store.distanceUnit}{' '}
{intl.formatMessage({
id: 'store_locator.description.away',
defaultMessage: 'away'
})}
</Box>
</>
)}
{store.phone && (
<>
<br />
<Box fontSize="md" color="gray.600">
{intl.formatMessage({
id: 'store_locator.description.phone',
defaultMessage: 'Phone:'
})}{' '}
{store.phone}
</Box>
</>
)}
{store.storeHours && (
<>
{' '}
<AccordionButton
color="blue.700"
sx={{marginTop: '10px', paddingBottom: '0px'}}
>
<Box fontSize="lg">
{intl.formatMessage({
id: 'store_locator.action.viewMore',
defaultMessage: 'View More'
})}
</Box>
<AccordionIcon />
</AccordionButton>
<AccordionPanel mb={6} mt={4}>
<div
dangerouslySetInnerHTML={{
__html: store?.storeHours
Copy link
Collaborator

Choose a reason for hiding this comment

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

Why dangerouslySetInnerHTML here? Can't we just interpolate the value in the place where we declare vars via useState and similar?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

The /store-search API actually returns the store hours as html
Screenshot 2025-01-17 at 2 15 51 PM

Copy link
Collaborator

Choose a reason for hiding this comment

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

Oh wow, SUPER weird, but ok 🚢

}}
/>
</AccordionPanel>{' '}
</>
)}
</Box>
</HStack>
</AccordionItem>
)
})}
</RadioGroup>
)
}

StoresList.propTypes = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,10 @@
import React from 'react'
import StoresList from '@salesforce/retail-react-app/app/components/store-locator-modal/stores-list'
import {renderWithProviders} from '@salesforce/retail-react-app/app/utils/test-utils'
import {waitFor, screen} from '@testing-library/react'
import {waitFor, screen, fireEvent} from '@testing-library/react'
import userEvent from '@testing-library/user-event'
import {Accordion} from '@salesforce/retail-react-app/app/components/shared/ui'
import mockConfig from '@salesforce/retail-react-app/config/mocks/default'

const mockSearchStoresData = [
{
Expand Down Expand Up @@ -73,10 +74,9 @@ const mockSearchStoresData = [
distance: 81.1,
distanceUnit: 'km',
id: '00021',
inventoryId: 'inventory_m_store_store13',
latitude: 49.4077,
longitude: 8.6908,
name: 'Heidelberg Tech Mart',
name: 'Store with no inventoryId',
phone: '+49 6221 123456',
posEnabled: false,
postalCode: '69117',
Expand Down Expand Up @@ -104,18 +104,26 @@ describe('StoresList', () => {
</Accordion>
)

expect(screen.queryAllByRole('radio')).toHaveLength(mockSearchStoresData.length)

await waitFor(async () => {
const aStoreName = screen.getByText(/Wiesbaden Tech Depot/i)
const aStoreAddress = screen.getByText(/Kirchgasse 12/i)
const aStoreCityAndPostalCode = screen.getByText(/Wiesbaden, 65185/i)
const aStoreDistance = screen.getByText(/0.74 km away/i)
const aStorePhoneNumber = screen.getByText(/49 611 876543/i)

expect(aStoreName).toBeInTheDocument()
expect(aStoreAddress).toBeInTheDocument()
expect(aStoreCityAndPostalCode).toBeInTheDocument()
expect(aStoreDistance).toBeInTheDocument()
expect(aStorePhoneNumber).toBeInTheDocument()
mockSearchStoresData.forEach((store) => {
const storeName = screen.getByText(store.name)
const storeAddress = screen.getByText(store.address1)
const storeCityAndPostalCode = screen.getByText(
`${store.city}, ${store.postalCode}`
)
const storeDistance = screen.getByText(
`${store.distance} ${store.distanceUnit} away`
)
const storePhoneNumber = screen.getByText(`Phone: ${store.phone}`)

expect(storeName).toBeInTheDocument()
expect(storeAddress).toBeInTheDocument()
expect(storeCityAndPostalCode).toBeInTheDocument()
expect(storeDistance).toBeInTheDocument()
expect(storePhoneNumber).toBeInTheDocument()
})
})
})

Expand Down Expand Up @@ -169,4 +177,23 @@ describe('StoresList', () => {
expect(positions).toEqual([...positions].sort((a, b) => a - b))
})
})

test('Can select store', async () => {
renderWithProviders(
<Accordion>
<StoresList storesInfo={mockSearchStoresData} />
</Accordion>
)

await waitFor(async () => {
const {id, name, inventoryId} = mockSearchStoresData[1]
const radioButton = screen.getByDisplayValue(id)
fireEvent.click(radioButton)

const expectedStoreInfo = {id, name, inventoryId}
expect(localStorage.getItem(`store_${mockConfig.app.defaultSite}`)).toEqual(
JSON.stringify(expectedStoreInfo)
)
})
})
})
Loading