Skip to content

Commit

Permalink
Merge branch 'develop' into bendvc/W-15226710-fix-promo-code
Browse files Browse the repository at this point in the history
Signed-off-by: Ben Chypak <[email protected]>
  • Loading branch information
bendvc authored Mar 11, 2024
2 parents c6349d9 + 8593b95 commit 3dd1865
Show file tree
Hide file tree
Showing 14 changed files with 212 additions and 9 deletions.
1 change: 1 addition & 0 deletions packages/commerce-sdk-react/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
## v1.4.0-dev (Jan 22, 2024)
- Fix invalid query params warnings and allow custom query [#1655](https://github.com/SalesforceCommerceCloud/pwa-kit/pull/1655)
- Add Shopper SEO hook [#1688](https://github.com/SalesforceCommerceCloud/pwa-kit/pull/1688)

## v1.3.0 (Jan 19, 2024)

Expand Down
8 changes: 4 additions & 4 deletions packages/commerce-sdk-react/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion packages/commerce-sdk-react/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@
"version": "node ./scripts/version.js"
},
"dependencies": {
"commerce-sdk-isomorphic": "^1.11.0",
"commerce-sdk-isomorphic": "^1.12.0",
"js-cookie": "^3.0.1",
"jwt-decode": "^4.0.0"
},
Expand Down
16 changes: 16 additions & 0 deletions packages/commerce-sdk-react/src/hooks/ShopperSeo/index.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
/*
* Copyright (c) 2024, 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 {ShopperSeo} from 'commerce-sdk-isomorphic'
import {getUnimplementedEndpoints} from '../../test-utils'
import * as queries from './query'

describe('Shopper Seo hooks', () => {
test('all endpoints have hooks', () => {
const unimplemented = getUnimplementedEndpoints(ShopperSeo, queries)
expect(unimplemented).toEqual([])
})
})
8 changes: 8 additions & 0 deletions packages/commerce-sdk-react/src/hooks/ShopperSeo/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
/*
* Copyright (c) 2024, 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
*/

export * from './query'
12 changes: 12 additions & 0 deletions packages/commerce-sdk-react/src/hooks/ShopperSeo/paramKeys.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
/*
* Copyright (c) 2024, 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
*/

const getUrlMapping = ['organizationId', 'urlSegment', 'siteId', 'locale'] as const

export default {
getUrlMapping
}
58 changes: 58 additions & 0 deletions packages/commerce-sdk-react/src/hooks/ShopperSeo/query.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
/*
* Copyright (c) 2024, 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 nock from 'nock'
import {
mockQueryEndpoint,
renderHookWithProviders,
waitAndExpectError,
waitAndExpectSuccess
} from '../../test-utils'
import * as queries from './query'

jest.mock('../../auth/index.ts', () => {
const {default: mockAuth} = jest.requireActual('../../auth/index.ts')
mockAuth.prototype.ready = jest.fn().mockResolvedValue({access_token: 'access_token'})
return mockAuth
})

type Queries = typeof queries
const seoEndpoint = '/site/shopper-seo'
// Not all endpoints use all parameters, but unused parameters are safely discarded
const OPTIONS = {parameters: {urlSegment: '/something'}}

/** Map of query name to returned data type */
type DataType<K extends keyof Queries> = NonNullable<ReturnType<Queries[K]>['data']>
type TestMap = {[K in keyof Queries]: DataType<K>}
// This is an object rather than an array to more easily ensure we cover all hooks
const testMap: TestMap = {
// Type assertion so we don't need to use the full type
useUrlMapping: {urlSegment: '/sample-product-redirected'} as DataType<'useUrlMapping'>
}
// Type assertion is necessary because `Object.entries` is limited
const testCases = Object.entries(testMap) as Array<[keyof TestMap, TestMap[keyof TestMap]]>
describe('Shopper SEO query hooks', () => {
beforeEach(() => nock.cleanAll())
afterEach(() => {
expect(nock.pendingMocks()).toHaveLength(0)
})
test.each(testCases)('`%s` returns data on success', async (queryName, data) => {
mockQueryEndpoint(seoEndpoint, data)
const {result} = renderHookWithProviders(() => {
return queries[queryName](OPTIONS)
})
await waitAndExpectSuccess(() => result.current)
expect(result.current.data).toEqual(data)
})

test.each(testCases)('`%s` returns error on error', async (queryName) => {
mockQueryEndpoint(seoEndpoint, {}, 400)
const {result} = renderHookWithProviders(() => {
return queries[queryName](OPTIONS)
})
await waitAndExpectError(() => result.current)
})
})
57 changes: 57 additions & 0 deletions packages/commerce-sdk-react/src/hooks/ShopperSeo/query.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
/*
* Copyright (c) 2024, 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 {UseQueryResult} from '@tanstack/react-query'
import {ApiClients, ApiQueryOptions, Argument, DataType, NullableParameters} from '../types'
import useCommerceApi from '../useCommerceApi'
import {useQuery} from '../useQuery'
import {getCustomKeys, mergeOptions, omitNullableParameters, pick} from '../utils'
import * as queryKeyHelpers from './queryKeyHelpers'
import paramKeysMap from './paramKeys'

type Client = ApiClients['shopperSeo']

/**
* Gets URL mapping information for a URL that a shopper clicked or typed in.
*
* The mapping information is based on URL rules and redirects set up in Business Manager.
* For more information about prerequisites and sample usage, see [URL Resolution](https://developer.salesforce.com/docs/commerce/commerce-api/guide/url-resolution.html). You can customize the behavior of this endpoint by using hooks.
* @group ShopperSeo
* @category Query
* @parameter apiOptions - Options to pass through to `commerce-sdk-isomorphic`, with `null` accepted for unset API parameters.
* @parameter queryOptions - TanStack Query query options, with `enabled` by default set to check that all required API parameters have been set.
* @returns A TanStack Query query hook with data from the Shopper Seo `getUrlMapping` endpoint.
* @see {@link https://developer.salesforce.com/docs/commerce/commerce-api/references/shopper-seo?meta=getUrlMapping| Salesforce Developer Center} for more information about the API endpoint.
* @see {@link https://salesforcecommercecloud.github.io/commerce-sdk-isomorphic/classes/shopperseo.shopperseo-1.html#geturlmapping | `commerce-sdk-isomorphic` documentation} for more information on the parameters and returned data type.
* @see {@link https://tanstack.com/query/latest/docs/react/reference/useQuery | TanStack Query `useQuery` reference} for more information about the return value.
*/
export const useUrlMapping = (
apiOptions: NullableParameters<Argument<Client['getUrlMapping']>>,
queryOptions: ApiQueryOptions<Client['getUrlMapping']> = {}
): UseQueryResult<DataType<Client['getUrlMapping']>> => {
type Options = Argument<Client['getUrlMapping']>
type Data = DataType<Client['getUrlMapping']>
const {shopperSeo: client} = useCommerceApi()
const methodName = 'getUrlMapping'
const requiredParameters = ['organizationId', 'siteId'] as const

// Parameters can be set in `apiOptions` or `client.clientConfig`;
// we must merge them in order to generate the correct query key.
const netOptions = omitNullableParameters(mergeOptions(client, apiOptions))
const paramKeys = [...paramKeysMap[methodName], ...getCustomKeys(netOptions.parameters)]
const parameters = pick(netOptions.parameters, paramKeys)
const queryKey = queryKeyHelpers[methodName].queryKey(netOptions.parameters)
// We don't use `netOptions` here because we manipulate the options in `useQuery`.
const method = async (options: Options) => await client[methodName](options)

// For some reason, if we don't explicitly set these generic parameters, the inferred type for
// `Data` sometimes, but not always, includes `Response`, which is incorrect. I don't know why.
return useQuery<Client, Options, Data>({...netOptions, parameters}, queryOptions, {
method,
queryKey,
requiredParameters
})
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
/*
* Copyright (c) 2024, 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 type {ShopperSeo} from 'commerce-sdk-isomorphic'
import {Argument, ExcludeTail} from '../types'
import {getCustomKeys, pick} from '../utils'
import paramKeysMap from './paramKeys'
// We must use a client with no parameters in order to have required/optional match the API spec
type Client = ShopperSeo<{shortCode: string}>
type Params<T extends keyof QueryKeys> = Partial<Argument<Client[T]>['parameters']>
export type QueryKeys = {
getUrlMapping: [
'/commerce-sdk-react',
'/organizations/',
string | undefined,
'/url-mapping',
Params<'getUrlMapping'>
]
}

// This is defined here, rather than `types.ts`, because it relies on `Client` and `QueryKeys`,
// and making those generic would add too much complexity.
type QueryKeyHelper<T extends keyof QueryKeys> = {
/** Generates the path component of the query key for an endpoint. */
path: (params: Params<T>) => ExcludeTail<QueryKeys[T]>
/** Generates the full query key for an endpoint. */
queryKey: (params: Params<T>) => QueryKeys[T]
}

export const getUrlMapping: QueryKeyHelper<'getUrlMapping'> = {
path: (params) => [
'/commerce-sdk-react',
'/organizations/',
params.organizationId,
'/url-mapping'
],
queryKey: (params: Params<'getUrlMapping'>) => {
const paramKeys = [...paramKeysMap['getUrlMapping'], ...getCustomKeys(params)]

return [...getUrlMapping.path(params), pick(params, paramKeys)]
}
}
3 changes: 2 additions & 1 deletion packages/commerce-sdk-react/src/hooks/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2023, Salesforce, Inc.
* Copyright (c) 2024, 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
Expand All @@ -14,6 +14,7 @@ export * from './ShopperOrders'
export * from './ShopperProducts'
export * from './ShopperPromotions'
export * from './ShopperSearch'
export * from './ShopperSeo'
export * from './useAuthHelper'
export {default as useAccessToken} from './useAccessToken'
export {default as useCommerceApi} from './useCommerceApi'
Expand Down
4 changes: 3 additions & 1 deletion packages/commerce-sdk-react/src/hooks/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,8 @@ import {
ShopperOrders,
ShopperProducts,
ShopperPromotions,
ShopperSearch
ShopperSearch,
ShopperSeo
} from 'commerce-sdk-isomorphic'

// --- GENERAL UTILITIES --- //
Expand Down Expand Up @@ -90,6 +91,7 @@ export interface ApiClients {
shopperProducts: ShopperProducts<ApiClientConfigParams>
shopperPromotions: ShopperPromotions<ApiClientConfigParams>
shopperSearch: ShopperSearch<ApiClientConfigParams>
shopperSeo: ShopperSeo<ApiClientConfigParams>
}

export type ApiClient = ApiClients[keyof ApiClients]
Expand Down
4 changes: 3 additions & 1 deletion packages/commerce-sdk-react/src/provider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import {
ShopperPromotions,
ShopperGiftCertificates,
ShopperSearch,
ShopperSeo,
ShopperBasketsTypes
} from 'commerce-sdk-isomorphic'
import Auth from './auth'
Expand Down Expand Up @@ -122,7 +123,8 @@ const CommerceApiProvider = (props: CommerceApiProviderProps): ReactElement => {
shopperOrders: new ShopperOrders(config),
shopperProducts: new ShopperProducts(config),
shopperPromotions: new ShopperPromotions(config),
shopperSearch: new ShopperSearch(config)
shopperSearch: new ShopperSearch(config),
shopperSeo: new ShopperSeo(config)
}
}, [
clientId,
Expand Down
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 Fixes

- Fix promo codes not being properly applied in cart [#1692](https://github.com/SalesforceCommerceCloud/pwa-kit/pull/1692)
- Fix checkout shipping method fetching [#1693](https://github.com/SalesforceCommerceCloud/pwa-kit/pull/1693)
- Fix invalid query params warnings [#1655](https://github.com/SalesforceCommerceCloud/pwa-kit/pull/1655)
- Fix internal server error on account pages [#1675](https://github.com/SalesforceCommerceCloud/pwa-kit/pull/1675)
- Fix `product-item` component imports to ensure that it is overridable. [#1672](https://github.com/SalesforceCommerceCloud/pwa-kit/pull/1672)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ export default function ShippingOptions() {
}
},
{
enabled: Boolean(basket?.basketId) && step === STEPS.ShippingOptions
enabled: Boolean(basket?.basketId) && step === STEPS.SHIPPING_OPTIONS
}
)

Expand Down

0 comments on commit 3dd1865

Please sign in to comment.