UNPKG

@scayle/storefront-nuxt

Version:

Nuxt integration for the SCAYLE Commerce Engine and Storefront API

225 lines (224 loc) 6.81 kB
import { AddToBasketFailureKind, ExistingItemHandling, getShippingDates, generateBasketKey, wasAddedWithReducedQuantity } from "@scayle/storefront-core"; import { extendPromise } from "../../utils/promise.js"; import { useRpc } from "../core/useRpc.js"; import { useRpcCall } from "../core/useRpcCall.js"; import { toValue, computed } from "vue"; import { FetchError } from "ofetch"; export function useBasket({ params } = {}, key = "useBasket") { const addItemToBasketRpc = useRpcCall("addItemToBasket"); const addItemsToBasketRpc = useRpcCall("addItemsToBasket"); const updateBasketItemRpc = useRpcCall("updateBasketItem"); const mergeBasketsRpc = useRpcCall("mergeBaskets"); const clearBasketRpc = useRpcCall("clearBasket"); const removeItemFromBasketRpc = useRpcCall("removeItemFromBasket"); const getApplicablePromotionsByCodeRpc = useRpcCall( "getApplicablePromotionsByCode" ); const sanitizedParams = computed(() => { const rawParams = toValue(params); if (rawParams && "orderCustomData" in rawParams) { const { orderCustomData, ...sanitized } = rawParams; return sanitized; } return rawParams; }); const asyncData = useRpc("getBasket", key, sanitizedParams, { server: false, // NOTE: In some cases it might be ok to fetch on server watch: [sanitizedParams], dedupe: "defer", transform: (basketResponse) => basketResponse.basket, getCachedData: (cacheKey, nuxtApp) => { return toValue(nuxtApp._asyncData[cacheKey]?.data) ?? void 0; } }); const { data, error, refresh, status } = asyncData; const handleBasketError = (basketErrors) => { if (wasAddedWithReducedQuantity(basketErrors)) { throw new Error("Item was added with reduced quantity", { cause: AddToBasketFailureKind.ITEM_ADDED_WITH_REDUCED_QUANTITY }); } }; const handleFetchError = (error2) => { if (error2 instanceof FetchError && isAddOrUpdateItemError(error2.data.errors?.[0]) && error2.data.errors?.[0].operation !== "delete") { throw new Error(error2.message, { cause: error2.data.errors?.[0].kind }); } throw error2; }; const addItem = async ({ variantId, promotionId, promotions, quantity, existingItemHandling, displayData, customData, itemGroup }) => { try { const { basket, errors: basketErrors } = await addItemToBasketRpc({ promotionId, promotions, variantId, quantity, existingItemHandling, displayData, customData, itemGroup, with: toValue(sanitizedParams) }); data.value = basket; handleBasketError(basketErrors); } catch (error2) { handleFetchError(error2); } }; const addItems = async (items2, existingItemHandling = ExistingItemHandling.ADD_QUANTITY_TO_EXISTING) => { try { const { basket, errors: basketErrors } = await addItemsToBasketRpc({ items: items2, existingItemHandling, with: toValue(sanitizedParams) }); data.value = basket; handleBasketError(basketErrors); } catch (error2) { handleFetchError(error2); } }; function isAddOrUpdateItemError(error2) { return typeof error2 === "object" && error2 !== null && "operation" in error2 && "kind" in error2; } const mergeBaskets = async (args) => { return await mergeBasketsRpc({ ...args, with: toValue(sanitizedParams) }); }; const findItem = (item) => { return data.value?.items?.find((entry) => { if ("variantId" in item) { return entry.variant.id === item.variantId; } return entry.product.id === item.productId; }); }; const contains = (item) => { return findItem(item) !== void 0; }; const removeItem = async (item) => { const element = findItem(item); if (!element) { throw new Error( `Could not find basket item by variant-id: ${item.variantId}` ); } const { basket } = await removeItemFromBasketRpc({ itemKey: element.key, with: toValue(sanitizedParams) }); data.value = basket; }; const removeItemByKey = async (itemKey) => { const { basket } = await removeItemFromBasketRpc({ itemKey, with: toValue(sanitizedParams) }); data.value = basket; }; const updateItem = async (basketItemKey, updateData) => { const { basket } = await updateBasketItemRpc({ basketItemKey, update: updateData, with: toValue(sanitizedParams) }); data.value = basket; }; const getApplicablePromotionsByCode = async (promotionCode) => { const { basket } = await getApplicablePromotionsByCodeRpc({ promotionCode }); return basket; }; const clear = async () => { await clearBasketRpc(); }; const products = computed( () => data.value?.items.map((item) => item.product) || [] ); const count = computed(() => { return data.value?.items?.reduce((prev, current) => { if (current.itemGroup?.id && !current.itemGroup.isMainItem) { return prev; } return prev + current.quantity; }, 0); }); const countWithoutSoldOutItems = computed(() => { const mainItemQuantities = /* @__PURE__ */ new Map(); const soldOutItemGroups = /* @__PURE__ */ new Set(); let count2 = (data.value?.items ?? []).reduce((prev, current) => { if (current.itemGroup?.id) { if (current.itemGroup.isMainItem) { mainItemQuantities.set(current.itemGroup.id, current.quantity); } else { if (current.product.isSoldOut && current.itemGroup.isRequired) { soldOutItemGroups.add(current.itemGroup.id); } return prev; } } if (current.product.isSoldOut) { return prev; } return prev + current.quantity; }, 0); soldOutItemGroups.forEach((itemGroupId) => { count2 -= mainItemQuantities.get(itemGroupId) || 0; }); return count2; }); const items = computed(() => data.value?.items); const cost = computed(() => data.value?.cost); const basketKey = computed(() => data.value?.key); const isEmpty = computed(() => count.value === 0); const packages = computed(() => data.value?.packages); const shippingDates = computed( () => packages.value ? getShippingDates(packages.value) : void 0 ); return extendPromise(asyncData, { data, items, count, cost, refresh, error, status, addItem, addItems, removeItemByKey, updateItem, clear, getApplicablePromotionsByCode, key: basketKey, packages, shippingDates, isEmpty, countWithoutSoldOutItems, removeItem, contains, products, findItem, generateBasketKey, mergeBaskets }); }