diff --git a/packages/extension-chakra-store-locator/package.json b/packages/extension-chakra-store-locator/package.json index 20440f26a1..30120082d3 100644 --- a/packages/extension-chakra-store-locator/package.json +++ b/packages/extension-chakra-store-locator/package.json @@ -27,7 +27,8 @@ "prop-types": "^15", "react": "^18", "react-dom": "^18", - "react-router-dom": "^5.3.4" + "react-router-dom": "^5.3.4", + "react-hook-form": "^7" }, "peerDependenciesMeta": { "@chakra-ui/react": { @@ -53,6 +54,9 @@ }, "react-dom": { "optional": true + }, + "react-hook-form": { + "optional": true } }, "devDependencies": { diff --git a/packages/extension-chakra-store-locator/src/components/with-store-locator.test.jsx b/packages/extension-chakra-store-locator/src/components/with-store-locator.test.jsx index e2e3e67df9..0a2ad16364 100644 --- a/packages/extension-chakra-store-locator/src/components/with-store-locator.test.jsx +++ b/packages/extension-chakra-store-locator/src/components/with-store-locator.test.jsx @@ -8,13 +8,29 @@ import React from 'react' import {render, screen} from '@testing-library/react' import {withStoreLocator} from './with-store-locator' import {useStoreLocator} from './use-store-locator' +import {useExtensionStore} from '../hooks/use-extension-store' import PropTypes from 'prop-types' -// Mock the hook jest.mock('./use-store-locator', () => ({ useStoreLocator: jest.fn() })) +jest.mock('../hooks/use-extension-store', () => ({ + useExtensionStore: jest.fn() +})) + +// Mock the StoreLocatorModal component +jest.mock('./modal', () => ({ + // eslint-disable-next-line react/prop-types + StoreLocatorModal: ({isOpen, onClose}) => + isOpen ? ( +
+ Modal Content + +
+ ) : null +})) + describe('withStoreLocator', () => { const mockConfig = { defaultCountryCode: 'US', @@ -25,12 +41,18 @@ describe('withStoreLocator', () => { supportedCountries: [] } + const mockStore = { + isModalOpen: false, + closeModal: jest.fn() + } + beforeEach(() => { useStoreLocator.mockReturnValue({ searchStoresParams: {}, setSearchStoresParams: jest.fn(), config: mockConfig }) + useExtensionStore.mockReturnValue(mockStore) }) it('wraps component with StoreLocatorProvider', () => { @@ -67,4 +89,47 @@ describe('withStoreLocator', () => { expect(WrappedComponent.displayName).toBe('WithStoreLocator(TestComponent)') }) + + it('renders modal when isModalOpen is true', () => { + const TestComponent = () =>
Test Component
+ const WrappedComponent = withStoreLocator(TestComponent, mockConfig) + + useExtensionStore.mockReturnValue({ + ...mockStore, + isModalOpen: true + }) + + render() + expect(screen.getByTestId('store-locator-modal')).toBeTruthy() + }) + + it('does not render modal when isModalOpen is false', () => { + const TestComponent = () =>
Test Component
+ const WrappedComponent = withStoreLocator(TestComponent, mockConfig) + + useExtensionStore.mockReturnValue({ + ...mockStore, + isModalOpen: false + }) + + render() + expect(screen.queryByTestId('store-locator-modal')).toBeNull() + }) + + it('calls closeModal when modal is closed', () => { + const TestComponent = () =>
Test Component
+ const WrappedComponent = withStoreLocator(TestComponent, mockConfig) + const mockCloseModal = jest.fn() + + useExtensionStore.mockReturnValue({ + isModalOpen: true, + closeModal: mockCloseModal + }) + + render() + const closeButton = screen.getByText('Close') + closeButton.click() + + expect(mockCloseModal).toHaveBeenCalled() + }) }) diff --git a/packages/extension-chakra-store-locator/src/components/with-store-locator.tsx b/packages/extension-chakra-store-locator/src/components/with-store-locator.tsx index 955287f7ba..38d78d598b 100644 --- a/packages/extension-chakra-store-locator/src/components/with-store-locator.tsx +++ b/packages/extension-chakra-store-locator/src/components/with-store-locator.tsx @@ -5,8 +5,10 @@ * 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 {useExtensionStore} from '../hooks/use-extension-store' import {Config as StoreLocatorConfig} from '../types/config' import {StoreLocatorProvider} from './provider' +import {StoreLocatorModal} from './modal' /** * Higher-order component that wraps a component with the StoreLocatorProvider @@ -18,9 +20,12 @@ export const withStoreLocator =

( config: StoreLocatorConfig ): React.ComponentType

=> { const WithConfig = (props: P) => { + const {isModalOpen, closeModal} = useExtensionStore() + return ( + ) } diff --git a/packages/extension-chakra-store-locator/src/setup-app.ts b/packages/extension-chakra-store-locator/src/setup-app.ts index c1898113d6..f32d4150cc 100644 --- a/packages/extension-chakra-store-locator/src/setup-app.ts +++ b/packages/extension-chakra-store-locator/src/setup-app.ts @@ -27,20 +27,20 @@ import StoreLocatorPage from './pages/store-locator' import {logger} from './logger' import extensionMeta from '../extension-meta.json' -// NOTE: Hey Kevin, this is where you are going to define the type of the store slice for your extension. I imagine that you'll -// have something that manages the modal being open/closed here. interface StoreSlice { - count: number - increment: () => void - decrement: () => void + isModalOpen: boolean + openModal: () => void + closeModal: () => void } -// This is the store slice definition that we are adding via the `withApplicationExtensionStore` HOC below in the extendApp -// method. const storeSliceInitializer: SliceInitializer = (set) => ({ - count: 0, - increment: () => set((state) => ({count: state.count + 1})), - decrement: () => set((state) => ({count: state.count - 1})) + isModalOpen: false, + openModal: () => { + set((state) => ({...state, isModalOpen: true})) + }, + closeModal: () => { + set((state) => ({...state, isModalOpen: false})) + } }) class StoreLocatorExtension extends ApplicationExtension { static readonly id = extensionMeta.id @@ -57,15 +57,15 @@ class StoreLocatorExtension extends ApplicationExtension { } const HOCs = [ - (component: React.ComponentType) => withStoreLocator(component, config), - (component: React.ComponentType) => - withOptionalCommerceSdkReactProvider(component, config), - (component: React.ComponentType) => withOptionalChakra(component), (component: React.ComponentType) => withApplicationExtensionStore(component, { id: extensionMeta.id, initializer: storeSliceInitializer - }) + }), + (component: React.ComponentType) => withStoreLocator(component, config), + (component: React.ComponentType) => + withOptionalCommerceSdkReactProvider(component, config), + (component: React.ComponentType) => withOptionalChakra(component) ] return applyHOCs(App, HOCs) diff --git a/packages/extension-chakra-storefront/src/components/header/index.jsx b/packages/extension-chakra-storefront/src/components/header/index.jsx index 1798525b16..25a4945c6c 100644 --- a/packages/extension-chakra-storefront/src/components/header/index.jsx +++ b/packages/extension-chakra-storefront/src/components/header/index.jsx @@ -28,6 +28,10 @@ import { useMediaQuery } from '@chakra-ui/react' import {AuthHelpers, useAuthHelper, useCustomerType} from '@salesforce/commerce-sdk-react' +import { + useApplicationExtension, + useApplicationExtensionsStore +} from '@salesforce/pwa-kit-extension-sdk/react' import {useCurrentBasket} from '../../hooks/use-current-basket' @@ -41,7 +45,8 @@ import { HamburgerIcon, ChevronDownIcon, HeartIcon, - SignoutIcon + SignoutIcon, + StoreIcon } from '../../components/icons' import {navLinks, messages} from '../../pages/account/constant' @@ -123,6 +128,13 @@ const Header = ({ onOpen: onAccountMenuOpen } = useDisclosure() const [isDesktop] = useMediaQuery('(min-width: 992px)') + const storeLocatorExtension = useApplicationExtension( + '@salesforce/extension-chakra-store-locator' + ) + const isStoreLocatorEnabled = !!storeLocatorExtension && storeLocatorExtension.isEnabled + const {openModal} = useApplicationExtensionsStore((state) => { + return state.state['@salesforce/extension-chakra-store-locator'] || {} + }) const [showLoading, setShowLoading] = useState(false) // tracking if users enter the popover Content, @@ -304,6 +316,20 @@ const Header = ({ {...styles.wishlistIcon} onClick={onWishlistClick} /> + {isStoreLocatorEnabled && ( + } + {...styles.icons} + variant="unstyled" + onClick={() => { + openModal() + }} + /> + )} { useMediaQuery: jest.fn().mockReturnValue([true]) } }) + +jest.mock('@salesforce/pwa-kit-extension-sdk/react', () => ({ + ...jest.requireActual('@salesforce/pwa-kit-extension-sdk/react'), + useApplicationExtensionsStore: jest.fn().mockReturnValue({ + isModalOpen: false, + closeModal: jest.fn() + }) +})) + const MockedComponent = ({history}) => { const onAccountClick = () => { history.push(createPathWithDefaults('/account'))