diff --git a/.changeset/friendly-peas-mix.md b/.changeset/friendly-peas-mix.md new file mode 100644 index 00000000..7f4d7226 --- /dev/null +++ b/.changeset/friendly-peas-mix.md @@ -0,0 +1,7 @@ +--- +"@elasticpath/react-shopper-hooks": minor +--- + +- Moved scoped hooks outside cart provider +- Added default cart id to cart provider using the sdks cart id +- Added useProducts hook diff --git a/packages/react-shopper-hooks/src/cart/cart-provider.tsx b/packages/react-shopper-hooks/src/cart/cart-provider.tsx index 26f17498..1056e8b8 100644 --- a/packages/react-shopper-hooks/src/cart/cart-provider.tsx +++ b/packages/react-shopper-hooks/src/cart/cart-provider.tsx @@ -1,40 +1,16 @@ import React, { createContext, ReactNode } from "react" -import { - Cart, - CartIncluded, - ResourceIncluded, - CartItem, - CartItemsResponse, -} from "@moltin/sdk" +import { Cart, CartIncluded, ResourceIncluded, CartItem } from "@moltin/sdk" import { CartState } from "./types/cart-types" import { enhanceCartResponse } from "./util/enhance-cart-response" import { StoreEvent } from "../shared" -import { cartQueryKeys, useGetCart } from "./hooks/use-get-cart" -import { useUpdateCartItem } from "./hooks/use-update-cart-items" -import { useQueryClient } from "@tanstack/react-query" -import { useRemoveCartItem } from "./hooks/use-remove-cart-item" -import { - useAddBundleProductToCart, - useAddProductToCart, - useAddPromotionToCart, - useDeleteCartItems, -} from "./hooks" -import { useRemovePromotionCode } from "./hooks/use-remove-promotion" +import { useGetCart } from "./hooks/use-get-cart" +import { useElasticPath } from "../elasticpath" export const CartItemsContext = createContext< | ({ state: CartState | undefined - cartId?: string + cartId: string emit?: (event: StoreEvent) => void - useScopedUpdateCartItem: () => ReturnType - useScopedRemoveCartItem: () => ReturnType - useScopedAddPromotion: () => ReturnType - useScopedRemovePromotion: () => ReturnType - useScopedAddProductToCart: () => ReturnType - useScopedAddBundleProductToCart: () => ReturnType< - typeof useAddBundleProductToCart - > - useClearCart: () => ReturnType } & Omit, "data">) | undefined >(undefined) @@ -52,28 +28,16 @@ export function CartProvider({ initialState, children, emit, - cartId = "", + cartId: sourceCartId, }: CartProviderProps) { - const queryClient = useQueryClient() + const { client } = useElasticPath() + + const cartId = sourceCartId ?? client.Cart().cartId const { data: rawCartData, ...rest } = useGetCart(cartId, { initialData: initialState?.cart, }) - async function invalidateCartQuery() { - return queryClient.invalidateQueries({ - queryKey: cartQueryKeys.detail(cartId), - }) - } - - function setCartQueryData(updatedData: CartItemsResponse) { - // Updates the cart items in the query cache - return queryClient.setQueryData( - cartQueryKeys.detail(cartId), - createCartItemsUpdater(updatedData.data), - ) - } - const state = rawCartData && enhanceCartResponse({ @@ -81,75 +45,12 @@ export function CartProvider({ included: rest.included, }) - const updateCartItem = () => - useUpdateCartItem(cartId, { - onSuccess: (updatedData) => { - setCartQueryData(updatedData) - invalidateCartQuery() - }, - }) - - const addProductToCart = () => - useAddProductToCart(cartId, { - onSuccess: (updatedData) => { - setCartQueryData(updatedData) - invalidateCartQuery() - }, - }) - - const removeCartItem = () => - useRemoveCartItem(cartId, { - onSuccess: (updatedData) => { - setCartQueryData(updatedData) - invalidateCartQuery() - }, - }) - - const addPromotion = () => - useAddPromotionToCart(cartId, { - onSuccess: (updatedData) => { - setCartQueryData(updatedData) - invalidateCartQuery() - }, - }) - - const removePromotion = () => - useRemovePromotionCode(cartId, { - onSuccess: (updatedData) => { - setCartQueryData(updatedData) - invalidateCartQuery() - }, - }) - - const addBundleItemToCart = () => - useAddBundleProductToCart(cartId, { - onSuccess: (updatedData) => { - setCartQueryData(updatedData) - invalidateCartQuery() - }, - }) - - const clearCart = () => - useDeleteCartItems(cartId, { - onSuccess: async (updatedData) => { - setCartQueryData(updatedData) - await invalidateCartQuery() - }, - }) - return ( @@ -158,7 +59,7 @@ export function CartProvider({ ) } -function createCartItemsUpdater(updatedData: CartItem[]) { +export function createCartItemsUpdater(updatedData: CartItem[]) { return function cartItemsUpdater( oldData: ResourceIncluded, ) { diff --git a/packages/react-shopper-hooks/src/cart/hooks/index.ts b/packages/react-shopper-hooks/src/cart/hooks/index.ts index 135b0575..fefd2783 100644 --- a/packages/react-shopper-hooks/src/cart/hooks/index.ts +++ b/packages/react-shopper-hooks/src/cart/hooks/index.ts @@ -7,3 +7,11 @@ export * from "./use-add-bundle-product-to-cart" export * from "./use-checkout" export * from "./use-add-custom-item" export * from "./use-delete-cart-items" +export * from "./use-cart-add-product" +export * from "./use-cart-remove-item" +export * from "./use-cart-update-item" +export * from "./use-cart-add-promotion" +export * from "./use-cart-add-bundle-item" +export * from "./use-cart-clear" +export * from "./use-cart-remove-promotion" +export * from "./use-cart-remove-item" diff --git a/packages/react-shopper-hooks/src/cart/hooks/use-cart-add-bundle-item.tsx b/packages/react-shopper-hooks/src/cart/hooks/use-cart-add-bundle-item.tsx new file mode 100644 index 00000000..8084f389 --- /dev/null +++ b/packages/react-shopper-hooks/src/cart/hooks/use-cart-add-bundle-item.tsx @@ -0,0 +1,21 @@ +import { createCartItemsUpdater, useCart } from "../cart-provider" +import { useQueryClient } from "@tanstack/react-query" +import { cartQueryKeys } from "./use-get-cart" +import { useAddBundleProductToCart } from "./use-add-bundle-product-to-cart" + +export function useCartAddBundleItem() { + const { cartId } = useCart() + const queryClient = useQueryClient() + return useAddBundleProductToCart(cartId, { + onSuccess: (updatedData) => { + // Updates the cart items in the query cache + queryClient.setQueryData( + cartQueryKeys.detail(cartId), + createCartItemsUpdater(updatedData.data), + ) + return queryClient.invalidateQueries({ + queryKey: cartQueryKeys.detail(cartId), + }) + }, + }) +} diff --git a/packages/react-shopper-hooks/src/cart/hooks/use-cart-add-product.tsx b/packages/react-shopper-hooks/src/cart/hooks/use-cart-add-product.tsx new file mode 100644 index 00000000..203c05d6 --- /dev/null +++ b/packages/react-shopper-hooks/src/cart/hooks/use-cart-add-product.tsx @@ -0,0 +1,21 @@ +import { useAddProductToCart } from "./use-add-product" +import { createCartItemsUpdater, useCart } from "../cart-provider" +import { useQueryClient } from "@tanstack/react-query" +import { cartQueryKeys } from "./use-get-cart" + +export function useCartAddProduct() { + const { cartId } = useCart() + const queryClient = useQueryClient() + return useAddProductToCart(cartId, { + onSuccess: (updatedData) => { + // Updates the cart items in the query cache + queryClient.setQueryData( + cartQueryKeys.detail(cartId), + createCartItemsUpdater(updatedData.data), + ) + return queryClient.invalidateQueries({ + queryKey: cartQueryKeys.detail(cartId), + }) + }, + }) +} diff --git a/packages/react-shopper-hooks/src/cart/hooks/use-cart-add-promotion.tsx b/packages/react-shopper-hooks/src/cart/hooks/use-cart-add-promotion.tsx new file mode 100644 index 00000000..33a8be36 --- /dev/null +++ b/packages/react-shopper-hooks/src/cart/hooks/use-cart-add-promotion.tsx @@ -0,0 +1,21 @@ +import { createCartItemsUpdater, useCart } from "../cart-provider" +import { useQueryClient } from "@tanstack/react-query" +import { cartQueryKeys } from "./use-get-cart" +import { useAddPromotionToCart } from "./use-add-promotion" + +export function useCartAddPromotion() { + const { cartId } = useCart() + const queryClient = useQueryClient() + return useAddPromotionToCart(cartId, { + onSuccess: (updatedData) => { + // Updates the cart items in the query cache + queryClient.setQueryData( + cartQueryKeys.detail(cartId), + createCartItemsUpdater(updatedData.data), + ) + return queryClient.invalidateQueries({ + queryKey: cartQueryKeys.detail(cartId), + }) + }, + }) +} diff --git a/packages/react-shopper-hooks/src/cart/hooks/use-cart-clear.tsx b/packages/react-shopper-hooks/src/cart/hooks/use-cart-clear.tsx new file mode 100644 index 00000000..2b26da8c --- /dev/null +++ b/packages/react-shopper-hooks/src/cart/hooks/use-cart-clear.tsx @@ -0,0 +1,21 @@ +import { createCartItemsUpdater, useCart } from "../cart-provider" +import { useQueryClient } from "@tanstack/react-query" +import { cartQueryKeys } from "./use-get-cart" +import { useDeleteCartItems } from "./use-delete-cart-items" + +export function useCartClear() { + const { cartId } = useCart() + const queryClient = useQueryClient() + return useDeleteCartItems(cartId, { + onSuccess: (updatedData) => { + // Updates the cart items in the query cache + queryClient.setQueryData( + cartQueryKeys.detail(cartId), + createCartItemsUpdater(updatedData.data), + ) + return queryClient.invalidateQueries({ + queryKey: cartQueryKeys.detail(cartId), + }) + }, + }) +} diff --git a/packages/react-shopper-hooks/src/cart/hooks/use-cart-remove-item.tsx b/packages/react-shopper-hooks/src/cart/hooks/use-cart-remove-item.tsx new file mode 100644 index 00000000..bb77b723 --- /dev/null +++ b/packages/react-shopper-hooks/src/cart/hooks/use-cart-remove-item.tsx @@ -0,0 +1,21 @@ +import { createCartItemsUpdater, useCart } from "../cart-provider" +import { useQueryClient } from "@tanstack/react-query" +import { cartQueryKeys } from "./use-get-cart" +import { useRemoveCartItem } from "./use-remove-cart-item" + +export function useCartRemoveItem() { + const { cartId } = useCart() + const queryClient = useQueryClient() + return useRemoveCartItem(cartId, { + onSuccess: (updatedData) => { + // Updates the cart items in the query cache + queryClient.setQueryData( + cartQueryKeys.detail(cartId), + createCartItemsUpdater(updatedData.data), + ) + return queryClient.invalidateQueries({ + queryKey: cartQueryKeys.detail(cartId), + }) + }, + }) +} diff --git a/packages/react-shopper-hooks/src/cart/hooks/use-cart-remove-promotion.tsx b/packages/react-shopper-hooks/src/cart/hooks/use-cart-remove-promotion.tsx new file mode 100644 index 00000000..a4954792 --- /dev/null +++ b/packages/react-shopper-hooks/src/cart/hooks/use-cart-remove-promotion.tsx @@ -0,0 +1,21 @@ +import { createCartItemsUpdater, useCart } from "../cart-provider" +import { useQueryClient } from "@tanstack/react-query" +import { cartQueryKeys } from "./use-get-cart" +import { useRemovePromotionCode } from "./use-remove-promotion" + +export function useCartRemovePromotion() { + const { cartId } = useCart() + const queryClient = useQueryClient() + return useRemovePromotionCode(cartId, { + onSuccess: (updatedData) => { + // Updates the cart items in the query cache + queryClient.setQueryData( + cartQueryKeys.detail(cartId), + createCartItemsUpdater(updatedData.data), + ) + return queryClient.invalidateQueries({ + queryKey: cartQueryKeys.detail(cartId), + }) + }, + }) +} diff --git a/packages/react-shopper-hooks/src/cart/hooks/use-cart-update-item.tsx b/packages/react-shopper-hooks/src/cart/hooks/use-cart-update-item.tsx new file mode 100644 index 00000000..a99d1022 --- /dev/null +++ b/packages/react-shopper-hooks/src/cart/hooks/use-cart-update-item.tsx @@ -0,0 +1,21 @@ +import { createCartItemsUpdater, useCart } from "../cart-provider" +import { useQueryClient } from "@tanstack/react-query" +import { cartQueryKeys } from "./use-get-cart" +import { useUpdateCartItem } from "./use-update-cart-items" + +export function useCartUpdateItem() { + const { cartId } = useCart() + const queryClient = useQueryClient() + return useUpdateCartItem(cartId, { + onSuccess: (updatedData) => { + // Updates the cart items in the query cache + queryClient.setQueryData( + cartQueryKeys.detail(cartId), + createCartItemsUpdater(updatedData.data), + ) + return queryClient.invalidateQueries({ + queryKey: cartQueryKeys.detail(cartId), + }) + }, + }) +} diff --git a/packages/react-shopper-hooks/src/product/hooks/index.ts b/packages/react-shopper-hooks/src/product/hooks/index.ts index a99982f1..ba568aff 100644 --- a/packages/react-shopper-hooks/src/product/hooks/index.ts +++ b/packages/react-shopper-hooks/src/product/hooks/index.ts @@ -1 +1,2 @@ export * from "./use-product" +export * from "./use-products" diff --git a/packages/react-shopper-hooks/src/product/hooks/use-products.ts b/packages/react-shopper-hooks/src/product/hooks/use-products.ts new file mode 100644 index 00000000..0ce5b30e --- /dev/null +++ b/packages/react-shopper-hooks/src/product/hooks/use-products.ts @@ -0,0 +1,48 @@ +import { useElasticPath } from "../../elasticpath/elasticpath" +import { UseQueryOptionsWrapper } from "../../types" +import type { + Moltin, + ShopperCatalogResourcePage, + ProductResponse, +} from "@moltin/sdk" +import { useQuery, UseQueryResult } from "@tanstack/react-query" +import { queryKeysFactory } from "../../shared/util/query-keys-factory" + +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 function useProducts( + params: UseProductsParams & { + limit?: number + offset?: number + filter?: object + }, + options?: UseQueryOptionsWrapper< + ShopperCatalogResourcePage, + Error, + ReturnType + >, +): UseQueryResult, Error> { + const { client } = useElasticPath() + + const { limit = 25, offset = 0, filter = {} } = params + + return useQuery({ + queryKey: [ + ...productsQueryKeys.list({ limit, offset, filter, ...options }), + ], + queryFn: () => + client.ShopperCatalog.Products.Limit(limit) + .Offset(offset) + .Filter(filter) + .With(["main_image", "component_products", "files"]) + .All(), + ...options, + }) +} diff --git a/packages/react-shopper-hooks/src/stories/Cart.stories.tsx b/packages/react-shopper-hooks/src/stories/Cart.stories.tsx index 12a50130..2c959280 100644 --- a/packages/react-shopper-hooks/src/stories/Cart.stories.tsx +++ b/packages/react-shopper-hooks/src/stories/Cart.stories.tsx @@ -5,6 +5,8 @@ import Layout from "./components/Layout" import { useGetCart } from "../cart/hooks/use-get-cart" import { CartProvider, useCart } from "../cart" import { CartItem } from "@moltin/sdk" +import { useCartUpdateItem } from "../cart/hooks/use-cart-update-item" +import { useCartRemoveItem } from "../cart/hooks/use-cart-remove-item" const Cart = ({ showHookData, id }: { showHookData: boolean; id: string }) => { const { data, isLoading } = useGetCart(id) // TODO add real token @@ -51,11 +53,10 @@ GetOne.argTypes = { } function Item({ item }: { item: CartItem }) { - const { useScopedUpdateCartItem, useScopedRemoveCartItem } = useCart() const { mutate: mutateUpdate, isPending: isUpdateItemPending } = - useScopedUpdateCartItem() + useCartUpdateItem() const { mutate: mutateRemove, isPending: isRemovePending } = - useScopedRemoveCartItem() + useCartRemoveItem() return (
  • {item.name} - {item.quantity} -{" "}