diff --git a/spec/components/CioPlp/CioPlp.server.test.jsx b/spec/components/CioPlp/CioPlp.server.test.jsx
index 93bda3a4..c46da3bf 100644
--- a/spec/components/CioPlp/CioPlp.server.test.jsx
+++ b/spec/components/CioPlp/CioPlp.server.test.jsx
@@ -57,7 +57,7 @@ describe('CioPlp React Server-Side Rendering', () => {
,
);
expect(html).toContain(
- '
{"cioClient":null,"cioClientOptions":{},"staticRequestConfigs":{},"itemFieldGetters":{},"formatters":{},"callbacks":{},"urlHelpers":{"defaultQueryStringMap":{"query":"q","page":"page","offset":"offset","resultsPerPage":"numResults","filters":"filters","sortBy":"sortBy","sortOrder":"sortOrder","section":"section"}}}
',
+ '{"cioClient":null,"cioClientOptions":{},"staticRequestConfigs":{},"customConfigs":{},"itemFieldGetters":{},"formatters":{},"callbacks":{},"urlHelpers":{"defaultQueryStringMap":{"query":"q","page":"page","offset":"offset","resultsPerPage":"numResults","filters":"filters","sortBy":"sortBy","sortOrder":"sortOrder","section":"section"}}}
',
);
});
});
diff --git a/spec/hooks/useProductInfo/useProductInfo.test.js b/spec/hooks/useProductInfo/useProductInfo.test.js
index 4b98c326..da0b70a7 100644
--- a/spec/hooks/useProductInfo/useProductInfo.test.js
+++ b/spec/hooks/useProductInfo/useProductInfo.test.js
@@ -22,15 +22,14 @@ describe('Testing Hook: useProductInfo', () => {
const transformedItem = transformResultItem(mockItem);
- it('Should return productSwatch, itemId, itemName, itemImageUrl, itemUrl, itemPrice', async () => {
+ it('Should return itemId, itemName, itemImageUrl, itemUrl, itemPrice', async () => {
const { result } = renderHookWithCioPlp(() => useProductInfo({ item: transformedItem }));
await waitFor(() => {
const {
- current: { productSwatch, itemName, itemImageUrl, itemUrl, itemPrice, itemId },
+ current: { itemName, itemImageUrl, itemUrl, itemPrice, itemId },
} = result;
- expect(productSwatch).not.toBeNull();
expect(itemId).toEqual(transformedItem.itemId);
expect(itemName).toEqual(transformedItem.itemName);
expect(itemImageUrl).toEqual(transformedItem.imageUrl);
@@ -46,37 +45,67 @@ describe('Testing Hook: useProductInfo', () => {
getPrice: () => {},
getSwatches: () => {},
getSwatchPreview: () => {},
+ getName: () => {},
+ getItemUrl: () => {},
+ getImageUrl: () => {},
},
},
});
await waitFor(() => {
const {
- current: { productSwatch, itemName, itemImageUrl, itemUrl, itemPrice },
+ current: { itemName, itemImageUrl, itemUrl, itemPrice },
} = result;
- expect(productSwatch).not.toBeNull();
- expect(itemName).toEqual(transformedItem.itemName);
- expect(itemImageUrl).toEqual(transformedItem.imageUrl);
- expect(itemUrl).toEqual(transformedItem.url);
+ expect(itemName).toBeUndefined();
+ expect(itemImageUrl).toBeUndefined();
+ expect(itemUrl).toBeUndefined();
expect(itemPrice).toBeUndefined();
});
});
- it('Should return correctly after different variation is selected', async () => {
- const { result } = renderHookWithCioPlp(() => useProductInfo({ item: transformedItem }));
+ it('Should return properly with getters that override defaults', async () => {
+ const { result } = renderHookWithCioPlp(() => useProductInfo({ item: transformedItem }), {
+ initialProps: {
+ itemFieldGetters: {
+ getPrice: () => 'override',
+ getSwatches: () => [],
+ getSwatchPreview: () => 'override',
+ getName: () => 'override',
+ getItemUrl: () => 'override',
+ getImageUrl: () => 'override',
+ },
+ },
+ });
+
+ await waitFor(() => {
+ const {
+ current: { itemName, itemImageUrl, itemUrl, itemPrice },
+ } = result;
+
+ expect(itemName).toEqual('override');
+ expect(itemUrl).toEqual('override');
+ expect(itemImageUrl).toEqual('override');
+ expect(itemPrice).toEqual('override');
+ });
+ });
+
+ it('Should return image properly with overridden baseUrl', async () => {
+ const { result } = renderHookWithCioPlp(() => useProductInfo({ item: transformedItem }), {
+ initialProps: {
+ customConfigs: { imageBaseUrl: 'test.com' },
+ },
+ });
await waitFor(() => {
const {
- current: { productSwatch, itemName, itemImageUrl, itemUrl, itemPrice },
+ current: { itemName, itemImageUrl, itemUrl, itemPrice },
} = result;
- const { selectVariation, swatchList } = productSwatch;
- selectVariation(swatchList[1]);
- expect(itemName).toEqual(swatchList[1].itemName);
- expect(itemImageUrl).toEqual(swatchList[1].imageUrl || transformedItem.imageUrl);
- expect(itemUrl).toEqual(swatchList[1].url || transformedItem.url);
- expect(itemPrice).toEqual(swatchList[1].price || transformedItem.data.price);
+ expect(itemName).toEqual(transformedItem.itemName);
+ expect(itemImageUrl).toEqual(`test.com${transformedItem.imageUrl}`);
+ expect(itemUrl).toEqual(transformedItem.url);
+ expect(itemPrice).toEqual(transformedItem.data.price);
});
});
@@ -99,14 +128,27 @@ describe('Testing Hook: useProductInfo', () => {
await waitFor(() => {
const {
- current: { productSwatch, itemName, itemImageUrl, itemUrl, itemPrice },
+ current: { itemName, itemImageUrl, itemUrl, itemPrice },
} = result;
- expect(productSwatch).not.toBeNull();
expect(itemName).toEqual(transformedItem.itemName);
expect(itemImageUrl).toEqual(transformedItem.imageUrl);
expect(itemUrl).toEqual(transformedItem.url);
expect(itemPrice).toBeUndefined();
});
});
+
+ it('should merge product info fields with selectedVariation when provided', async () => {
+ const transformedItem = transformResultItem(mockItem);
+ const { result } = renderHookWithCioPlp(() => useProductInfo({ item: transformedItem, selectedVariation: transformedItem.variations[0] }));
+
+ console.log(transformedItem.variations[0])
+ await waitFor(() => {
+ const { current: { itemName, itemPrice, itemImageUrl, itemUrl } } = result;
+ expect(itemName).toEqual(transformedItem.variations[0].itemName);
+ expect(itemPrice).toEqual(transformedItem.variations[0].data.price);
+ expect(itemImageUrl).toEqual(transformedItem.variations[0].imageUrl);
+ expect(itemUrl).toEqual(transformedItem.variations[0].url);
+ });
+ });
});
diff --git a/spec/hooks/useProductSwatch/useProductSwatch.test.js b/spec/hooks/useProductSwatch/useProductSwatch.test.js
index 88d06ba8..2db34de3 100644
--- a/spec/hooks/useProductSwatch/useProductSwatch.test.js
+++ b/spec/hooks/useProductSwatch/useProductSwatch.test.js
@@ -18,7 +18,7 @@ describe('Testing Hook: useProductSwatch', () => {
});
const transformedItem = transformResultItem(mockItem);
- const expectedSwatch = getSwatches(transformedItem, getPrice, getSwatchPreview);
+ const expectedSwatch = getSwatches(transformedItem, getSwatchPreview);
it('Should throw error if called outside of PlpContext', () => {
expect(() => renderHook(() => useProductSwatch())).toThrow();
@@ -73,7 +73,7 @@ describe('Testing Hook: useProductSwatch', () => {
} = result;
expect(typeof selectVariation).toBe('function');
- expect(selectedVariation).toBeUndefined();
+ expect(selectedVariation).toBe(transformedItem.variations[0]);
expect(swatchList.length).toBe(0);
});
});
@@ -101,7 +101,7 @@ describe('Testing Hook: useProductSwatch', () => {
} = result;
expect(typeof selectVariation).toBe('function');
- expect(selectedVariation).toBeUndefined();
+ expect(selectedVariation).toBe(transformedItem.variations[0]);
expect(swatchList.length).toBe(0);
});
});
diff --git a/spec/local_examples/item.json b/spec/local_examples/item.json
index c0c26782..acd94180 100644
--- a/spec/local_examples/item.json
+++ b/spec/local_examples/item.json
@@ -32,6 +32,8 @@
"variation_id": "BKT00110DG1733LR",
"swatchPreview": "#e04062",
"price": 90,
+ "image_url": "https://constructorio-integrations.s3.amazonaws.com/tikus-threads/2022-06-29/Casual-Shirts_Washed-Poplin-Shirts_19145-MTX64_40_category-outfitter.jpg",
+ "url": "https://constructorio-integrations.s3.amazonaws.com/tikus-threads/2022-06-29/Casual-Shirts_Washed-Poplin-Shirts_19145-MTX64_40_category-outfitter.jpg",
"facets": [
{
"name": "Color",
diff --git a/spec/utils.test.tsx b/spec/utils.test.tsx
index f98d1e75..b38129be 100644
--- a/spec/utils.test.tsx
+++ b/spec/utils.test.tsx
@@ -9,7 +9,7 @@ const transformedItem = transformResultItem(mockItem);
describe('Testing Utils, getProductCardCnstrcDataAttributes', () => {
test('Should return relevant data attributes for Product Card', async () => {
- const { result } = renderHookWithCioPlp(() => useProductInfo({ item: transformedItem }));
+ const { result } = renderHookWithCioPlp(() => useProductInfo({ item: transformedItem, selectedVariation: { variationId: 'BKT00110DG1733LR', swatchPreview: '#FFFFFF' }}));
await waitFor(() => {
const dataAttributes = getProductCardCnstrcDataAttributes(result.current);
diff --git a/src/components/ProductCard/ProductCard.tsx b/src/components/ProductCard/ProductCard.tsx
index 16a65f82..d27043cb 100644
--- a/src/components/ProductCard/ProductCard.tsx
+++ b/src/components/ProductCard/ProductCard.tsx
@@ -4,6 +4,7 @@ import { useOnAddToCart, useOnProductCardClick } from '../../hooks/callbacks';
import { CnstrcData, IncludeRenderProps, Item, ProductInfoObject } from '../../types';
import ProductSwatch from '../ProductSwatch';
import useProductInfo from '../../hooks/useProduct';
+import useProductSwatch from '../../hooks/useProductSwatch';
import { getProductCardCnstrcDataAttributes } from '../../utils';
interface Props {
@@ -55,8 +56,10 @@ export type ProductCardProps = IncludeRenderProps
export default function ProductCard(props: ProductCardProps) {
const { item, children } = props;
const state = useCioPlpContext();
- const productInfo = useProductInfo({ item });
- const { productSwatch, itemName, itemPrice, itemImageUrl, itemUrl } = productInfo;
+ const productSwatch = useProductSwatch({ item });
+ const { selectedVariation } = productSwatch;
+ const productInfo = useProductInfo({ item, selectedVariation });
+ const { itemName, itemPrice, itemImageUrl, itemUrl } = productInfo;
if (!state) {
throw new Error('This component is meant to be used within the CioPlp provider.');
diff --git a/src/hooks/useCioPlpProvider.ts b/src/hooks/useCioPlpProvider.ts
index 44a85a97..f41fd4ef 100644
--- a/src/hooks/useCioPlpProvider.ts
+++ b/src/hooks/useCioPlpProvider.ts
@@ -15,6 +15,7 @@ export default function useCioPlpProvider(
itemFieldGetters,
urlHelpers,
staticRequestConfigs = {},
+ customConfigs = {},
cioClient: customCioClient,
cioClientOptions: customCioClientOptions = {},
} = props;
@@ -28,6 +29,7 @@ export default function useCioPlpProvider(
cioClientOptions,
setCioClientOptions,
staticRequestConfigs,
+ customConfigs,
itemFieldGetters: { ...defaultGetters, ...itemFieldGetters },
formatters: { ...defaultFormatters, ...formatters },
callbacks: { ...callbacks },
@@ -41,6 +43,7 @@ export default function useCioPlpProvider(
callbacks,
urlHelpers,
staticRequestConfigs,
+ customConfigs,
],
);
diff --git a/src/hooks/useProduct.ts b/src/hooks/useProduct.ts
index d9a9f8bd..efd3dc72 100644
--- a/src/hooks/useProduct.ts
+++ b/src/hooks/useProduct.ts
@@ -1,32 +1,38 @@
-import useProductSwatch from './useProductSwatch';
import { useCioPlpContext } from './useCioPlpContext';
-import { UseProductInfo } from '../types';
import { tryCatchify } from '../utils';
+import { Item, SwatchItem } from '../types';
-const useProductInfo: UseProductInfo = ({ item }) => {
+interface UseProductInfoArgs {
+ item: Item;
+ selectedVariation?: SwatchItem;
+}
+
+const useProductInfo = ({ item, selectedVariation }: UseProductInfoArgs) => {
const state = useCioPlpContext();
- const productSwatch = useProductSwatch({ item });
if (!item.data || !item.itemId || !item.itemName) {
throw new Error('data, itemId, or itemName are required.');
}
const getPrice = tryCatchify(state?.itemFieldGetters?.getPrice);
+ const getImageUrl = tryCatchify(state?.itemFieldGetters?.getImageUrl);
+ const getItemUrl = tryCatchify(state?.itemFieldGetters?.getItemUrl);
+ const getName = tryCatchify(state?.itemFieldGetters?.getName);
- const itemName = productSwatch?.selectedVariation?.itemName || item.itemName;
- const itemPrice = productSwatch?.selectedVariation?.price || getPrice(item);
- const itemImageUrl = productSwatch?.selectedVariation?.imageUrl || item.imageUrl;
- const itemUrl = productSwatch?.selectedVariation?.url || item.url;
- const variationId = productSwatch?.selectedVariation?.variationId;
+ const itemName = getName(item, selectedVariation);
+ const itemPrice = getPrice(item, selectedVariation);
+ const itemImageUrl = getImageUrl(item, selectedVariation, {
+ imageBaseUrl: state.customConfigs.imageBaseUrl,
+ });
+ const itemUrl = getItemUrl(item, selectedVariation);
const { itemId } = item;
return {
- productSwatch,
itemName,
itemPrice,
itemImageUrl,
itemUrl,
- variationId,
+ variationId: selectedVariation?.variationId,
itemId,
};
};
diff --git a/src/hooks/useProductSwatch.ts b/src/hooks/useProductSwatch.ts
index 8ed9eec8..b117d3b4 100644
--- a/src/hooks/useProductSwatch.ts
+++ b/src/hooks/useProductSwatch.ts
@@ -1,43 +1,42 @@
import { useEffect, useState } from 'react';
import { useCioPlpContext } from './useCioPlpContext';
-import { SwatchItem, UseProductSwatch } from '../types';
+import { SwatchItem, UseProductSwatch, Variation } from '../types';
import {
getSwatches as defaultGetSwatches,
- getPrice as defaultGetPrice,
getSwatchPreview as defaultGetSwatchPreview,
} from '../utils/itemFieldGetters';
const useProductSwatch: UseProductSwatch = ({ item }) => {
- const [selectedVariation, setSelectedVariation] = useState();
+ const [selectedVariation, setSelectedVariation] = useState();
const [swatchList, setSwatchList] = useState([]);
const state = useCioPlpContext();
const getSwatches = state?.itemFieldGetters?.getSwatches || defaultGetSwatches;
- const getPrice = state?.itemFieldGetters?.getPrice || defaultGetPrice;
const getSwatchPreview = state?.itemFieldGetters?.getSwatchPreview || defaultGetSwatchPreview;
useEffect(() => {
if (item?.variations) {
try {
- setSwatchList(getSwatches(item, getPrice, getSwatchPreview) || []);
+ const swatches = getSwatches(item, getSwatchPreview);
+ setSwatchList(swatches || []);
} catch (e) {
// do nothing
}
}
- }, [item, getSwatches, getPrice, getSwatchPreview]);
+ }, [item, getSwatches, getSwatchPreview]);
useEffect(() => {
if (item?.variations) {
- const initialSwatch = swatchList?.find((swatch) => swatch?.variationId === item?.variationId);
- if (initialSwatch) {
- setSelectedVariation(initialSwatch);
+ const initialVariation = item?.variations?.[0];
+ if (initialVariation) {
+ setSelectedVariation(initialVariation);
}
}
}, [swatchList, item]);
- const selectVariation = (swatch: SwatchItem) => {
- setSelectedVariation(swatch);
+ const selectVariation = (variation: Variation) => {
+ setSelectedVariation(variation);
};
return {
diff --git a/src/stories/components/CioPlp/CioPlpProps.md b/src/stories/components/CioPlp/CioPlpProps.md
index 12981f2c..d84fc9fe 100644
--- a/src/stories/components/CioPlp/CioPlpProps.md
+++ b/src/stories/components/CioPlp/CioPlpProps.md
@@ -31,9 +31,12 @@ Callbacks will be composed with the library's internal tracking calls for a give
ItemFieldGetters maps the fields sent in the catalog feeds to the fields the libary expects for rendering
-| property | type | description |
-| -------- | ------------------------ | ------------------ |
-| getPrice | `(item: Item) => number` | Get price funciton |
+| property | type | description |
+| ----------- | -----------------------------------------------| ---------------------- |
+| getPrice | `(item: Item, variation: Variation) => number` | Get price funciton |
+| getImageUrl | `(item: Item, variation: Variation) => string` | Get image url funciton |
+| getItemUrl | `(item: Item, variation: Variation) => stirng` | Get href url funciton |
+| getName | `(item: Item, variation: Variation) => string` | Get item name funciton |
diff --git a/src/types.ts b/src/types.ts
index 2f1b7edc..ed702e82 100644
--- a/src/types.ts
+++ b/src/types.ts
@@ -39,11 +39,13 @@ export interface ApiHierarchicalFacetOption extends ApiFacetOption {
export type CioClientOptions = Omit;
export interface ItemFieldGetters {
- getPrice: (item: Item | Variation) => number;
+ getPrice: (item: Item, variation?: Variation) => number | undefined;
+ getItemUrl: (item: Item, variation?: Variation) => string | undefined;
+ getImageUrl: (item: Item, variation?: Variation) => string | undefined;
+ getName: (item: Item, variation?: Variation) => string;
getSwatchPreview: (variation: Variation) => string;
getSwatches: (
item: Item,
- retrievePrice: ItemFieldGetters['getPrice'],
retrieveSwatchPreview: ItemFieldGetters['getSwatchPreview'],
) => SwatchItem[] | undefined;
}
@@ -109,6 +111,10 @@ export interface UrlHelpers {
defaultQueryStringMap: Readonly;
}
+export interface CustomConfigs {
+ imageBaseUrl?: string;
+}
+
export interface RequestConfigs {
// Search
query?: string;
@@ -137,6 +143,7 @@ export interface PlpContextValue {
cioClientOptions: CioClientOptions;
setCioClientOptions: React.Dispatch;
staticRequestConfigs: RequestConfigs;
+ customConfigs: CustomConfigs;
itemFieldGetters: ItemFieldGetters;
formatters: Formatters;
callbacks: Callbacks;
@@ -197,6 +204,7 @@ export interface SwatchItem {
price?: number;
swatchPreview: string;
variationId?: string;
+ variation?: Variation;
}
export interface PlpBrowseData {
@@ -219,6 +227,7 @@ export interface CioPlpProviderProps {
initialSearchResponse?: SearchResponse;
initialBrowseResponse?: GetBrowseResultsResponse;
staticRequestConfigs?: Partial;
+ customConfigs?: Partial;
}
export type UseSortReturn = {
@@ -229,8 +238,8 @@ export type UseSortReturn = {
export interface ProductSwatchObject {
swatchList: SwatchItem[] | undefined;
- selectedVariation: SwatchItem | undefined;
- selectVariation: (swatch: SwatchItem) => void;
+ selectedVariation: Variation | undefined;
+ selectVariation: (variation: Variation) => void;
}
export type UseProductSwatchProps = {
@@ -240,7 +249,6 @@ export type UseProductSwatchProps = {
export type UseProductSwatch = (props: UseProductSwatchProps) => ProductSwatchObject;
export interface ProductInfoObject {
- productSwatch?: ProductSwatchObject;
itemName: string;
itemId: string;
itemPrice?: number;
diff --git a/src/utils/itemFieldGetters.ts b/src/utils/itemFieldGetters.ts
index d310229e..c2219a58 100644
--- a/src/utils/itemFieldGetters.ts
+++ b/src/utils/itemFieldGetters.ts
@@ -1,13 +1,28 @@
import { ItemFieldGetters, Item, SwatchItem, Variation } from '../types';
-// eslint-disable-next-line import/prefer-default-export
-export function getPrice(item: Item | Variation): number {
- return item.data.price;
+export function getPrice(item: Item, variation?: Variation): number {
+ return variation?.data?.price || item?.data?.price;
+}
+
+export function getImageUrl(item: Item, variation?: Variation, options?: any): string | undefined {
+ const { imageBaseUrl } = options;
+
+ if (imageBaseUrl) {
+ return `${imageBaseUrl}${variation?.imageUrl || item?.imageUrl}`;
+ }
+ return variation?.imageUrl || item?.imageUrl;
+}
+
+export function getItemUrl(item: Item, variation?: Variation): string | undefined {
+ return variation?.url || item.url;
+}
+
+export function getName(item: Item, variation?: Variation): string {
+ return variation?.itemName || item.itemName;
}
export function getSwatches(
item: Item,
- retrievePrice: ItemFieldGetters['getPrice'],
retrieveSwatchPreview: ItemFieldGetters['getSwatchPreview'],
): SwatchItem[] | undefined {
const swatchList: SwatchItem[] = [];
@@ -19,8 +34,8 @@ export function getSwatches(
url: variation?.url || item?.url,
imageUrl: variation?.url,
variationId: variation?.variationId,
- price: retrievePrice(variation),
swatchPreview: retrieveSwatchPreview(variation),
+ variation,
});
}
});