@heyframe/composables
Version:
HeyFrame Frontends composables for Vue
315 lines (289 loc) • 7.62 kB
text/typescript
import { createSharedComposable } from "@vueuse/core";
import { computed } from "vue";
import type { ComputedRef } from "vue";
import { useContext, useHeyFrameContext } from "#imports";
import type { Schemas, operations } from "#heyframe";
/**
* Composable to manage cart
*
* @public
* @category Cart & Checkout
*/
export type UseCartReturn = {
/**
* Add product by id and quantity
*/
addProduct(params: {
id: string;
quantity?: number;
}): Promise<Schemas["Cart"]>;
/**
* Add products by array of items
*/
addProducts(
items: operations["addLineItem post /checkout/cart/line-item"]["body"]["items"],
): Promise<Schemas["Cart"]>;
/**
* Adds a promotion code to the cart
*/
addPromotionCode(promotionCode: string): Promise<Schemas["Cart"]>;
/**
* Lists all applied and active promotion codes
*/
appliedPromotionCodes: ComputedRef<Schemas["LineItem"][]>;
/**
* Current Cart object
*/
cart: ComputedRef<Schemas["Cart"] | undefined>;
/**
* All items in the cart
*/
cartItems: ComputedRef<Schemas["LineItem"][]>;
/**
* Changes the quantity of a product in the cart
*/
changeProductQuantity(params: {
id: string;
quantity: number;
}): Promise<Schemas["Cart"]>;
/**
* The number of items in the cart
*/
count: ComputedRef<number>;
/**
* Refreshes the cart object and related data
* If @param newCart is provided, it will be used as a new cart object
*/
refreshCart(newCart?: Schemas["Cart"]): Promise<Schemas["Cart"]>;
/**
* Removes the provided LineItem from the cart
*/
removeItem(lineItem: Schemas["LineItem"]): Promise<Schemas["Cart"]>;
/**
* The total price of the cart (including calculated costs like shipping)
*/
totalPrice: ComputedRef<number>;
/**
* Shipping price
*
* @deprecated Use `shippingCosts` instead
*/
shippingTotal: ComputedRef<number>;
/**
* Shipping costs
*/
shippingCosts: ComputedRef<Schemas["CartDelivery"][]>;
/**
* The total price of all cart items
*/
subtotal: ComputedRef<number>;
/**
* `true` if the cart contains no items
*/
isEmpty: ComputedRef<boolean>;
/**
* `true` if cart contains only digital items
*/
isVirtualCart: ComputedRef<boolean>;
/**
* Get cart errors
*/
consumeCartErrors(): Schemas["Cart"]["errors"];
};
/**
* Cart management logic.
*
* Used as [Shared](https://frontends.heyframe.com/framework/composables/shared-composables.html) Composable `useCart`
*
* @category Cart & Checkout
*/
export function useCartFunction(): UseCartReturn {
const { apiClient } = useHeyFrameContext();
const _storeCart = useContext<Schemas["Cart"]>("swCart");
const _storeCartErrors = useContext<Schemas["Cart"]["errors"] | null>(
"swCartErrors",
);
async function refreshCart(
newCart?: Schemas["Cart"],
): Promise<Schemas["Cart"]> {
if (newCart) {
_storeCart.value = newCart;
return newCart;
}
const { data } = await apiClient.invoke("readCart get /checkout/cart");
_storeCart.value = data;
setCartErrors(data);
return data;
}
async function addProduct(params: {
id: string;
quantity?: number;
}): Promise<Schemas["Cart"]> {
return addProducts([
{
id: params.id,
quantity: params.quantity ?? 0,
type: "product",
},
]);
}
/**
* Add multiple products to the cart
*
* @param {operations["addLineItem post /checkout/cart/line-item"]["body"]["items"]} items
* @returns
*/
async function addProducts(
items: operations["addLineItem post /checkout/cart/line-item"]["body"]["items"],
): Promise<Schemas["Cart"]> {
const { data: addToCartResult } = await apiClient.invoke(
"addLineItem post /checkout/cart/line-item",
{
body: {
items,
},
},
);
_storeCart.value = addToCartResult;
setCartErrors(addToCartResult);
return addToCartResult;
}
async function removeItem(lineItem: Schemas["LineItem"]) {
const { data } = await apiClient.invoke(
"removeLineItem post /checkout/cart/line-item/delete",
{
body: { ids: [lineItem.id] },
},
);
_storeCart.value = data;
setCartErrors(data);
return data;
}
async function changeProductQuantity(params: {
id: string;
quantity: number;
}) {
const { data } = await apiClient.invoke(
"updateLineItem patch /checkout/cart/line-item",
{
body: {
items: [
{
id: params.id,
quantity: +params.quantity,
},
],
},
},
);
_storeCart.value = data;
setCartErrors(data);
return data;
}
async function submitPromotionCode(promotionCode: string) {
const { data } = await apiClient.invoke(
"addLineItem post /checkout/cart/line-item",
{
body: {
items: [
{
referencedId: promotionCode,
type: "promotion",
},
],
},
},
);
_storeCart.value = data;
setCartErrors(data);
return data;
}
const appliedPromotionCodes = computed(() => {
return cartItems.value.filter(
(cartItem: Schemas["LineItem"]) => cartItem.type === "promotion",
);
});
const cart: ComputedRef<Schemas["Cart"] | undefined> = computed(
() => _storeCart.value,
);
const cartItems = computed<Schemas["LineItem"][]>(() => {
return cart.value?.lineItems || [];
});
const count = computed(() => {
return cartItems.value.reduce(
(accumulator: number, lineItem: Schemas["LineItem"]) =>
lineItem.good === true ? lineItem.quantity + accumulator : accumulator,
0,
);
});
const isEmpty = computed(() => count.value <= 0);
const totalPrice = computed(() => {
const cartPrice = cart.value?.price?.totalPrice;
return cartPrice || 0;
});
const shippingTotal = computed(() => {
const shippingTotal =
cart.value?.deliveries?.[0]?.shippingCosts?.totalPrice;
return shippingTotal || 0;
});
const shippingCosts = computed(() => {
return cart.value?.deliveries || [];
});
const subtotal = computed(() => {
const cartPrice = cart.value?.price?.positionPrice;
return cartPrice || 0;
});
const isVirtualCart = computed(() => {
return (
cartItems.value.length > 0 &&
cartItems.value
.filter((element) => element.type !== "promotion")
.every((item) => item.states.includes("is-download"))
);
});
/**
* Add cart errors to the sharable variable
*
* @param {Cart} cart
*/
const setCartErrors = (cart: Schemas["Cart"]) => {
if (Object.keys(cart.errors || {}).length) {
_storeCartErrors.value = Object.assign(
_storeCartErrors.value ? _storeCartErrors.value : {},
cart.errors,
);
}
};
/**
* Get cart errors and clear variable
*
* @returns {CartErrors}
*/
const consumeCartErrors = () => {
const errors = _storeCartErrors.value
? JSON.parse(JSON.stringify(_storeCartErrors.value))
: null;
_storeCartErrors.value = null;
return errors;
};
return {
addProduct,
addProducts,
addPromotionCode: submitPromotionCode,
appliedPromotionCodes,
cart,
cartItems,
changeProductQuantity,
count,
refreshCart,
removeItem,
totalPrice,
shippingTotal,
subtotal,
isEmpty,
isVirtualCart,
consumeCartErrors,
shippingCosts,
};
}
export const useCart = createSharedComposable(useCartFunction);