From 091eebd9726aae0925f1d015366642cb0f896cda Mon Sep 17 00:00:00 2001 From: Robert Field Date: Wed, 1 May 2024 16:30:37 +0100 Subject: [PATCH] feat: add enhanced use products (#207) * feat: add use product enhanced hooks - adds a hook that gives back the main image and other files associated with the products returned * chore: add changeset - adds a hook that gives back the main image and other files associated with the products returned * fix: remove empty object on cart clear * chore: add changeset - adds a hook that gives back the main image and other files associated with the products returned * feat: use latest hooks - adds a hook that gives back the main image and other files associated with the products returned --- .changeset/cuddly-camels-swim.md | 5 + .changeset/great-pumas-obey.md | 5 + .../(checkout)/checkout/checkout-provider.tsx | 5 +- .../(checkout)/checkout/checkout-provider.tsx | 5 +- .../(checkout)/checkout/checkout-provider.tsx | 5 +- .../checkout/checkout-provider.tsx.template | 5 +- .../checkout/checkout-provider.tsx.template | 5 +- .../utility/latest-versions/package.json | 2 +- .../example/ProductListExample.tsx | 13 ++- .../product/hooks/use-products-enhanced.ts | 107 ++++++++++++++++++ .../src/product/hooks/use-products.ts | 30 ++--- 11 files changed, 150 insertions(+), 37 deletions(-) create mode 100644 .changeset/cuddly-camels-swim.md create mode 100644 .changeset/great-pumas-obey.md create mode 100644 packages/react-shopper-hooks/src/product/hooks/use-products-enhanced.ts diff --git a/.changeset/cuddly-camels-swim.md b/.changeset/cuddly-camels-swim.md new file mode 100644 index 00000000..76d4071c --- /dev/null +++ b/.changeset/cuddly-camels-swim.md @@ -0,0 +1,5 @@ +--- +"@elasticpath/d2c-schematics": minor +--- + +remove empty object on cart clear diff --git a/.changeset/great-pumas-obey.md b/.changeset/great-pumas-obey.md new file mode 100644 index 00000000..4cfc47b8 --- /dev/null +++ b/.changeset/great-pumas-obey.md @@ -0,0 +1,5 @@ +--- +"@elasticpath/react-shopper-hooks": minor +--- + +- adds a hook that gives back the main image and other files associated with the products returned \ No newline at end of file diff --git a/examples/algolia/src/app/(checkout)/checkout/checkout-provider.tsx b/examples/algolia/src/app/(checkout)/checkout/checkout-provider.tsx index a555c047..6ab8d1a1 100644 --- a/examples/algolia/src/app/(checkout)/checkout/checkout-provider.tsx +++ b/examples/algolia/src/app/(checkout)/checkout/checkout-provider.tsx @@ -65,10 +65,7 @@ export function CheckoutProvider({ children }: CheckoutProviderProps) { { onSuccess: async (data) => { setConfirmationData(data); - state?.id && - (await mutateClearCart({ - cartId: state.id, - })); + await mutateClearCart(); }, }, ); diff --git a/examples/payments/src/app/(checkout)/checkout/checkout-provider.tsx b/examples/payments/src/app/(checkout)/checkout/checkout-provider.tsx index b236c72a..7f5c26c8 100644 --- a/examples/payments/src/app/(checkout)/checkout/checkout-provider.tsx +++ b/examples/payments/src/app/(checkout)/checkout/checkout-provider.tsx @@ -77,10 +77,7 @@ export function StripeCheckoutProvider({ children }: CheckoutProviderProps) { { onSuccess: async (data) => { setConfirmationData(data); - state?.id && - (await mutateClearCart({ - cartId: state.id, - })); + await mutateClearCart(); }, }, ); diff --git a/examples/simple/src/app/(checkout)/checkout/checkout-provider.tsx b/examples/simple/src/app/(checkout)/checkout/checkout-provider.tsx index a555c047..6ab8d1a1 100644 --- a/examples/simple/src/app/(checkout)/checkout/checkout-provider.tsx +++ b/examples/simple/src/app/(checkout)/checkout/checkout-provider.tsx @@ -65,10 +65,7 @@ export function CheckoutProvider({ children }: CheckoutProviderProps) { { onSuccess: async (data) => { setConfirmationData(data); - state?.id && - (await mutateClearCart({ - cartId: state.id, - })); + await mutateClearCart(); }, }, ); diff --git a/packages/d2c-schematics/ep-payments-payment-gateway/files/src/app/(checkout)/checkout/checkout-provider.tsx.template b/packages/d2c-schematics/ep-payments-payment-gateway/files/src/app/(checkout)/checkout/checkout-provider.tsx.template index b236c72a..7f5c26c8 100644 --- a/packages/d2c-schematics/ep-payments-payment-gateway/files/src/app/(checkout)/checkout/checkout-provider.tsx.template +++ b/packages/d2c-schematics/ep-payments-payment-gateway/files/src/app/(checkout)/checkout/checkout-provider.tsx.template @@ -77,10 +77,7 @@ export function StripeCheckoutProvider({ children }: CheckoutProviderProps) { { onSuccess: async (data) => { setConfirmationData(data); - state?.id && - (await mutateClearCart({ - cartId: state.id, - })); + await mutateClearCart(); }, }, ); diff --git a/packages/d2c-schematics/manual-payment-gateway/files/src/app/(checkout)/checkout/checkout-provider.tsx.template b/packages/d2c-schematics/manual-payment-gateway/files/src/app/(checkout)/checkout/checkout-provider.tsx.template index a555c047..6ab8d1a1 100644 --- a/packages/d2c-schematics/manual-payment-gateway/files/src/app/(checkout)/checkout/checkout-provider.tsx.template +++ b/packages/d2c-schematics/manual-payment-gateway/files/src/app/(checkout)/checkout/checkout-provider.tsx.template @@ -65,10 +65,7 @@ export function CheckoutProvider({ children }: CheckoutProviderProps) { { onSuccess: async (data) => { setConfirmationData(data); - state?.id && - (await mutateClearCart({ - cartId: state.id, - })); + await mutateClearCart(); }, }, ); diff --git a/packages/d2c-schematics/utility/latest-versions/package.json b/packages/d2c-schematics/utility/latest-versions/package.json index f15dc628..df827440 100644 --- a/packages/d2c-schematics/utility/latest-versions/package.json +++ b/packages/d2c-schematics/utility/latest-versions/package.json @@ -4,7 +4,7 @@ "private": true, "dependencies": { "@moltin/sdk": "28.12.0", - "@elasticpath/react-shopper-hooks": "0.9.0", + "@elasticpath/react-shopper-hooks": "0.10.0", "@elasticpath/shopper-common": "0.3.0", "clsx": "^1.2.1", "cookies-next": "^4.0.0", diff --git a/packages/react-shopper-hooks/example/ProductListExample.tsx b/packages/react-shopper-hooks/example/ProductListExample.tsx index aeba61fe..98a155ff 100644 --- a/packages/react-shopper-hooks/example/ProductListExample.tsx +++ b/packages/react-shopper-hooks/example/ProductListExample.tsx @@ -1,8 +1,8 @@ import React from "react" -import { useProducts } from "../src" +import { useProductsEnhanced } from "../src/product/hooks/use-products-enhanced" export function ProductListExample() { - const { data: products } = useProducts() + const { data: products } = useProductsEnhanced() return (

Product List

@@ -10,6 +10,15 @@ export function ProductListExample() {

{product.attributes.name}

{product.id}

+

Main image: {product.enhanced.mainImage?.link?.href}

+

+ Other images:{" "} + {product.enhanced.otherImages + ?.map((x) => { + return x.link.href + }) + .toString()} +

))}
diff --git a/packages/react-shopper-hooks/src/product/hooks/use-products-enhanced.ts b/packages/react-shopper-hooks/src/product/hooks/use-products-enhanced.ts new file mode 100644 index 00000000..1fda0851 --- /dev/null +++ b/packages/react-shopper-hooks/src/product/hooks/use-products-enhanced.ts @@ -0,0 +1,107 @@ +import type { + ShopperCatalogResourcePage, + ProductResponse, + File, +} from "@moltin/sdk" +import { UseQueryResult } from "@tanstack/react-query" +import { + ShopperCatalogProductsInclude, + useProducts, + UseProductsParams, + UseProductsQueryOptions, +} from "./use-products" + +type UseProductsEnhancedData = ShopperCatalogResourcePage< + ProductResponse & { + enhanced: { mainImage: File | null; otherImages: File[] | null } + } +> + +export function useProductsEnhanced( + params?: UseProductsParams, + options?: UseProductsQueryOptions, +): UseQueryResult { + return useProducts( + { + ...params, + include: combineIncludes( + ["main_image", "files", "component_products"], + params, + ), + }, + { + ...options, + select: (data): UseProductsEnhancedData => { + const transformedData = options?.select?.(data) ?? data + + const fileLookup = createFileLookupDict(transformedData.included) + + const enhancedData = transformedData.data.map((originalData) => { + return { + ...originalData, + enhanced: { + mainImage: getProductMainImage(originalData, fileLookup), + otherImages: getProductOtherImages(originalData, fileLookup), + }, + } + }) + + return { + ...transformedData, + data: enhancedData, + } + }, + }, + ) as UseQueryResult +} + +function getProductMainImage( + product: ProductResponse, + fileLookup: Record, +): File | null { + const mainImageId = product?.relationships?.main_image?.data?.id + return mainImageId ? fileLookup[mainImageId] : null +} + +function getProductOtherImages( + product: ProductResponse, + fileLookup: Record, +): File[] | null { + const otherImagesIds = + product?.relationships?.files?.data?.map((file) => file.id) ?? [] + const mainImageId = product?.relationships?.main_image?.data?.id + return otherImagesIds + .map((id) => fileLookup[id]) + .filter((x) => x.id !== mainImageId) +} + +function createFileLookupDict( + includes: ShopperCatalogResourcePage["included"], +): Record { + return ( + includes?.files?.reduce((acc, curr) => { + return { ...acc, [curr.id]: curr } + }, {}) ?? {} + ) +} + +function combineIncludes( + include: ShopperCatalogProductsInclude[], + params: UseProductsParams, +): ShopperCatalogProductsInclude[] { + return [ + ...new Set([ + ...include, + ...extractIncludeFromParams(params), + ]), + ] +} + +function extractIncludeFromParams( + params: UseProductsParams, +): ShopperCatalogProductsInclude[] { + if (!params?.include) { + return [] + } + return Array.isArray(params.include) ? params.include : [params.include] +} diff --git a/packages/react-shopper-hooks/src/product/hooks/use-products.ts b/packages/react-shopper-hooks/src/product/hooks/use-products.ts index 8dc218da..ac7705c9 100644 --- a/packages/react-shopper-hooks/src/product/hooks/use-products.ts +++ b/packages/react-shopper-hooks/src/product/hooks/use-products.ts @@ -13,27 +13,29 @@ const PRODUCTS_QUERY_KEY = "products" as const export const productsQueryKeys = queryKeysFactory(PRODUCTS_QUERY_KEY) type ProductsQueryKey = typeof productsQueryKeys -export type UseProductsParams = NonNullable< - Parameters ->[0] +export type UseProductsParams = + | (NonNullable>[0] & { + limit?: number + offset?: number + filter?: object + include?: ShopperCatalogProductsInclude | ShopperCatalogProductsInclude[] + }) + | undefined export type ShopperCatalogProductsInclude = | "main_image" | "files" | "component_products" +export type UseProductsQueryOptions = UseQueryOptionsWrapper< + ShopperCatalogResourcePage, + Error, + ReturnType +> + export function useProducts( - params?: UseProductsParams & { - limit?: number - offset?: number - filter?: object - include?: ShopperCatalogProductsInclude | ShopperCatalogProductsInclude[] - }, - options?: UseQueryOptionsWrapper< - ShopperCatalogResourcePage, - Error, - ReturnType - >, + params?: UseProductsParams, + options?: UseProductsQueryOptions, ): UseQueryResult, Error> { const { client } = useElasticPath()