@salesforce/retail-react-app 9.1.0-preview.2 → 9.1.1-preview.0
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 +7 -4
- package/app/components/_app/index.jsx +15 -20
- package/app/components/_error/index.jsx +6 -1
- package/app/components/_error/index.test.js +16 -0
- package/app/components/drawer-menu/drawer-menu.jsx +5 -1
- package/app/components/footer/index.jsx +3 -2
- package/app/utils/site-utils.js +21 -1
- package/app/utils/site-utils.test.js +70 -0
- package/app/utils/url.js +14 -3
- package/app/utils/url.test.js +43 -16
- package/config/default.js +1 -1
- package/package.json +6 -6
package/CHANGELOG.md
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
## v9.1.
|
|
2
|
-
-
|
|
3
|
-
|
|
4
|
-
|
|
1
|
+
## v9.1.1-preview.0 (Mar 12, 2026)
|
|
2
|
+
- Add base path prefix to support multiple MRT environments under 1 domain [#3614](https://github.com/SalesforceCommerceCloud/pwa-kit/pull/3614)
|
|
3
|
+
|
|
4
|
+
## v9.1.0 (Mar 12, 2026)
|
|
5
5
|
- Add Page Designer Support [#3727](https://github.com/SalesforceCommerceCloud/pwa-kit/pull/3727)
|
|
6
6
|
- [Feature] Add Salesforce Payments support in checkout [#3725](https://github.com/SalesforceCommerceCloud/pwa-kit/pull/3725)
|
|
7
7
|
- One Click Checkout removed from Developer Preview. When shoppers use passwordless OTP login with one-click checkout, the system saves their shipping and payment information for faster checkout in the future. Security safeguards required: (1) Captcha - Protects the passwordless login from bots. (2) OTP for Email Changes - Verifies identity before an email update, prevents accidental account lockouts from typos, and prevents unauthorized access to saved payment methods.
|
|
@@ -12,6 +12,9 @@
|
|
|
12
12
|
- [Bugfix] Fix for custom billing address as returning shoppers in 1CC [#3693](https://github.com/SalesforceCommerceCloud/pwa-kit/pull/3693)
|
|
13
13
|
- [Feature] Add translations for text in 1CC [#3703](https://github.com/SalesforceCommerceCloud/pwa-kit/pull/3703)
|
|
14
14
|
- [Bugfix] Fix lost custom billing address after OTP registration [#3741](https://github.com/SalesforceCommerceCloud/pwa-kit/pull/3741)
|
|
15
|
+
- [Bugfix] Fix edirect payment methods status value to pascal [#3734](https://github.com/SalesforceCommerceCloud/pwa-kit/pull/3734)
|
|
16
|
+
- [Bugfix] Fix in checkout and cart page: LoadingSpinner to have full screen overlay [#3730](https://github.com/SalesforceCommerceCloud/pwa-kit/pull/3730)
|
|
17
|
+
- [Bugfix] Fix adding to cart from a master product in the wishlist [#3732](https://github.com/SalesforceCommerceCloud/pwa-kit/pull/3732)
|
|
15
18
|
|
|
16
19
|
## v9.0.0 (Feb 12, 2026)
|
|
17
20
|
- [Feature] One Click Checkout (in Developer Preview) [#3552](https://github.com/SalesforceCommerceCloud/pwa-kit/pull/3552)
|
|
@@ -5,23 +5,23 @@
|
|
|
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 React, {useState, useEffect, useMemo} from 'react'
|
|
9
|
+
import PropTypes from 'prop-types'
|
|
10
|
+
import {useHistory, useLocation} from 'react-router-dom'
|
|
11
|
+
import {StorefrontPreview} from '@salesforce/commerce-sdk-react/components'
|
|
12
|
+
import {getAssetUrl, getRouterBasePath} from '@salesforce/pwa-kit-react-sdk/ssr/universal/utils'
|
|
13
|
+
import useActiveData from '@salesforce/retail-react-app/app/hooks/use-active-data'
|
|
14
|
+
import {useQuery} from '@tanstack/react-query'
|
|
8
15
|
import {
|
|
9
16
|
useAccessToken,
|
|
10
17
|
useCategory,
|
|
11
18
|
useShopperBasketsMutation,
|
|
12
19
|
useUsid
|
|
13
20
|
} from '@salesforce/commerce-sdk-react'
|
|
14
|
-
import {StorefrontPreview} from '@salesforce/commerce-sdk-react/components'
|
|
15
|
-
import {getAssetUrl} from '@salesforce/pwa-kit-react-sdk/ssr/universal/utils'
|
|
16
21
|
import {useServerContext} from '@salesforce/pwa-kit-react-sdk/ssr/universal/hooks'
|
|
17
22
|
import {getConfig} from '@salesforce/pwa-kit-runtime/utils/ssr-config'
|
|
18
|
-
import useActiveData from '@salesforce/retail-react-app/app/hooks/use-active-data'
|
|
19
23
|
import {useAppOrigin} from '@salesforce/retail-react-app/app/hooks/use-app-origin'
|
|
20
24
|
import logger from '@salesforce/retail-react-app/app/utils/logger-instance'
|
|
21
|
-
import {useQuery} from '@tanstack/react-query'
|
|
22
|
-
import PropTypes from 'prop-types'
|
|
23
|
-
import React, {useEffect, useMemo, useState} from 'react'
|
|
24
|
-
import {useHistory, useLocation} from 'react-router-dom'
|
|
25
25
|
|
|
26
26
|
// Chakra
|
|
27
27
|
import {SkipNavContent, SkipNavLink} from '@chakra-ui/skip-nav'
|
|
@@ -315,9 +315,14 @@ const App = (props) => {
|
|
|
315
315
|
trackPage()
|
|
316
316
|
}, [location])
|
|
317
317
|
|
|
318
|
+
const getHrefForLocale = (localeId) =>
|
|
319
|
+
`${appOrigin}${getRouterBasePath()}${getPathWithLocale(localeId, buildUrl, {
|
|
320
|
+
location: {...location, search: ''}
|
|
321
|
+
})}`
|
|
322
|
+
|
|
318
323
|
return (
|
|
319
324
|
<Box className="sf-app" {...styles.container}>
|
|
320
|
-
<StorefrontPreview getToken={getTokenWhenReady}>
|
|
325
|
+
<StorefrontPreview getToken={getTokenWhenReady} getBasePath={getRouterBasePath}>
|
|
321
326
|
<IntlProvider
|
|
322
327
|
onError={(err) => {
|
|
323
328
|
if (!messages) {
|
|
@@ -362,12 +367,7 @@ const App = (props) => {
|
|
|
362
367
|
<link
|
|
363
368
|
rel="alternate"
|
|
364
369
|
hrefLang={locale.id.toLowerCase()}
|
|
365
|
-
href={
|
|
366
|
-
location: {
|
|
367
|
-
...location,
|
|
368
|
-
search: ''
|
|
369
|
-
}
|
|
370
|
-
})}`}
|
|
370
|
+
href={getHrefForLocale(locale.id)}
|
|
371
371
|
key={locale.id}
|
|
372
372
|
/>
|
|
373
373
|
))}
|
|
@@ -375,12 +375,7 @@ const App = (props) => {
|
|
|
375
375
|
<link
|
|
376
376
|
rel="alternate"
|
|
377
377
|
hrefLang={site.l10n.defaultLocale.slice(0, 2)}
|
|
378
|
-
href={
|
|
379
|
-
location: {
|
|
380
|
-
...location,
|
|
381
|
-
search: ''
|
|
382
|
-
}
|
|
383
|
-
})}`}
|
|
378
|
+
href={getHrefForLocale(locale.id)}
|
|
384
379
|
/>
|
|
385
380
|
{/* A wider fallback for user locales that the app does not support */}
|
|
386
381
|
<link rel="alternate" hrefLang="x-default" href={`${appOrigin}/`} />
|
|
@@ -18,6 +18,7 @@ import {
|
|
|
18
18
|
} from '@salesforce/retail-react-app/app/components/shared/ui'
|
|
19
19
|
|
|
20
20
|
import {BrandLogo, FileIcon} from '@salesforce/retail-react-app/app/components/icons'
|
|
21
|
+
import {getRouterBasePath} from '@salesforce/pwa-kit-react-sdk/ssr/universal/utils'
|
|
21
22
|
|
|
22
23
|
// <Error> is rendered when:
|
|
23
24
|
//
|
|
@@ -53,7 +54,11 @@ const Error = (props) => {
|
|
|
53
54
|
// We need to use window.location.href here rather than history
|
|
54
55
|
// as the application is in an error state. We need to force a
|
|
55
56
|
// hard navigation to get back to the normal state.
|
|
56
|
-
|
|
57
|
+
// Include base path since this bypasses React Router
|
|
58
|
+
onClick={() => {
|
|
59
|
+
const basePath = getRouterBasePath()
|
|
60
|
+
window.location.href = basePath ? `${basePath}/` : '/'
|
|
61
|
+
}}
|
|
57
62
|
/>
|
|
58
63
|
</Box>
|
|
59
64
|
</Box>
|
|
@@ -6,11 +6,17 @@
|
|
|
6
6
|
*/
|
|
7
7
|
import React from 'react'
|
|
8
8
|
import Error from '@salesforce/retail-react-app/app/components/_error/index'
|
|
9
|
+
import {getRouterBasePath} from '@salesforce/pwa-kit-react-sdk/ssr/universal/utils'
|
|
9
10
|
// !!! ----- WARNING ----- WARNING ----- WARNING ----- !!!
|
|
10
11
|
// Tests use render instead of renderWithProviders because
|
|
11
12
|
// error component is rendered outside provider tree
|
|
12
13
|
// !!! ----------------------------------------------- !!!
|
|
13
14
|
import {screen, render} from '@testing-library/react'
|
|
15
|
+
|
|
16
|
+
jest.mock('@salesforce/pwa-kit-react-sdk/ssr/universal/utils', () => ({
|
|
17
|
+
getRouterBasePath: jest.fn(() => '')
|
|
18
|
+
}))
|
|
19
|
+
|
|
14
20
|
const originalLocation = window.location
|
|
15
21
|
|
|
16
22
|
afterEach(() => {
|
|
@@ -68,3 +74,13 @@ test('renders custom error message', () => {
|
|
|
68
74
|
render(<Error message="Custom error occurred" />)
|
|
69
75
|
expect(screen.getByText('Custom error occurred')).toBeInTheDocument()
|
|
70
76
|
})
|
|
77
|
+
|
|
78
|
+
test('clicking logo navigates to base path when set', () => {
|
|
79
|
+
delete window.location
|
|
80
|
+
window.location = {href: ''}
|
|
81
|
+
getRouterBasePath.mockReturnValueOnce('/my-base')
|
|
82
|
+
render(<Error />)
|
|
83
|
+
const logoBtn = screen.getByLabelText('logo')
|
|
84
|
+
logoBtn.click()
|
|
85
|
+
expect(window.location.href).toBe('/my-base/')
|
|
86
|
+
})
|
|
@@ -54,6 +54,7 @@ import {
|
|
|
54
54
|
// Others
|
|
55
55
|
import {noop} from '@salesforce/retail-react-app/app/utils/utils'
|
|
56
56
|
import {getPathWithLocale, categoryUrlBuilder} from '@salesforce/retail-react-app/app/utils/url'
|
|
57
|
+
import {getRouterBasePath} from '@salesforce/pwa-kit-react-sdk/ssr/universal/utils'
|
|
57
58
|
import LoadingSpinner from '@salesforce/retail-react-app/app/components/loading-spinner'
|
|
58
59
|
|
|
59
60
|
import useNavigation from '@salesforce/retail-react-app/app/hooks/use-navigation'
|
|
@@ -317,7 +318,10 @@ const DrawerMenu = ({
|
|
|
317
318
|
const newUrl = getPathWithLocale(newLocale, buildUrl, {
|
|
318
319
|
disallowParams: ['refine']
|
|
319
320
|
})
|
|
320
|
-
|
|
321
|
+
const basePath = getRouterBasePath()
|
|
322
|
+
window.location = basePath
|
|
323
|
+
? `${basePath}${newUrl}`
|
|
324
|
+
: newUrl
|
|
321
325
|
}}
|
|
322
326
|
/>
|
|
323
327
|
</Box>
|
|
@@ -22,6 +22,7 @@ import LinksList from '@salesforce/retail-react-app/app/components/links-list'
|
|
|
22
22
|
import SubscribeMarketingConsent from '@salesforce/retail-react-app/app/components/subscription'
|
|
23
23
|
import {HideOnDesktop, HideOnMobile} from '@salesforce/retail-react-app/app/components/responsive'
|
|
24
24
|
import {getPathWithLocale} from '@salesforce/retail-react-app/app/utils/url'
|
|
25
|
+
import {getRouterBasePath} from '@salesforce/pwa-kit-react-sdk/ssr/universal/utils'
|
|
25
26
|
import LocaleText from '@salesforce/retail-react-app/app/components/locale-text'
|
|
26
27
|
import useMultiSite from '@salesforce/retail-react-app/app/hooks/use-multi-site'
|
|
27
28
|
import styled from '@emotion/styled'
|
|
@@ -151,8 +152,8 @@ const Footer = ({...otherProps}) => {
|
|
|
151
152
|
const newUrl = getPathWithLocale(target.value, buildUrl, {
|
|
152
153
|
disallowParams: ['refine']
|
|
153
154
|
})
|
|
154
|
-
|
|
155
|
-
window.location = newUrl
|
|
155
|
+
const basePath = getRouterBasePath()
|
|
156
|
+
window.location = basePath ? `${basePath}${newUrl}` : newUrl
|
|
156
157
|
}}
|
|
157
158
|
variant="filled"
|
|
158
159
|
aria-label={intl.formatMessage({
|
package/app/utils/site-utils.js
CHANGED
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
8
|
import {getConfig} from '@salesforce/pwa-kit-runtime/utils/ssr-config'
|
|
9
|
+
import {getRouterBasePath} from '@salesforce/pwa-kit-react-sdk/ssr/universal/utils'
|
|
9
10
|
|
|
10
11
|
/**
|
|
11
12
|
* This functions takes an url and returns a site object,
|
|
@@ -94,6 +95,20 @@ export const getSiteByReference = (siteRef) => {
|
|
|
94
95
|
)
|
|
95
96
|
}
|
|
96
97
|
|
|
98
|
+
/**
|
|
99
|
+
* Remove the base path from a path string only when path equals basePath or path starts with basePath + '/'.
|
|
100
|
+
* @param {string} path - the path to strip
|
|
101
|
+
* @param {string} basePath - the base path to remove
|
|
102
|
+
* @returns {string} the path with base path removed, or the original path
|
|
103
|
+
*/
|
|
104
|
+
export const removeBasePathFromPath = (path, basePath) => {
|
|
105
|
+
if (!basePath) return path
|
|
106
|
+
if (path.startsWith(basePath + '/') || path === basePath) {
|
|
107
|
+
return path.substring(basePath.length) || '/'
|
|
108
|
+
}
|
|
109
|
+
return path
|
|
110
|
+
}
|
|
111
|
+
|
|
97
112
|
/**
|
|
98
113
|
* This function return the identifiers (site and locale) from the given url
|
|
99
114
|
* The site will always go before locale if both of them are presented in the pathname
|
|
@@ -101,7 +116,12 @@ export const getSiteByReference = (siteRef) => {
|
|
|
101
116
|
* @returns {{siteRef: string, localeRef: string}} - site and locale reference (it could either be id or alias)
|
|
102
117
|
*/
|
|
103
118
|
export const getParamsFromPath = (path) => {
|
|
104
|
-
|
|
119
|
+
let {pathname, search} = getPathnameAndSearch(path)
|
|
120
|
+
|
|
121
|
+
// Remove the base path from the pathname if present since
|
|
122
|
+
// it shifts the location of the site and locale in the pathname
|
|
123
|
+
const basePath = getRouterBasePath()
|
|
124
|
+
pathname = removeBasePathFromPath(pathname, basePath)
|
|
105
125
|
|
|
106
126
|
const config = getConfig()
|
|
107
127
|
const {pathMatcher, searchMatcherForSite, searchMatcherForLocale} = getConfigMatcher(config)
|
|
@@ -11,11 +11,13 @@ import {
|
|
|
11
11
|
resolveSiteFromUrl
|
|
12
12
|
} from '@salesforce/retail-react-app/app/utils/site-utils'
|
|
13
13
|
import {getConfig} from '@salesforce/pwa-kit-runtime/utils/ssr-config'
|
|
14
|
+
import {getRouterBasePath} from '@salesforce/pwa-kit-react-sdk/ssr/universal/utils'
|
|
14
15
|
|
|
15
16
|
import mockConfig from '@salesforce/retail-react-app/config/mocks/default'
|
|
16
17
|
import {
|
|
17
18
|
getParamsFromPath,
|
|
18
19
|
resolveLocaleFromUrl,
|
|
20
|
+
removeBasePathFromPath,
|
|
19
21
|
resolvePageDesignerParamsFromUrl
|
|
20
22
|
} from '@salesforce/retail-react-app/app/utils/site-utils'
|
|
21
23
|
jest.mock('@salesforce/pwa-kit-runtime/utils/ssr-config', () => {
|
|
@@ -26,8 +28,18 @@ jest.mock('@salesforce/pwa-kit-runtime/utils/ssr-config', () => {
|
|
|
26
28
|
}
|
|
27
29
|
})
|
|
28
30
|
|
|
31
|
+
jest.mock('@salesforce/pwa-kit-react-sdk/ssr/universal/utils', () => {
|
|
32
|
+
const original = jest.requireActual('@salesforce/pwa-kit-react-sdk/ssr/universal/utils')
|
|
33
|
+
return {
|
|
34
|
+
...original,
|
|
35
|
+
getRouterBasePath: jest.fn(() => '')
|
|
36
|
+
}
|
|
37
|
+
})
|
|
38
|
+
|
|
29
39
|
beforeEach(() => {
|
|
30
40
|
jest.resetModules()
|
|
41
|
+
// Reset the mock after resetModules
|
|
42
|
+
getRouterBasePath.mockReturnValue('')
|
|
31
43
|
})
|
|
32
44
|
|
|
33
45
|
afterEach(() => {
|
|
@@ -311,6 +323,64 @@ describe('getParamsFromPath', function () {
|
|
|
311
323
|
expect(getParamsFromPath(path)).toEqual(expectedRes)
|
|
312
324
|
})
|
|
313
325
|
})
|
|
326
|
+
|
|
327
|
+
describe('getParamsFromPath with base path', () => {
|
|
328
|
+
test('should remove base path from path when showBasePath is true', () => {
|
|
329
|
+
const basePath = '/test-base'
|
|
330
|
+
getRouterBasePath.mockReturnValue(basePath)
|
|
331
|
+
getConfig.mockImplementation(() => ({
|
|
332
|
+
...mockConfig,
|
|
333
|
+
app: {
|
|
334
|
+
...mockConfig.app,
|
|
335
|
+
url: {
|
|
336
|
+
...mockConfig.app.url,
|
|
337
|
+
showBasePath: true
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
}))
|
|
341
|
+
|
|
342
|
+
const path = `${basePath}/us/en-US/category/womens`
|
|
343
|
+
const result = getParamsFromPath(path)
|
|
344
|
+
expect(result).toEqual({siteRef: 'us', localeRef: 'en-US'})
|
|
345
|
+
})
|
|
346
|
+
|
|
347
|
+
test('should not strip when path has basePath only as substring (e.g. /shop vs /shopping/cart)', () => {
|
|
348
|
+
const basePath = '/shop'
|
|
349
|
+
getRouterBasePath.mockReturnValue(basePath)
|
|
350
|
+
getConfig.mockImplementation(() => ({
|
|
351
|
+
...mockConfig,
|
|
352
|
+
app: {
|
|
353
|
+
...mockConfig.app,
|
|
354
|
+
url: {
|
|
355
|
+
...mockConfig.app.url,
|
|
356
|
+
showBasePath: true
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
}))
|
|
360
|
+
|
|
361
|
+
const result = getParamsFromPath('/shopping/cart')
|
|
362
|
+
expect(result).toBeDefined()
|
|
363
|
+
})
|
|
364
|
+
})
|
|
365
|
+
})
|
|
366
|
+
|
|
367
|
+
describe('removeBasePathFromPath', () => {
|
|
368
|
+
test('removes when path starts with basePath + "/"', () => {
|
|
369
|
+
expect(removeBasePathFromPath('/shop/cart', '/shop')).toBe('/cart')
|
|
370
|
+
expect(removeBasePathFromPath('/test-base/uk/en-GB/foo', '/test-base')).toBe(
|
|
371
|
+
'/uk/en-GB/foo'
|
|
372
|
+
)
|
|
373
|
+
})
|
|
374
|
+
test('removes to "/" when path exactly equals basePath', () => {
|
|
375
|
+
expect(removeBasePathFromPath('/shop', '/shop')).toBe('/')
|
|
376
|
+
})
|
|
377
|
+
test('does not remove when basePath is only a substring (e.g. /shop vs /shopping/cart)', () => {
|
|
378
|
+
expect(removeBasePathFromPath('/shopping/cart', '/shop')).toBe('/shopping/cart')
|
|
379
|
+
expect(removeBasePathFromPath('/shopping', '/shop')).toBe('/shopping')
|
|
380
|
+
})
|
|
381
|
+
test('returns path unchanged when basePath is empty', () => {
|
|
382
|
+
expect(removeBasePathFromPath('/any/path', '')).toBe('/any/path')
|
|
383
|
+
})
|
|
314
384
|
})
|
|
315
385
|
|
|
316
386
|
describe('resolveLocaleFromUrl', function () {
|
package/app/utils/url.js
CHANGED
|
@@ -9,9 +9,11 @@ import {
|
|
|
9
9
|
getLocaleByReference,
|
|
10
10
|
getParamsFromPath,
|
|
11
11
|
getDefaultSite,
|
|
12
|
-
getSiteByReference
|
|
12
|
+
getSiteByReference,
|
|
13
|
+
removeBasePathFromPath
|
|
13
14
|
} from '@salesforce/retail-react-app/app/utils/site-utils'
|
|
14
15
|
import {HOME_HREF, urlPartPositions} from '@salesforce/retail-react-app/app/constants'
|
|
16
|
+
import {getRouterBasePath} from '@salesforce/pwa-kit-react-sdk/ssr/universal/utils'
|
|
15
17
|
|
|
16
18
|
/**
|
|
17
19
|
* Modifies a given url by adding/updating query parameters.
|
|
@@ -105,19 +107,23 @@ export const searchUrlBuilder = (searchTerm) => '/search?q=' + encodeURIComponen
|
|
|
105
107
|
|
|
106
108
|
/**
|
|
107
109
|
* Returns a relative URL for a locale short code.
|
|
108
|
-
* Based on your app configuration, this function will replace your current locale shortCode with a new one
|
|
110
|
+
* Based on your app configuration, this function will replace your current locale shortCode with a new one.
|
|
109
111
|
*
|
|
110
112
|
* @param {String} shortCode - The locale short code.
|
|
111
113
|
* @param {function(*, *, *, *=): string} - Generates a site URL from the provided path, site and locale.
|
|
112
114
|
* @param {string[]} opts.disallowParams - URL parameters to remove
|
|
113
115
|
* @param {Object} opts.location - location object to replace the default `window.location`
|
|
114
|
-
* @returns {String} url - The relative URL for the specific locale.
|
|
116
|
+
* @returns {String} url - The relative URL for the specific locale (without base path).
|
|
115
117
|
*/
|
|
116
118
|
export const getPathWithLocale = (shortCode, buildUrl, opts = {}) => {
|
|
117
119
|
const location = opts.location ? opts.location : window.location
|
|
118
120
|
let {siteRef, localeRef} = getParamsFromPath(`${location.pathname}${location.search}`)
|
|
119
121
|
let {pathname, search} = location
|
|
120
122
|
|
|
123
|
+
// sanitize the base path from current url if existing
|
|
124
|
+
const basePath = getRouterBasePath()
|
|
125
|
+
pathname = removeBasePathFromPath(pathname, basePath)
|
|
126
|
+
|
|
121
127
|
// sanitize the site from current url if existing
|
|
122
128
|
if (siteRef) {
|
|
123
129
|
pathname = pathname.replace(`/${siteRef}`, '')
|
|
@@ -153,6 +159,7 @@ export const getPathWithLocale = (shortCode, buildUrl, opts = {}) => {
|
|
|
153
159
|
site.alias || site.id,
|
|
154
160
|
locale?.alias || locale?.id
|
|
155
161
|
)
|
|
162
|
+
|
|
156
163
|
return newUrl
|
|
157
164
|
}
|
|
158
165
|
|
|
@@ -210,6 +217,7 @@ export const createUrlTemplate = (appConfig, siteRef, localeRef) => {
|
|
|
210
217
|
queryLocale && locale && searchParams.append('locale', locale)
|
|
211
218
|
queryString = `?${searchParams.toString()}`
|
|
212
219
|
}
|
|
220
|
+
|
|
213
221
|
return `${sitePath}${localePath}${path}${queryString}`
|
|
214
222
|
}
|
|
215
223
|
}
|
|
@@ -259,6 +267,9 @@ export const removeQueryParamsFromPath = (path, keys) => {
|
|
|
259
267
|
export const removeSiteLocaleFromPath = (pathName = '') => {
|
|
260
268
|
let {siteRef, localeRef} = getParamsFromPath(pathName)
|
|
261
269
|
|
|
270
|
+
const basePath = getRouterBasePath()
|
|
271
|
+
pathName = removeBasePathFromPath(pathName, basePath)
|
|
272
|
+
|
|
262
273
|
// remove the site alias from the current pathName
|
|
263
274
|
if (siteRef) {
|
|
264
275
|
pathName = pathName.replace(new RegExp(`/${siteRef}`, 'g'), '')
|
package/app/utils/url.test.js
CHANGED
|
@@ -17,27 +17,20 @@ import {
|
|
|
17
17
|
removeSiteLocaleFromPath,
|
|
18
18
|
serverSafeEncode
|
|
19
19
|
} from '@salesforce/retail-react-app/app/utils/url'
|
|
20
|
-
import {getUrlConfig} from '@salesforce/retail-react-app/app/utils/site-utils'
|
|
21
20
|
import mockConfig from '@salesforce/retail-react-app/config/mocks/default'
|
|
21
|
+
import {getRouterBasePath} from '@salesforce/pwa-kit-react-sdk/ssr/universal/utils'
|
|
22
|
+
import {getConfig} from '@salesforce/pwa-kit-runtime/utils/ssr-config'
|
|
22
23
|
|
|
23
24
|
afterEach(() => {
|
|
24
25
|
jest.clearAllMocks()
|
|
25
26
|
})
|
|
26
27
|
|
|
27
|
-
jest.mock('
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
...original,
|
|
31
|
-
getConfig: jest.fn(() => mockConfig)
|
|
32
|
-
}
|
|
33
|
-
})
|
|
28
|
+
jest.mock('@salesforce/pwa-kit-runtime/utils/ssr-config', () => ({
|
|
29
|
+
getConfig: jest.fn()
|
|
30
|
+
}))
|
|
34
31
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
return {
|
|
38
|
-
...original,
|
|
39
|
-
getUrlConfig: jest.fn()
|
|
40
|
-
}
|
|
32
|
+
beforeEach(() => {
|
|
33
|
+
getConfig.mockReturnValue(mockConfig)
|
|
41
34
|
})
|
|
42
35
|
jest.mock('@salesforce/pwa-kit-runtime/utils/ssr-namespace-paths', () => {
|
|
43
36
|
const original = jest.requireActual('@salesforce/pwa-kit-runtime/utils/ssr-namespace-paths')
|
|
@@ -47,6 +40,14 @@ jest.mock('@salesforce/pwa-kit-runtime/utils/ssr-namespace-paths', () => {
|
|
|
47
40
|
}
|
|
48
41
|
})
|
|
49
42
|
|
|
43
|
+
jest.mock('@salesforce/pwa-kit-react-sdk/ssr/universal/utils', () => {
|
|
44
|
+
const original = jest.requireActual('@salesforce/pwa-kit-react-sdk/ssr/universal/utils')
|
|
45
|
+
return {
|
|
46
|
+
...original,
|
|
47
|
+
getRouterBasePath: jest.fn(() => '')
|
|
48
|
+
}
|
|
49
|
+
})
|
|
50
|
+
|
|
50
51
|
describe('buildUrlSet returns the expected set of urls', () => {
|
|
51
52
|
test('when no values are passed in', () => {
|
|
52
53
|
const set = buildUrlSet()
|
|
@@ -138,8 +139,6 @@ describe('url builder test', () => {
|
|
|
138
139
|
})
|
|
139
140
|
|
|
140
141
|
describe('getPathWithLocale', () => {
|
|
141
|
-
getUrlConfig.mockImplementation(() => mockConfig.app.url)
|
|
142
|
-
|
|
143
142
|
test('getPathWithLocale returns expected for PLP', () => {
|
|
144
143
|
const location = new URL('http://localhost:3000/uk/it-IT/category/newarrivals-womens')
|
|
145
144
|
const buildUrl = createUrlTemplate(mockConfig.app, 'uk', 'it-IT')
|
|
@@ -186,6 +185,34 @@ describe('getPathWithLocale', () => {
|
|
|
186
185
|
const relativeUrl = getPathWithLocale('en-GB', buildUrl, {location})
|
|
187
186
|
expect(relativeUrl).toBe(`/`)
|
|
188
187
|
})
|
|
188
|
+
|
|
189
|
+
test('getPathWithLocale returns path without base path if base path is present', () => {
|
|
190
|
+
const basePath = '/test-base'
|
|
191
|
+
getRouterBasePath.mockReturnValue(basePath)
|
|
192
|
+
|
|
193
|
+
const location = new URL(
|
|
194
|
+
`http://localhost:3000${basePath}/uk/it-IT/category/newarrivals-womens`
|
|
195
|
+
)
|
|
196
|
+
const buildUrl = createUrlTemplate(mockConfig.app, 'uk', 'it-IT')
|
|
197
|
+
|
|
198
|
+
const path = getPathWithLocale('fr-FR', buildUrl, {location})
|
|
199
|
+
expect(path).toBe('/uk/fr/category/newarrivals-womens')
|
|
200
|
+
expect(path).not.toContain(basePath)
|
|
201
|
+
// Caller uses basePath + path for window.location or full href
|
|
202
|
+
expect(`${basePath}${path}`).toBe(`${basePath}/uk/fr/category/newarrivals-womens`)
|
|
203
|
+
})
|
|
204
|
+
|
|
205
|
+
test('getPathWithLocale does not strip when path has basePath only as substring (e.g. /shop vs /shopping/cart)', () => {
|
|
206
|
+
const basePath = '/shop'
|
|
207
|
+
getRouterBasePath.mockReturnValue(basePath)
|
|
208
|
+
|
|
209
|
+
const location = new URL('http://localhost:3000/shopping/cart')
|
|
210
|
+
const buildUrl = createUrlTemplate(mockConfig.app, 'uk', 'en-GB')
|
|
211
|
+
|
|
212
|
+
const path = getPathWithLocale('en-GB', buildUrl, {location})
|
|
213
|
+
expect(path).toContain('/shopping')
|
|
214
|
+
expect(path).not.toBe('/cart')
|
|
215
|
+
})
|
|
189
216
|
})
|
|
190
217
|
|
|
191
218
|
describe('createUrlTemplate tests', () => {
|
package/config/default.js
CHANGED
|
@@ -27,6 +27,7 @@ module.exports = {
|
|
|
27
27
|
site: 'path',
|
|
28
28
|
locale: 'path',
|
|
29
29
|
showDefaults: true,
|
|
30
|
+
showBasePath: false,
|
|
30
31
|
interpretPlusSignAsSpace: false
|
|
31
32
|
},
|
|
32
33
|
login: {
|
|
@@ -106,7 +107,6 @@ module.exports = {
|
|
|
106
107
|
apiKey: process.env.GOOGLE_CLOUD_API_KEY
|
|
107
108
|
}
|
|
108
109
|
},
|
|
109
|
-
envBasePath: '/',
|
|
110
110
|
externals: [],
|
|
111
111
|
pageNotFoundURL: '/page-not-found',
|
|
112
112
|
ssrEnabled: true,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@salesforce/retail-react-app",
|
|
3
|
-
"version": "9.1.
|
|
3
|
+
"version": "9.1.1-preview.0",
|
|
4
4
|
"license": "See license in LICENSE",
|
|
5
5
|
"author": "cc-pwa-kit@salesforce.com",
|
|
6
6
|
"ccExtensibility": {
|
|
@@ -46,10 +46,10 @@
|
|
|
46
46
|
"@loadable/component": "^5.15.3",
|
|
47
47
|
"@peculiar/webcrypto": "^1.4.2",
|
|
48
48
|
"@salesforce/cc-datacloud-typescript": "1.1.2",
|
|
49
|
-
"@salesforce/commerce-sdk-react": "5.1.
|
|
50
|
-
"@salesforce/pwa-kit-dev": "3.17.
|
|
51
|
-
"@salesforce/pwa-kit-react-sdk": "3.17.
|
|
52
|
-
"@salesforce/pwa-kit-runtime": "3.17.
|
|
49
|
+
"@salesforce/commerce-sdk-react": "5.1.1-preview.0",
|
|
50
|
+
"@salesforce/pwa-kit-dev": "3.17.1-preview.0",
|
|
51
|
+
"@salesforce/pwa-kit-react-sdk": "3.17.1-preview.0",
|
|
52
|
+
"@salesforce/pwa-kit-runtime": "3.17.1-preview.0",
|
|
53
53
|
"@salesforce/storefront-next-runtime": "0.1.1",
|
|
54
54
|
"@tanstack/react-query": "^4.28.0",
|
|
55
55
|
"@tanstack/react-query-devtools": "^4.29.1",
|
|
@@ -113,5 +113,5 @@
|
|
|
113
113
|
"maxSize": "391 kB"
|
|
114
114
|
}
|
|
115
115
|
],
|
|
116
|
-
"gitHead": "
|
|
116
|
+
"gitHead": "beabe8a971b6170bddfe08ae3f5ca2f1bfe66aa7"
|
|
117
117
|
}
|