diff --git a/packages/commerce-sdk-react/src/constant.ts b/packages/commerce-sdk-react/src/constant.ts index 6e447b35e1..af94f3e73c 100644 --- a/packages/commerce-sdk-react/src/constant.ts +++ b/packages/commerce-sdk-react/src/constant.ts @@ -46,6 +46,7 @@ export const SERVER_AFFINITY_HEADER_KEY = 'sfdc_dwsid' export const CLIENT_KEYS = { SHOPPER_BASKETS: 'shopperBaskets', + SHOPPER_BASKETS_V2: 'shopperBasketsV2', SHOPPER_CONFIGURATIONS: 'shopperConfigurations', SHOPPER_CONTEXTS: 'shopperContexts', SHOPPER_CUSTOMERS: 'shopperCustomers', diff --git a/packages/commerce-sdk-react/src/hooks/ShopperBaskets/cache.ts b/packages/commerce-sdk-react/src/hooks/ShopperBaskets/cache.ts index de0061ff13..e6ece1907d 100644 --- a/packages/commerce-sdk-react/src/hooks/ShopperBaskets/cache.ts +++ b/packages/commerce-sdk-react/src/hooks/ShopperBaskets/cache.ts @@ -5,7 +5,7 @@ * For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/BSD-3-Clause */ import { - ShopperBasketsV2Types, + ShopperBasketsTypes, ShopperCustomers, ShopperCustomersTypes } from 'commerce-sdk-isomorphic' @@ -30,7 +30,7 @@ import {CLIENT_KEYS} from '../../constant' const CLIENT_KEY = CLIENT_KEYS.SHOPPER_BASKETS type Client = NonNullable /** Data returned by every Shopper Baskets endpoint (except `deleteBasket`) */ -type Basket = ShopperBasketsV2Types.Basket +type Basket = ShopperBasketsTypes.Basket /** Data returned by `getCustomerBaskets` */ type CustomerBasketsResult = ShopperCustomersTypes.BasketsResult /** Parameters that get passed around, includes client config and possible parameters from other endpoints */ diff --git a/packages/commerce-sdk-react/src/hooks/ShopperBaskets/helpers.test.tsx b/packages/commerce-sdk-react/src/hooks/ShopperBaskets/helpers.test.tsx index f67b72f924..a17e74e377 100644 --- a/packages/commerce-sdk-react/src/hooks/ShopperBaskets/helpers.test.tsx +++ b/packages/commerce-sdk-react/src/hooks/ShopperBaskets/helpers.test.tsx @@ -9,7 +9,7 @@ import {useShopperBasketsMutationHelper} from './helpers' import {useCustomerBaskets} from '../ShopperCustomers' import {renderWithProviders} from '../../test-utils' import {screen, waitFor} from '@testing-library/react' -import {ShopperBasketsV2Types} from 'commerce-sdk-isomorphic' +import {ShopperBasketsTypes} from 'commerce-sdk-isomorphic' import jwt from 'jsonwebtoken' const basketId = '10cf6aa40edba4fcfcc6915594' @@ -52,7 +52,7 @@ const MockComponent = () => { price: 100, quantity: 1 } - ] as ShopperBasketsV2Types.ProductItem[] & Record<`c_${string}`, any>) + ] as ShopperBasketsTypes.ProductItem[] & Record<`c_${string}`, any>) .catch((e) => console.log('e', e)) }} > diff --git a/packages/commerce-sdk-react/src/hooks/ShopperBaskets/helpers.ts b/packages/commerce-sdk-react/src/hooks/ShopperBaskets/helpers.ts index 39c68fde27..705342fd79 100644 --- a/packages/commerce-sdk-react/src/hooks/ShopperBaskets/helpers.ts +++ b/packages/commerce-sdk-react/src/hooks/ShopperBaskets/helpers.ts @@ -8,12 +8,12 @@ import {useCustomerId, useShopperBasketsMutation} from '../index' import {useCustomerBaskets} from '../ShopperCustomers' import {ApiClients, Argument} from '../types' -import {ShopperBasketsV2Types} from 'commerce-sdk-isomorphic' +import {ShopperBasketsTypes} from 'commerce-sdk-isomorphic' import {CLIENT_KEYS} from '../../constant' const CLIENT_KEY = CLIENT_KEYS.SHOPPER_BASKETS type Client = NonNullable -type Basket = ShopperBasketsV2Types.Basket +type Basket = ShopperBasketsTypes.Basket /** * This is a helper function for Basket Mutations. diff --git a/packages/commerce-sdk-react/src/hooks/ShopperBaskets/index.test.ts b/packages/commerce-sdk-react/src/hooks/ShopperBaskets/index.test.ts index 3d193a3168..995072398a 100644 --- a/packages/commerce-sdk-react/src/hooks/ShopperBaskets/index.test.ts +++ b/packages/commerce-sdk-react/src/hooks/ShopperBaskets/index.test.ts @@ -4,7 +4,7 @@ * 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 {ShopperBasketsV2} from 'commerce-sdk-isomorphic' +import {ShopperBaskets} from 'commerce-sdk-isomorphic' import {getUnimplementedEndpoints} from '../../test-utils' import {cacheUpdateMatrix} from './cache' import {ShopperBasketsMutations as mutations} from './mutation' @@ -13,7 +13,7 @@ import * as queries from './query' describe('Shopper Baskets hooks', () => { test('all endpoints have hooks', () => { // unimplemented = SDK method exists, but no query hook or value in mutations enum - const unimplemented = getUnimplementedEndpoints(ShopperBasketsV2, queries, mutations) + const unimplemented = getUnimplementedEndpoints(ShopperBaskets, queries, mutations) // If this test fails: create a new query hook, add the endpoint to the mutations enum, // or add it to the `expected` array with a comment explaining "TODO" or "never" (and why). expect(unimplemented).toEqual([ diff --git a/packages/commerce-sdk-react/src/hooks/ShopperBaskets/mutation.test.ts b/packages/commerce-sdk-react/src/hooks/ShopperBaskets/mutation.test.ts index 8270c59e7d..38eff7ac2e 100644 --- a/packages/commerce-sdk-react/src/hooks/ShopperBaskets/mutation.test.ts +++ b/packages/commerce-sdk-react/src/hooks/ShopperBaskets/mutation.test.ts @@ -5,7 +5,7 @@ * For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/BSD-3-Clause */ import {act} from '@testing-library/react' -import {ShopperBasketsV2Types, ShopperCustomersTypes} from 'commerce-sdk-isomorphic' +import {ShopperBasketsTypes, ShopperCustomersTypes} from 'commerce-sdk-isomorphic' import nock from 'nock' import { assertInvalidateQuery, @@ -32,9 +32,9 @@ jest.mock('../../auth/index.ts', () => { const CLIENT_KEY = CLIENT_KEYS.SHOPPER_BASKETS type Client = NonNullable -type Basket = ShopperBasketsV2Types.Basket +type Basket = ShopperBasketsTypes.Basket type BasketsResult = ShopperCustomersTypes.BasketsResult -type ProductItem = ShopperBasketsV2Types.ProductItem +type ProductItem = ShopperBasketsTypes.ProductItem /** Create an options object for Shopper Baskets endpoints, with `basketId` pre-filled. */ const createOptions = >( diff --git a/packages/commerce-sdk-react/src/hooks/ShopperBaskets/query.ts b/packages/commerce-sdk-react/src/hooks/ShopperBaskets/query.ts index b62da1868b..0abd972050 100644 --- a/packages/commerce-sdk-react/src/hooks/ShopperBaskets/query.ts +++ b/packages/commerce-sdk-react/src/hooks/ShopperBaskets/query.ts @@ -9,7 +9,7 @@ import {ApiClients, ApiQueryOptions, Argument, DataType, NullableParameters} fro import {useQuery} from '../useQuery' import {mergeOptions, omitNullableParameters, pickValidParams} from '../utils' import * as queryKeyHelpers from './queryKeyHelpers' -import {ShopperBasketsV2} from 'commerce-sdk-isomorphic' +import {ShopperBaskets} from 'commerce-sdk-isomorphic' import {CLIENT_KEYS} from '../../constant' import useCommerceApi from '../useCommerceApi' @@ -35,15 +35,12 @@ export const useBasket = ( type Data = DataType const client = useCommerceApi(CLIENT_KEY) const methodName = 'getBasket' - const requiredParameters = ShopperBasketsV2.paramKeys[`${methodName}Required`] + const requiredParameters = ShopperBaskets.paramKeys[`${methodName}Required`] // 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 parameters = pickValidParams( - netOptions.parameters, - ShopperBasketsV2.paramKeys[methodName] - ) + const parameters = pickValidParams(netOptions.parameters, ShopperBaskets.paramKeys[methodName]) 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) @@ -80,15 +77,12 @@ export const usePaymentMethodsForBasket = ( type Data = DataType const client = useCommerceApi(CLIENT_KEY) const methodName = 'getPaymentMethodsForBasket' - const requiredParameters = ShopperBasketsV2.paramKeys[`${methodName}Required`] + const requiredParameters = ShopperBaskets.paramKeys[`${methodName}Required`] // 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 parameters = pickValidParams( - netOptions.parameters, - ShopperBasketsV2.paramKeys[methodName] - ) + const parameters = pickValidParams(netOptions.parameters, ShopperBaskets.paramKeys[methodName]) 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) @@ -125,15 +119,12 @@ export const usePriceBooksForBasket = ( type Data = DataType const client = useCommerceApi(CLIENT_KEY) const methodName = 'getPriceBooksForBasket' - const requiredParameters = ShopperBasketsV2.paramKeys[`${methodName}Required`] + const requiredParameters = ShopperBaskets.paramKeys[`${methodName}Required`] // 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 parameters = pickValidParams( - netOptions.parameters, - ShopperBasketsV2.paramKeys[methodName] - ) + const parameters = pickValidParams(netOptions.parameters, ShopperBaskets.paramKeys[methodName]) 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) @@ -170,15 +161,12 @@ export const useShippingMethodsForShipment = ( type Data = DataType const client = useCommerceApi(CLIENT_KEY) const methodName = 'getShippingMethodsForShipment' - const requiredParameters = ShopperBasketsV2.paramKeys[`${methodName}Required`] + const requiredParameters = ShopperBaskets.paramKeys[`${methodName}Required`] // 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 parameters = pickValidParams( - netOptions.parameters, - ShopperBasketsV2.paramKeys[methodName] - ) + const parameters = pickValidParams(netOptions.parameters, ShopperBaskets.paramKeys[methodName]) 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) @@ -215,15 +203,12 @@ export const useTaxesFromBasket = ( type Data = DataType const client = useCommerceApi(CLIENT_KEY) const methodName = 'getTaxesFromBasket' - const requiredParameters = ShopperBasketsV2.paramKeys[`${methodName}Required`] + const requiredParameters = ShopperBaskets.paramKeys[`${methodName}Required`] // 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 parameters = pickValidParams( - netOptions.parameters, - ShopperBasketsV2.paramKeys[methodName] - ) + const parameters = pickValidParams(netOptions.parameters, ShopperBaskets.paramKeys[methodName]) 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) diff --git a/packages/commerce-sdk-react/src/hooks/ShopperBaskets/queryKeyHelpers.ts b/packages/commerce-sdk-react/src/hooks/ShopperBaskets/queryKeyHelpers.ts index feb173302a..df58b08241 100644 --- a/packages/commerce-sdk-react/src/hooks/ShopperBaskets/queryKeyHelpers.ts +++ b/packages/commerce-sdk-react/src/hooks/ShopperBaskets/queryKeyHelpers.ts @@ -4,12 +4,12 @@ * 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 {ShopperBasketsV2} from 'commerce-sdk-isomorphic' +import {ShopperBaskets} from 'commerce-sdk-isomorphic' import {Argument, ExcludeTail} from '../types' import {pickValidParams} from '../utils' // We must use a client with no parameters in order to have required/optional match the API spec -type Client = ShopperBasketsV2<{shortCode: string}> +type Client = ShopperBaskets<{shortCode: string}> type Params = Partial['parameters']> export type QueryKeys = { getBasket: [ @@ -80,7 +80,7 @@ export const getBasket: QueryKeyHelper<'getBasket'> = { queryKey: (params: Params<'getBasket'>) => { return [ ...getBasket.path(params), - pickValidParams(params || {}, ShopperBasketsV2.paramKeys.getBasket) + pickValidParams(params || {}, ShopperBaskets.paramKeys.getBasket) ] } } @@ -97,7 +97,7 @@ export const getPaymentMethodsForBasket: QueryKeyHelper<'getPaymentMethodsForBas queryKey: (params: Params<'getPaymentMethodsForBasket'>) => { return [ ...getPaymentMethodsForBasket.path(params), - pickValidParams(params || {}, ShopperBasketsV2.paramKeys.getPaymentMethodsForBasket) + pickValidParams(params || {}, ShopperBaskets.paramKeys.getPaymentMethodsForBasket) ] } } @@ -114,7 +114,7 @@ export const getPriceBooksForBasket: QueryKeyHelper<'getPriceBooksForBasket'> = queryKey: (params: Params<'getPriceBooksForBasket'>) => { return [ ...getPriceBooksForBasket.path(params), - pickValidParams(params || {}, ShopperBasketsV2.paramKeys.getPriceBooksForBasket) + pickValidParams(params || {}, ShopperBaskets.paramKeys.getPriceBooksForBasket) ] } } @@ -133,7 +133,7 @@ export const getShippingMethodsForShipment: QueryKeyHelper<'getShippingMethodsFo queryKey: (params: Params<'getShippingMethodsForShipment'>) => { return [ ...getShippingMethodsForShipment.path(params), - pickValidParams(params || {}, ShopperBasketsV2.paramKeys.getShippingMethodsForShipment) + pickValidParams(params || {}, ShopperBaskets.paramKeys.getShippingMethodsForShipment) ] } } @@ -150,7 +150,7 @@ export const getTaxesFromBasket: QueryKeyHelper<'getTaxesFromBasket'> = { queryKey: (params: Params<'getTaxesFromBasket'>) => { return [ ...getTaxesFromBasket.path(params), - pickValidParams(params || {}, ShopperBasketsV2.paramKeys.getTaxesFromBasket) + pickValidParams(params || {}, ShopperBaskets.paramKeys.getTaxesFromBasket) ] } } diff --git a/packages/commerce-sdk-react/src/hooks/ShopperBasketsV2/cache.ts b/packages/commerce-sdk-react/src/hooks/ShopperBasketsV2/cache.ts new file mode 100644 index 0000000000..9ba60a6164 --- /dev/null +++ b/packages/commerce-sdk-react/src/hooks/ShopperBasketsV2/cache.ts @@ -0,0 +1,407 @@ +/* + * 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 {ShopperBasketsV2Types, ShopperCustomers, ShopperCustomersTypes} from 'commerce-sdk-isomorphic' +import { + ApiClients, + Argument, + CacheUpdateInvalidate, + CacheUpdateMatrix, + CacheUpdateUpdate, + MergedOptions +} from '../types' +import { + getBasket, + getPaymentMethodsForBasket, + getPriceBooksForBasket, + getShippingMethodsForShipment, + getTaxesFromBasket +} from './queryKeyHelpers' +import {getCustomerBaskets} from '../ShopperCustomers/queryKeyHelpers' +import {CLIENT_KEYS} from '../../constant' + +const CLIENT_KEY = CLIENT_KEYS.SHOPPER_BASKETS_V2 +type Client = NonNullable +/** Data returned by every Shopper Baskets endpoint (except `deleteBasket`) */ +type Basket = ShopperBasketsV2Types.Basket +/** Data returned by `getCustomerBaskets` */ +type CustomerBasketsResult = ShopperCustomersTypes.BasketsResult +/** Parameters that get passed around, includes client config and possible parameters from other endpoints */ +type BasketParameters = MergedOptions>['parameters'] +/** Parameters that we actually send to the API for `getCustomerBaskets` */ +type GetCustomerBasketsParameters = Argument< + ShopperCustomers<{shortCode: string}>['getCustomerBaskets'] +>['parameters'] + +const invalidateCustomerBasketsQuery = ( + customerId: string, + parameters: Omit +): CacheUpdateInvalidate => { + return { + queryKey: getCustomerBaskets.queryKey({...parameters, customerId}) + } +} + +const updateCustomerBasketsQuery = ( + customerId: string, + parameters: BasketParameters, + response: Basket +): CacheUpdateUpdate => { + return { + queryKey: getCustomerBaskets.queryKey({...parameters, customerId}), + updater: (oldData: CustomerBasketsResult | undefined) => { + if (!oldData?.baskets?.length) { + return { + baskets: [response], + total: 1 + } + } + const updatedBaskets = oldData.baskets.map((basket) => + basket.basketId === parameters.basketId ? response : basket + ) + return { + ...oldData, + // Shopper Customers and Shopper Baskets have different definitions for the `Basket` + // type. (99% similar, but that's not good enough for TypeScript.) + // TODO: Remove this type assertion when the RAML specs match. + baskets: updatedBaskets as CustomerBasketsResult['baskets'] + } + } + } +} + +export const cacheUpdateMatrix: CacheUpdateMatrix = { + addCouponToBasket(customerId, {parameters}, response) { + return { + update: [ + {queryKey: getBasket.queryKey(parameters)}, + ...(customerId + ? [updateCustomerBasketsQuery(customerId, parameters, response)] + : []) + ] + } + }, + addGiftCertificateItemToBasket(customerId, {parameters}, response) { + return { + update: [ + {queryKey: getBasket.queryKey(parameters)}, + ...(customerId + ? [updateCustomerBasketsQuery(customerId, parameters, response)] + : []) + ] + } + }, + addItemToBasket(customerId, {parameters}, response) { + return { + update: [ + {queryKey: getBasket.queryKey(parameters)}, + ...(customerId + ? [updateCustomerBasketsQuery(customerId, parameters, response)] + : []) + ] + } + }, + addPaymentInstrumentToBasket(customerId, {parameters}, response) { + return { + update: [ + {queryKey: getBasket.queryKey(parameters)}, + ...(customerId + ? [updateCustomerBasketsQuery(customerId, parameters, response)] + : []) + ] + } + }, + addPriceBooksToBasket(customerId, {parameters}) { + return { + invalidate: [ + {queryKey: getBasket.queryKey(parameters)}, + {queryKey: getPriceBooksForBasket.queryKey(parameters)}, + // TODO: Convert invalidate to an update that removes the matching basket + ...(customerId ? [invalidateCustomerBasketsQuery(customerId, parameters)] : []) + ] + } + }, + addTaxesForBasket(customerId, {parameters}) { + return { + invalidate: [ + {queryKey: getBasket.queryKey(parameters)}, + {queryKey: getTaxesFromBasket.queryKey(parameters)}, + // TODO: Convert invalidate to an update that removes the matching basket + ...(customerId ? [invalidateCustomerBasketsQuery(customerId, parameters)] : []) + ] + } + }, + addTaxesForBasketItem(customerId, {parameters}) { + return { + // TODO: Convert invalidate to an update that removes the matching basket + invalidate: [ + ...(customerId ? [invalidateCustomerBasketsQuery(customerId, parameters)] : []) + ], + update: [{queryKey: getBasket.queryKey(parameters)}] + } + }, + createBasket(customerId, {parameters}, response) { + const {basketId} = response + + return { + // TODO: Convert invalidate to an update that removes the matching basket + invalidate: [ + ...(customerId && !basketId + ? [invalidateCustomerBasketsQuery(customerId, parameters)] + : []) + ], + update: [ + {queryKey: getBasket.queryKey({...parameters, basketId})}, + ...(customerId && basketId + ? [updateCustomerBasketsQuery(customerId, {...parameters, basketId}, response)] + : []) + ] + } + }, + createShipmentForBasket(customerId, {parameters}, response) { + return { + update: [ + {queryKey: getBasket.queryKey(parameters)}, + ...(customerId + ? [updateCustomerBasketsQuery(customerId, parameters, response)] + : []) + ] + } + }, + deleteBasket(customerId, {parameters}) { + return { + // TODO: Convert invalidate to an update that removes the matching basket + invalidate: [ + ...(customerId ? [invalidateCustomerBasketsQuery(customerId, parameters)] : []) + ], + remove: [ + // We want to fuzzy match all queryKeys with `basketId` in their path + // [`/commerce-sdk-react,/organizations/,${organization},/baskets/,${basketId}`] + {queryKey: getBasket.path(parameters)} + ] + } + }, + mergeBasket(customerId, {parameters}, response) { + const {basketId} = response + const registeredCustomerId = response?.customerInfo?.customerId + + return { + // TODO: Convert invalidate to an update that removes the matching basket + invalidate: [ + ...(customerId && !basketId + ? [invalidateCustomerBasketsQuery(customerId, parameters)] + : []) + ], + update: [ + {queryKey: getBasket.queryKey({...parameters, basketId})}, + ...(registeredCustomerId && basketId + ? [ + updateCustomerBasketsQuery( + registeredCustomerId, + {...parameters, basketId}, + response + ) + ] + : []) + ] + } + }, + removeCouponFromBasket(customerId, {parameters}, response) { + return { + update: [ + {queryKey: getBasket.queryKey(parameters)}, + ...(customerId + ? [updateCustomerBasketsQuery(customerId, parameters, response)] + : []) + ] + } + }, + removeGiftCertificateItemFromBasket(customerId, {parameters}, response) { + return { + update: [ + {queryKey: getBasket.queryKey(parameters)}, + ...(customerId + ? [updateCustomerBasketsQuery(customerId, parameters, response)] + : []) + ] + } + }, + removeItemFromBasket(customerId, {parameters}, response) { + return { + update: [ + {queryKey: getBasket.queryKey(parameters)}, + ...(customerId + ? [updateCustomerBasketsQuery(customerId, parameters, response)] + : []) + ] + } + }, + removePaymentInstrumentFromBasket(customerId, {parameters}, response) { + return { + invalidate: [ + {queryKey: getPaymentMethodsForBasket.queryKey(parameters)}, + // TODO: Convert invalidate to an update that removes the matching basket + ...(customerId ? [invalidateCustomerBasketsQuery(customerId, parameters)] : []) + ], + update: [ + {queryKey: getBasket.queryKey(parameters)}, + ...(customerId + ? [updateCustomerBasketsQuery(customerId, parameters, response)] + : []) + ] + } + }, + removeShipmentFromBasket(customerId, {parameters}, response) { + return { + update: [ + {queryKey: getBasket.queryKey(parameters)}, + ...(customerId + ? [updateCustomerBasketsQuery(customerId, parameters, response)] + : []) + ] + } + }, + transferBasket(customerId, {parameters}, response) { + const {basketId} = response + const transferedTo = response?.customerInfo?.customerId + + return { + // TODO: Convert invalidate to an update that removes the matching basket + invalidate: [ + ...(customerId && !basketId + ? [invalidateCustomerBasketsQuery(customerId, parameters)] + : []) + ], + update: [ + {queryKey: getBasket.queryKey({...parameters, basketId})}, + ...(transferedTo && basketId + ? [ + updateCustomerBasketsQuery( + transferedTo, + {...parameters, basketId}, + response + ) + ] + : []) + ] + } + }, + updateBasket(customerId, {parameters}, response) { + return { + update: [ + {queryKey: getBasket.queryKey(parameters)}, + ...(customerId + ? [updateCustomerBasketsQuery(customerId, parameters, response)] + : []) + ] + } + }, + updateBillingAddressForBasket(customerId, {parameters}, response) { + return { + update: [ + {queryKey: getBasket.queryKey(parameters)}, + ...(customerId + ? [updateCustomerBasketsQuery(customerId, parameters, response)] + : []) + ] + } + }, + updateCustomerForBasket(customerId, {parameters}, response) { + return { + update: [ + {queryKey: getBasket.queryKey(parameters)}, + ...(customerId + ? [updateCustomerBasketsQuery(customerId, parameters, response)] + : []) + ] + } + }, + updateGiftCertificateItemInBasket(customerId, {parameters}, response) { + return { + update: [ + {queryKey: getBasket.queryKey(parameters)}, + ...(customerId + ? [updateCustomerBasketsQuery(customerId, parameters, response)] + : []) + ] + } + }, + updateItemInBasket(customerId, {parameters}, response) { + return { + update: [ + {queryKey: getBasket.queryKey(parameters)}, + ...(customerId + ? [updateCustomerBasketsQuery(customerId, parameters, response)] + : []) + ] + } + }, + updateItemsInBasket(customerId, {parameters}, response) { + return { + update: [ + {queryKey: getBasket.queryKey(parameters)}, + ...(customerId + ? [updateCustomerBasketsQuery(customerId, parameters, response)] + : []) + ] + } + }, + updatePaymentInstrumentInBasket(customerId, {parameters}, response) { + return { + invalidate: [ + {queryKey: getPaymentMethodsForBasket.queryKey(parameters)}, + // TODO: Convert invalidate to an update that removes the matching basket + ...(customerId ? [invalidateCustomerBasketsQuery(customerId, parameters)] : []) + ], + update: [ + {queryKey: getBasket.queryKey(parameters)}, + ...(customerId + ? [updateCustomerBasketsQuery(customerId, parameters, response)] + : []) + ] + } + }, + updateShipmentForBasket(customerId, {parameters}, response) { + return { + update: [ + {queryKey: getBasket.queryKey(parameters)}, + ...(customerId + ? [updateCustomerBasketsQuery(customerId, parameters, response)] + : []) + ] + } + }, + updateShippingAddressForShipment(customerId, {parameters}, response) { + return { + // TODO: Convert invalidate to an update that removes the matching basket + invalidate: [ + {queryKey: getShippingMethodsForShipment.queryKey(parameters)}, + ...(customerId ? [invalidateCustomerBasketsQuery(customerId, parameters)] : []) + ], + update: [ + {queryKey: getBasket.queryKey(parameters)}, + ...(customerId + ? [updateCustomerBasketsQuery(customerId, parameters, response)] + : []) + ] + } + }, + updateShippingMethodForShipment(customerId, {parameters}, response) { + return { + // TODO: Convert invalidate to an update that removes the matching basket + invalidate: [ + {queryKey: getShippingMethodsForShipment.queryKey(parameters)}, + ...(customerId ? [invalidateCustomerBasketsQuery(customerId, parameters)] : []) + ], + update: [ + {queryKey: getBasket.queryKey(parameters)}, + ...(customerId + ? [updateCustomerBasketsQuery(customerId, parameters, response)] + : []) + ] + } + } +} diff --git a/packages/commerce-sdk-react/src/hooks/ShopperBasketsV2/helpers.test.tsx b/packages/commerce-sdk-react/src/hooks/ShopperBasketsV2/helpers.test.tsx new file mode 100644 index 0000000000..f67b72f924 --- /dev/null +++ b/packages/commerce-sdk-react/src/hooks/ShopperBasketsV2/helpers.test.tsx @@ -0,0 +1,154 @@ +/* + * Copyright (c) 2024, salesforce.com, 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 {useShopperBasketsMutationHelper} from './helpers' +import {useCustomerBaskets} from '../ShopperCustomers' +import {renderWithProviders} from '../../test-utils' +import {screen, waitFor} from '@testing-library/react' +import {ShopperBasketsV2Types} from 'commerce-sdk-isomorphic' +import jwt from 'jsonwebtoken' + +const basketId = '10cf6aa40edba4fcfcc6915594' +const mockAsyncMutate = jest.fn() + +jest.mock('../ShopperCustomers', () => { + const originalModule = jest.requireActual('../ShopperCustomers') + return { + ...originalModule, + useCustomerBaskets: jest.fn() + } +}) +jest.mock('../index', () => { + const originalModule = jest.requireActual('./index') + return { + ...originalModule, + useCustomerId: jest.fn(() => 'customer-id') + } +}) + +jest.mock('./mutation', () => { + const originalModule = jest.requireActual('./mutation') + return { + ...originalModule, + useShopperBasketsMutation: () => ({ + mutateAsync: mockAsyncMutate + }) + } +}) +const MockComponent = () => { + const helpers = useShopperBasketsMutationHelper() + return ( +
+ +
+ ) +} +describe('useShopperBasketsMutationHelper.addItemToNewOrExistingBasket', function () { + afterEach(() => { + jest.resetModules() + }) + + test('should perform add to cart mutation when basket is already created', async () => { + mockAsyncMutate.mockImplementationOnce(() => ({ + productItems: [{id: 'product-id-111', quantity: 20}], + basketId + })) + // @ts-expect-error ts complains because mockImplementation is not part of declared type from useCustomerBaskets queries + useCustomerBaskets.mockImplementation(() => { + return { + data: {total: 1, baskets: [{basketId}]}, + isLoading: false + } + }) + const fetchedToken = jwt.sign( + { + sub: `cc-slas::zzrf_001::scid:xxxxxx::usid:usidddddd`, + isb: `uido:ecom::upn:Guest::uidn:firstname lastname::gcid:customerId::rcid:registeredCid::chid:siteId` + }, + 'secret' + ) + const {user} = renderWithProviders(, { + fetchedToken + }) + const addToCartBtn = screen.getByText(/add to cart/i) + await user.click(addToCartBtn) + + await waitFor(() => + expect(mockAsyncMutate.mock.calls[0][0]).toEqual({ + parameters: {basketId}, + body: [ + { + productId: 'product-123', + price: 100, + quantity: 1 + } + ] + }) + ) + }) + + test('should call a basket mutation before calling add to cart mutation', async () => { + // order is important since mockAsyncMutate will represent createBasket and addToBasket mutation in the order of executions + mockAsyncMutate + .mockImplementationOnce(() => ({ + productItems: [], + basketId + })) + .mockImplementationOnce(() => ({ + productItems: [{id: 'product-id', quantity: 2}], + basketId + })) + // @ts-expect-error ts complains because mockImplementation is not part of declared type from useCustomerBaskets queries + useCustomerBaskets.mockImplementation(() => { + return { + data: {total: 0}, + isLoading: false + } + }) + const fetchedToken = jwt.sign( + { + sub: `cc-slas::zzrf_001::scid:xxxxxx::usid:usidddddd`, + isb: `uido:ecom::upn:Guest::uidn:firstname lastname::gcid:customerId::rcid:registeredCid::chid:siteId` + }, + 'secret' + ) + const {user} = renderWithProviders(, { + fetchedToken + }) + const addToCartBtn = screen.getByText(/add to cart/i) + await user.click(addToCartBtn) + expect(mockAsyncMutate).toHaveBeenCalledTimes(2) + + await waitFor(() => expect(mockAsyncMutate.mock.calls[0][0]).toEqual({body: {}})) + await waitFor(() => + expect(mockAsyncMutate.mock.calls[1][0]).toEqual({ + parameters: {basketId}, + body: [ + { + productId: 'product-123', + price: 100, + quantity: 1 + } + ] + }) + ) + }) +}) diff --git a/packages/commerce-sdk-react/src/hooks/ShopperBasketsV2/helpers.ts b/packages/commerce-sdk-react/src/hooks/ShopperBasketsV2/helpers.ts new file mode 100644 index 0000000000..b2b3a8bca7 --- /dev/null +++ b/packages/commerce-sdk-react/src/hooks/ShopperBasketsV2/helpers.ts @@ -0,0 +1,78 @@ +/* + * 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 {useCustomerId} from '../index' +// make sure to import the v2 mutation from the correct file +import {useShopperBasketsMutation} from './mutation' +import {useCustomerBaskets} from '../ShopperCustomers' +import {ApiClients, Argument} from '../types' +import {ShopperBasketsV2Types} from 'commerce-sdk-isomorphic' +import {CLIENT_KEYS} from '../../constant' + +const CLIENT_KEY = CLIENT_KEYS.SHOPPER_BASKETS_V2 +type Client = NonNullable +type Basket = ShopperBasketsV2Types.Basket + +/** + * This is a helper function for Basket Mutations. + * useShopperBasketsMutationHelper.addItemToNewOrExistingBasket: is responsible for managing the process of adding an item to a basket. + * - If a basket already exists, add the item to the basket immediately. + * - If a basket does not exist, create a new basket using the createBasket mutation + * and then add the item to the newly created basket using the addItemToBasket mutation. + * + * @example + * import useShopperBasketsMutationHelper from '@salesforce/commerce-sdk-react' + * + * const Page = () => { + * const helpers = useShopperBasketsMutationHelper() + * + * const addToCart = async () => { + * const productItems = [{id: 123, quantity: 2}] + * await basketMutationHelpers.addItemToNewOrExistingBasket(productItems) + * } + * + * } + */ +export function useShopperBasketsMutationHelper() { + const customerId = useCustomerId() + const {data: basketsData} = useCustomerBaskets( + {parameters: {customerId}}, + { + enabled: !!customerId + } + ) + const createBasket = useShopperBasketsMutation('createBasket') + const addItemToBasketMutation = useShopperBasketsMutation('addItemToBasket') + return { + addItemToNewOrExistingBasket: async ( + productItem: Argument extends {body: infer B} ? B : undefined + ): Promise => { + if (basketsData && basketsData.total > 0) { + // we know that if basketData.total > 0, current basket will always be available + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + const currentBasket = basketsData?.baskets && basketsData.baskets[0]! + return await addItemToBasketMutation.mutateAsync({ + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + parameters: {basketId: currentBasket!.basketId!}, + body: productItem + }) + } else { + const data = await createBasket.mutateAsync({ + body: {} + }) + if (!data || !data.basketId) { + throw Error('Something is wrong. Please try again') + } else { + return await addItemToBasketMutation.mutateAsync({ + parameters: {basketId: data.basketId}, + body: productItem + }) + } + } + } + } +} diff --git a/packages/commerce-sdk-react/src/hooks/ShopperBasketsV2/index.test.ts b/packages/commerce-sdk-react/src/hooks/ShopperBasketsV2/index.test.ts new file mode 100644 index 0000000000..3d193a3168 --- /dev/null +++ b/packages/commerce-sdk-react/src/hooks/ShopperBasketsV2/index.test.ts @@ -0,0 +1,37 @@ +/* + * 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 {ShopperBasketsV2} from 'commerce-sdk-isomorphic' +import {getUnimplementedEndpoints} from '../../test-utils' +import {cacheUpdateMatrix} from './cache' +import {ShopperBasketsMutations as mutations} from './mutation' +import * as queries from './query' + +describe('Shopper Baskets hooks', () => { + test('all endpoints have hooks', () => { + // unimplemented = SDK method exists, but no query hook or value in mutations enum + const unimplemented = getUnimplementedEndpoints(ShopperBasketsV2, queries, mutations) + // If this test fails: create a new query hook, add the endpoint to the mutations enum, + // or add it to the `expected` array with a comment explaining "TODO" or "never" (and why). + expect(unimplemented).toEqual([ + 'addPriceAdjustmentToBasket', //TODO: implement later + 'removePriceAdjustmentFromBasket', //TODO: implement later + 'updateAsAgentBasket', //TODO: implement later + 'updateAsStorefrontBasket', //TODO: implement later + 'updatePriceAdjustmentInBasket' //TODO: implement later + ]) + }) + test('all mutations have cache update logic', () => { + // unimplemented = value in mutations enum, but no method in cache update matrix + const unimplemented = new Set(Object.values(mutations)) + Object.entries(cacheUpdateMatrix).forEach(([method, implementation]) => { + if (implementation) unimplemented.delete(method) + }) + // If this test fails: add cache update logic, remove the endpoint from the mutations enum, + // or add it to the `expected` array to indicate that it is still a TODO. + expect([...unimplemented]).toEqual([]) + }) +}) diff --git a/packages/commerce-sdk-react/src/hooks/ShopperBasketsV2/index.ts b/packages/commerce-sdk-react/src/hooks/ShopperBasketsV2/index.ts new file mode 100644 index 0000000000..1413649783 --- /dev/null +++ b/packages/commerce-sdk-react/src/hooks/ShopperBasketsV2/index.ts @@ -0,0 +1,9 @@ +/* + * 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 + */ +export * from './mutation' +export * from './query' +export * from './helpers' diff --git a/packages/commerce-sdk-react/src/hooks/ShopperBasketsV2/mutation.test.ts b/packages/commerce-sdk-react/src/hooks/ShopperBasketsV2/mutation.test.ts new file mode 100644 index 0000000000..ab7abe381a --- /dev/null +++ b/packages/commerce-sdk-react/src/hooks/ShopperBasketsV2/mutation.test.ts @@ -0,0 +1,344 @@ +/* + * 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 {act} from '@testing-library/react' +import {ShopperBasketsV2Types, ShopperCustomersTypes} from 'commerce-sdk-isomorphic' +import nock from 'nock' +import { + assertInvalidateQuery, + assertRemoveQuery, + assertUpdateQuery, + DEFAULT_TEST_CONFIG, + mockMutationEndpoints, + mockQueryEndpoint, + renderHookWithProviders, + waitAndExpectError, + waitAndExpectSuccess +} from '../../test-utils' +import {useCustomerBaskets} from '../ShopperCustomers' +import {ApiClients, Argument} from '../types' +import {ShopperBasketsMutation, useShopperBasketsMutation} from './mutation' +import * as queries from './query' +import {CLIENT_KEYS} from '../../constant' + +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 +}) + +const CLIENT_KEY = CLIENT_KEYS.SHOPPER_BASKETS_V2 +type Client = NonNullable +type Basket = ShopperBasketsV2Types.Basket +type BasketsResult = ShopperCustomersTypes.BasketsResult +type ProductItem = ShopperBasketsV2Types.ProductItem + +/** Create an options object for Shopper Baskets endpoints, with `basketId` pre-filled. */ +const createOptions = >( + body: Argument extends {body: infer B} ? B : undefined, + parameters: Omit['parameters'], 'basketId'> +): Argument => ({ + body, + parameters: {basketId: BASKET_ID, ...parameters} +}) + +// --- getBasket constants --- // +const basketsEndpoint = '/checkout/shopper-baskets/' +const BASKET_ID = 'basket_id' +const getBasketOptions = createOptions<'getBasket'>(undefined, {}) +const EMPTY_BASKET: Basket = {} +const oldBasket: Basket = {basketId: BASKET_ID, mockData: 'old basket'} +const newBasket: Basket = {basketId: BASKET_ID, mockData: 'new basket'} +// --- getCustomerBaskets constants --- // +const customersEndpoint = '/customer/shopper-customers/' +const CUSTOMER_ID = 'customer_id' +const getCustomerBasketsOptions: Argument< + NonNullable['getCustomerBaskets'] +> = { + parameters: { + customerId: CUSTOMER_ID + } +} +const emptyCustomerBaskets: BasketsResult = { + baskets: [] as BasketsResult['baskets'], + total: 0 +} +const oneCustomerBasket: BasketsResult = { + baskets: [newBasket] as BasketsResult['baskets'], + total: 1 +} +const oldCustomerBaskets: BasketsResult = { + // We aren't implementing the full basket, so we assert to pretend we are + baskets: [{basketId: 'other_basket'}, oldBasket] as BasketsResult['baskets'], + total: 2 +} +const newCustomerBaskets: BasketsResult = { + // We aren't implementing the full basket, so we assert to pretend we are + baskets: [{basketId: 'other_basket'}, newBasket] as BasketsResult['baskets'], + total: 2 +} +const deletedCustomerBaskets: BasketsResult = { + // We aren't implementing the full basket, so we assert to pretend we are + baskets: [{basketId: 'other_basket'}] as BasketsResult['baskets'], + total: 1 +} + +// --- TEST CASES --- // +/** All Shopper Baskets mutations except these have the same cache update logic. */ +type NonEmptyResponseMutations = Exclude< + ShopperBasketsMutation, + 'deleteBasket' | 'addPriceBooksToBasket' | 'addTaxesForBasket' | 'addTaxesForBasketItem' +> +// This is an object rather than an array to more easily ensure we cover all mutations +type TestMap = {[Mut in NonEmptyResponseMutations]: Argument} +const testMap: TestMap = { + addGiftCertificateItemToBasket: createOptions<'addGiftCertificateItemToBasket'>( + {recipientEmail: 'customer@email', amount: 100}, + {} + ), + createShipmentForBasket: createOptions<'createShipmentForBasket'>({}, {}), + removeGiftCertificateItemFromBasket: createOptions<'removeGiftCertificateItemFromBasket'>( + undefined, + {giftCertificateItemId: 'giftCertificateItemId'} + ), + removeShipmentFromBasket: createOptions<'removeShipmentFromBasket'>(undefined, { + shipmentId: 'shipmentId' + }), + transferBasket: createOptions<'transferBasket'>(undefined, {}), + updateGiftCertificateItemInBasket: createOptions<'updateGiftCertificateItemInBasket'>( + { + amount: 100, + recipientEmail: 'customer@email' + }, + {giftCertificateItemId: 'giftCertificateItemId'} + ), + updateShipmentForBasket: createOptions<'updateShipmentForBasket'>( + {}, + {shipmentId: 'shipmentId'} + ), + addCouponToBasket: createOptions<'addCouponToBasket'>({code: 'coupon'}, {}), + addItemToBasket: createOptions<'addItemToBasket'>( + [{productId: 'test-product', price: 10, quantity: 1}] as ProductItem[] & + Record<`c_${string}`, any>, + {} + ), + addPaymentInstrumentToBasket: createOptions<'addPaymentInstrumentToBasket'>({}, {}), + createBasket: createOptions<'createBasket'>({}, {}), + mergeBasket: createOptions<'mergeBasket'>(undefined, {}), + removeCouponFromBasket: createOptions<'removeCouponFromBasket'>(undefined, { + couponItemId: 'couponIemId' + }), + removeItemFromBasket: createOptions<'removeItemFromBasket'>(undefined, {itemId: 'itemId'}), + removePaymentInstrumentFromBasket: createOptions<'removePaymentInstrumentFromBasket'>( + undefined, + { + paymentInstrumentId: 'paymentInstrumentId' + } + ), + updateBasket: createOptions<'updateBasket'>({}, {}), + updateBillingAddressForBasket: createOptions<'updateBillingAddressForBasket'>({}, {}), + updateCustomerForBasket: createOptions<'updateCustomerForBasket'>( + {email: 'customer@email'}, + {} + ), + updateItemInBasket: createOptions<'updateItemInBasket'>({}, {itemId: 'itemId'}), + updateItemsInBasket: createOptions<'updateItemsInBasket'>( + [{productId: 'test-product', price: 10, quantity: 1}] as ProductItem[] & + Record<`c_${string}`, any>, + {} + ), + updatePaymentInstrumentInBasket: createOptions<'updatePaymentInstrumentInBasket'>( + {}, + {paymentInstrumentId: 'paymentInstrumentId'} + ), + updateShippingAddressForShipment: createOptions<'updateShippingAddressForShipment'>( + {}, + {shipmentId: 'shipmentId'} + ), + updateShippingMethodForShipment: createOptions<'updateShippingMethodForShipment'>( + {id: 'ship'}, + {shipmentId: 'shipmentId'} + ) +} +const createTestCase = ['createBasket', createOptions<'createBasket'>({}, {})] as const +const deleteTestCase = ['deleteBasket', createOptions<'deleteBasket'>(undefined, {})] as const +const addPriceBooksToBasketTestCase = [ + 'addPriceBooksToBasket', + createOptions<'addPriceBooksToBasket'>( + ['price-book-1'] as string[] & Record<`c_${string}`, any>, + {} + ) +] as const +const addTaxesForBasketTestCase = [ + 'addTaxesForBasket', + createOptions<'addTaxesForBasket'>( + { + taxes: {} + }, + {} + ) +] as const +const addTaxesForBasketItemTestCase = [ + 'addTaxesForBasketItem', + createOptions<'addTaxesForBasketItem'>({}, {itemId: 'itemId'}) +] as const + +// Type assertion because the built-in type definition for `Object.entries` is limited :\ +const nonEmptyResponseTestCases = Object.entries(testMap) as Array< + [NonEmptyResponseMutations, Argument] +> + +// Endpoints returning void response on success +const emptyResponseTestCases = [ + addPriceBooksToBasketTestCase, + addTaxesForBasketTestCase, + addTaxesForBasketItemTestCase, + // FIXME: This test only passed if run last. + deleteTestCase +] + +// Most test cases only apply to non-empty response test cases, some (error handling) can include deleteBasket +const allTestCases = [...nonEmptyResponseTestCases, ...emptyResponseTestCases] + +describe('ShopperBaskets mutations', () => { + const storedCustomerIdKey = `customer_id_${DEFAULT_TEST_CONFIG.siteId}` + beforeAll(() => { + // Make sure we don't accidentally overwrite something before setting up our test state + if (window.localStorage.length > 0) throw new Error('Unexpected data in local storage.') + window.localStorage.setItem(storedCustomerIdKey, CUSTOMER_ID) + }) + afterAll(() => { + window.localStorage.removeItem(storedCustomerIdKey) + }) + + beforeEach(() => nock.cleanAll()) + test.each(nonEmptyResponseTestCases)( + '`%s` returns data on success', + async (mutationName, options) => { + mockMutationEndpoints(basketsEndpoint, oldBasket) + const {result} = renderHookWithProviders(() => { + return useShopperBasketsMutation(mutationName) + }) + expect(result.current.data).toBeUndefined() + act(() => result.current.mutate(options)) + await waitAndExpectSuccess(() => result.current) + expect(result.current.data).toEqual(oldBasket) + } + ) + test.each(allTestCases)('`%s` returns error on error', async (mutationName, options) => { + mockMutationEndpoints(basketsEndpoint, {error: true}, 400) + const {result} = renderHookWithProviders(() => { + return useShopperBasketsMutation(mutationName) + }) + expect(result.current.error).toBeNull() + act(() => result.current.mutate(options)) + await waitAndExpectError(() => result.current) + // Validate that we get a `ResponseError` from commerce-sdk-isomorphic. Ideally, we could do + // `.toBeInstanceOf(ResponseError)`, but the class isn't exported. :\ + expect(result.current.error).toHaveProperty('response') + }) + test.each(nonEmptyResponseTestCases)( + '`%s` updates the cache on success', + async (mutationName, options) => { + mockQueryEndpoint(basketsEndpoint, oldBasket) // getBasket + mockQueryEndpoint(customersEndpoint, oldCustomerBaskets) // getCustomerBaskets + mockMutationEndpoints(basketsEndpoint, newBasket) // this mutation + const {result} = renderHookWithProviders(() => ({ + basket: queries.useBasket(getBasketOptions), + customerBaskets: useCustomerBaskets(getCustomerBasketsOptions), + mutation: useShopperBasketsMutation(mutationName) + })) + await waitAndExpectSuccess(() => result.current.basket) + expect(result.current.basket.data).toEqual(oldBasket) + expect(result.current.customerBaskets.data).toEqual(oldCustomerBaskets) + act(() => result.current.mutation.mutate(options)) + await waitAndExpectSuccess(() => result.current.mutation) + assertUpdateQuery(result.current.basket, newBasket) + // Because 'transferBasket` and `mergeBasket` returns basket information applicable to a different (registered) customer + // the `result.current.customerBaskets` isn't effected here as the customer id is static. + // https://github.com/SalesforceCommerceCloud/pwa-kit/pull/1887 + if (mutationName !== 'transferBasket' && mutationName !== 'mergeBasket') { + assertUpdateQuery(result.current.customerBaskets, newCustomerBaskets) + } + } + ) + test.each(allTestCases)( + '`%s` does not change cache on error', + async (mutationName, options) => { + mockQueryEndpoint(basketsEndpoint, oldBasket) // getBasket + mockQueryEndpoint(customersEndpoint, oldCustomerBaskets) // getCustomerBaskets + mockMutationEndpoints(basketsEndpoint, {error: true}, 400) // this mutation + const {result} = renderHookWithProviders(() => ({ + basket: queries.useBasket(getBasketOptions), + customerBaskets: useCustomerBaskets(getCustomerBasketsOptions), + mutation: useShopperBasketsMutation(mutationName) + })) + await waitAndExpectSuccess(() => result.current.basket) + expect(result.current.basket.data).toEqual(oldBasket) + expect(result.current.customerBaskets.data).toEqual(oldCustomerBaskets) + expect(result.current.mutation.error).toBeNull() + act(() => result.current.mutation.mutate(options)) + await waitAndExpectError(() => result.current.mutation) + // Validate that we get a `ResponseError` from commerce-sdk-isomorphic. Ideally, we could do + // `.toBeInstanceOf(ResponseError)`, but the class isn't exported. :\ + expect(result.current.mutation.error).toHaveProperty('response') + assertUpdateQuery(result.current.basket, oldBasket) + assertUpdateQuery(result.current.customerBaskets, oldCustomerBaskets) + } + ) + test.each(emptyResponseTestCases)( + '`%s` returns void on success', + async (mutationName, options) => { + // Almost the standard 'returns data' test, just a different return type + mockMutationEndpoints(basketsEndpoint, oldBasket) + const {result} = renderHookWithProviders(() => { + return useShopperBasketsMutation(mutationName) + }) + expect(result.current.data).toBeUndefined() + act(() => result.current.mutate(options)) + await waitAndExpectSuccess(() => result.current) + expect(result.current.data).toBeUndefined() + } + ) + test('`createBasket` adds the basket to the cache on success if customer has no basket', async () => { + const [mutationName, options] = createTestCase + mockQueryEndpoint(basketsEndpoint, EMPTY_BASKET) // getBasket + mockQueryEndpoint(customersEndpoint, emptyCustomerBaskets) // getCustomerBaskets + mockMutationEndpoints(basketsEndpoint, newBasket) // this mutation + const {result} = renderHookWithProviders(() => ({ + basket: queries.useBasket(getBasketOptions), + customerBaskets: useCustomerBaskets(getCustomerBasketsOptions), + mutation: useShopperBasketsMutation(mutationName) + })) + await waitAndExpectSuccess(() => result.current.basket) + expect(result.current.basket.data).toEqual(EMPTY_BASKET) + expect(result.current.customerBaskets.data).toEqual(emptyCustomerBaskets) + act(() => result.current.mutation.mutate(options)) + await waitAndExpectSuccess(() => result.current.mutation) + assertUpdateQuery(result.current.basket, newBasket) + assertUpdateQuery(result.current.customerBaskets, oneCustomerBasket) + }) + test('`deleteBasket` removes the basket from the cache on success', async () => { + // Almost the standard 'updates cache' test, but the cache changes are different + const [mutationName, options] = deleteTestCase + mockQueryEndpoint(basketsEndpoint, oldBasket) // getBasket + mockQueryEndpoint(customersEndpoint, oldCustomerBaskets) // getCustomerBaskets + mockMutationEndpoints(basketsEndpoint, newBasket) // this mutation + mockQueryEndpoint(customersEndpoint, deletedCustomerBaskets) // getCustomerBaskets refetch + const {result} = renderHookWithProviders(() => ({ + basket: queries.useBasket(getBasketOptions), + customerBaskets: useCustomerBaskets(getCustomerBasketsOptions), + mutation: useShopperBasketsMutation(mutationName) + })) + await waitAndExpectSuccess(() => result.current.basket) + expect(result.current.basket.data).toEqual(oldBasket) + expect(result.current.customerBaskets.data).toEqual(oldCustomerBaskets) + act(() => result.current.mutation.mutate(options)) + await waitAndExpectSuccess(() => result.current.mutation) + assertRemoveQuery(result.current.basket) + assertInvalidateQuery(result.current.customerBaskets, oldCustomerBaskets) + }) +}) diff --git a/packages/commerce-sdk-react/src/hooks/ShopperBasketsV2/mutation.ts b/packages/commerce-sdk-react/src/hooks/ShopperBasketsV2/mutation.ts new file mode 100644 index 0000000000..5db11d1348 --- /dev/null +++ b/packages/commerce-sdk-react/src/hooks/ShopperBasketsV2/mutation.ts @@ -0,0 +1,198 @@ +/* + * 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 {ApiClients, ApiMethod, Argument, CacheUpdateGetter, DataType, MergedOptions} from '../types' +import {useMutation} from '../useMutation' +import {UseMutationResult} from '@tanstack/react-query' +import {cacheUpdateMatrix} from './cache' +import {CLIENT_KEYS} from '../../constant' +import useCommerceApi from '../useCommerceApi' + +const CLIENT_KEY = CLIENT_KEYS.SHOPPER_BASKETS_V2 +type Client = NonNullable + +/** + * Mutations available for Shopper Baskets. + * @group ShopperBaskets + * @category Mutation + * @enum + */ +export const ShopperBasketsMutations = { + /** + * Creates a new basket. + + The created basket is initialized with default values. + */ + CreateBasket: 'createBasket', + /** + * Transfer the previous shopper's basket to the current shopper by updating the basket's owner. No other values change. You must obtain the shopper authorization token via SLAS and you must provide the ‘guest usid‘ in both the ‘/oauth2/login‘ and ‘/oauth2/token‘ calls while fetching the registered user JWT token. + + A success response contains the transferred basket. + + If the current shopper has an active basket, and the `overrideExisting` request parameter is `false`, then the transfer request returns a BasketTransferException (HTTP status 409). You can proceed with one of these options: + - Keep the current shopper's active basket. + - Merge the previous and current shoppers' baskets by calling the `baskets/merge` endpoint. + - Force the transfer by calling the `baskets/transfer` endpoint again, with the parameter `overrideExisting=true`. Forcing the transfer deletes the current shopper's active basket. + */ + TransferBasket: 'transferBasket', + /** + * Merge data from the previous shopper's basket into the current shopper's active basket and delete the previous shopper's basket. This endpoint doesn't merge Personally Identifiable Information (PII). You must obtain the shopper authorization token via SLAS and you must provide the ‘guest usid‘ in both the ‘/oauth2/login‘ and ‘/oauth2/token‘ calls while fetching the registered user JWT token. After the merge, all basket amounts are recalculated and totaled, including lookups for prices, taxes, shipping, and promotions. + */ + MergeBasket: 'mergeBasket', + /** + * Removes a basket. + */ + DeleteBasket: 'deleteBasket', + /** + * Updates a basket. Only the currency of the basket, source code, the custom + properties of the basket, and the shipping items will be considered. + */ + UpdateBasket: 'updateBasket', + /** + * Sets the billing address of a basket. + */ + UpdateBillingAddressForBasket: 'updateBillingAddressForBasket', + /** + * Adds a coupon to an existing basket. + */ + AddCouponToBasket: 'addCouponToBasket', + /** + * Removes a coupon from the basket. + */ + RemoveCouponFromBasket: 'removeCouponFromBasket', + /** + * Sets customer information for an existing basket. + */ + UpdateCustomerForBasket: 'updateCustomerForBasket', + /** + * Adds a gift certificate item to an existing basket. + */ + AddGiftCertificateItemToBasket: 'addGiftCertificateItemToBasket', + /** + * Deletes a gift certificate item from an existing basket. + */ + RemoveGiftCertificateItemFromBasket: 'removeGiftCertificateItemFromBasket', + /** + * Updates a gift certificate item of an existing basket. + */ + UpdateGiftCertificateItemInBasket: 'updateGiftCertificateItemInBasket', + /** + * Adds new items to a basket. + */ + AddItemToBasket: 'addItemToBasket', + /** + * Removes a product item from the basket. + */ + RemoveItemFromBasket: 'removeItemFromBasket', + /** + * Updates an item in a basket. + */ + UpdateItemInBasket: 'updateItemInBasket', + /** + * Updates multiple items in a basket. + */ + UpdateItemsInBasket: 'updateItemsInBasket', + /** + * This method allows you to apply external taxation data to an existing basket to be able to pass tax rates and optional values for a specific taxable line item. This endpoint can be called only if external taxation mode was used for basket creation. See POST /baskets for more information. + */ + AddTaxesForBasketItem: 'addTaxesForBasketItem', + /** + * Adds a payment instrument to a basket. + */ + AddPaymentInstrumentToBasket: 'addPaymentInstrumentToBasket', + /** + * Removes a payment instrument of a basket. + */ + RemovePaymentInstrumentFromBasket: 'removePaymentInstrumentFromBasket', + /** + * Updates payment instrument of an existing basket. + */ + UpdatePaymentInstrumentInBasket: 'updatePaymentInstrumentInBasket', + /** + * This method allows you to put an array of priceBookIds to an existing basket, which will be used for basket calculation. + */ + AddPriceBooksToBasket: 'addPriceBooksToBasket', + /** + * Creates a new shipment for a basket. + + The created shipment is initialized with values provided in the body + document and can be updated with further data API calls. Considered from + the body are the following properties if specified: + + - the ID + - the shipping address + - the shipping method + - gift boolean flag + - gift message + - custom properties + */ + CreateShipmentForBasket: 'createShipmentForBasket', + /** + * Removes a specified shipment and all associated product, gift certificate, + shipping, and price adjustment line items from a basket. + It is not allowed to remove the default shipment. + */ + RemoveShipmentFromBasket: 'removeShipmentFromBasket', + /** + * Updates a shipment for a basket. + + The shipment is initialized with values provided in the body + document and can be updated with further data API calls. Considered from + the body are the following properties if specified: + - the ID + - the shipping address + - the shipping method + - gift boolean flag + - gift message + - custom properties + */ + UpdateShipmentForBasket: 'updateShipmentForBasket', + /** + * Sets a shipping address of a specific shipment of a basket. + */ + UpdateShippingAddressForShipment: 'updateShippingAddressForShipment', + /** + * Sets a shipping method to a specific shipment of a basket. + */ + UpdateShippingMethodForShipment: 'updateShippingMethodForShipment', + /** + * This method allows you to apply external taxation data to an existing basket to be able to pass tax rates and optional values for all taxable line items. This endpoint can be called only if external taxation mode was used for basket creation. See POST /baskets for more information. + */ + AddTaxesForBasket: 'addTaxesForBasket' +} as const + +/** + * Type for Shopper Baskets Mutation. + * @group ShopperBaskets + * @category Mutation + */ +export type ShopperBasketsMutation = + (typeof ShopperBasketsMutations)[keyof typeof ShopperBasketsMutations] + +/** + * Mutation hook for Shopper Baskets. + * @group ShopperBaskets + * @category Mutation + */ +export function useShopperBasketsMutation( + mutation: Mutation +): UseMutationResult, unknown, Argument> { + const getCacheUpdates = cacheUpdateMatrix[mutation] + + // The `Options` and `Data` types for each mutation are similar, but distinct, and the union + // type generated from `Client[Mutation]` seems to be too complex for TypeScript to handle. + // I'm not sure if there's a way to avoid the type assertions in here for the methods that + // use them. However, I'm fairly confident that they are safe to do, as they seem to be simply + // re-asserting what we already have. + const client = useCommerceApi(CLIENT_KEY) + type Options = Argument + type Data = DataType + return useMutation({ + client, + method: (opts: Options) => (client[mutation] as ApiMethod)(opts), + getCacheUpdates: getCacheUpdates as CacheUpdateGetter, Data> + }) +} diff --git a/packages/commerce-sdk-react/src/hooks/ShopperBasketsV2/query.test.ts b/packages/commerce-sdk-react/src/hooks/ShopperBasketsV2/query.test.ts new file mode 100644 index 0000000000..e8cdd0dc46 --- /dev/null +++ b/packages/commerce-sdk-react/src/hooks/ShopperBasketsV2/query.test.ts @@ -0,0 +1,78 @@ +/* + * 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 nock from 'nock' +import { + mockQueryEndpoint, + renderHookWithProviders, + waitAndExpectError, + waitAndExpectSuccess, + createQueryClient +} from '../../test-utils' + +import {Argument} from '../types' +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 basketsEndpoint = '/checkout/shopper-baskets/' +// Not all endpoints use all parameters, but unused parameters are safely discarded +const OPTIONS: Argument = { + parameters: {basketId: 'basketId', shipmentId: 'shipmentId'} +} + +/** Map of query name to returned data type */ +type TestMap = {[K in keyof Queries]: NonNullable['data']>} +// This is an object rather than an array to more easily ensure we cover all hooks +const testMap: TestMap = { + useBasket: {basketId: 'basketId'}, + usePaymentMethodsForBasket: {applicablePaymentMethods: []}, + usePriceBooksForBasket: ['priceBookId'], + useShippingMethodsForShipment: {defaultShippingMethodId: 'defaultShippingMethodId'}, + useTaxesFromBasket: {taxes: {}} +} +// Type assertion is necessary because `Object.entries` is limited +const testCases = Object.entries(testMap) as Array<[keyof TestMap, TestMap[keyof TestMap]]> +describe('Shopper Baskets query hooks', () => { + beforeEach(() => nock.cleanAll()) + afterEach(() => { + expect(nock.pendingMocks()).toHaveLength(0) + }) + test.each(testCases)('`%s` has meta.displayName defined', async (queryName, data) => { + mockQueryEndpoint(basketsEndpoint, data) + const queryClient = createQueryClient() + const {result} = renderHookWithProviders( + () => { + return queries[queryName](OPTIONS) + }, + {queryClient} + ) + await waitAndExpectSuccess(() => result.current) + expect(queryClient.getQueryCache().getAll()[0].meta?.displayName).toBe(queryName) + }) + + test.each(testCases)('`%s` returns data on success', async (queryName, data) => { + mockQueryEndpoint(basketsEndpoint, 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(basketsEndpoint, {}, 400) + const {result} = renderHookWithProviders(() => { + return queries[queryName](OPTIONS) + }) + await waitAndExpectError(() => result.current) + }) +}) diff --git a/packages/commerce-sdk-react/src/hooks/ShopperBasketsV2/query.ts b/packages/commerce-sdk-react/src/hooks/ShopperBasketsV2/query.ts new file mode 100644 index 0000000000..ad41bf8dc9 --- /dev/null +++ b/packages/commerce-sdk-react/src/hooks/ShopperBasketsV2/query.ts @@ -0,0 +1,228 @@ +/* + * 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 {UseQueryResult} from '@tanstack/react-query' +import {ApiClients, ApiQueryOptions, Argument, DataType, NullableParameters} from '../types' +import {useQuery} from '../useQuery' +import {mergeOptions, omitNullableParameters, pickValidParams} from '../utils' +import * as queryKeyHelpers from './queryKeyHelpers' +import {ShopperBasketsV2} from 'commerce-sdk-isomorphic' +import {CLIENT_KEYS} from '../../constant' +import useCommerceApi from '../useCommerceApi' + +const CLIENT_KEY = CLIENT_KEYS.SHOPPER_BASKETS_V2 +type Client = NonNullable + +/** + * Gets a basket. + * @group ShopperBaskets + * @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 Baskets `getBasket` endpoint. + * @see {@link https://developer.salesforce.com/docs/commerce/commerce-api/references/shopper-baskets?meta=getBasket| Salesforce Developer Center} for more information about the API endpoint. + * @see {@link https://salesforcecommercecloud.github.io/commerce-sdk-isomorphic/classes/shopperbaskets.shopperbaskets-1.html#getbasket | `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 useBasket = ( + apiOptions: NullableParameters>, + queryOptions: ApiQueryOptions = {} +): UseQueryResult> => { + type Options = Argument + type Data = DataType + const client = useCommerceApi(CLIENT_KEY) + const methodName = 'getBasket' + const requiredParameters = ShopperBasketsV2.paramKeys[`${methodName}Required`] + + // 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 parameters = pickValidParams(netOptions.parameters, ShopperBasketsV2.paramKeys[methodName]) + 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) + + queryOptions.meta = { + displayName: 'useBasket', + ...queryOptions.meta + } + + // 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({...netOptions, parameters}, queryOptions, { + method, + queryKey, + requiredParameters + }) +} +/** + * Gets applicable payment methods for an existing basket considering the open payment amount only. + * @group ShopperBaskets + * @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 Baskets `getPaymentMethodsForBasket` endpoint. + * @see {@link https://developer.salesforce.com/docs/commerce/commerce-api/references/shopper-baskets?meta=getPaymentMethodsForBasket| Salesforce Developer Center} for more information about the API endpoint. + * @see {@link https://salesforcecommercecloud.github.io/commerce-sdk-isomorphic/classes/shopperbaskets.shopperbaskets-1.html#getpaymentmethodsforbasket | `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 usePaymentMethodsForBasket = ( + apiOptions: NullableParameters>, + queryOptions: ApiQueryOptions = {} +): UseQueryResult> => { + type Options = Argument + type Data = DataType + const client = useCommerceApi(CLIENT_KEY) + const methodName = 'getPaymentMethodsForBasket' + const requiredParameters = ShopperBasketsV2.paramKeys[`${methodName}Required`] + + // 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 parameters = pickValidParams(netOptions.parameters, ShopperBasketsV2.paramKeys[methodName]) + 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) + + queryOptions.meta = { + displayName: 'usePaymentMethodsForBasket', + ...queryOptions.meta + } + + // 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({...netOptions, parameters}, queryOptions, { + method, + queryKey, + requiredParameters + }) +} +/** + * Gets applicable price books for an existing basket. + * @group ShopperBaskets + * @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 Baskets `getPriceBooksForBasket` endpoint. + * @see {@link https://developer.salesforce.com/docs/commerce/commerce-api/references/shopper-baskets?meta=getPriceBooksForBasket| Salesforce Developer Center} for more information about the API endpoint. + * @see {@link https://salesforcecommercecloud.github.io/commerce-sdk-isomorphic/classes/shopperbaskets.shopperbaskets-1.html#getpricebooksforbasket | `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 usePriceBooksForBasket = ( + apiOptions: NullableParameters>, + queryOptions: ApiQueryOptions = {} +): UseQueryResult> => { + type Options = Argument + type Data = DataType + const client = useCommerceApi(CLIENT_KEY) + const methodName = 'getPriceBooksForBasket' + const requiredParameters = ShopperBasketsV2.paramKeys[`${methodName}Required`] + + // 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 parameters = pickValidParams(netOptions.parameters, ShopperBasketsV2.paramKeys[methodName]) + 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) + + queryOptions.meta = { + displayName: 'usePriceBooksForBasket', + ...queryOptions.meta + } + + // 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({...netOptions, parameters}, queryOptions, { + method, + queryKey, + requiredParameters + }) +} +/** + * Gets the applicable shipping methods for a certain shipment of a basket. + * @group ShopperBaskets + * @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 Baskets `getShippingMethodsForShipment` endpoint. + * @see {@link https://developer.salesforce.com/docs/commerce/commerce-api/references/shopper-baskets?meta=getShippingMethodsForShipment| Salesforce Developer Center} for more information about the API endpoint. + * @see {@link https://salesforcecommercecloud.github.io/commerce-sdk-isomorphic/classes/shopperbaskets.shopperbaskets-1.html#getshippingmethodsforshipment | `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 useShippingMethodsForShipment = ( + apiOptions: NullableParameters>, + queryOptions: ApiQueryOptions = {} +): UseQueryResult> => { + type Options = Argument + type Data = DataType + const client = useCommerceApi(CLIENT_KEY) + const methodName = 'getShippingMethodsForShipment' + const requiredParameters = ShopperBasketsV2.paramKeys[`${methodName}Required`] + + // 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 parameters = pickValidParams(netOptions.parameters, ShopperBasketsV2.paramKeys[methodName]) + 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) + + queryOptions.meta = { + displayName: 'useShippingMethodsForShipment', + ...queryOptions.meta + } + + // 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({...netOptions, parameters}, queryOptions, { + method, + queryKey, + requiredParameters + }) +} +/** + * This method gives you the external taxation data set by the PUT taxes API. This endpoint can be called only if external taxation mode was used for basket creation. See POST /baskets for more information. + * @group ShopperBaskets + * @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 Baskets `getTaxesFromBasket` endpoint. + * @see {@link https://developer.salesforce.com/docs/commerce/commerce-api/references/shopper-baskets?meta=getTaxesFromBasket| Salesforce Developer Center} for more information about the API endpoint. + * @see {@link https://salesforcecommercecloud.github.io/commerce-sdk-isomorphic/classes/shopperbaskets.shopperbaskets-1.html#gettaxesfrombasket | `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 useTaxesFromBasket = ( + apiOptions: NullableParameters>, + queryOptions: ApiQueryOptions = {} +): UseQueryResult> => { + type Options = Argument + type Data = DataType + const client = useCommerceApi(CLIENT_KEY) + const methodName = 'getTaxesFromBasket' + const requiredParameters = ShopperBasketsV2.paramKeys[`${methodName}Required`] + + // 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 parameters = pickValidParams(netOptions.parameters, ShopperBasketsV2.paramKeys[methodName]) + 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) + + queryOptions.meta = { + displayName: 'useTaxesFromBasket', + ...queryOptions.meta + } + + // 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({...netOptions, parameters}, queryOptions, { + method, + queryKey, + requiredParameters + }) +} diff --git a/packages/commerce-sdk-react/src/hooks/ShopperBasketsV2/queryKeyHelpers.ts b/packages/commerce-sdk-react/src/hooks/ShopperBasketsV2/queryKeyHelpers.ts new file mode 100644 index 0000000000..feb173302a --- /dev/null +++ b/packages/commerce-sdk-react/src/hooks/ShopperBasketsV2/queryKeyHelpers.ts @@ -0,0 +1,156 @@ +/* + * 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 {ShopperBasketsV2} from 'commerce-sdk-isomorphic' +import {Argument, ExcludeTail} from '../types' +import {pickValidParams} from '../utils' + +// We must use a client with no parameters in order to have required/optional match the API spec +type Client = ShopperBasketsV2<{shortCode: string}> +type Params = Partial['parameters']> +export type QueryKeys = { + getBasket: [ + '/commerce-sdk-react', + '/organizations/', + string | undefined, + '/baskets/', + string | undefined, + Params<'getBasket'> + ] + getPaymentMethodsForBasket: [ + '/commerce-sdk-react', + '/organizations/', + string | undefined, + '/baskets/', + string | undefined, + '/payment-methods', + Params<'getPaymentMethodsForBasket'> + ] + getPriceBooksForBasket: [ + '/commerce-sdk-react', + '/organizations/', + string | undefined, + '/baskets/', + string | undefined, + '/price-books', + Params<'getPriceBooksForBasket'> + ] + getShippingMethodsForShipment: [ + '/commerce-sdk-react', + '/organizations/', + string | undefined, + '/baskets/', + string | undefined, + '/shipments/', + string | undefined, + '/shipping-methods', + Params<'getShippingMethodsForShipment'> + ] + getTaxesFromBasket: [ + '/commerce-sdk-react', + '/organizations/', + string | undefined, + '/baskets/', + string | undefined, + '/taxes', + Params<'getTaxesFromBasket'> + ] +} + +// 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 = { + /** Generates the path component of the query key for an endpoint. */ + path: (params: Params) => ExcludeTail + /** Generates the full query key for an endpoint. */ + queryKey: (params: Params) => QueryKeys[T] +} + +export const getBasket: QueryKeyHelper<'getBasket'> = { + path: (params) => [ + '/commerce-sdk-react', + '/organizations/', + params?.organizationId, + '/baskets/', + params?.basketId + ], + queryKey: (params: Params<'getBasket'>) => { + return [ + ...getBasket.path(params), + pickValidParams(params || {}, ShopperBasketsV2.paramKeys.getBasket) + ] + } +} + +export const getPaymentMethodsForBasket: QueryKeyHelper<'getPaymentMethodsForBasket'> = { + path: (params) => [ + '/commerce-sdk-react', + '/organizations/', + params?.organizationId, + '/baskets/', + params?.basketId, + '/payment-methods' + ], + queryKey: (params: Params<'getPaymentMethodsForBasket'>) => { + return [ + ...getPaymentMethodsForBasket.path(params), + pickValidParams(params || {}, ShopperBasketsV2.paramKeys.getPaymentMethodsForBasket) + ] + } +} + +export const getPriceBooksForBasket: QueryKeyHelper<'getPriceBooksForBasket'> = { + path: (params) => [ + '/commerce-sdk-react', + '/organizations/', + params?.organizationId, + '/baskets/', + params?.basketId, + '/price-books' + ], + queryKey: (params: Params<'getPriceBooksForBasket'>) => { + return [ + ...getPriceBooksForBasket.path(params), + pickValidParams(params || {}, ShopperBasketsV2.paramKeys.getPriceBooksForBasket) + ] + } +} + +export const getShippingMethodsForShipment: QueryKeyHelper<'getShippingMethodsForShipment'> = { + path: (params) => [ + '/commerce-sdk-react', + '/organizations/', + params?.organizationId, + '/baskets/', + params?.basketId, + '/shipments/', + params?.shipmentId, + '/shipping-methods' + ], + queryKey: (params: Params<'getShippingMethodsForShipment'>) => { + return [ + ...getShippingMethodsForShipment.path(params), + pickValidParams(params || {}, ShopperBasketsV2.paramKeys.getShippingMethodsForShipment) + ] + } +} + +export const getTaxesFromBasket: QueryKeyHelper<'getTaxesFromBasket'> = { + path: (params) => [ + '/commerce-sdk-react', + '/organizations/', + params?.organizationId, + '/baskets/', + params?.basketId, + '/taxes' + ], + queryKey: (params: Params<'getTaxesFromBasket'>) => { + return [ + ...getTaxesFromBasket.path(params), + pickValidParams(params || {}, ShopperBasketsV2.paramKeys.getTaxesFromBasket) + ] + } +} diff --git a/packages/commerce-sdk-react/src/hooks/ShopperCustomers/index.test.ts b/packages/commerce-sdk-react/src/hooks/ShopperCustomers/index.test.ts index 41f53ce236..3796ed0d37 100644 --- a/packages/commerce-sdk-react/src/hooks/ShopperCustomers/index.test.ts +++ b/packages/commerce-sdk-react/src/hooks/ShopperCustomers/index.test.ts @@ -20,7 +20,6 @@ describe('Shopper Customers hooks', () => { 'getExternalProfile', // TODO: Implement when the endpoint exits closed beta 'getPublicProductListItems', // TODO: Implement when the endpoint exits closed beta 'registerExternalProfile', // TODO: Implement when the endpoint exits closed beta - 'updateCustomerPaymentInstrument' // TODO: Implement when needed ]) }) test('all mutations have cache update logic', () => { diff --git a/packages/commerce-sdk-react/src/hooks/ShopperOrders/index.test.ts b/packages/commerce-sdk-react/src/hooks/ShopperOrders/index.test.ts index 3d31fca4f7..9ab9c6dbb4 100644 --- a/packages/commerce-sdk-react/src/hooks/ShopperOrders/index.test.ts +++ b/packages/commerce-sdk-react/src/hooks/ShopperOrders/index.test.ts @@ -16,7 +16,7 @@ describe('Shopper Orders hooks', () => { const unimplemented = getUnimplementedEndpoints(ShopperOrders, queries, mutations) // If this test fails: create a new query hook, add the endpoint to the mutations enum, // or add it to the `expected` array with a comment explaining "TODO" or "never" (and why). - expect(unimplemented).toEqual(['failOrder', 'guestOrderLookup']) + expect(unimplemented).toEqual(['guestOrderLookup']) }) test('all mutations have cache update logic', () => { // unimplemented = value in mutations enum, but no method in cache update matrix diff --git a/packages/commerce-sdk-react/src/hooks/index.ts b/packages/commerce-sdk-react/src/hooks/index.ts index 7082fa1ea8..96359cec55 100644 --- a/packages/commerce-sdk-react/src/hooks/index.ts +++ b/packages/commerce-sdk-react/src/hooks/index.ts @@ -5,6 +5,20 @@ * For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/BSD-3-Clause */ export * from './ShopperBaskets' +// V2 — available under explicit V2 names +export { + useBasket as useBasketV2, + usePaymentMethodsForBasket as usePaymentMethodsForBasketV2, + usePriceBooksForBasket as usePriceBooksForBasketV2, + useShippingMethodsForShipment as useShippingMethodsForShipmentV2, + useTaxesFromBasket as useTaxesFromBasketV2, + ShopperBasketsMutations as ShopperBasketsV2Mutations, + useShopperBasketsMutation as useShopperBasketsV2Mutation, + useShopperBasketsMutationHelper as useShopperBasketsV2MutationHelper, +} from './ShopperBasketsV2' +// Only needed if consumers want to type-annotate variables with it. +export type { ShopperBasketsMutation as ShopperBasketsV2Mutation } from './ShopperBasketsV2' + export * from './ShopperContexts' export * from './ShopperCustomers' export * from './ShopperExperience' diff --git a/packages/commerce-sdk-react/src/hooks/types.ts b/packages/commerce-sdk-react/src/hooks/types.ts index 150d671a04..f567cdceda 100644 --- a/packages/commerce-sdk-react/src/hooks/types.ts +++ b/packages/commerce-sdk-react/src/hooks/types.ts @@ -7,8 +7,8 @@ import {InvalidateQueryFilters, QueryFilters, Updater, UseQueryOptions} from '@tanstack/react-query' import { ShopperBaskets, - ShopperConfigurations, ShopperBasketsV2, + ShopperConfigurations, ShopperContexts, ShopperCustomers, ShopperExperience, @@ -88,6 +88,7 @@ export type ApiClientConfigParams = { */ export interface ApiClients { shopperBaskets?: ShopperBaskets + shopperBasketsV2?: ShopperBaskets shopperContexts?: ShopperContexts shopperCustomers?: ShopperCustomers shopperExperience?: ShopperExperience diff --git a/packages/commerce-sdk-react/src/provider.tsx b/packages/commerce-sdk-react/src/provider.tsx index 23ccf778d8..c97fe191fe 100644 --- a/packages/commerce-sdk-react/src/provider.tsx +++ b/packages/commerce-sdk-react/src/provider.tsx @@ -10,6 +10,7 @@ import {ApiClientConfigParams, ApiClients, SDKClientTransformer} from './hooks/t import {Logger} from './types' import {DWSID_COOKIE_NAME, SERVER_AFFINITY_HEADER_KEY} from './constant' import { + ShopperBaskets, ShopperBasketsV2, ShopperContexts, ShopperConfigurations, @@ -257,7 +258,8 @@ const CommerceApiProvider = (props: CommerceApiProviderProps): ReactElement => { } return { - shopperBaskets: new ShopperBasketsV2(config), + shopperBaskets: new ShopperBaskets(config), + shopperBasketsV2: new ShopperBasketsV2(config), shopperContexts: new ShopperContexts(config), shopperConfigurations: new ShopperConfigurations(config), shopperCustomers: new ShopperCustomers(config), diff --git a/packages/template-retail-react-app/app/components/_app/index.jsx b/packages/template-retail-react-app/app/components/_app/index.jsx index 565418dd43..8ef320b923 100644 --- a/packages/template-retail-react-app/app/components/_app/index.jsx +++ b/packages/template-retail-react-app/app/components/_app/index.jsx @@ -15,7 +15,7 @@ import {useQuery} from '@tanstack/react-query' import { useAccessToken, useCategory, - useShopperBasketsMutation + useShopperBasketsV2Mutation as useShopperBasketsMutation } from '@salesforce/commerce-sdk-react' import logger from '@salesforce/retail-react-app/app/utils/logger-instance' import {useAppOrigin} from '@salesforce/retail-react-app/app/hooks/use-app-origin' diff --git a/packages/template-retail-react-app/app/components/bonus-product-view-modal/index.jsx b/packages/template-retail-react-app/app/components/bonus-product-view-modal/index.jsx index b9456f4380..96f58b4fac 100644 --- a/packages/template-retail-react-app/app/components/bonus-product-view-modal/index.jsx +++ b/packages/template-retail-react-app/app/components/bonus-product-view-modal/index.jsx @@ -23,7 +23,7 @@ import ProductView from '@salesforce/retail-react-app/app/components/product-vie import {useProductViewModal} from '@salesforce/retail-react-app/app/hooks/use-product-view-modal' import {useControlledVariations} from '@salesforce/retail-react-app/app/hooks/use-controlled-variations' import {useIntl} from 'react-intl' -import {useShopperBasketsMutationHelper} from '@salesforce/commerce-sdk-react' +import {useShopperBasketsV2MutationHelper as useShopperBasketsMutationHelper} from '@salesforce/commerce-sdk-react' import {useCurrentBasket} from '@salesforce/retail-react-app/app/hooks/use-current-basket' import {processProductsForBonusCart} from '@salesforce/retail-react-app/app/utils/bonus-product/cart' import {useBonusProductCounts} from '@salesforce/retail-react-app/app/utils/bonus-product/hooks' diff --git a/packages/template-retail-react-app/app/components/bonus-product-view-modal/index.test.js b/packages/template-retail-react-app/app/components/bonus-product-view-modal/index.test.js index 642e6b2a5b..f0c7bc6e82 100644 --- a/packages/template-retail-react-app/app/components/bonus-product-view-modal/index.test.js +++ b/packages/template-retail-react-app/app/components/bonus-product-view-modal/index.test.js @@ -22,7 +22,7 @@ import { } from '@salesforce/retail-react-app/app/utils/bonus-product/hooks' import {processProductsForBonusCart} from '@salesforce/retail-react-app/app/utils/bonus-product/cart' import {useCurrentBasket} from '@salesforce/retail-react-app/app/hooks/use-current-basket' -import {useShopperBasketsMutationHelper} from '@salesforce/commerce-sdk-react' +import {useShopperBasketsV2MutationHelper as useShopperBasketsMutationHelper} from '@salesforce/commerce-sdk-react' import {useProductViewModal} from '@salesforce/retail-react-app/app/hooks/use-product-view-modal' // Mock the use-product-view-modal hook at the top @@ -32,7 +32,7 @@ jest.mock('@salesforce/retail-react-app/app/hooks/use-product-view-modal', () => // Mock commerce-sdk-react for CommerceApiProvider jest.mock('@salesforce/commerce-sdk-react', () => ({ - useShopperBasketsMutationHelper: jest.fn(), + useShopperBasketsV2MutationHelper: jest.fn(), useCustomerId: jest.fn(() => 'test-customer-id'), useCustomerType: jest.fn(() => ({ isRegistered: true, diff --git a/packages/template-retail-react-app/app/components/product-view/index.jsx b/packages/template-retail-react-app/app/components/product-view/index.jsx index e68655ba48..181e4a29e9 100644 --- a/packages/template-retail-react-app/app/components/product-view/index.jsx +++ b/packages/template-retail-react-app/app/components/product-view/index.jsx @@ -34,7 +34,7 @@ import {useCurrency, useDerivedProduct} from '@salesforce/retail-react-app/app/h import {useAddToCartModalContext} from '@salesforce/retail-react-app/app/hooks/use-add-to-cart-modal' import {STORE_LOCATOR_IS_ENABLED} from '@salesforce/retail-react-app/app/constants' import {getConfig} from '@salesforce/pwa-kit-runtime/utils/ssr-config' -import {useShopperBasketsMutation} from '@salesforce/commerce-sdk-react' +import {useShopperBasketsV2Mutation as useShopperBasketsMutation} from '@salesforce/commerce-sdk-react' import { useSFPaymentsEnabled, useSFPayments diff --git a/packages/template-retail-react-app/app/components/promo-code/index.jsx b/packages/template-retail-react-app/app/components/promo-code/index.jsx index 7eaeae441c..b44ac5656b 100644 --- a/packages/template-retail-react-app/app/components/promo-code/index.jsx +++ b/packages/template-retail-react-app/app/components/promo-code/index.jsx @@ -20,7 +20,7 @@ import {useForm} from 'react-hook-form' import {ChevronDownIcon, ChevronUpIcon} from '@salesforce/retail-react-app/app/components/icons' import PromoCodeFields from '@salesforce/retail-react-app/app/components/forms/promo-code-fields' import {API_ERROR_MESSAGE} from '@salesforce/retail-react-app/app/constants' -import {useShopperBasketsMutation} from '@salesforce/commerce-sdk-react' +import {useShopperBasketsV2Mutation as useShopperBasketsMutation} from '@salesforce/commerce-sdk-react' import {useCurrentBasket} from '@salesforce/retail-react-app/app/hooks/use-current-basket' export const usePromoCode = () => { diff --git a/packages/template-retail-react-app/app/components/sf-payments-express-buttons/index.jsx b/packages/template-retail-react-app/app/components/sf-payments-express-buttons/index.jsx index 4a6f8fbf43..26588a76ef 100644 --- a/packages/template-retail-react-app/app/components/sf-payments-express-buttons/index.jsx +++ b/packages/template-retail-react-app/app/components/sf-payments-express-buttons/index.jsx @@ -12,9 +12,9 @@ import {useIntl} from 'react-intl' import {Box} from '@salesforce/retail-react-app/app/components/shared/ui' import logger from '@salesforce/retail-react-app/app/utils/logger-instance' import {DEFAULT_SHIPMENT_ID} from '@salesforce/retail-react-app/app/constants' -import {useShopperBasketsMutation} from '@salesforce/commerce-sdk-react' +import {useShopperBasketsV2Mutation as useShopperBasketsMutation} from '@salesforce/commerce-sdk-react' import {useShopperOrdersMutation} from '@salesforce/commerce-sdk-react' -import {useShippingMethodsForShipment} from '@salesforce/commerce-sdk-react' +import {useShippingMethodsForShipmentV2 as useShippingMethodsForShipment} from '@salesforce/commerce-sdk-react' import {usePaymentConfiguration} from '@salesforce/commerce-sdk-react' import useNavigation from '@salesforce/retail-react-app/app/hooks/use-navigation' import {useSFPaymentsCountry} from '@salesforce/retail-react-app/app/hooks/use-sf-payments-country' diff --git a/packages/template-retail-react-app/app/components/sf-payments-express-buttons/index.test.js b/packages/template-retail-react-app/app/components/sf-payments-express-buttons/index.test.js index 1697d0db96..1803244477 100644 --- a/packages/template-retail-react-app/app/components/sf-payments-express-buttons/index.test.js +++ b/packages/template-retail-react-app/app/components/sf-payments-express-buttons/index.test.js @@ -278,10 +278,10 @@ describe('failOrder error handling', () => { paymentMethodSetAccounts: [] } }), - useShopperBasketsMutation: () => ({ + useShopperBasketsV2Mutation: () => ({ mutateAsync: jest.fn() }), - useShippingMethodsForShipment: () => ({ + useShippingMethodsForShipmentV2: () => ({ refetch: jest.fn() }) } diff --git a/packages/template-retail-react-app/app/hooks/use-auth-modal.js b/packages/template-retail-react-app/app/hooks/use-auth-modal.js index d81b62ddea..ae1a60c960 100644 --- a/packages/template-retail-react-app/app/hooks/use-auth-modal.js +++ b/packages/template-retail-react-app/app/hooks/use-auth-modal.js @@ -24,7 +24,7 @@ import { useCustomerId, useCustomerType, useCustomerBaskets, - useShopperBasketsMutation + useShopperBasketsV2Mutation as useShopperBasketsMutation } from '@salesforce/commerce-sdk-react' import LoginForm from '@salesforce/retail-react-app/app/components/login' import ResetPasswordForm from '@salesforce/retail-react-app/app/components/reset-password' diff --git a/packages/template-retail-react-app/app/hooks/use-cleanup-temporary-baskets.js b/packages/template-retail-react-app/app/hooks/use-cleanup-temporary-baskets.js index 740c3e90c8..28cccde567 100644 --- a/packages/template-retail-react-app/app/hooks/use-cleanup-temporary-baskets.js +++ b/packages/template-retail-react-app/app/hooks/use-cleanup-temporary-baskets.js @@ -5,7 +5,7 @@ * For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/BSD-3-Clause */ import {useCustomerId, useCustomerBaskets} from '@salesforce/commerce-sdk-react' -import {useShopperBasketsMutation} from '@salesforce/commerce-sdk-react' +import {useShopperBasketsV2Mutation as useShopperBasketsMutation} from '@salesforce/commerce-sdk-react' import {isServer} from '@salesforce/retail-react-app/app/utils/utils' import logger from '@salesforce/retail-react-app/app/utils/logger-instance' diff --git a/packages/template-retail-react-app/app/hooks/use-cleanup-temporary-baskets.test.js b/packages/template-retail-react-app/app/hooks/use-cleanup-temporary-baskets.test.js index 4bf3049cce..ccf9a1f6ef 100644 --- a/packages/template-retail-react-app/app/hooks/use-cleanup-temporary-baskets.test.js +++ b/packages/template-retail-react-app/app/hooks/use-cleanup-temporary-baskets.test.js @@ -10,7 +10,7 @@ import {useCleanupTemporaryBaskets} from '@salesforce/retail-react-app/app/hooks import { useCustomerId, useCustomerBaskets, - useShopperBasketsMutation + useShopperBasketsV2Mutation as useShopperBasketsMutation } from '@salesforce/commerce-sdk-react' import logger from '@salesforce/retail-react-app/app/utils/logger-instance' @@ -47,7 +47,7 @@ jest.mock('@salesforce/commerce-sdk-react', () => { ...originalModule, useCustomerId: jest.fn(), useCustomerBaskets: jest.fn(), - useShopperBasketsMutation: jest.fn() + useShopperBasketsV2Mutation: jest.fn() } }) diff --git a/packages/template-retail-react-app/app/hooks/use-current-basket.test.js b/packages/template-retail-react-app/app/hooks/use-current-basket.test.js index b25d16d489..0c2268d432 100644 --- a/packages/template-retail-react-app/app/hooks/use-current-basket.test.js +++ b/packages/template-retail-react-app/app/hooks/use-current-basket.test.js @@ -45,7 +45,7 @@ jest.mock('@salesforce/commerce-sdk-react', () => { ...originalModule, useCustomerId: jest.fn(() => 'abmuc2wupJxeoRxuo3wqYYmbhI'), useCustomerBaskets: jest.fn(), - useShopperBasketsMutation: () => ({ + useShopperBasketsV2Mutation: () => ({ mutateAsync: mockAsyncMutate }) } diff --git a/packages/template-retail-react-app/app/hooks/use-item-shipment-management.js b/packages/template-retail-react-app/app/hooks/use-item-shipment-management.js index 0493a09927..7f94b745f1 100644 --- a/packages/template-retail-react-app/app/hooks/use-item-shipment-management.js +++ b/packages/template-retail-react-app/app/hooks/use-item-shipment-management.js @@ -5,7 +5,7 @@ * For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/BSD-3-Clause */ -import {useShopperBasketsMutation} from '@salesforce/commerce-sdk-react' +import {useShopperBasketsV2Mutation as useShopperBasketsMutation} from '@salesforce/commerce-sdk-react' import {useCallback} from 'react' import {DEFAULT_SHIPMENT_ID} from '@salesforce/retail-react-app/app/constants' diff --git a/packages/template-retail-react-app/app/hooks/use-item-shipment-management.test.js b/packages/template-retail-react-app/app/hooks/use-item-shipment-management.test.js index 42d09b0725..5dfb72e40d 100644 --- a/packages/template-retail-react-app/app/hooks/use-item-shipment-management.test.js +++ b/packages/template-retail-react-app/app/hooks/use-item-shipment-management.test.js @@ -6,12 +6,12 @@ */ import {renderHook} from '@testing-library/react' -import {useShopperBasketsMutation} from '@salesforce/commerce-sdk-react' +import {useShopperBasketsV2Mutation as useShopperBasketsMutation} from '@salesforce/commerce-sdk-react' import {useItemShipmentManagement} from '@salesforce/retail-react-app/app/hooks/use-item-shipment-management' // Mock the commerce SDK hooks jest.mock('@salesforce/commerce-sdk-react', () => ({ - useShopperBasketsMutation: jest.fn() + useShopperBasketsV2Mutation: jest.fn() })) describe('useItemShipmentManagement', () => { diff --git a/packages/template-retail-react-app/app/hooks/use-multiship.js b/packages/template-retail-react-app/app/hooks/use-multiship.js index 50e5d380ce..fc9e04adbd 100644 --- a/packages/template-retail-react-app/app/hooks/use-multiship.js +++ b/packages/template-retail-react-app/app/hooks/use-multiship.js @@ -6,7 +6,7 @@ */ import {useCallback} from 'react' -import {useShippingMethodsForShipment} from '@salesforce/commerce-sdk-react' +import {useShippingMethodsForShipmentV2 as useShippingMethodsForShipment} from '@salesforce/commerce-sdk-react' import {usePickupShipment} from '@salesforce/retail-react-app/app/hooks/use-pickup-shipment' import {useShipmentOperations} from '@salesforce/retail-react-app/app/hooks/use-shipment-operations' import {useItemShipmentManagement} from '@salesforce/retail-react-app/app/hooks/use-item-shipment-management' diff --git a/packages/template-retail-react-app/app/hooks/use-multiship.test.js b/packages/template-retail-react-app/app/hooks/use-multiship.test.js index c3095431c3..b0862f16b6 100644 --- a/packages/template-retail-react-app/app/hooks/use-multiship.test.js +++ b/packages/template-retail-react-app/app/hooks/use-multiship.test.js @@ -8,7 +8,7 @@ import {renderHook, act} from '@testing-library/react' import {useMultiship} from '@salesforce/retail-react-app/app/hooks/use-multiship' import logger from '@salesforce/retail-react-app/app/utils/logger-instance' -import {useShippingMethodsForShipment} from '@salesforce/commerce-sdk-react' +import {useShippingMethodsForShipmentV2 as useShippingMethodsForShipment} from '@salesforce/commerce-sdk-react' import {usePickupShipment} from '@salesforce/retail-react-app/app/hooks/use-pickup-shipment' import {useShipmentOperations} from '@salesforce/retail-react-app/app/hooks/use-shipment-operations' import {useItemShipmentManagement} from '@salesforce/retail-react-app/app/hooks/use-item-shipment-management' @@ -19,7 +19,7 @@ import { // Mock dependencies jest.mock('@salesforce/commerce-sdk-react', () => ({ - useShippingMethodsForShipment: jest.fn() + useShippingMethodsForShipmentV2: jest.fn() })) jest.mock('@salesforce/retail-react-app/app/hooks/use-pickup-shipment', () => ({ diff --git a/packages/template-retail-react-app/app/hooks/use-pickup-shipment.js b/packages/template-retail-react-app/app/hooks/use-pickup-shipment.js index 573ba42450..28b31b53be 100644 --- a/packages/template-retail-react-app/app/hooks/use-pickup-shipment.js +++ b/packages/template-retail-react-app/app/hooks/use-pickup-shipment.js @@ -6,8 +6,8 @@ */ import { - useShopperBasketsMutation, - useShippingMethodsForShipment + useShopperBasketsV2Mutation as useShopperBasketsMutation, + useShippingMethodsForShipmentV2 as useShippingMethodsForShipment } from '@salesforce/commerce-sdk-react' import {DEFAULT_SHIPMENT_ID} from '@salesforce/retail-react-app/app/constants' import {getShippingAddressForStore} from '@salesforce/retail-react-app/app/utils/address-utils' diff --git a/packages/template-retail-react-app/app/hooks/use-pickup-shipment.test.js b/packages/template-retail-react-app/app/hooks/use-pickup-shipment.test.js index de48ef67d9..fba93a1939 100644 --- a/packages/template-retail-react-app/app/hooks/use-pickup-shipment.test.js +++ b/packages/template-retail-react-app/app/hooks/use-pickup-shipment.test.js @@ -11,11 +11,11 @@ import mockProductDetail from '@salesforce/retail-react-app/app/mocks/variant-75 // Mock the dependencies jest.mock('@salesforce/commerce-sdk-react', () => ({ - useShopperBasketsMutation: jest.fn(() => ({ + useShopperBasketsV2Mutation: jest.fn(() => ({ mutateAsync: jest.fn(), isLoading: false })), - useShippingMethodsForShipment: jest.fn(() => ({ + useShippingMethodsForShipmentV2: jest.fn(() => ({ refetch: jest.fn() })) })) @@ -481,7 +481,7 @@ describe('usePickupShipment', () => { // Get the mocked module and update the mock to include mutateAsync const commerceSdkMock = jest.requireMock('@salesforce/commerce-sdk-react') - commerceSdkMock.useShopperBasketsMutation.mockReturnValue({ + commerceSdkMock.useShopperBasketsV2Mutation.mockReturnValue({ mutateAsync: mockMutateAsync, isLoading: false }) @@ -601,11 +601,11 @@ describe('usePickupShipment', () => { // Get the mocked module and update the mock to include mutateAsync and refetch const commerceSdkMock = jest.requireMock('@salesforce/commerce-sdk-react') - commerceSdkMock.useShopperBasketsMutation.mockReturnValue({ + commerceSdkMock.useShopperBasketsV2Mutation.mockReturnValue({ mutateAsync: mockMutateAsync, isLoading: false }) - commerceSdkMock.useShippingMethodsForShipment.mockReturnValue({ + commerceSdkMock.useShippingMethodsForShipmentV2.mockReturnValue({ refetch: mockRefetchShippingMethods }) }) @@ -963,7 +963,7 @@ describe('usePickupShipment', () => { // Get the mocked module and update the mock to include mutateAsync const commerceSdkMock = jest.requireMock('@salesforce/commerce-sdk-react') - commerceSdkMock.useShopperBasketsMutation.mockReturnValue({ + commerceSdkMock.useShopperBasketsV2Mutation.mockReturnValue({ mutateAsync: mockMutateAsync, isLoading: false }) diff --git a/packages/template-retail-react-app/app/hooks/use-shipment-operations.js b/packages/template-retail-react-app/app/hooks/use-shipment-operations.js index 158bc73c90..e04b32c7ae 100644 --- a/packages/template-retail-react-app/app/hooks/use-shipment-operations.js +++ b/packages/template-retail-react-app/app/hooks/use-shipment-operations.js @@ -5,7 +5,7 @@ * For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/BSD-3-Clause */ -import {useShopperBasketsMutation} from '@salesforce/commerce-sdk-react' +import {useShopperBasketsV2Mutation as useShopperBasketsMutation} from '@salesforce/commerce-sdk-react' import {useCallback} from 'react' import {cleanAddressForOrder} from '@salesforce/retail-react-app/app/utils/address-utils' import {nanoid} from 'nanoid' diff --git a/packages/template-retail-react-app/app/hooks/use-shipment-operations.test.js b/packages/template-retail-react-app/app/hooks/use-shipment-operations.test.js index 071c65e23a..efdc90eb93 100644 --- a/packages/template-retail-react-app/app/hooks/use-shipment-operations.test.js +++ b/packages/template-retail-react-app/app/hooks/use-shipment-operations.test.js @@ -6,12 +6,12 @@ */ import {renderHook} from '@testing-library/react' -import {useShopperBasketsMutation} from '@salesforce/commerce-sdk-react' +import {useShopperBasketsV2Mutation as useShopperBasketsMutation} from '@salesforce/commerce-sdk-react' import {useShipmentOperations} from '@salesforce/retail-react-app/app/hooks/use-shipment-operations' // Mock the commerce SDK hooks jest.mock('@salesforce/commerce-sdk-react', () => ({ - useShopperBasketsMutation: jest.fn() + useShopperBasketsV2Mutation: jest.fn() })) jest.mock('nanoid', () => ({ diff --git a/packages/template-retail-react-app/app/pages/account/wishlist/partials/wishlist-primary-action.jsx b/packages/template-retail-react-app/app/pages/account/wishlist/partials/wishlist-primary-action.jsx index 5adadab379..33bf4c5a3a 100644 --- a/packages/template-retail-react-app/app/pages/account/wishlist/partials/wishlist-primary-action.jsx +++ b/packages/template-retail-react-app/app/pages/account/wishlist/partials/wishlist-primary-action.jsx @@ -12,7 +12,7 @@ import ProductViewModal from '@salesforce/retail-react-app/app/components/produc import {useToast} from '@salesforce/retail-react-app/app/hooks/use-toast' import {API_ERROR_MESSAGE} from '@salesforce/retail-react-app/app/constants' import Link from '@salesforce/retail-react-app/app/components/link' -import {useShopperBasketsMutationHelper} from '@salesforce/commerce-sdk-react' +import {useShopperBasketsV2MutationHelper as useShopperBasketsMutationHelper} from '@salesforce/commerce-sdk-react' /** * Renders primary action on a product-item card in the form of a button. diff --git a/packages/template-retail-react-app/app/pages/cart/index.jsx b/packages/template-retail-react-app/app/pages/cart/index.jsx index b809a8fb76..7a4033a6c9 100644 --- a/packages/template-retail-react-app/app/pages/cart/index.jsx +++ b/packages/template-retail-react-app/app/pages/cart/index.jsx @@ -75,7 +75,7 @@ import {REMOVE_CART_ITEM_CONFIRMATION_DIALOG_CONFIG} from '@salesforce/retail-re import debounce from 'lodash/debounce' import {useCurrentBasket} from '@salesforce/retail-react-app/app/hooks/use-current-basket' import { - useShopperBasketsMutation, + useShopperBasketsV2Mutation as useShopperBasketsMutation, useProducts, useShopperCustomersMutation, useStores diff --git a/packages/template-retail-react-app/app/pages/checkout-one-click/index.jsx b/packages/template-retail-react-app/app/pages/checkout-one-click/index.jsx index 1b6274dc2d..1de9fb982a 100644 --- a/packages/template-retail-react-app/app/pages/checkout-one-click/index.jsx +++ b/packages/template-retail-react-app/app/pages/checkout-one-click/index.jsx @@ -21,7 +21,7 @@ import { import {FormattedMessage, useIntl} from 'react-intl' import {useForm} from 'react-hook-form' import { - useShopperBasketsMutation, + useShopperBasketsV2Mutation as useShopperBasketsMutation, useShopperOrdersMutation, useShopperCustomersMutation, ShopperBasketsMutations, diff --git a/packages/template-retail-react-app/app/pages/checkout-one-click/index.test.js b/packages/template-retail-react-app/app/pages/checkout-one-click/index.test.js index 4535324a5b..787a6acf6a 100644 --- a/packages/template-retail-react-app/app/pages/checkout-one-click/index.test.js +++ b/packages/template-retail-react-app/app/pages/checkout-one-click/index.test.js @@ -60,7 +60,7 @@ jest.mock('@salesforce/commerce-sdk-react', () => { useAuthHelper: () => ({ mutateAsync: mockUseAuthHelper }), - useShopperBasketsMutation: (mutation) => { + useShopperBasketsV2Mutation: (mutation) => { if (mutation === 'removeItemFromBasket') { return { mutateAsync: (_, {onSuccess} = {}) => { @@ -69,7 +69,7 @@ jest.mock('@salesforce/commerce-sdk-react', () => { } } } - return originalModule.useShopperBasketsMutation(mutation) + return originalModule.useShopperBasketsV2Mutation(mutation) }, useShopperCustomersMutation: (mutation) => { if (mutation === 'createCustomerPaymentInstrument') { diff --git a/packages/template-retail-react-app/app/pages/checkout-one-click/partials/one-click-contact-info.jsx b/packages/template-retail-react-app/app/pages/checkout-one-click/partials/one-click-contact-info.jsx index d82a56610b..95091ab81b 100644 --- a/packages/template-retail-react-app/app/pages/checkout-one-click/partials/one-click-contact-info.jsx +++ b/packages/template-retail-react-app/app/pages/checkout-one-click/partials/one-click-contact-info.jsx @@ -45,7 +45,7 @@ import {useCurrentBasket} from '@salesforce/retail-react-app/app/hooks/use-curre import { AuthHelpers, useAuthHelper, - useShopperBasketsMutation, + useShopperBasketsV2Mutation as useShopperBasketsMutation, useCustomerType, useShopperCustomersMutation } from '@salesforce/commerce-sdk-react' diff --git a/packages/template-retail-react-app/app/pages/checkout-one-click/partials/one-click-contact-info.test.js b/packages/template-retail-react-app/app/pages/checkout-one-click/partials/one-click-contact-info.test.js index f55bb54646..51beeab21a 100644 --- a/packages/template-retail-react-app/app/pages/checkout-one-click/partials/one-click-contact-info.test.js +++ b/packages/template-retail-react-app/app/pages/checkout-one-click/partials/one-click-contact-info.test.js @@ -34,7 +34,7 @@ jest.mock('@salesforce/commerce-sdk-react', () => { useAuthHelper: jest .fn() .mockImplementation((helperType) => mockAuthHelperFunctions[helperType]), - useShopperBasketsMutation: jest.fn().mockImplementation((mutationType) => { + useShopperBasketsV2Mutation: jest.fn().mockImplementation((mutationType) => { if (mutationType === 'updateCustomerForBasket') return mockUpdateCustomerForBasket if (mutationType === 'transferBasket') return mockTransferBasket if (mutationType === 'updateBillingAddressForBasket') diff --git a/packages/template-retail-react-app/app/pages/checkout-one-click/partials/one-click-payment.jsx b/packages/template-retail-react-app/app/pages/checkout-one-click/partials/one-click-payment.jsx index 551b2fe69d..a7bf8d9518 100644 --- a/packages/template-retail-react-app/app/pages/checkout-one-click/partials/one-click-payment.jsx +++ b/packages/template-retail-react-app/app/pages/checkout-one-click/partials/one-click-payment.jsx @@ -16,7 +16,7 @@ import { Divider } from '@salesforce/retail-react-app/app/components/shared/ui' import {useToast} from '@salesforce/retail-react-app/app/hooks/use-toast' -import {useShopperBasketsMutation, useCustomerType} from '@salesforce/commerce-sdk-react' +import {useShopperBasketsV2Mutation as useShopperBasketsMutation, useCustomerType} from '@salesforce/commerce-sdk-react' import {useCurrentBasket} from '@salesforce/retail-react-app/app/hooks/use-current-basket' import {useCurrentCustomer} from '@salesforce/retail-react-app/app/hooks/use-current-customer' import {useCheckoutAutoSelect} from '@salesforce/retail-react-app/app/hooks/use-checkout-auto-select' diff --git a/packages/template-retail-react-app/app/pages/checkout-one-click/partials/one-click-payment.test.js b/packages/template-retail-react-app/app/pages/checkout-one-click/partials/one-click-payment.test.js index ba097f8133..73f18e6464 100644 --- a/packages/template-retail-react-app/app/pages/checkout-one-click/partials/one-click-payment.test.js +++ b/packages/template-retail-react-app/app/pages/checkout-one-click/partials/one-click-payment.test.js @@ -12,7 +12,7 @@ import {useCurrentBasket} from '@salesforce/retail-react-app/app/hooks/use-curre import {useCurrentCustomer} from '@salesforce/retail-react-app/app/hooks/use-current-customer' import {useToast} from '@salesforce/retail-react-app/app/hooks/use-toast' import {useCurrency} from '@salesforce/retail-react-app/app/hooks/use-currency' -import {useShopperBasketsMutation, useCustomerType} from '@salesforce/commerce-sdk-react' +import {useShopperBasketsV2Mutation as useShopperBasketsMutation, useCustomerType} from '@salesforce/commerce-sdk-react' import {useCheckout} from '@salesforce/retail-react-app/app/pages/checkout-one-click/util/checkout-context' import Payment from '@salesforce/retail-react-app/app/pages/checkout-one-click/partials/one-click-payment' import {CurrencyProvider} from '@salesforce/retail-react-app/app/contexts' @@ -60,7 +60,7 @@ jest.mock('@salesforce/commerce-sdk-react', () => { const original = jest.requireActual('@salesforce/commerce-sdk-react') return { ...original, - useShopperBasketsMutation: jest.fn(), + useShopperBasketsV2Mutation: jest.fn(), useAuthHelper: jest.fn(() => ({mutateAsync: jest.fn()})), useUsid: () => ({getUsidWhenReady: jest.fn().mockResolvedValue('usid-123')}), useCustomerType: jest.fn(() => ({isGuest: true, isRegistered: false})), diff --git a/packages/template-retail-react-app/app/pages/checkout-one-click/partials/one-click-pickup-address.jsx b/packages/template-retail-react-app/app/pages/checkout-one-click/partials/one-click-pickup-address.jsx index 76ffa059ec..5488910ec3 100644 --- a/packages/template-retail-react-app/app/pages/checkout-one-click/partials/one-click-pickup-address.jsx +++ b/packages/template-retail-react-app/app/pages/checkout-one-click/partials/one-click-pickup-address.jsx @@ -18,7 +18,7 @@ import StoreDisplay from '@salesforce/retail-react-app/app/components/store-disp // Hooks import {useCheckout} from '@salesforce/retail-react-app/app/pages/checkout-one-click/util/checkout-context' import {useCurrentBasket} from '@salesforce/retail-react-app/app/hooks/use-current-basket' -import {useShopperBasketsMutation, useStores} from '@salesforce/commerce-sdk-react' +import {useShopperBasketsV2Mutation as useShopperBasketsMutation, useStores} from '@salesforce/commerce-sdk-react' import {isPickupShipment} from '@salesforce/retail-react-app/app/utils/shipment-utils' const PickupAddress = () => { diff --git a/packages/template-retail-react-app/app/pages/checkout-one-click/partials/one-click-pickup-address.test.js b/packages/template-retail-react-app/app/pages/checkout-one-click/partials/one-click-pickup-address.test.js index e3ff80b173..1602a2873d 100644 --- a/packages/template-retail-react-app/app/pages/checkout-one-click/partials/one-click-pickup-address.test.js +++ b/packages/template-retail-react-app/app/pages/checkout-one-click/partials/one-click-pickup-address.test.js @@ -15,7 +15,7 @@ jest.mock('@salesforce/commerce-sdk-react', () => { const originalModule = jest.requireActual('@salesforce/commerce-sdk-react') return { ...originalModule, - useShopperBasketsMutation: () => ({ + useShopperBasketsV2Mutation: () => ({ mutateAsync: mockMutateAsync }), useStores: () => ({ diff --git a/packages/template-retail-react-app/app/pages/checkout-one-click/partials/one-click-shipping-address.jsx b/packages/template-retail-react-app/app/pages/checkout-one-click/partials/one-click-shipping-address.jsx index f93e110975..c7991008a2 100644 --- a/packages/template-retail-react-app/app/pages/checkout-one-click/partials/one-click-shipping-address.jsx +++ b/packages/template-retail-react-app/app/pages/checkout-one-click/partials/one-click-shipping-address.jsx @@ -18,7 +18,7 @@ import AddressDisplay from '@salesforce/retail-react-app/app/components/address- import OneClickShippingMultiAddress from '@salesforce/retail-react-app/app/pages/checkout-one-click/partials/one-click-shipping-multi-address' import { useShopperCustomersMutation, - useShopperBasketsMutation, + useShopperBasketsV2Mutation as useShopperBasketsMutation, useShippingMethodsForShipment } from '@salesforce/commerce-sdk-react' import {useCurrentCustomer} from '@salesforce/retail-react-app/app/hooks/use-current-customer' diff --git a/packages/template-retail-react-app/app/pages/checkout-one-click/partials/one-click-shipping-address.test.js b/packages/template-retail-react-app/app/pages/checkout-one-click/partials/one-click-shipping-address.test.js index 6dabd6cddc..9cfcfcf46b 100644 --- a/packages/template-retail-react-app/app/pages/checkout-one-click/partials/one-click-shipping-address.test.js +++ b/packages/template-retail-react-app/app/pages/checkout-one-click/partials/one-click-shipping-address.test.js @@ -41,7 +41,7 @@ jest.mock('@salesforce/commerce-sdk-react', () => { const originalModule = jest.requireActual('@salesforce/commerce-sdk-react') return { ...originalModule, - useShopperBasketsMutation: jest.fn().mockImplementation((mutationType) => { + useShopperBasketsV2Mutation: jest.fn().mockImplementation((mutationType) => { if (mutationType === 'updateShippingAddressForShipment') return mockUpdateShippingAddress return {mutateAsync: jest.fn()} @@ -830,7 +830,7 @@ describe('ShippingAddress Component', () => { const originalModule = jest.requireActual('@salesforce/commerce-sdk-react') return { ...originalModule, - useShopperBasketsMutation: jest.fn().mockImplementation((mutationType) => { + useShopperBasketsV2Mutation: jest.fn().mockImplementation((mutationType) => { if (mutationType === 'updateShippingAddressForShipment') return mockUpdateShippingAddress return {mutateAsync: jest.fn()} @@ -934,7 +934,7 @@ describe('ShippingAddress Component', () => { const originalModule = jest.requireActual('@salesforce/commerce-sdk-react') return { ...originalModule, - useShopperBasketsMutation: jest.fn().mockImplementation((mutationType) => { + useShopperBasketsV2Mutation: jest.fn().mockImplementation((mutationType) => { if (mutationType === 'updateShippingAddressForShipment') return mockUpdateShippingAddress return {mutateAsync: jest.fn()} @@ -1095,7 +1095,7 @@ describe('ShippingAddress Component', () => { const originalModule = jest.requireActual('@salesforce/commerce-sdk-react') return { ...originalModule, - useShopperBasketsMutation: jest.fn().mockImplementation((mutationType) => { + useShopperBasketsV2Mutation: jest.fn().mockImplementation((mutationType) => { if (mutationType === 'updateShippingAddressForShipment') return mockUpdateShippingAddress return {mutateAsync: jest.fn()} diff --git a/packages/template-retail-react-app/app/pages/checkout-one-click/partials/one-click-shipping-options.jsx b/packages/template-retail-react-app/app/pages/checkout-one-click/partials/one-click-shipping-options.jsx index 586f0ded71..6afb6e6321 100644 --- a/packages/template-retail-react-app/app/pages/checkout-one-click/partials/one-click-shipping-options.jsx +++ b/packages/template-retail-react-app/app/pages/checkout-one-click/partials/one-click-shipping-options.jsx @@ -25,7 +25,7 @@ import { } from '@salesforce/retail-react-app/app/components/toggle-card' import { useShippingMethodsForShipment, - useShopperBasketsMutation + useShopperBasketsV2Mutation as useShopperBasketsMutation } from '@salesforce/commerce-sdk-react' import {useCurrentBasket} from '@salesforce/retail-react-app/app/hooks/use-current-basket' import {useCurrentCustomer} from '@salesforce/retail-react-app/app/hooks/use-current-customer' diff --git a/packages/template-retail-react-app/app/pages/checkout-one-click/partials/one-click-shipping-options.test.js b/packages/template-retail-react-app/app/pages/checkout-one-click/partials/one-click-shipping-options.test.js index 07b9630a00..a00080c13a 100644 --- a/packages/template-retail-react-app/app/pages/checkout-one-click/partials/one-click-shipping-options.test.js +++ b/packages/template-retail-react-app/app/pages/checkout-one-click/partials/one-click-shipping-options.test.js @@ -43,7 +43,7 @@ jest.mock('@salesforce/commerce-sdk-react', () => { const originalModule = jest.requireActual('@salesforce/commerce-sdk-react') return { ...originalModule, - useShopperBasketsMutation: jest.fn().mockImplementation((mutationType) => { + useShopperBasketsV2Mutation: jest.fn().mockImplementation((mutationType) => { if (mutationType === 'updateShippingMethodForShipment') return mockUpdateShippingMethod return {mutateAsync: jest.fn()} }), diff --git a/packages/template-retail-react-app/app/pages/checkout/index.jsx b/packages/template-retail-react-app/app/pages/checkout/index.jsx index e7c2435bab..0b8ae1d45e 100644 --- a/packages/template-retail-react-app/app/pages/checkout/index.jsx +++ b/packages/template-retail-react-app/app/pages/checkout/index.jsx @@ -33,7 +33,10 @@ import OrderSummary from '@salesforce/retail-react-app/app/components/order-summ import {useCurrentCustomer} from '@salesforce/retail-react-app/app/hooks/use-current-customer' import {useCurrentBasket} from '@salesforce/retail-react-app/app/hooks/use-current-basket' import CheckoutSkeleton from '@salesforce/retail-react-app/app/pages/checkout/partials/checkout-skeleton' -import {useShopperOrdersMutation, useShopperBasketsMutation} from '@salesforce/commerce-sdk-react' +import { + useShopperOrdersMutation, + useShopperBasketsV2Mutation as useShopperBasketsMutation +} from '@salesforce/commerce-sdk-react' import UnavailableProductConfirmationModal from '@salesforce/retail-react-app/app/components/unavailable-product-confirmation-modal' import { API_ERROR_MESSAGE, diff --git a/packages/template-retail-react-app/app/pages/checkout/partials/contact-info.jsx b/packages/template-retail-react-app/app/pages/checkout/partials/contact-info.jsx index 05e0eebd6c..b7e6aa7af8 100644 --- a/packages/template-retail-react-app/app/pages/checkout/partials/contact-info.jsx +++ b/packages/template-retail-react-app/app/pages/checkout/partials/contact-info.jsx @@ -41,7 +41,13 @@ import { import useNavigation from '@salesforce/retail-react-app/app/hooks/use-navigation' import {useCurrentCustomer} from '@salesforce/retail-react-app/app/hooks/use-current-customer' import {useCurrentBasket} from '@salesforce/retail-react-app/app/hooks/use-current-basket' -import {AuthHelpers, useAuthHelper, useShopperBasketsMutation} from '@salesforce/commerce-sdk-react' +import {isAbsoluteURL} from '@salesforce/retail-react-app/app/page-designer/utils' +import {useAppOrigin} from '@salesforce/retail-react-app/app/hooks/use-app-origin' +import { + AuthHelpers, + useAuthHelper, + useShopperBasketsV2Mutation as useShopperBasketsMutation +} from '@salesforce/commerce-sdk-react' import {getConfig} from '@salesforce/pwa-kit-runtime/utils/ssr-config' import { getPasswordlessCallbackUrl, diff --git a/packages/template-retail-react-app/app/pages/checkout/partials/payment.jsx b/packages/template-retail-react-app/app/pages/checkout/partials/payment.jsx index 70ce68baec..dd1f76b0de 100644 --- a/packages/template-retail-react-app/app/pages/checkout/partials/payment.jsx +++ b/packages/template-retail-react-app/app/pages/checkout/partials/payment.jsx @@ -19,7 +19,7 @@ import { } from '@salesforce/retail-react-app/app/components/shared/ui' import {useForm} from 'react-hook-form' import {useToast} from '@salesforce/retail-react-app/app/hooks/use-toast' -import {useShopperBasketsMutation} from '@salesforce/commerce-sdk-react' +import {useShopperBasketsV2Mutation as useShopperBasketsMutation} from '@salesforce/commerce-sdk-react' import {useCurrentBasket} from '@salesforce/retail-react-app/app/hooks/use-current-basket' import {useCheckout} from '@salesforce/retail-react-app/app/pages/checkout/util/checkout-context' import { diff --git a/packages/template-retail-react-app/app/pages/checkout/partials/sf-payments-sheet.events.test.js b/packages/template-retail-react-app/app/pages/checkout/partials/sf-payments-sheet.events.test.js index 6040577a35..5b936590cf 100644 --- a/packages/template-retail-react-app/app/pages/checkout/partials/sf-payments-sheet.events.test.js +++ b/packages/template-retail-react-app/app/pages/checkout/partials/sf-payments-sheet.events.test.js @@ -24,7 +24,7 @@ jest.mock('@salesforce/commerce-sdk-react', () => { const actual = jest.requireActual('@salesforce/commerce-sdk-react') return { ...actual, - useShopperBasketsMutation: (mutationKey) => { + useShopperBasketsV2Mutation: (mutationKey) => { if (mutationKey === 'addPaymentInstrumentToBasket') { return {mutateAsync: mockAddPaymentInstrument} } diff --git a/packages/template-retail-react-app/app/pages/checkout/partials/sf-payments-sheet.jsx b/packages/template-retail-react-app/app/pages/checkout/partials/sf-payments-sheet.jsx index 5fe236eb2a..8d61ad900c 100644 --- a/packages/template-retail-react-app/app/pages/checkout/partials/sf-payments-sheet.jsx +++ b/packages/template-retail-react-app/app/pages/checkout/partials/sf-payments-sheet.jsx @@ -20,7 +20,7 @@ import { Divider } from '@salesforce/retail-react-app/app/components/shared/ui' import {useForm} from 'react-hook-form' -import {useShopperBasketsMutation, useCustomerType} from '@salesforce/commerce-sdk-react' +import {useShopperBasketsV2Mutation as useShopperBasketsMutation} from '@salesforce/commerce-sdk-react' import {useCurrentBasket} from '@salesforce/retail-react-app/app/hooks/use-current-basket' import {useCurrentCustomer} from '@salesforce/retail-react-app/app/hooks/use-current-customer' import {useCurrency} from '@salesforce/retail-react-app/app/hooks/use-currency' @@ -53,6 +53,7 @@ import { } from '@salesforce/retail-react-app/app/utils/sf-payments-utils' import logger from '@salesforce/retail-react-app/app/utils/logger-instance' import {PAYMENT_GATEWAYS} from '@salesforce/retail-react-app/app/constants' +import {useCustomerType} from '@salesforce/commerce-sdk-react' const SFPaymentsSheet = forwardRef((props, ref) => { const {onRequiresPayButtonChange, onCreateOrder, onError} = props diff --git a/packages/template-retail-react-app/app/pages/checkout/partials/sf-payments-sheet.test.js b/packages/template-retail-react-app/app/pages/checkout/partials/sf-payments-sheet.test.js index 5ab591fd8c..11e1d2d232 100644 --- a/packages/template-retail-react-app/app/pages/checkout/partials/sf-payments-sheet.test.js +++ b/packages/template-retail-react-app/app/pages/checkout/partials/sf-payments-sheet.test.js @@ -30,7 +30,7 @@ jest.mock('@salesforce/commerce-sdk-react', () => { const actual = jest.requireActual('@salesforce/commerce-sdk-react') return { ...actual, - useShopperBasketsMutation: (mutationKey) => { + useShopperBasketsV2Mutation: (mutationKey) => { if (mutationKey === 'addPaymentInstrumentToBasket') { return {mutateAsync: mockAddPaymentInstrument} } @@ -99,7 +99,7 @@ jest.mock('@salesforce/commerce-sdk-react', () => { ] } }), - useShippingMethodsForShipment: () => ({ + useShippingMethodsForShipmentV2: () => ({ data: { applicableShippingMethods: [ { diff --git a/packages/template-retail-react-app/app/pages/checkout/partials/shipping-address.jsx b/packages/template-retail-react-app/app/pages/checkout/partials/shipping-address.jsx index 243d3236d5..a2a9ba0894 100644 --- a/packages/template-retail-react-app/app/pages/checkout/partials/shipping-address.jsx +++ b/packages/template-retail-react-app/app/pages/checkout/partials/shipping-address.jsx @@ -18,7 +18,7 @@ import ShippingAddressSelection from '@salesforce/retail-react-app/app/pages/che import AddressDisplay from '@salesforce/retail-react-app/app/components/address-display' import { useShopperCustomersMutation, - useShopperBasketsMutation + useShopperBasketsV2Mutation as useShopperBasketsMutation } from '@salesforce/commerce-sdk-react' import {useCurrentCustomer} from '@salesforce/retail-react-app/app/hooks/use-current-customer' import {useCurrentBasket} from '@salesforce/retail-react-app/app/hooks/use-current-basket' diff --git a/packages/template-retail-react-app/app/pages/checkout/partials/shipping-address.test.js b/packages/template-retail-react-app/app/pages/checkout/partials/shipping-address.test.js index ec620595d5..b3ccb29ab8 100644 --- a/packages/template-retail-react-app/app/pages/checkout/partials/shipping-address.test.js +++ b/packages/template-retail-react-app/app/pages/checkout/partials/shipping-address.test.js @@ -83,7 +83,7 @@ jest.mock('@salesforce/commerce-sdk-react', () => { useShopperCustomersMutation: () => ({ mutateAsync: mockCustomerMutateAsync }), - useShopperBasketsMutation: () => ({ + useShopperBasketsV2Mutation: () => ({ mutateAsync: mockMutateAsync }) } diff --git a/packages/template-retail-react-app/app/pages/checkout/partials/shipping-method-options.jsx b/packages/template-retail-react-app/app/pages/checkout/partials/shipping-method-options.jsx index 95264509fe..f2f01e6507 100644 --- a/packages/template-retail-react-app/app/pages/checkout/partials/shipping-method-options.jsx +++ b/packages/template-retail-react-app/app/pages/checkout/partials/shipping-method-options.jsx @@ -16,7 +16,7 @@ import { RadioGroup } from '@salesforce/retail-react-app/app/components/shared/ui' import {Controller} from 'react-hook-form' -import {useShippingMethodsForShipment} from '@salesforce/commerce-sdk-react' +import {useShippingMethodsForShipmentV2 as useShippingMethodsForShipment} from '@salesforce/commerce-sdk-react' import PropTypes from 'prop-types' import LoadingSpinner from '@salesforce/retail-react-app/app/components/loading-spinner' import {STORE_LOCATOR_IS_ENABLED} from '@salesforce/retail-react-app/app/constants' diff --git a/packages/template-retail-react-app/app/pages/checkout/partials/shipping-methods.jsx b/packages/template-retail-react-app/app/pages/checkout/partials/shipping-methods.jsx index 6985d1f029..ac672ab542 100644 --- a/packages/template-retail-react-app/app/pages/checkout/partials/shipping-methods.jsx +++ b/packages/template-retail-react-app/app/pages/checkout/partials/shipping-methods.jsx @@ -23,8 +23,8 @@ import { ToggleCardSummary } from '@salesforce/retail-react-app/app/components/toggle-card' import { - useShippingMethodsForShipment, - useShopperBasketsMutation + useShippingMethodsForShipmentV2 as useShippingMethodsForShipment, + useShopperBasketsV2Mutation as useShopperBasketsMutation } from '@salesforce/commerce-sdk-react' import {useCurrentBasket} from '@salesforce/retail-react-app/app/hooks/use-current-basket' import {useCurrency} from '@salesforce/retail-react-app/app/hooks' diff --git a/packages/template-retail-react-app/app/pages/checkout/partials/shipping-methods.test.js b/packages/template-retail-react-app/app/pages/checkout/partials/shipping-methods.test.js index 326b45d8c6..a6d72f7be5 100644 --- a/packages/template-retail-react-app/app/pages/checkout/partials/shipping-methods.test.js +++ b/packages/template-retail-react-app/app/pages/checkout/partials/shipping-methods.test.js @@ -13,9 +13,9 @@ import {useCheckout} from '@salesforce/retail-react-app/app/pages/checkout/util/ import {useCurrentBasket} from '@salesforce/retail-react-app/app/hooks/use-current-basket' import {useCurrency} from '@salesforce/retail-react-app/app/hooks' import { - useShippingMethodsForShipment, + useShippingMethodsForShipmentV2 as useShippingMethodsForShipment, useProducts, - useShopperBasketsMutation + useShopperBasketsV2Mutation as useShopperBasketsMutation } from '@salesforce/commerce-sdk-react' // Mock the hooks diff --git a/packages/template-retail-react-app/app/pages/checkout/partials/shipping-multi-address.test.js b/packages/template-retail-react-app/app/pages/checkout/partials/shipping-multi-address.test.js index 056c2f110e..f746d788ac 100644 --- a/packages/template-retail-react-app/app/pages/checkout/partials/shipping-multi-address.test.js +++ b/packages/template-retail-react-app/app/pages/checkout/partials/shipping-multi-address.test.js @@ -38,10 +38,10 @@ jest.mock('@salesforce/commerce-sdk-react', () => ({ mutateAsync: jest.fn().mockResolvedValue({}) } }), - useShopperBasketsMutation: jest.fn(() => ({ + useShopperBasketsV2Mutation: jest.fn(() => ({ mutateAsync: jest.fn().mockResolvedValue({}) })), - useShippingMethodsForShipment: jest.fn(() => ({ + useShippingMethodsForShipmentV2: jest.fn(() => ({ refetch: jest.fn() })) })) diff --git a/packages/template-retail-react-app/app/pages/login/index.jsx b/packages/template-retail-react-app/app/pages/login/index.jsx index 62c5ff40cb..4cda8e3fa0 100644 --- a/packages/template-retail-react-app/app/pages/login/index.jsx +++ b/packages/template-retail-react-app/app/pages/login/index.jsx @@ -15,7 +15,7 @@ import { useCustomerBaskets, useCustomerId, useCustomerType, - useShopperBasketsMutation + useShopperBasketsV2Mutation as useShopperBasketsMutation } from '@salesforce/commerce-sdk-react' import useNavigation from '@salesforce/retail-react-app/app/hooks/use-navigation' import Seo from '@salesforce/retail-react-app/app/components/seo' diff --git a/packages/template-retail-react-app/app/pages/product-detail/index.jsx b/packages/template-retail-react-app/app/pages/product-detail/index.jsx index de950d8a0f..ef41dd8067 100644 --- a/packages/template-retail-react-app/app/pages/product-detail/index.jsx +++ b/packages/template-retail-react-app/app/pages/product-detail/index.jsx @@ -18,9 +18,9 @@ import { useProducts, useCategory, useShopperCustomersMutation, - useShopperBasketsMutation, + useShopperBasketsV2Mutation as useShopperBasketsMutation, useCustomerId, - useShopperBasketsMutationHelper + useShopperBasketsV2MutationHelper as useShopperBasketsMutationHelper } from '@salesforce/commerce-sdk-react' // Hooks diff --git a/packages/template-retail-react-app/app/pages/social-login-redirect/index.jsx b/packages/template-retail-react-app/app/pages/social-login-redirect/index.jsx index cda5935451..35ca61a613 100644 --- a/packages/template-retail-react-app/app/pages/social-login-redirect/index.jsx +++ b/packages/template-retail-react-app/app/pages/social-login-redirect/index.jsx @@ -19,7 +19,11 @@ import {AlertIcon} from '@salesforce/retail-react-app/app/components/icons' // Hooks import useNavigation from '@salesforce/retail-react-app/app/hooks/use-navigation' -import {useAuthHelper, AuthHelpers, useShopperBasketsMutation} from '@salesforce/commerce-sdk-react' +import { + useAuthHelper, + AuthHelpers, + useShopperBasketsV2Mutation as useShopperBasketsMutation +} from '@salesforce/commerce-sdk-react' import {useSearchParams} from '@salesforce/retail-react-app/app/hooks' import {useCurrentCustomer} from '@salesforce/retail-react-app/app/hooks/use-current-customer' import {getConfig} from '@salesforce/pwa-kit-runtime/utils/ssr-config'