@salesforce/retail-react-app 1.1.0-preview.0 → 2.1.0-nightly-20230927165653
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +30 -20
- package/app/components/_app/index.jsx +3 -2
- package/app/components/display-price/index.jsx +67 -0
- package/app/components/display-price/index.test.js +36 -0
- package/app/components/forms/useAddressFields.jsx +2 -4
- package/app/components/icons/index.jsx +1 -1
- package/app/components/item-variant/item-price.jsx +1 -1
- package/app/components/product-view/index.jsx +16 -21
- package/app/constants.js +5 -0
- package/app/hooks/use-add-to-cart-modal.js +16 -13
- package/app/hooks/use-derived-product.js +6 -1
- package/app/mocks/mock-data.js +8 -0
- package/app/page-designer/index.js +1 -1
- package/app/pages/checkout/partials/payment.jsx +2 -1
- package/app/pages/product-detail/index.jsx +38 -3
- package/app/pages/registration/index.test.jsx +0 -1
- package/app/ssr.js +54 -8
- package/app/utils/cc-utils.js +15 -0
- package/app/utils/product-utils.js +18 -0
- package/app/utils/product-utils.test.js +27 -1
- package/package.json +6 -6
package/CHANGELOG.md
CHANGED
|
@@ -1,10 +1,20 @@
|
|
|
1
|
-
##
|
|
1
|
+
## v2.1.0-dev (Sep 26, 2023)
|
|
2
|
+
- Support Storefront Preview
|
|
3
|
+
- [#1413](https://github.com/SalesforceCommerceCloud/pwa-kit/pull/1413)
|
|
4
|
+
- [#1440](https://github.com/SalesforceCommerceCloud/pwa-kit/pull/1440)
|
|
5
|
+
|
|
6
|
+
## v2.0.0 (Sep 21, 2023)
|
|
2
7
|
|
|
3
|
-
- Fix
|
|
4
|
-
-
|
|
8
|
+
- V3 Fix checkout card number [#1424](https://github.com/SalesforceCommerceCloud/pwa-kit/pull/1424)
|
|
9
|
+
- Cleanup wrong import path for page-designer component [#1441](https://github.com/SalesforceCommerceCloud/pwa-kit/pull/1441) @clavery
|
|
10
|
+
- Modularize country code source for targeting via extensibility [#1445](https://github.com/SalesforceCommerceCloud/pwa-kit/pull/1445) @bfeister
|
|
11
|
+
- Export icon helper function for target via overrides [#1420](https://github.com/SalesforceCommerceCloud/pwa-kit/pull/1420) @cbrother-csu
|
|
12
|
+
- Migrate Page Designer core types to commerce-sdk-react [#1441](https://github.com/SalesforceCommerceCloud/pwa-kit/pull/1441) @clavery
|
|
5
13
|
|
|
6
|
-
## v1.1
|
|
14
|
+
## v1.0.1 (Jul 26, 2023)
|
|
7
15
|
|
|
16
|
+
- Fix price display when a discounted price is zero [#1361](https://github.com/SalesforceCommerceCloud/pwa-kit/pull/1361)
|
|
17
|
+
- Fix price adjustments of a line item when more than one is present [#1362](https://github.com/SalesforceCommerceCloud/pwa-kit/pull/1362)
|
|
8
18
|
- Registered user checkout should set order with an email [#1363](https://github.com/SalesforceCommerceCloud/pwa-kit/pull/1363)
|
|
9
19
|
|
|
10
20
|
## v1.0.0 (June 15, 2023)
|
|
@@ -16,9 +26,9 @@
|
|
|
16
26
|
|
|
17
27
|
## Older Versions Without Namespace
|
|
18
28
|
|
|
19
|
-
The
|
|
29
|
+
The versions published below were not published on npm, and the versioning matched that of the PWA Kit SDK. These versions did not use the `@salesforce` namespace.
|
|
20
30
|
|
|
21
|
-
|
|
31
|
+
### v2.7.1 (May 11, 2023)
|
|
22
32
|
|
|
23
33
|
- Replace invalid row value with nowrap [#1179](https://github.com/SalesforceCommerceCloud/pwa-kit/pull/1179)
|
|
24
34
|
- Add a redirect to login page after user signs out from checkout page [#1172](https://github.com/SalesforceCommerceCloud/pwa-kit/pull/1172)
|
|
@@ -32,7 +42,7 @@ The older versions below were published without the `@salesforce` namespace.
|
|
|
32
42
|
- Fix addresses not having preferred address first. [#1051](https://github.com/SalesforceCommerceCloud/pwa-kit/pull/1051)
|
|
33
43
|
- Changed type of the phone number field to bring up numberic keyboard on mobile devices - W-9871940 [#1016)](https://github.com/SalesforceCommerceCloud/pwa-kit/pull/1016)
|
|
34
44
|
|
|
35
|
-
|
|
45
|
+
### v2.7.0 (Mar 03, 2023)
|
|
36
46
|
|
|
37
47
|
- Add Page Designer ImageTile component [#967](https://github.com/SalesforceCommerceCloud/pwa-kit/pull/967)
|
|
38
48
|
- Add Page Designer ImageWithText component [#991](https://github.com/SalesforceCommerceCloud/pwa-kit/pull/991)
|
|
@@ -40,7 +50,7 @@ The older versions below were published without the `@salesforce` namespace.
|
|
|
40
50
|
- Add Page Designer layout components [#993](https://github.com/SalesforceCommerceCloud/pwa-kit/pull/993)
|
|
41
51
|
- Support the product-set type [#1019](https://github.com/SalesforceCommerceCloud/pwa-kit/pull/1019)
|
|
42
52
|
|
|
43
|
-
|
|
53
|
+
### v2.6.0 (Jan 25, 2023)
|
|
44
54
|
|
|
45
55
|
- Mega menu fixes [#875](https://github.com/SalesforceCommerceCloud/pwa-kit/pull/875) and [#910](https://github.com/SalesforceCommerceCloud/pwa-kit/pull/910)
|
|
46
56
|
- Cache SLAS callback using request processor [#884](https://github.com/SalesforceCommerceCloud/pwa-kit/pull/884)
|
|
@@ -49,14 +59,14 @@ The older versions below were published without the `@salesforce` namespace.
|
|
|
49
59
|
- Fix PropTypes [#924](https://github.com/SalesforceCommerceCloud/pwa-kit/pull/924)
|
|
50
60
|
- Remove unnecessary map statement [#929](https://github.com/SalesforceCommerceCloud/pwa-kit/pull/929)
|
|
51
61
|
|
|
52
|
-
|
|
62
|
+
### v2.5.0 (Jan 5, 2023)
|
|
53
63
|
|
|
54
64
|
- Add instanceType to Einstein activity body [#858](https://github.com/SalesforceCommerceCloud/pwa-kit/pull/858)
|
|
55
65
|
- Do not use a proxy to call Einstein [#857](https://github.com/SalesforceCommerceCloud/pwa-kit/pull/857)
|
|
56
66
|
- Einstein handle variants [#867](https://github.com/SalesforceCommerceCloud/pwa-kit/pull/867)
|
|
57
67
|
- Fix cc exp year [#874](https://github.com/SalesforceCommerceCloud/pwa-kit/pull/874)
|
|
58
68
|
|
|
59
|
-
|
|
69
|
+
### v2.4.0 (Dec 1, 2022)
|
|
60
70
|
|
|
61
71
|
- Dynamic footer Copyright date [#741](https://github.com/SalesforceCommerceCloud/pwa-kit/pull/741)
|
|
62
72
|
- Footer copyright: remove the remaining hardcoded year [#760](https://github.com/SalesforceCommerceCloud/pwa-kit/pull/760)
|
|
@@ -66,7 +76,7 @@ The older versions below were published without the `@salesforce` namespace.
|
|
|
66
76
|
- pwa-kit-dev command for tailing logs [#789](https://github.com/SalesforceCommerceCloud/pwa-kit/pull/789)
|
|
67
77
|
- Update usages of zzrf-001 ODS instance to the new short URL format [#816](https://github.com/SalesforceCommerceCloud/pwa-kit/pull/816)
|
|
68
78
|
|
|
69
|
-
|
|
79
|
+
### v2.3.0 (Oct 27, 2022)
|
|
70
80
|
|
|
71
81
|
- Fix locale `alias` by including `locale` object inside the MultiSite Context. [#716](https://github.com/SalesforceCommerceCloud/pwa-kit/pull/716)
|
|
72
82
|
- Updated translations. [#725](https://github.com/SalesforceCommerceCloud/pwa-kit/pull/725)
|
|
@@ -77,20 +87,20 @@ The older versions below were published without the `@salesforce` namespace.
|
|
|
77
87
|
- Bugfix: image gallery thumbnails not working without DIS [#786](https://github.com/SalesforceCommerceCloud/pwa-kit/pull/786)
|
|
78
88
|
- Ensure that a valid target locale is use if none is provided [#788](https://github.com/SalesforceCommerceCloud/pwa-kit/pull/788)
|
|
79
89
|
|
|
80
|
-
|
|
90
|
+
### v2.2.0 (Aug 25, 2022)
|
|
81
91
|
|
|
82
92
|
- Update zzrf-001 url [#694](https://github.com/SalesforceCommerceCloud/pwa-kit/pull/694)
|
|
83
93
|
- Optimize Server-side performance [#667](https://github.com/SalesforceCommerceCloud/pwa-kit/pull/667)
|
|
84
94
|
- Remove references to session bridging [#684](https://github.com/SalesforceCommerceCloud/pwa-kit/pull/684)
|
|
85
95
|
|
|
86
|
-
|
|
96
|
+
### v2.1.0 (Jul 05, 2022)
|
|
87
97
|
|
|
88
98
|
- Update translations [#643](https://github.com/SalesforceCommerceCloud/pwa-kit/pull/643)
|
|
89
99
|
- Update translations [#653](https://github.com/SalesforceCommerceCloud/pwa-kit/pull/653)
|
|
90
100
|
- Add bundlesize test back into CI [#652](https://github.com/SalesforceCommerceCloud/pwa-kit/pull/652)
|
|
91
101
|
- Fix UI bug on notifications badge [#620](https://github.com/SalesforceCommerceCloud/pwa-kit/pull/620)
|
|
92
102
|
|
|
93
|
-
|
|
103
|
+
### v2.0.0 (May 16, 2022)
|
|
94
104
|
|
|
95
105
|
- Update translation docs [#570](https://github.com/SalesforceCommerceCloud/pwa-kit/pull/570)
|
|
96
106
|
- Fixed loading correct amount of skeletons [#576] (<https://github.com/SalesforceCommerceCloud/pwa-kit/pull/576>)
|
|
@@ -110,7 +120,7 @@ The older versions below were published without the `@salesforce` namespace.
|
|
|
110
120
|
- Service worker loading for dev server [#464](https://github.com/SalesforceCommerceCloud/pwa-kit/pull/464)
|
|
111
121
|
- Environment Specific Configuration Support [#477](https://github.com/SalesforceCommerceCloud/pwa-kit/pull/447)
|
|
112
122
|
|
|
113
|
-
|
|
123
|
+
### v1.5.0 (Mar 17, 2022)
|
|
114
124
|
|
|
115
125
|
- Support storing authentication tokens in cookie [#429](https://github.com/SalesforceCommerceCloud/pwa-kit/pull/429)
|
|
116
126
|
- Make sure the forgot-password modal also shows up in the checkout flow [#373](https://github.com/SalesforceCommerceCloud/pwa-kit/pull/373)
|
|
@@ -118,7 +128,7 @@ The older versions below were published without the `@salesforce` namespace.
|
|
|
118
128
|
- Import cross-fetch to make OCAPI client isomorphic [#382](https://github.com/SalesforceCommerceCloud/pwa-kit/pull/382)
|
|
119
129
|
- Multi-site implementation for Retail React App [#391](https://github.com/SalesforceCommerceCloud/pwa-kit/pull/391)
|
|
120
130
|
|
|
121
|
-
|
|
131
|
+
### v1.4.0 (Jan 27, 2022)
|
|
122
132
|
|
|
123
133
|
- Do not send HSTS header during local development [#288](https://github.com/SalesforceCommerceCloud/pwa-kit/pull/288)
|
|
124
134
|
- Add constants for `<meta>` tags `theme-color` and `apple-mobile-web-app-title` [#287](https://github.com/SalesforceCommerceCloud/pwa-kit/pull/287)
|
|
@@ -126,7 +136,7 @@ The older versions below were published without the `@salesforce` namespace.
|
|
|
126
136
|
- Import preliminary translations [#324](https://github.com/SalesforceCommerceCloud/pwa-kit/pull/324)
|
|
127
137
|
- Remove old dependencies that are no longer used [#317](https://github.com/SalesforceCommerceCloud/pwa-kit/pull/317)
|
|
128
138
|
|
|
129
|
-
|
|
139
|
+
### v1.3.0 (Jan 06, 2022)
|
|
130
140
|
|
|
131
141
|
- Remove Einstein from home page [#208](https://github.com/SalesforceCommerceCloud/pwa-kit/pull/208)
|
|
132
142
|
- Add git2gus config to allow git2gus integration [210](https://github.com/SalesforceCommerceCloud/pwa-kit/pull/210)
|
|
@@ -148,7 +158,7 @@ The older versions below were published without the `@salesforce` namespace.
|
|
|
148
158
|
- Fix show applied promotion codes case sensitive issue [#224](https://github.com/SalesforceCommerceCloud/pwa-kit/pull/224)
|
|
149
159
|
- Fix section subtitle incorrect prop warning [#282](https://github.com/SalesforceCommerceCloud/pwa-kit/pull/282)
|
|
150
160
|
|
|
151
|
-
|
|
161
|
+
### v1.2.0 (Nov 18, 2021)
|
|
152
162
|
|
|
153
163
|
- Simplify homepage for Retail React App [#201](https://github.com/SalesforceCommerceCloud/pwa-kit/pull/201)
|
|
154
164
|
- Ensure `cookieId` value is sent always for Einstein recommendations [#179](https://github.com/SalesforceCommerceCloud/pwa-kit/pull/179)
|
|
@@ -169,7 +179,7 @@ The older versions below were published without the `@salesforce` namespace.
|
|
|
169
179
|
- Add cache control header to product details page [#172](https://github.com/SalesforceCommerceCloud/pwa-kit/pull/172)
|
|
170
180
|
- Clear SLAS tokens when OID is changed [#178](https://github.com/SalesforceCommerceCloud/pwa-kit/pull/178)
|
|
171
181
|
|
|
172
|
-
|
|
182
|
+
### v1.1.0 (Sep 27, 2021)
|
|
173
183
|
|
|
174
184
|
- Fix wishlist bugs and provide better hooks for wishlist features. [#64](https://github.com/SalesforceCommerceCloud/pwa-kit/pull/64)
|
|
175
185
|
- Lazy load Popover component. [#134](https://github.com/SalesforceCommerceCloud/pwa-kit/pull/134)
|
|
@@ -182,6 +192,6 @@ The older versions below were published without the `@salesforce` namespace.
|
|
|
182
192
|
- Security - `inquirer` package upgrade. [#121](https://github.com/SalesforceCommerceCloud/pwa-kit/pull/121)
|
|
183
193
|
- New quantity picker. [#102](https://github.com/SalesforceCommerceCloud/pwa-kit/pull/102)
|
|
184
194
|
|
|
185
|
-
|
|
195
|
+
### v1.0.0 (Sep 08, 2021)
|
|
186
196
|
|
|
187
197
|
- PWA Kit General Availability and open source. 🎉
|
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
import React, {useState, useEffect} from 'react'
|
|
9
9
|
import PropTypes from 'prop-types'
|
|
10
10
|
import {useHistory, useLocation} from 'react-router-dom'
|
|
11
|
+
import StorefrontPreview from '@salesforce/pwa-kit-react-sdk/storefront-preview'
|
|
11
12
|
import {getAssetUrl} from '@salesforce/pwa-kit-react-sdk/ssr/universal/utils'
|
|
12
13
|
import {getAppOrigin} from '@salesforce/pwa-kit-react-sdk/utils/url'
|
|
13
14
|
import {getConfig} from '@salesforce/pwa-kit-runtime/utils/ssr-config'
|
|
@@ -112,7 +113,7 @@ const App = (props) => {
|
|
|
112
113
|
const {children} = props
|
|
113
114
|
const {data: categoriesTree} = useLazyLoadCategories()
|
|
114
115
|
const categories = flatten(categoriesTree || {}, 'categories')
|
|
115
|
-
|
|
116
|
+
const {getTokenWhenReady} = useAccessToken()
|
|
116
117
|
const appOrigin = getAppOrigin()
|
|
117
118
|
|
|
118
119
|
const history = useHistory()
|
|
@@ -267,7 +268,6 @@ const App = (props) => {
|
|
|
267
268
|
const path = buildUrl('/account/wishlist')
|
|
268
269
|
history.push(path)
|
|
269
270
|
}
|
|
270
|
-
|
|
271
271
|
return (
|
|
272
272
|
<Box className="sf-app" {...styles.container}>
|
|
273
273
|
<IntlProvider
|
|
@@ -294,6 +294,7 @@ const App = (props) => {
|
|
|
294
294
|
defaultLocale={DEFAULT_LOCALE}
|
|
295
295
|
>
|
|
296
296
|
<CurrencyProvider currency={currency}>
|
|
297
|
+
<StorefrontPreview getToken={getTokenWhenReady} />
|
|
297
298
|
<Seo>
|
|
298
299
|
<meta name="theme-color" content={THEME_COLOR} />
|
|
299
300
|
<meta name="apple-mobile-web-app-title" content={DEFAULT_SITE_TITLE} />
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright (c) 2023, Salesforce, Inc.
|
|
3
|
+
* All rights reserved.
|
|
4
|
+
* SPDX-License-Identifier: BSD-3-Clause
|
|
5
|
+
* For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/BSD-3-Clause
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import React from 'react'
|
|
9
|
+
import PropTypes from 'prop-types'
|
|
10
|
+
import {Skeleton, Text} from '@salesforce/retail-react-app/app/components/shared/ui'
|
|
11
|
+
import {useIntl} from 'react-intl'
|
|
12
|
+
import {useCurrency} from '@salesforce/retail-react-app/app/hooks'
|
|
13
|
+
|
|
14
|
+
const DisplayPrice = ({
|
|
15
|
+
basePrice,
|
|
16
|
+
discountPrice,
|
|
17
|
+
isProductASet = false,
|
|
18
|
+
currency,
|
|
19
|
+
discountPriceProps,
|
|
20
|
+
basePriceProps,
|
|
21
|
+
skeletonProps
|
|
22
|
+
}) => {
|
|
23
|
+
const intl = useIntl()
|
|
24
|
+
const {currency: activeCurrency} = useCurrency()
|
|
25
|
+
return (
|
|
26
|
+
<Skeleton isLoaded={basePrice} display={'flex'} {...skeletonProps}>
|
|
27
|
+
<Text fontWeight="bold" fontSize="md" mr={1}>
|
|
28
|
+
{isProductASet &&
|
|
29
|
+
`${intl.formatMessage({
|
|
30
|
+
id: 'product_view.label.starting_at_price',
|
|
31
|
+
defaultMessage: 'Starting at'
|
|
32
|
+
})} `}
|
|
33
|
+
</Text>
|
|
34
|
+
{typeof discountPrice === 'number' && (
|
|
35
|
+
<Text as="b" {...discountPriceProps}>
|
|
36
|
+
{intl.formatNumber(discountPrice, {
|
|
37
|
+
style: 'currency',
|
|
38
|
+
currency: currency || activeCurrency
|
|
39
|
+
})}
|
|
40
|
+
</Text>
|
|
41
|
+
)}
|
|
42
|
+
<Text
|
|
43
|
+
as={discountPrice > 0 ? 's' : 'b'}
|
|
44
|
+
ml={discountPrice > 0 ? 2 : 0}
|
|
45
|
+
fontWeight={discountPrice ? 'normal' : 'bold'}
|
|
46
|
+
{...basePriceProps}
|
|
47
|
+
>
|
|
48
|
+
{intl.formatNumber(basePrice, {
|
|
49
|
+
style: 'currency',
|
|
50
|
+
currency: currency || activeCurrency
|
|
51
|
+
})}
|
|
52
|
+
</Text>
|
|
53
|
+
</Skeleton>
|
|
54
|
+
)
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
DisplayPrice.propTypes = {
|
|
58
|
+
basePrice: PropTypes.oneOfType([PropTypes.number, PropTypes.string]),
|
|
59
|
+
discountPrice: PropTypes.number,
|
|
60
|
+
currency: PropTypes.string,
|
|
61
|
+
isProductASet: PropTypes.bool,
|
|
62
|
+
discountPriceProps: PropTypes.object,
|
|
63
|
+
basePriceProps: PropTypes.object,
|
|
64
|
+
skeletonProps: PropTypes.object
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export default DisplayPrice
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright (c) 2021, salesforce.com, inc.
|
|
3
|
+
* All rights reserved.
|
|
4
|
+
* SPDX-License-Identifier: BSD-3-Clause
|
|
5
|
+
* For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/BSD-3-Clause
|
|
6
|
+
*/
|
|
7
|
+
import React from 'react'
|
|
8
|
+
import {screen, within} from '@testing-library/react'
|
|
9
|
+
import DisplayPrice from '@salesforce/retail-react-app/app/components/display-price/index'
|
|
10
|
+
import {renderWithProviders} from '@salesforce/retail-react-app/app/utils/test-utils'
|
|
11
|
+
|
|
12
|
+
describe('DisplayPrice', function () {
|
|
13
|
+
test('should render without error', () => {
|
|
14
|
+
renderWithProviders(<DisplayPrice currency="GBP" basePrice={100} discountPrice={90} />)
|
|
15
|
+
expect(screen.getByText(/£90\.00/i)).toBeInTheDocument()
|
|
16
|
+
expect(screen.getByText(/£100\.00/i)).toBeInTheDocument()
|
|
17
|
+
})
|
|
18
|
+
|
|
19
|
+
test('should render according html tag for prices', () => {
|
|
20
|
+
const {container} = renderWithProviders(
|
|
21
|
+
<DisplayPrice currency="GBP" basePrice={100} discountPrice={90} />
|
|
22
|
+
)
|
|
23
|
+
const discountPriceTag = container.querySelectorAll('b')
|
|
24
|
+
const basePriceTag = container.querySelectorAll('s')
|
|
25
|
+
expect(within(discountPriceTag[0]).getByText(/£90\.00/i)).toBeDefined()
|
|
26
|
+
expect(within(basePriceTag[0]).getByText(/£100\.00/i)).toBeDefined()
|
|
27
|
+
expect(discountPriceTag).toHaveLength(1)
|
|
28
|
+
expect(basePriceTag).toHaveLength(1)
|
|
29
|
+
})
|
|
30
|
+
|
|
31
|
+
test('should not render discount price if not available', () => {
|
|
32
|
+
renderWithProviders(<DisplayPrice currency="GBP" basePrice={100} />)
|
|
33
|
+
expect(screen.queryByText(/£90\.00/i)).not.toBeInTheDocument()
|
|
34
|
+
expect(screen.getByText(/£100\.00/i)).toBeInTheDocument()
|
|
35
|
+
})
|
|
36
|
+
})
|
|
@@ -10,6 +10,7 @@ import {
|
|
|
10
10
|
stateOptions,
|
|
11
11
|
provinceOptions
|
|
12
12
|
} from '@salesforce/retail-react-app/app/components/forms/state-province-options'
|
|
13
|
+
import {SHIPPING_COUNTRY_CODES} from '@salesforce/retail-react-app/app/constants'
|
|
13
14
|
|
|
14
15
|
const messages = defineMessages({
|
|
15
16
|
required: {defaultMessage: 'Required', id: 'use_address_fields.error.required'},
|
|
@@ -103,10 +104,7 @@ export default function useAddressFields({
|
|
|
103
104
|
label: formatMessage(messages.country),
|
|
104
105
|
defaultValue: 'US',
|
|
105
106
|
type: 'select',
|
|
106
|
-
options:
|
|
107
|
-
{value: 'CA', label: 'Canada'},
|
|
108
|
-
{value: 'US', label: 'United States'}
|
|
109
|
-
],
|
|
107
|
+
options: SHIPPING_COUNTRY_CODES,
|
|
110
108
|
rules: {
|
|
111
109
|
required: formatMessage({
|
|
112
110
|
defaultMessage: 'Please select your country.',
|
|
@@ -84,7 +84,7 @@ VisaSymbol.viewBox = VisaSymbol.viewBox || '0 0 38 22'
|
|
|
84
84
|
* @param {string} name - the filename of the imported svg (does not include extension)
|
|
85
85
|
*/
|
|
86
86
|
/* istanbul ignore next */
|
|
87
|
-
const icon = (name, passProps) => {
|
|
87
|
+
export const icon = (name, passProps) => {
|
|
88
88
|
const displayName = name
|
|
89
89
|
.toLowerCase()
|
|
90
90
|
.replace(/(?:^|[\s-/])\w/g, (match) => match.toUpperCase())
|
|
@@ -31,16 +31,15 @@ import ImageGallery from '@salesforce/retail-react-app/app/components/image-gall
|
|
|
31
31
|
import Breadcrumb from '@salesforce/retail-react-app/app/components/breadcrumb'
|
|
32
32
|
import Link from '@salesforce/retail-react-app/app/components/link'
|
|
33
33
|
import withRegistration from '@salesforce/retail-react-app/app/components/with-registration'
|
|
34
|
-
import {useCurrency} from '@salesforce/retail-react-app/app/hooks'
|
|
35
34
|
import {Skeleton as ImageGallerySkeleton} from '@salesforce/retail-react-app/app/components/image-gallery'
|
|
36
35
|
import {HideOnDesktop, HideOnMobile} from '@salesforce/retail-react-app/app/components/responsive'
|
|
37
36
|
import QuantityPicker from '@salesforce/retail-react-app/app/components/quantity-picker'
|
|
38
37
|
import {useToast} from '@salesforce/retail-react-app/app/hooks/use-toast'
|
|
39
38
|
import {API_ERROR_MESSAGE} from '@salesforce/retail-react-app/app/constants'
|
|
39
|
+
import DisplayPrice from '@salesforce/retail-react-app/app/components/display-price'
|
|
40
|
+
import {getDisplayPrice} from '@salesforce/retail-react-app/app/utils/product-utils'
|
|
40
41
|
|
|
41
|
-
const ProductViewHeader = ({name,
|
|
42
|
-
const intl = useIntl()
|
|
43
|
-
const {currency: activeCurrency} = useCurrency()
|
|
42
|
+
const ProductViewHeader = ({name, basePrice, discountPrice, currency, category, productType}) => {
|
|
44
43
|
const isProductASet = productType?.set
|
|
45
44
|
|
|
46
45
|
return (
|
|
@@ -56,27 +55,20 @@ const ProductViewHeader = ({name, price, currency, category, productType}) => {
|
|
|
56
55
|
<Heading fontSize="2xl">{`${name}`}</Heading>
|
|
57
56
|
</Skeleton>
|
|
58
57
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
defaultMessage: 'Starting at'
|
|
66
|
-
})} `}
|
|
67
|
-
{intl.formatNumber(price, {
|
|
68
|
-
style: 'currency',
|
|
69
|
-
currency: currency || activeCurrency
|
|
70
|
-
})}
|
|
71
|
-
</Text>
|
|
72
|
-
</Skeleton>
|
|
58
|
+
<DisplayPrice
|
|
59
|
+
basePrice={basePrice}
|
|
60
|
+
discountPrice={discountPrice}
|
|
61
|
+
currency={currency}
|
|
62
|
+
isProductASet={isProductASet}
|
|
63
|
+
/>
|
|
73
64
|
</VStack>
|
|
74
65
|
)
|
|
75
66
|
}
|
|
76
67
|
|
|
77
68
|
ProductViewHeader.propTypes = {
|
|
78
69
|
name: PropTypes.string,
|
|
79
|
-
|
|
70
|
+
basePrice: PropTypes.number,
|
|
71
|
+
discountPrice: PropTypes.number,
|
|
80
72
|
currency: PropTypes.string,
|
|
81
73
|
category: PropTypes.array,
|
|
82
74
|
productType: PropTypes.object
|
|
@@ -134,6 +126,7 @@ const ProductView = forwardRef(
|
|
|
134
126
|
stockLevel,
|
|
135
127
|
stepQuantity
|
|
136
128
|
} = useDerivedProduct(product, isProductPartOfSet)
|
|
129
|
+
const {basePrice, discountPrice} = getDisplayPrice(product)
|
|
137
130
|
const canAddToWishlist = !isProductLoading
|
|
138
131
|
const isProductASet = product?.type.set
|
|
139
132
|
const errorContainerRef = useRef(null)
|
|
@@ -299,7 +292,8 @@ const ProductView = forwardRef(
|
|
|
299
292
|
<Box display={['block', 'block', 'block', 'none']}>
|
|
300
293
|
<ProductViewHeader
|
|
301
294
|
name={product?.name}
|
|
302
|
-
|
|
295
|
+
basePrice={basePrice}
|
|
296
|
+
discountPrice={discountPrice}
|
|
303
297
|
productType={product?.type}
|
|
304
298
|
currency={product?.currency}
|
|
305
299
|
category={category}
|
|
@@ -338,7 +332,8 @@ const ProductView = forwardRef(
|
|
|
338
332
|
<Box display={['none', 'none', 'none', 'block']}>
|
|
339
333
|
<ProductViewHeader
|
|
340
334
|
name={product?.name}
|
|
341
|
-
|
|
335
|
+
basePrice={basePrice}
|
|
336
|
+
discountPrice={discountPrice}
|
|
342
337
|
productType={product?.type}
|
|
343
338
|
currency={product?.currency}
|
|
344
339
|
category={category}
|
package/app/constants.js
CHANGED
|
@@ -107,3 +107,8 @@ export const EINSTEIN_RECOMMENDERS = {
|
|
|
107
107
|
EMPTY_SEARCH_RESULTS_TOP_SELLERS: 'home-top-revenue-for-category',
|
|
108
108
|
EMPTY_SEARCH_RESULTS_MOST_VIEWED: 'products-in-all-categories'
|
|
109
109
|
}
|
|
110
|
+
|
|
111
|
+
export const SHIPPING_COUNTRY_CODES = [
|
|
112
|
+
{value: 'CA', label: 'Canada'},
|
|
113
|
+
{value: 'US', label: 'United States'}
|
|
114
|
+
]
|
|
@@ -29,8 +29,12 @@ import Link from '@salesforce/retail-react-app/app/components/link'
|
|
|
29
29
|
import RecommendedProducts from '@salesforce/retail-react-app/app/components/recommended-products'
|
|
30
30
|
import {LockIcon} from '@salesforce/retail-react-app/app/components/icons'
|
|
31
31
|
import {findImageGroupBy} from '@salesforce/retail-react-app/app/utils/image-groups-utils'
|
|
32
|
-
import {
|
|
32
|
+
import {
|
|
33
|
+
getDisplayPrice,
|
|
34
|
+
getDisplayVariationValues
|
|
35
|
+
} from '@salesforce/retail-react-app/app/utils/product-utils'
|
|
33
36
|
import {EINSTEIN_RECOMMENDERS} from '@salesforce/retail-react-app/app/constants'
|
|
37
|
+
import DisplayPrice from '@salesforce/retail-react-app/app/components/display-price'
|
|
34
38
|
|
|
35
39
|
/**
|
|
36
40
|
* This is the context for managing the AddToCartModal.
|
|
@@ -63,7 +67,7 @@ export const AddToCartModal = () => {
|
|
|
63
67
|
derivedData: {totalItems}
|
|
64
68
|
} = useCurrentBasket()
|
|
65
69
|
const size = useBreakpointValue({base: 'full', lg: '2xl', xl: '4xl'})
|
|
66
|
-
const {currency,
|
|
70
|
+
const {currency, productSubTotal} = basket
|
|
67
71
|
const numerOfItemsAdded = itemsAdded.reduce((acc, {quantity}) => acc + quantity, 0)
|
|
68
72
|
|
|
69
73
|
if (!isOpen) {
|
|
@@ -110,10 +114,10 @@ export const AddToCartModal = () => {
|
|
|
110
114
|
viewType: 'small',
|
|
111
115
|
selectedVariationAttributes: variant.variationValues
|
|
112
116
|
})?.images?.[0]
|
|
113
|
-
const
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
+
const {
|
|
118
|
+
basePrice: lineItemBasePrice,
|
|
119
|
+
discountPrice: lineItemDiscountPrice
|
|
120
|
+
} = getDisplayPrice(product)
|
|
117
121
|
const variationAttributeValues = getDisplayVariationValues(
|
|
118
122
|
product.variationAttributes,
|
|
119
123
|
variant.variationValues
|
|
@@ -165,13 +169,12 @@ export const AddToCartModal = () => {
|
|
|
165
169
|
</Flex>
|
|
166
170
|
|
|
167
171
|
<Box flex="none" alignSelf="flex-end" fontWeight="600">
|
|
168
|
-
<
|
|
169
|
-
{
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
</Text>
|
|
172
|
+
<DisplayPrice
|
|
173
|
+
discountPriceProps={{as: 'p'}}
|
|
174
|
+
basePrice={lineItemBasePrice * quantity}
|
|
175
|
+
discountPrice={lineItemDiscountPrice * quantity}
|
|
176
|
+
currency={currency}
|
|
177
|
+
/>
|
|
175
178
|
</Box>
|
|
176
179
|
</Flex>
|
|
177
180
|
)
|
|
@@ -10,6 +10,7 @@ import {useVariant} from '@salesforce/retail-react-app/app/hooks/use-variant'
|
|
|
10
10
|
import {useIntl} from 'react-intl'
|
|
11
11
|
import {useVariationParams} from '@salesforce/retail-react-app/app/hooks/use-variation-params'
|
|
12
12
|
import {useVariationAttributes} from '@salesforce/retail-react-app/app/hooks/use-variation-attributes'
|
|
13
|
+
import {getDisplayPrice} from '@salesforce/retail-react-app/app/utils/product-utils'
|
|
13
14
|
|
|
14
15
|
const OUT_OF_STOCK = 'OUT_OF_STOCK'
|
|
15
16
|
const UNFULFILLABLE = 'UNFULFILLABLE'
|
|
@@ -54,6 +55,8 @@ export const useDerivedProduct = (product, isProductPartOfSet = false) => {
|
|
|
54
55
|
(isOutOfStock && inventoryMessages[OUT_OF_STOCK]) ||
|
|
55
56
|
(unfulfillable && inventoryMessages[UNFULFILLABLE])
|
|
56
57
|
|
|
58
|
+
const {basePrice, discountPrice} = getDisplayPrice(product)
|
|
59
|
+
|
|
57
60
|
// If the `initialQuantity` changes, update the state. This typically happens
|
|
58
61
|
// when either the master product changes, or the inventory of the product changes
|
|
59
62
|
// from out-of-stock to in-stock or vice versa.
|
|
@@ -72,6 +75,8 @@ export const useDerivedProduct = (product, isProductPartOfSet = false) => {
|
|
|
72
75
|
variationParams,
|
|
73
76
|
setQuantity,
|
|
74
77
|
variant,
|
|
75
|
-
stockLevel
|
|
78
|
+
stockLevel,
|
|
79
|
+
basePrice,
|
|
80
|
+
discountPrice
|
|
76
81
|
}
|
|
77
82
|
}
|
package/app/mocks/mock-data.js
CHANGED
|
@@ -2211,6 +2211,14 @@ export const mockedCustomerProductListsDetails = {
|
|
|
2211
2211
|
{
|
|
2212
2212
|
calloutMsg: '$50offOrderCountAbove5',
|
|
2213
2213
|
promotionId: '$50offOrderCountAbove5'
|
|
2214
|
+
},
|
|
2215
|
+
{
|
|
2216
|
+
promotionalPrice: 189.0,
|
|
2217
|
+
promotionId: '10$offIpod'
|
|
2218
|
+
},
|
|
2219
|
+
{
|
|
2220
|
+
promotionalPrice: 194.0,
|
|
2221
|
+
promotionId: '5$offIpod'
|
|
2214
2222
|
}
|
|
2215
2223
|
],
|
|
2216
2224
|
shortDescription:
|
|
@@ -5,6 +5,6 @@
|
|
|
5
5
|
* For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/BSD-3-Clause
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
-
import {Page, pageType} from '@salesforce/
|
|
8
|
+
import {Page, pageType} from '@salesforce/commerce-sdk-react/components'
|
|
9
9
|
|
|
10
10
|
export {Page, pageType}
|
|
@@ -24,6 +24,7 @@ import {useCurrentBasket} from '@salesforce/retail-react-app/app/hooks/use-curre
|
|
|
24
24
|
import {useCheckout} from '@salesforce/retail-react-app/app/pages/checkout/util/checkout-context'
|
|
25
25
|
import {
|
|
26
26
|
getPaymentInstrumentCardType,
|
|
27
|
+
getMaskCreditCardNumber,
|
|
27
28
|
getCreditCardIcon
|
|
28
29
|
} from '@salesforce/retail-react-app/app/utils/cc-utils'
|
|
29
30
|
import {
|
|
@@ -84,7 +85,7 @@ const Payment = () => {
|
|
|
84
85
|
paymentMethodId: 'CREDIT_CARD',
|
|
85
86
|
paymentCard: {
|
|
86
87
|
holder: formValue.holder,
|
|
87
|
-
|
|
88
|
+
maskedNumber: getMaskCreditCardNumber(formValue.number),
|
|
88
89
|
cardType: getPaymentInstrumentCardType(formValue.cardType),
|
|
89
90
|
expirationMonth: parseInt(expirationMonth),
|
|
90
91
|
expirationYear: parseInt(`20${expirationYear}`)
|
|
@@ -31,6 +31,8 @@ import RecommendedProducts from '@salesforce/retail-react-app/app/components/rec
|
|
|
31
31
|
import ProductView from '@salesforce/retail-react-app/app/components/product-view'
|
|
32
32
|
import InformationAccordion from '@salesforce/retail-react-app/app/pages/product-detail/partials/information-accordion'
|
|
33
33
|
|
|
34
|
+
import {HTTPNotFound, HTTPError} from '@salesforce/pwa-kit-react-sdk/ssr/universal/errors'
|
|
35
|
+
|
|
34
36
|
// constant
|
|
35
37
|
import {
|
|
36
38
|
API_ERROR_MESSAGE,
|
|
@@ -66,7 +68,12 @@ const ProductDetail = () => {
|
|
|
66
68
|
/*************************** Product Detail and Category ********************/
|
|
67
69
|
const {productId} = useParams()
|
|
68
70
|
const urlParams = new URLSearchParams(location.search)
|
|
69
|
-
const {
|
|
71
|
+
const {
|
|
72
|
+
data: product,
|
|
73
|
+
isLoading: isProductLoading,
|
|
74
|
+
isError: isProductError,
|
|
75
|
+
error: productError
|
|
76
|
+
} = useProduct(
|
|
70
77
|
{
|
|
71
78
|
parameters: {
|
|
72
79
|
id: urlParams.get('pid') || productId,
|
|
@@ -79,15 +86,43 @@ const ProductDetail = () => {
|
|
|
79
86
|
keepPreviousData: true
|
|
80
87
|
}
|
|
81
88
|
)
|
|
82
|
-
|
|
89
|
+
|
|
83
90
|
// Note: Since category needs id from product detail, it can't be server side rendered atm
|
|
84
91
|
// until we can do dependent query on server
|
|
85
|
-
const {
|
|
92
|
+
const {
|
|
93
|
+
data: category,
|
|
94
|
+
isError: isCategoryError,
|
|
95
|
+
error: categoryError
|
|
96
|
+
} = useCategory({
|
|
86
97
|
parameters: {
|
|
87
98
|
id: product?.primaryCategoryId,
|
|
88
99
|
level: 1
|
|
89
100
|
}
|
|
90
101
|
})
|
|
102
|
+
|
|
103
|
+
/**************** Error Handling ****************/
|
|
104
|
+
|
|
105
|
+
if (isProductError) {
|
|
106
|
+
const errorStatus = productError?.response?.status
|
|
107
|
+
switch (errorStatus) {
|
|
108
|
+
case 404:
|
|
109
|
+
throw new HTTPNotFound('Product Not Found.')
|
|
110
|
+
default:
|
|
111
|
+
throw new HTTPError(`HTTP Error ${errorStatus} occurred.`)
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
if (isCategoryError) {
|
|
115
|
+
const errorStatus = categoryError?.response?.status
|
|
116
|
+
switch (errorStatus) {
|
|
117
|
+
case 404:
|
|
118
|
+
throw new HTTPNotFound('Category Not Found.')
|
|
119
|
+
default:
|
|
120
|
+
throw new HTTPError(`HTTP Error ${errorStatus} occurred.`)
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
const isProductASet = product?.type.set
|
|
125
|
+
|
|
91
126
|
const [primaryCategory, setPrimaryCategory] = useState(category)
|
|
92
127
|
const variant = useVariant(product)
|
|
93
128
|
// This page uses the `primaryCategoryId` to retrieve the category data. This attribute
|
|
@@ -91,7 +91,6 @@ test('Allows customer to create an account', async () => {
|
|
|
91
91
|
await user.type(withinForm.getByLabelText('Last Name'), 'Tester')
|
|
92
92
|
await user.type(withinForm.getByPlaceholderText(/you@email.com/i), 'customer@test.com')
|
|
93
93
|
await user.type(withinForm.getAllByLabelText(/password/i)[0], 'Password!1')
|
|
94
|
-
screen.logTestingPlaygroundURL()
|
|
95
94
|
|
|
96
95
|
// login with credentials
|
|
97
96
|
global.server.use(
|
package/app/ssr.js
CHANGED
|
@@ -11,6 +11,7 @@ import path from 'path'
|
|
|
11
11
|
import {getRuntime} from '@salesforce/pwa-kit-runtime/ssr/server/express'
|
|
12
12
|
import {isRemote} from '@salesforce/pwa-kit-runtime/utils/ssr-server'
|
|
13
13
|
import {getConfig} from '@salesforce/pwa-kit-runtime/utils/ssr-config'
|
|
14
|
+
import {getAppOrigin} from '@salesforce/pwa-kit-react-sdk/utils/url'
|
|
14
15
|
import helmet from 'helmet'
|
|
15
16
|
|
|
16
17
|
const options = {
|
|
@@ -35,19 +36,64 @@ const options = {
|
|
|
35
36
|
const runtime = getRuntime()
|
|
36
37
|
|
|
37
38
|
const {handler} = runtime.createHandler(options, (app) => {
|
|
39
|
+
const getRuntimeEnv = () => {
|
|
40
|
+
if (process.env.NODE_ENV !== 'production') return process.env.NODE_ENV ?? 'development'
|
|
41
|
+
const origin = getAppOrigin()
|
|
42
|
+
// mobify-storefront-staging sites have NODE_ENV set to production, but for the purposes
|
|
43
|
+
// of CSP we consider the sites to be staging.
|
|
44
|
+
return origin.endsWith('.mobify-storefront-staging.com') ? 'staging' : 'production'
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// This is a temporary solution while we work on Storefront Preview - a full solution will
|
|
48
|
+
// prevent required CSP headers from being modified.
|
|
49
|
+
const getCSP = () => {
|
|
50
|
+
const trustedMap = {
|
|
51
|
+
development: [
|
|
52
|
+
'localhost:*',
|
|
53
|
+
'*.commercecloud.salesforce.com',
|
|
54
|
+
'*.demandware.net',
|
|
55
|
+
'*.mobify-staging.com',
|
|
56
|
+
'*.mobify-storefront-staging.com',
|
|
57
|
+
'*.mobify-storefront.com',
|
|
58
|
+
'runtime.commercecloud.com'
|
|
59
|
+
],
|
|
60
|
+
staging: [
|
|
61
|
+
'*.demandware.net',
|
|
62
|
+
'*.mobify-staging.com',
|
|
63
|
+
'*.mobify-storefront-staging.com',
|
|
64
|
+
'*.mobify-storefront.com',
|
|
65
|
+
'*.commercecloud.salesforce.com',
|
|
66
|
+
'runtime.commercecloud.com'
|
|
67
|
+
],
|
|
68
|
+
production: [
|
|
69
|
+
'*.demandware.com',
|
|
70
|
+
'*.mobify.com',
|
|
71
|
+
'*.mobify-storefront.com',
|
|
72
|
+
'*.commercecloud.salesforce.com',
|
|
73
|
+
'runtime.commercecloud.com'
|
|
74
|
+
]
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const env = getRuntimeEnv()
|
|
78
|
+
const trusted = ["'self'", ...(trustedMap[env] ? trustedMap[env] : [])]
|
|
79
|
+
|
|
80
|
+
return {
|
|
81
|
+
'connect-src': ['api.cquotient.com', ...trusted],
|
|
82
|
+
'frame-ancestors': [...trusted],
|
|
83
|
+
'img-src': ['data:', ...trusted],
|
|
84
|
+
'script-src': ["'unsafe-eval'", 'storage.googleapis.com', ...trusted],
|
|
85
|
+
|
|
86
|
+
// Do not upgrade insecure requests for local development
|
|
87
|
+
'upgrade-insecure-requests': isRemote() ? [] : null
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
38
91
|
// Set HTTP security headers
|
|
39
92
|
app.use(
|
|
40
93
|
helmet({
|
|
41
94
|
contentSecurityPolicy: {
|
|
42
95
|
useDefaults: true,
|
|
43
|
-
directives:
|
|
44
|
-
'img-src': ["'self'", '*.commercecloud.salesforce.com', 'data:'],
|
|
45
|
-
'script-src': ["'self'", "'unsafe-eval'", 'storage.googleapis.com'],
|
|
46
|
-
'connect-src': ["'self'", 'api.cquotient.com'],
|
|
47
|
-
|
|
48
|
-
// Do not upgrade insecure requests for local development
|
|
49
|
-
'upgrade-insecure-requests': isRemote() ? [] : null
|
|
50
|
-
}
|
|
96
|
+
directives: getCSP()
|
|
51
97
|
},
|
|
52
98
|
hsts: isRemote()
|
|
53
99
|
})
|
package/app/utils/cc-utils.js
CHANGED
|
@@ -83,6 +83,21 @@ export const getPaymentInstrumentCardType = (type) => {
|
|
|
83
83
|
}[type]
|
|
84
84
|
}
|
|
85
85
|
|
|
86
|
+
/**
|
|
87
|
+
* Returns the masked credit card number by removing white spaces,
|
|
88
|
+
* replacing all digits except the last 4 using the '*' character.
|
|
89
|
+
* @param cardNumber - The card number unmasked
|
|
90
|
+
* @returns {string} - The masked card number
|
|
91
|
+
*/
|
|
92
|
+
export const getMaskCreditCardNumber = (cardNumber) => {
|
|
93
|
+
const trimmedCardNumber = cardNumber.replace(/\s/g, '')
|
|
94
|
+
|
|
95
|
+
const maskedDigits = trimmedCardNumber.slice(0, -4).replace(/\d/g, '*')
|
|
96
|
+
const lastFourDigits = trimmedCardNumber.slice(-4)
|
|
97
|
+
|
|
98
|
+
return maskedDigits + lastFourDigits
|
|
99
|
+
}
|
|
100
|
+
|
|
86
101
|
export const createCreditCardPaymentBodyFromForm = (paymentFormData) => {
|
|
87
102
|
// Using destructuring to omit properties
|
|
88
103
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
@@ -33,3 +33,21 @@ export const getDisplayVariationValues = (variationAttributes, values = {}) => {
|
|
|
33
33
|
}, {})
|
|
34
34
|
return returnVal
|
|
35
35
|
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* This function extract the promotional price from a product. If there are more than one price, the smallest price will be picked
|
|
39
|
+
* @param {object} product - product detail object
|
|
40
|
+
* @returns {{discountPrice: number, basePrice: number | string}}
|
|
41
|
+
*/
|
|
42
|
+
export const getDisplayPrice = (product) => {
|
|
43
|
+
const basePrice = product?.pricePerUnit || product?.price
|
|
44
|
+
const promotionalPriceList = product?.productPromotions
|
|
45
|
+
?.map((promo) => promo.promotionalPrice)
|
|
46
|
+
.filter((i) => i !== null && i !== undefined)
|
|
47
|
+
// choose the smallest price among the promotionalPrice
|
|
48
|
+
const discountPrice = promotionalPriceList?.length ? Math.min(...promotionalPriceList) : null
|
|
49
|
+
return {
|
|
50
|
+
basePrice,
|
|
51
|
+
discountPrice
|
|
52
|
+
}
|
|
53
|
+
}
|
|
@@ -5,7 +5,11 @@
|
|
|
5
5
|
* For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/BSD-3-Clause
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
-
import {
|
|
8
|
+
import {
|
|
9
|
+
getDisplayPrice,
|
|
10
|
+
getDisplayVariationValues
|
|
11
|
+
} from '@salesforce/retail-react-app/app/utils/product-utils'
|
|
12
|
+
import {mockedCustomerProductListsDetails} from '@salesforce/retail-react-app/app/mocks/mock-data'
|
|
9
13
|
|
|
10
14
|
const variationAttributes = [
|
|
11
15
|
{
|
|
@@ -49,3 +53,25 @@ test('getDisplayVariationValues', () => {
|
|
|
49
53
|
Width: 'M'
|
|
50
54
|
})
|
|
51
55
|
})
|
|
56
|
+
|
|
57
|
+
describe('getDisplayPrice', function () {
|
|
58
|
+
test('returns basePrice and discountPrice', () => {
|
|
59
|
+
const {basePrice, discountPrice} = getDisplayPrice(
|
|
60
|
+
mockedCustomerProductListsDetails.data[0]
|
|
61
|
+
)
|
|
62
|
+
|
|
63
|
+
expect(basePrice).toBe(199.0)
|
|
64
|
+
expect(discountPrice).toBe(189.0)
|
|
65
|
+
})
|
|
66
|
+
|
|
67
|
+
test('returns null if there is not discount promotion', () => {
|
|
68
|
+
const data = {
|
|
69
|
+
...mockedCustomerProductListsDetails.data[0],
|
|
70
|
+
productPromotions: []
|
|
71
|
+
}
|
|
72
|
+
const {basePrice, discountPrice} = getDisplayPrice(data)
|
|
73
|
+
|
|
74
|
+
expect(basePrice).toBe(199.0)
|
|
75
|
+
expect(discountPrice).toBeNull()
|
|
76
|
+
})
|
|
77
|
+
})
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@salesforce/retail-react-app",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "2.1.0-nightly-20230927165653",
|
|
4
4
|
"license": "See license in LICENSE",
|
|
5
5
|
"author": "cc-pwa-kit@salesforce.com",
|
|
6
6
|
"ccExtensibility": {
|
|
@@ -45,10 +45,10 @@
|
|
|
45
45
|
"@lhci/cli": "^0.11.0",
|
|
46
46
|
"@loadable/component": "^5.15.3",
|
|
47
47
|
"@peculiar/webcrypto": "^1.4.2",
|
|
48
|
-
"@salesforce/commerce-sdk-react": "1.
|
|
49
|
-
"@salesforce/pwa-kit-dev": "3.
|
|
50
|
-
"@salesforce/pwa-kit-react-sdk": "3.
|
|
51
|
-
"@salesforce/pwa-kit-runtime": "3.
|
|
48
|
+
"@salesforce/commerce-sdk-react": "1.1.0-nightly-20230927165653",
|
|
49
|
+
"@salesforce/pwa-kit-dev": "3.2.0-nightly-20230927165653",
|
|
50
|
+
"@salesforce/pwa-kit-react-sdk": "3.2.0-nightly-20230927165653",
|
|
51
|
+
"@salesforce/pwa-kit-runtime": "3.2.0-nightly-20230927165653",
|
|
52
52
|
"@tanstack/react-query": "^4.28.0",
|
|
53
53
|
"@tanstack/react-query-devtools": "^4.29.1",
|
|
54
54
|
"@testing-library/dom": "^9.0.1",
|
|
@@ -103,5 +103,5 @@
|
|
|
103
103
|
"overrides": {
|
|
104
104
|
"nwsapi": "2.2.2"
|
|
105
105
|
},
|
|
106
|
-
"gitHead": "
|
|
106
|
+
"gitHead": "0ce804a25eeb347d8b4d2fb74752907b20ce4031"
|
|
107
107
|
}
|