Skip to content

Commit

Permalink
feat(composables): new total price property for useCartItem composable (
Browse files Browse the repository at this point in the history
#168)

* feat(composables): new total price property for useCartItem composable

* Update packages/composables/src/useCartItem.ts

Co-authored-by: Patryk Tomczyk <[email protected]>

* chore: changes after CR

* Update .changeset/sour-sheep-smile.md

---------

Co-authored-by: Patryk Tomczyk <[email protected]>
  • Loading branch information
mkucmus and patzick authored Apr 26, 2023
1 parent 4b323a1 commit eddcfcc
Show file tree
Hide file tree
Showing 6 changed files with 113 additions and 93 deletions.
5 changes: 5 additions & 0 deletions .changeset/cyan-yaks-return.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"vue-demo-store": minor
---

Display cart item total price instead of unit price
5 changes: 5 additions & 0 deletions .changeset/sour-sheep-smile.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@shopware-pwa/composables-next": patch
---

`getProductItemSeoUrlData` method of `useCart` marked as deprecated
5 changes: 5 additions & 0 deletions .changeset/warm-snakes-sort.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@shopware-pwa/composables-next": patch
---

Add item total price property for useCartItem composable
22 changes: 14 additions & 8 deletions packages/composables/src/useCart.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,8 +49,9 @@ export type UseCartReturn = {
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(): Promise<Cart>;
refreshCart(newCart?: Cart): Promise<Cart>;
/**
* Removes the provided LineItem from the cart
*/
Expand Down Expand Up @@ -95,7 +96,12 @@ export function useCartFunction(): UseCartReturn {

const _storeCart = _useContext<Cart | undefined>("swCart");

async function refreshCart(): Promise<Cart> {
async function refreshCart(newCart?: Cart): Promise<Cart> {
if (newCart) {
_storeCart.value = newCart;
return newCart;
}

const result = await getCart(apiInstance);
_storeCart.value = result;
return result;
Expand All @@ -108,7 +114,7 @@ export function useCartFunction(): UseCartReturn {
const addToCartResult = await addProductToCart(
params.id,
params.quantity,
apiInstance
apiInstance,
);
_storeCart.value = addToCartResult;
return addToCartResult;
Expand All @@ -126,7 +132,7 @@ export function useCartFunction(): UseCartReturn {
const result = await changeCartItemQuantity(
params.id,
params.quantity,
apiInstance
apiInstance,
);
_storeCart.value = result;
}
Expand Down Expand Up @@ -179,14 +185,14 @@ export function useCartFunction(): UseCartReturn {
// associations: (getDefaults() as any).getProductItemsSeoUrlsData
// .associations,
},
apiInstance
apiInstance,
);
return result?.elements || [];
}

const appliedPromotionCodes = computed(() => {
return cartItems.value.filter(
(cartItem: LineItem) => cartItem.type === "promotion"
(cartItem: LineItem) => cartItem.type === "promotion",
);
});

Expand All @@ -202,7 +208,7 @@ export function useCartFunction(): UseCartReturn {
lineItem.type === "product"
? lineItem.quantity + accumulator
: accumulator,
0
0,
);
});

Expand All @@ -226,7 +232,7 @@ export function useCartFunction(): UseCartReturn {
});

const cartErrors: ComputedRef<EntityError[]> = computed(
() => (cart.value?.errors && Object.values(cart.value.errors)) || []
() => (cart.value?.errors && Object.values(cart.value.errors)) || [],
);

const isVirtualCart = computed(() => {
Expand Down
67 changes: 54 additions & 13 deletions packages/composables/src/useCartItem.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { computed, ComputedRef, Ref, unref } from "vue";
import { computed, ComputedRef, Ref } from "vue";
import { removeCartItem, getProduct } from "@shopware-pwa/api-client";
import {
import type {
LineItem,
LineItemType,
ClientApiError,
Expand All @@ -13,14 +13,45 @@ import { getMainImageUrl } from "@shopware-pwa/helpers-next";
import { useShopwareContext, useCart } from ".";

export type UseCartItemReturn = {
/**
* Calculated price {number} for the current item
*/
itemRegularPrice: ComputedRef<number | undefined>;
/**
* Calculated price {number} for the current item if list price is set
*/
itemSpecialPrice: ComputedRef<number | undefined>;
/**
* Total price for the current item of given quantity in the cart
*/
itemTotalPrice: ComputedRef<number | undefined>;
/**
* Thumbnail url for the current item's entity
*/
itemImageThumbnailUrl: ComputedRef<string>;
/**
* Options (of variation) for the current item
*/
itemOptions: ComputedRef<PropertyGroupOptionCart[]>;
/**
* Type of the current item: "product" or "promotion"
*/
itemType: ComputedRef<LineItemType | undefined>;
/**
* Determines if the current item is a product
*/
isProduct: ComputedRef<boolean>;
/**
* Determines if the current item is a promotion
*/
isPromotion: ComputedRef<boolean>;
/**
* Stock information for the current item
*/
itemStock: ComputedRef<number | undefined>;
/**
* Quantity of the current item in the cart
*/
itemQuantity: ComputedRef<number | undefined>;
/**
* Changes the current item quantity in the cart
Expand All @@ -32,6 +63,8 @@ export type UseCartItemReturn = {
removeItem(): Promise<void>;
/**
* Get SEO data for the current item
*
* @deprecated
*/
getProductItemSeoUrlData(): Promise<ProductResponse | undefined>;
};
Expand All @@ -52,19 +85,25 @@ export function useCartItem(cartItem: Ref<LineItem>): UseCartItemReturn {
const itemQuantity = computed(() => cartItem.value.quantity);
const itemImageThumbnailUrl = computed(() => getMainImageUrl(cartItem.value));

// TODO: use helper instead

const itemRegularPrice = computed(() => cartItem.value.price?.unitPrice);
const itemRegularPrice = computed(
() =>
cartItem.value?.price?.listPrice?.price ||
cartItem.value?.price?.unitPrice,
);

const itemSpecialPrice = computed(
() => cartItem.value.price?.listPrice && cartItem.value.price.unitPrice
() =>
cartItem.value?.price?.listPrice?.price &&
cartItem.value?.price?.unitPrice,
);

const itemTotalPrice = computed(() => cartItem.value.price?.totalPrice);

const itemOptions = computed(
() =>
(cartItem.value.type === "product" &&
(cartItem.value.payload as CartProductItem)?.options) ||
[]
[],
);

const itemStock = computed(() => cartItem.value.deliveryInformation?.stock);
Expand All @@ -76,19 +115,20 @@ export function useCartItem(cartItem: Ref<LineItem>): UseCartItemReturn {
const isPromotion = computed(() => cartItem.value.type === "promotion");

async function removeItem() {
const result = await removeCartItem(cartItem.value.id, apiInstance);
// broadcastUpcomingErrors(result);
await refreshCart();
const newCart = await removeCartItem(cartItem.value.id, apiInstance);
await refreshCart(newCart);
}

async function changeItemQuantity(quantity: number): Promise<void> {
await changeProductQuantity({
id: cartItem.value.id,
quantity: quantity,
});
// broadcastUpcomingErrors(result);
}

/**
* @deprecated Method is not used anymore and the case should be solved on project level instead due to performance reasons.
*/
async function getProductItemSeoUrlData(): Promise<
ProductResponse | undefined
> {
Expand All @@ -104,13 +144,13 @@ export function useCartItem(cartItem: Ref<LineItem>): UseCartItemReturn {
// associations: (getDefaults() as any).getProductItemsSeoUrlsData
// .associations,
},
apiInstance
apiInstance,
);
return result.product as unknown as ProductResponse;
} catch (error) {
console.error(
"[useCart][getProductItemsSeoUrlsData]",
(error as ClientApiError).messages
(error as ClientApiError).messages,
);
}

Expand All @@ -123,6 +163,7 @@ export function useCartItem(cartItem: Ref<LineItem>): UseCartItemReturn {
getProductItemSeoUrlData,
itemRegularPrice,
itemSpecialPrice,
itemTotalPrice,
itemOptions,
itemStock,
itemQuantity,
Expand Down
102 changes: 30 additions & 72 deletions templates/vue-demo-store/components/checkout/CheckoutCartItem.vue
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ const props = withDefaults(
}>(),
{
maxQty: 100,
}
},
);
const { cartItem } = toRefs(props);
Expand All @@ -19,7 +19,7 @@ const isLoading = ref(false);
const {
itemOptions,
removeItem,
itemRegularPrice,
itemTotalPrice,
itemQuantity,
isPromotion,
changeItemQuantity,
Expand Down Expand Up @@ -49,78 +49,36 @@ const removeCartItem = async () => {
</script>

<template>
<div
v-if="!isPromotion"
class="mr-4 h-24 w-24 flex-shrink-0 overflow-hidden rounded-md border border-gray-200"
>
<img
:src="getSmallestThumbnailUrl(cartItem.cover)"
alt="Salmon orange fabric pouch with match zipper, gray zipper pull, and adjustable hip belt."
class="h-full w-full object-cover object-center"
data-testid="cart-product-image"
/>
</div>

<div class="flex flex-1 flex-col">
<div>
<div
class="flex flex-col lg:flex-row justify-between text-base font-medium text-gray-900"
>
<h3 class="text-base" data-testid="cart-product-name">
{{ cartItem.label }}
</h3>
<SharedPrice
v-if="itemRegularPrice"
:value="itemRegularPrice"
data-testid="cart-product-price"
/>
</div>

<p
v-if="itemOptions"
class="mt-1 text-sm text-gray-500"
data-testid="cart-product-options"
>
<span v-for="option in itemOptions" :key="option.group" class="mr-2">
{{ option.group }}: {{ option.option }}
</span>
</p>
<div v-if="!isPromotion" class="mr-4 h-24 w-24 flex-shrink-0 overflow-hidden rounded-md border border-gray-200">
<img :src="getSmallestThumbnailUrl(cartItem.cover)"
alt="Salmon orange fabric pouch with match zipper, gray zipper pull, and adjustable hip belt."
class="h-full w-full object-cover object-center" data-testid="cart-product-image" />
</div>
<div
v-if="!isPromotion"
class="flex flex-1 items-end justify-between text-sm"
>
<!-- v-if="itemStock && itemStock > 0" - example of using it on item when you want to block editing quantity -->
<input
v-model="quantity"
type="number"
:disabled="isLoading"
:min="cartItem.quantityInformation?.minPurchase || 1"
:max="cartItem.quantityInformation?.maxPurchase || maxQty"
:step="cartItem.quantityInformation?.purchaseSteps || 1"
data-testid="cart-product-qty-select"
name="quantity"
class="w-18 mt-1 inline-block py-2 px-3 border border-gray-300 bg-white rounded-md shadow-sm focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm"
/>
<!-- disabled quantity edition -->
<!-- <div v-else>
<div
data-testid="cart-product-qty"
class="w-18 mt-1 inline-block py-2 px-3 border border-gray-300 bg-white opacity-50 rounded-md shadow-sm focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm"
>
{{ quantity }}

<div class="flex flex-1 flex-col">
<div>
<div class="flex flex-col lg:flex-row justify-between text-base font-medium text-gray-900">
<h3 class="text-base" data-testid="cart-product-name">
{{ cartItem.label }} <span v-if="isPromotion"
class="bg-green-100 text-green-800 text-xs font-medium mr-2 px-2.5 py-0.5 rounded-full dark:bg-green-900 dark:text-green-300">Promotion</span>
</h3>
<SharedPrice v-if="itemTotalPrice" :value="itemTotalPrice" data-testid="cart-product-price" />
</div>
</div> -->
<div class="flex">
<button
v-if="!isPromotion"
type="button"
:disabled="isLoading"
class="font-medium text-brand-dark"
:class="{ 'text-gray-500': isLoading }"
data-testid="product-remove-button"
@click="removeCartItem"
>

<p v-if="itemOptions" class="mt-1 text-sm text-gray-500" data-testid="cart-product-options">
<span v-for="option in itemOptions" :key="option.group" class="mr-2">
{{ option.group }}: {{ option.option }}
</span>
</p>
</div>
<div v-if="!isPromotion" class="flex flex-1 items-end justify-between text-sm">
<input v-model="quantity" type="number" :disabled="isLoading" :min="cartItem.quantityInformation?.minPurchase || 1"
:max="cartItem.quantityInformation?.maxPurchase || maxQty"
:step="cartItem.quantityInformation?.purchaseSteps || 1" data-testid="cart-product-qty-select" name="quantity"
class="w-18 mt-1 inline-block py-2 px-3 border border-gray-300 bg-white rounded-md shadow-sm focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm" />
<div class="flex">
<button v-if="!isPromotion" type="button" :disabled="isLoading" class="font-medium text-brand-dark"
:class="{ 'text-gray-500': isLoading }" data-testid="product-remove-button" @click="removeCartItem">
Remove
</button>
</div>
Expand Down

2 comments on commit eddcfcc

@vercel
Copy link

@vercel vercel bot commented on eddcfcc Apr 26, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Successfully deployed to the following URLs:

frontends-demo – ./templates/vue-demo-store

frontends-demo.vercel.app
frontends-demo-shopware-frontends.vercel.app
frontends-demo-git-main-shopware-frontends.vercel.app

@vercel
Copy link

@vercel vercel bot commented on eddcfcc Apr 26, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please sign in to comment.