Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Merged
merged 6 commits into from
Apr 26, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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.
Copy link
Collaborator

Choose a reason for hiding this comment

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

let's add patch changeset entry for that

*/
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