-
Notifications
You must be signed in to change notification settings - Fork 146
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Keep feature branch
integrate-commerce-sdk-react
up to date (#1001)
* Back-port Shopper Experience Base Components into Retail Template (#992) * Backport experience base components into template * Fix linting * Fix test imports * Make types more robust * Remove temp example page * remove updatePw from not implemented list (#996) --------- Co-authored-by: Ben Chypak <[email protected]> Co-authored-by: Alex Vuong <[email protected]>
- Loading branch information
1 parent
5c786fa
commit 511a3dc
Showing
10 changed files
with
415 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
40 changes: 40 additions & 0 deletions
40
packages/template-retail-react-app/app/components/experience/component/index.jsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,40 @@ | ||
/* | ||
* Copyright (c) 2023, 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 {usePageContext} from '../page' | ||
import {componentType} from '../types' | ||
|
||
/** | ||
* This component will render a page designer page given its serialized data object. | ||
* | ||
* @param {PageProps} props | ||
* @param {Component} props.component - The page designer component data representation. | ||
* @returns {React.ReactElement} - Experience component. | ||
*/ | ||
export const Component = ({component}) => { | ||
const pageContext = usePageContext() | ||
const ComponentClass = | ||
pageContext?.components[component.typeId] || | ||
(({typeId}) => <div>{`Component type '${typeId}' not found!`}</div>) | ||
const {data, ...rest} = component | ||
|
||
return ( | ||
<div id={component.id} className="component"> | ||
<div className="container"> | ||
<ComponentClass {...rest} {...data} /> | ||
</div> | ||
</div> | ||
) | ||
} | ||
|
||
Component.displayName = 'Component' | ||
|
||
Component.propTypes = { | ||
component: componentType.isRequired | ||
} | ||
|
||
export default Component |
62 changes: 62 additions & 0 deletions
62
packages/template-retail-react-app/app/components/experience/component/index.test.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,62 @@ | ||
/* | ||
* Copyright (c) 2023, 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 {render} from '@testing-library/react' | ||
import Component from './index' | ||
import {PageContext} from '../page' | ||
|
||
const SAMPLE_COMPONENT = { | ||
id: 'rfdvj4ojtltljw3', | ||
typeId: 'commerce_assets.carousel', | ||
data: { | ||
title: 'Topseller', | ||
category: 'topseller' | ||
}, | ||
regions: [ | ||
{ | ||
id: 'regionB1', | ||
components: [ | ||
{ | ||
id: 'rfdvj4ojtltljw3', | ||
typeId: 'commerce_assets.carousel', | ||
data: { | ||
title: 'Topseller', | ||
category: 'topseller' | ||
} | ||
} | ||
] | ||
} | ||
] | ||
} | ||
|
||
const TEST_COMPONENTS = { | ||
['commerce_assets.carousel']: () => <div className="carousel">Carousel</div> | ||
} | ||
|
||
test('Page throws if used outside of a Page component', () => { | ||
expect(() => render(<Component component={SAMPLE_COMPONENT} />)).toThrow() | ||
}) | ||
|
||
test('Page renders correct component', () => { | ||
const component = <Component component={SAMPLE_COMPONENT} /> | ||
|
||
const {container} = render(component, { | ||
// eslint-disable-next-line react/display-name | ||
wrapper: () => ( | ||
<PageContext.Provider value={{components: TEST_COMPONENTS}}> | ||
{component} | ||
</PageContext.Provider> | ||
) | ||
}) | ||
|
||
// Component are in document. | ||
expect(container.querySelectorAll('.component')?.length).toEqual(1) | ||
|
||
// Prodived components are in document. (Note: Sub-regions/components aren't rendered because that is | ||
// the responsibility of the component definition.) | ||
expect(container.querySelectorAll('.carousel')?.length).toEqual(1) | ||
}) |
77 changes: 77 additions & 0 deletions
77
packages/template-retail-react-app/app/components/experience/page/index.jsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,77 @@ | ||
/* | ||
* Copyright (c) 2023, 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, {useContext, useEffect, useState} from 'react' | ||
import PropTypes from 'prop-types' | ||
import {Helmet} from 'react-helmet' | ||
import {Region} from '../region' | ||
import {pageType} from '../types' | ||
|
||
// This context will hold the component map as well as any other future context. | ||
export const PageContext = React.createContext(undefined) | ||
|
||
// This hook allows sub-components to use the page context. In our case we use it | ||
// so that the generic <Component /> can use the component map to know which react component | ||
// to render. | ||
export const usePageContext = () => { | ||
const value = useContext(PageContext) | ||
|
||
if (!value) { | ||
throw new Error('"usePageContext" cannot be used outside of a page component.') | ||
} | ||
|
||
return value | ||
} | ||
|
||
/** | ||
* This component will render a page designer page given its serialized data object. | ||
* | ||
* @param {PageProps} props | ||
* @param {Page} props.region - The page designer page data representation. | ||
* @param {ComponentMap} props.components - A mapping of typeId's to react components representing the type. | ||
* @returns {React.ReactElement} - Page component. | ||
*/ | ||
export const Page = (props) => { | ||
const {page, components, className = '', ...rest} = props | ||
const [contextValue, setContextValue] = useState({components}) | ||
const {id, regions, pageDescription, pageKeywords, pageTitle} = page || {} | ||
|
||
// NOTE: This probably is not required as the list of components is known at compile time, | ||
// but we might need this ability in the future if we are to lazy load components. | ||
useEffect(() => { | ||
setContextValue({ | ||
...contextValue, | ||
components | ||
}) | ||
}, [components]) | ||
|
||
return ( | ||
<PageContext.Provider value={contextValue}> | ||
<Helmet> | ||
{pageTitle && <title>{pageTitle}</title>} | ||
{pageDescription && <meta name="description" content={pageDescription} />} | ||
{pageKeywords && <meta name="keywords" content={pageKeywords} />} | ||
</Helmet> | ||
<div id={id} className={`page ${className}`} {...rest}> | ||
<div className="container"> | ||
{regions?.map((region) => ( | ||
<Region key={region.id} region={region} /> | ||
))} | ||
</div> | ||
</div> | ||
</PageContext.Provider> | ||
) | ||
} | ||
|
||
Page.displayName = 'Page' | ||
|
||
Page.propTypes = { | ||
page: pageType.isRequired, | ||
components: PropTypes.object.isRequired, | ||
className: PropTypes.string | ||
} | ||
|
||
export default Page |
94 changes: 94 additions & 0 deletions
94
packages/template-retail-react-app/app/components/experience/page/index.test.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,94 @@ | ||
/* | ||
* Copyright (c) 2023, 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 {render} from '@testing-library/react' | ||
import Page from './index' | ||
import {Helmet} from 'react-helmet' | ||
|
||
const SAMPLE_PAGE = { | ||
id: 'samplepage', | ||
typeId: 'storePage', | ||
aspectTypeId: 'pdpAspect', | ||
name: 'Sample Page', | ||
description: 'Sample page of the storefront.', | ||
pageTitle: 'title', | ||
pageDescription: 'description', | ||
pageKeywords: 'keywords', | ||
regions: [ | ||
{ | ||
id: 'regionA', | ||
components: [ | ||
{ | ||
id: 'iofwj38fhw3f', | ||
typeId: 'commerce_assets.banner', | ||
data: { | ||
title: 'Products On Sale', | ||
bannerImage: 'sale/topsellerPromo.jpg' | ||
} | ||
} | ||
] | ||
}, | ||
{ | ||
id: 'regionB', | ||
components: [ | ||
{ | ||
id: 'rfdvj4ojtltljw3', | ||
typeId: 'commerce_assets.carousel', | ||
data: { | ||
title: 'Topseller', | ||
category: 'topseller' | ||
}, | ||
regions: [ | ||
{ | ||
id: 'regionB1', | ||
components: [ | ||
{ | ||
id: 'rfdvj4ojtltljw3', | ||
typeId: 'commerce_assets.carousel', | ||
data: { | ||
title: 'Topseller', | ||
category: 'topseller' | ||
} | ||
} | ||
] | ||
} | ||
] | ||
} | ||
] | ||
}, | ||
{ | ||
id: 'regionC', | ||
components: [] | ||
} | ||
] | ||
} | ||
|
||
test('Page renders without errors', () => { | ||
const {container} = render(<Page page={SAMPLE_PAGE} components={{}} />) | ||
|
||
// Page is in document. | ||
expect(container.querySelector('[id=samplepage]')).toBeInTheDocument() | ||
|
||
// Meta data and title are set | ||
const helmet = Helmet.peek() | ||
expect(helmet.title).toEqual('title') | ||
expect( | ||
helmet.metaTags.find( | ||
({name, content}) => name === 'description' && content === 'description' | ||
) | ||
).toBeTruthy() | ||
expect( | ||
helmet.metaTags.find(({name, content}) => name === 'keywords' && content === 'keywords') | ||
).toBeTruthy() | ||
|
||
// Regions are in document. | ||
expect(container.querySelectorAll('.region')?.length).toEqual(3) | ||
|
||
// Components are in document. (Note: Sub-regions/components aren't rendered because that is | ||
// the responsibility of the component definition.) | ||
expect(container.querySelectorAll('.component')?.length).toEqual(2) | ||
}) |
41 changes: 41 additions & 0 deletions
41
packages/template-retail-react-app/app/components/experience/region/index.jsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,41 @@ | ||
/* | ||
* Copyright (c) 2023, 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 PropTypes from 'prop-types' | ||
import {Component} from '../component' | ||
import {regionType} from '../types' | ||
|
||
/** | ||
* This component will render a page designer region given its serialized data object. | ||
* | ||
* @param {RegionProps} props | ||
* @param {Region} props.region - The page designer region data representation. | ||
* @returns {React.ReactElement} - Region component. | ||
*/ | ||
export const Region = (props) => { | ||
const {region, className = '', ...rest} = props | ||
const {id, components} = region | ||
|
||
return ( | ||
<div id={id} className={`region ${className}`} {...rest}> | ||
<div className="container"> | ||
{components?.map((component) => ( | ||
<Component key={component.id} component={component} /> | ||
))} | ||
</div> | ||
</div> | ||
) | ||
} | ||
|
||
Region.displayName = 'Region' | ||
|
||
Region.propTypes = { | ||
region: regionType.isRequired, | ||
className: PropTypes.string | ||
} | ||
|
||
export default Region |
61 changes: 61 additions & 0 deletions
61
packages/template-retail-react-app/app/components/experience/region/index.test.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,61 @@ | ||
/* | ||
* Copyright (c) 2023, 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 {render} from '@testing-library/react' | ||
import Region from './index' | ||
import {PageContext} from '../page' | ||
|
||
const SAMPLE_REGION = { | ||
id: 'regionB', | ||
components: [ | ||
{ | ||
id: 'rfdvj4ojtltljw3', | ||
typeId: 'commerce_assets.carousel', | ||
data: { | ||
title: 'Topseller', | ||
category: 'topseller' | ||
}, | ||
regions: [ | ||
{ | ||
id: 'regionB1', | ||
components: [ | ||
{ | ||
id: 'rfdvj4ojtltljw3', | ||
typeId: 'commerce_assets.carousel', | ||
data: { | ||
title: 'Topseller', | ||
category: 'topseller' | ||
} | ||
} | ||
] | ||
} | ||
] | ||
} | ||
] | ||
} | ||
|
||
test('Region throws if used outside of a Page component', () => { | ||
expect(() => render(<Region region={SAMPLE_REGION} />)).toThrow() | ||
}) | ||
|
||
test('Region renders without errors', () => { | ||
const component = <Region region={SAMPLE_REGION} /> | ||
|
||
const {container} = render(component, { | ||
// eslint-disable-next-line react/display-name | ||
wrapper: () => ( | ||
<PageContext.Provider value={{components: {}}}>{component}</PageContext.Provider> | ||
) | ||
}) | ||
|
||
// Regions are in document. | ||
expect(container.querySelectorAll('.region')?.length).toEqual(1) | ||
|
||
// Components are in document. (Note: Sub-regions/components aren't rendered because that is | ||
// the responsibility of the component definition.) | ||
expect(container.querySelectorAll('.component')?.length).toEqual(1) | ||
}) |
Oops, something went wrong.