Skip to content

Commit

Permalink
fix: improve useCartItem reactivity and sidecart quantity changing
Browse files Browse the repository at this point in the history
  • Loading branch information
patzick committed Jan 19, 2023
1 parent 13e3110 commit 4d5b04b
Show file tree
Hide file tree
Showing 10 changed files with 65 additions and 51 deletions.
6 changes: 6 additions & 0 deletions .changeset/gorgeous-plums-return.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"@shopware-pwa/composables-next": minor
"docs": patch
---

`useCartItem` composable takes `Ref` instead of plain object as parameter
5 changes: 5 additions & 0 deletions .changeset/nervous-jeans-wait.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"vue-demo-store": patch
---

Fixed quantity changes in sidecart
7 changes: 2 additions & 5 deletions apps/docs/src/getting-started/cart.md
Original file line number Diff line number Diff line change
Expand Up @@ -144,11 +144,8 @@ await removeItem({ id: "7b5b97bd48454979b14f21c8ef38ce08" });
In case of the `useCartItem` composable, you pass the item identifier when calling the composable, but not when calling the `removeItem` method.

```ts
const { removeItem } = useCartItem({ cartItem });

const cartItem: LineItem = {
id: "7b5b97bd48454979b14f21c8ef38ce08",
};
const { cartItem } = toRefs(props);
const { removeItem } = useCartItem(cartItem);

await removeItem();
```
4 changes: 3 additions & 1 deletion apps/docs/src/packages/composables/useCartItem.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ Composable for cart item management.
Display and manage single cart item in your cart.

```ts
const { cartItem } = toRefs(props);

const {
itemOptions,
removeItem,
Expand All @@ -19,5 +21,5 @@ const {
isPromotion,
itemStock,
changeItemQuantity,
} = useCartItem(props.cartItem);
} = useCartItem(cartItem);
```
4 changes: 3 additions & 1 deletion packages/cms-base/components/SwProductAddToCart.vue
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,9 @@ const addToCartProxy = async () => {
id="qty"
type="number"
v-model="quantity"
min="1"
:min="product.minPurchase || 1"
:max="product.calculatedMaxPurchase"
:step="product.purchaseSteps || 1"
class="border rounded-md py-2 px-4 border-solid border-1 border-cyan-600 w-full"
data-testid="product-quantity"
/>
Expand Down
5 changes: 2 additions & 3 deletions packages/composables/src/useAddToCart.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { ref, Ref, computed, unref, ComputedRef, watch } from "vue";
import { Product, Cart, LineItem } from "@shopware-pwa/types";
import { Product, Cart, LineItem, EntityError } from "@shopware-pwa/types";
import { useCart } from "./useCart";

export type UseAddToCartReturn = {
Expand Down Expand Up @@ -28,7 +28,7 @@ export type UseAddToCartReturn = {
export function useAddToCart(product: Ref<Product>): UseAddToCartReturn {
const _product = computed(() => unref(product));

const { addProduct, cartItems, refreshCart } = useCart();
const { addProduct, cartItems } = useCart();
const quantity: Ref<number> = ref(1);

async function addToCart(): Promise<Cart> {
Expand All @@ -38,7 +38,6 @@ export function useAddToCart(product: Ref<Product>): UseAddToCartReturn {
quantity: quantity.value,
});
quantity.value = 1;
refreshCart();
return addToCartResponse;
}

Expand Down
41 changes: 20 additions & 21 deletions packages/composables/src/useCartItem.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,65 +33,64 @@ export type UseCartItemReturn = {
getProductItemSeoUrlData(): Promise<ProductResponse | undefined>;
};

export function useCartItem(cartItem: LineItem): UseCartItemReturn {
export function useCartItem(cartItem: Ref<LineItem>): UseCartItemReturn {
if (!cartItem) {
throw new Error("[useCartItem] mandatory cartItem argument is missing.");
}

const { apiInstance } = useShopwareContext();
const { refreshCart } = useCart();
const { refreshCart, changeProductQuantity } = useCart();

const itemQuantity = computed(() => cartItem.quantity);
const itemImageThumbnailUrl = computed(() => getMainImageUrl(cartItem));
const itemQuantity = computed(() => cartItem.value.quantity);
const itemImageThumbnailUrl = computed(() => getMainImageUrl(cartItem.value));

// TODO: use helper instead

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

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

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

const itemStock = computed(() => cartItem.deliveryInformation?.stock);
const itemStock = computed(() => cartItem.value.deliveryInformation?.stock);

const itemType = computed(() => cartItem.type);
const itemType = computed(() => cartItem.value.type);

const isProduct = computed(() => cartItem.type === "product");
const isProduct = computed(() => cartItem.value.type === "product");

const isPromotion = computed(() => cartItem.type === "promotion");
const isPromotion = computed(() => cartItem.value.type === "promotion");

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

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

async function getProductItemSeoUrlData(): Promise<
ProductResponse | undefined
> {
if (!cartItem.referencedId) {
if (!cartItem.value.referencedId) {
return;
}

try {
const result = await getProduct(
cartItem.referencedId,
cartItem.value.referencedId,
{
// includes: (getDefaults() as any).getProductItemsSeoUrlsData.includes,
// associations: (getDefaults() as any).getProductItemsSeoUrlsData
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ export type CrossSelling = {
*/
export type Product = {
calculatedCheapestPrice: CalculatedPrice;
calculatedMaxPurchase: number;
calculatedListingPrice: ListingPrice;
calculatedPrices: CalculatedPrice[];
calculatedPrice: CalculatedPrice;
Expand Down
39 changes: 20 additions & 19 deletions templates/vue-demo-store/components/checkout/CheckoutCartItem.vue
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ const props = withDefaults(
}
);
const { cartItem } = toRefs(props);
const isLoading = ref(false);
const {
Expand All @@ -22,9 +24,10 @@ const {
isPromotion,
itemStock,
changeItemQuantity,
} = useCartItem(props.cartItem);
} = useCartItem(cartItem);
const quantity = ref(itemQuantity.value);
const quantity = ref();
syncRefs(itemQuantity, quantity);
const updateQuantity = async (quantity: number | undefined) => {
if (quantity === itemQuantity.value) return;
Expand All @@ -35,8 +38,9 @@ const updateQuantity = async (quantity: number | undefined) => {
isLoading.value = false;
};
const debounceUpdate = useDebounceFn(updateQuantity, 800);
watch(quantity, () => updateQuantity(quantity.value));
watch(quantity, () => debounceUpdate(quantity.value));
const removeCartItem = async () => {
isLoading.value = true;
Expand Down Expand Up @@ -91,37 +95,34 @@ const removeCartItem = async () => {
v-if="!isPromotion"
class="flex flex-1 items-end justify-between text-sm"
>
<select
v-if="itemStock && itemStock > 0"
<!-- v-if="itemStock && itemStock > 0" - example of using it on item when you want to block editing quantity -->
<input
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"
v-model="quantity"
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"
>
<option
v-for="entry in itemStock > maxQty ? maxQty : itemStock"
:key="entry"
:value="entry"
data-testid="cart-product-qty-select-option"
>
{{ entry }}
</option>
</select>
<!-- Stock is lower than 1 -->
<div v-else>
/>
<!-- 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>
</div>
</div> -->
<div class="flex">
<button
v-if="!isPromotion"
type="button"
:class="{ 'animate-pulse': isLoading }"
:disabled="isLoading"
class="font-medium text-brand-dark"
:class="{ 'text-gray-500': isLoading }"
data-testid="product-remove-button"
@click="removeCartItem"
>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,9 @@ const addToCartProxy = async () => {
id="qty"
type="number"
v-model="quantity"
min="1"
:min="product.minPurchase || 1"
:max="product.calculatedMaxPurchase"
:step="product.purchaseSteps || 1"
class="border rounded-md py-2 px-4 border-solid border-1 border-cyan-600 w-full"
data-testid="product-quantity"
/>
Expand Down

0 comments on commit 4d5b04b

Please sign in to comment.