@scayle/storefront-nuxt
Version:
Nuxt integration for the SCAYLE Commerce Engine and Storefront API
225 lines (224 loc) • 6.81 kB
JavaScript
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
});
}