From 231621d2cf85aae806c681018ba4934383f833e5 Mon Sep 17 00:00:00 2001 From: w3bdesign <45217974+w3bdesign@users.noreply.github.com> Date: Thu, 12 Sep 2024 06:18:19 +0200 Subject: [PATCH 1/7] Update apollo.js --- plugins/apollo.js | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/plugins/apollo.js b/plugins/apollo.js index cc616acf..a89631c6 100644 --- a/plugins/apollo.js +++ b/plugins/apollo.js @@ -57,12 +57,29 @@ export default defineNuxtPlugin((nuxtApp) => { ); // Cache implementation - const cache = new InMemoryCache(); + const cache = new InMemoryCache({ + typePolicies: { + Query: { + fields: { + cart: { + merge(existing, incoming) { + return incoming; + }, + }, + }, + }, + }, + }); // Create the apollo client const apolloClient = new ApolloClient({ link: middleware.concat(afterware.concat(httpLink)), cache, + defaultOptions: { + watchQuery: { + fetchPolicy: 'cache-and-network', + }, + }, }); provideApolloClient(apolloClient); From c1a3062abad79c86ed4125348d5410e93f23add0 Mon Sep 17 00:00:00 2001 From: w3bdesign <45217974+w3bdesign@users.noreply.github.com> Date: Thu, 12 Sep 2024 06:18:25 +0200 Subject: [PATCH 2/7] Update useCart.js --- store/useCart.js | 200 ++++++++++++++++++++++++++--------------------- 1 file changed, 112 insertions(+), 88 deletions(-) diff --git a/store/useCart.js b/store/useCart.js index e47e2ee0..0d05db1e 100644 --- a/store/useCart.js +++ b/store/useCart.js @@ -1,103 +1,127 @@ import { defineStore } from "pinia"; - +import { useQuery, useMutation } from "@vue/apollo-composable"; +import { computed, ref, watch } from "vue"; import ADD_TO_CART_MUTATION from "@/apollo/mutations/ADD_TO_CART_MUTATION.gql"; +import UPDATE_CART_MUTATION from "@/apollo/mutations/UPDATE_CART_MUTATION.gql"; +import GET_CART_QUERY from "@/apollo/queries/GET_CART_QUERY.gql"; -/** - * Manages a shopping cart store using the Pinia library. - * - * @typedef {Object} CartState - * @property {Array} cart - The array representing the cart. - * @property {boolean} loading - The loading status of the cart. - * @property {string|null} error - The error message, if any. - */ +export const useCart = defineStore("cartState", () => { + const cart = ref([]); + const loading = ref(false); + const error = ref(null); + const cartTotals = ref({}); -const state = { - cart: [], - loading: false, - error: null, -}; + const { result: cartResult, loading: cartLoading, refetch } = useQuery(GET_CART_QUERY, null, { fetchPolicy: 'network-only' }); -export const useCart = defineStore("cartState", { - state: () => state, - actions: { - async addToCart(product) { - this.loading = true; - try { - const { mutate } = useMutation(ADD_TO_CART_MUTATION); - const response = await mutate({ - input: { - productId: product.databaseId, - quantity: 1, - }, - }); + watch(cartResult, (newCartResult) => { + if (newCartResult && newCartResult.cart) { + cart.value = newCartResult.cart.contents.nodes.map(item => ({ + key: item.key, + product: item.product.node, + variation: item.variation ? item.variation.node : null, + quantity: item.quantity, + total: item.total, + subtotal: item.subtotal, + subtotalTax: item.subtotalTax + })); - if (response.data && response.data.addToCart) { - this.loading = false; - const newCartItem = response.data.addToCart.cartItem; - const foundProductInCartIndex = this.cart.findIndex( - (cartProduct) => newCartItem.product.node.slug === cartProduct.slug - ); + cartTotals.value = { + subtotal: newCartResult.cart.subtotal, + subtotalTax: newCartResult.cart.subtotalTax, + shippingTax: newCartResult.cart.shippingTax, + shippingTotal: newCartResult.cart.shippingTotal, + total: newCartResult.cart.total, + totalTax: newCartResult.cart.totalTax, + feeTax: newCartResult.cart.feeTax, + feeTotal: newCartResult.cart.feeTotal, + discountTax: newCartResult.cart.discountTax, + discountTotal: newCartResult.cart.discountTotal + }; + } + }); - if (foundProductInCartIndex > -1) { - this.cart[foundProductInCartIndex].quantity += 1; - } else { - // We need to construct a cart item that matches the expected structure in `this.cart` - const productCopy = { - ...newCartItem.product.node, - quantity: newCartItem.quantity, - price: newCartItem.total, // Assuming 'total' is the price for one item - slug: newCartItem.product.node.slug, - }; + const addToCart = async (product, quantity = 1) => { + loading.value = true; + error.value = null; + try { + const { mutate } = useMutation(ADD_TO_CART_MUTATION); + await mutate({ + input: { + productId: product.databaseId, + quantity: quantity, + }, + }); + await refetch(); + } catch (err) { + console.error("Error adding to cart:", err); + error.value = err.message || "An error occurred while adding to cart."; + } finally { + loading.value = false; + } + }; - this.cart.push(productCopy); - } - } else { - // Handle the case where the mutation does not return the expected data - this.error = "Did not receive expected cart data from the server."; - } - } catch (error) { - this.error = error.message || "An error occurred while adding to cart."; - } finally { - this.loading = false; - } - }, + const updateCartItemQuantity = async (key, quantity) => { + loading.value = true; + error.value = null; + try { + const { mutate } = useMutation(UPDATE_CART_MUTATION); + await mutate({ + input: { + items: [{ key, quantity }], + }, + }); + await refetch(); + } catch (err) { + console.error("Error updating cart:", err); + error.value = err.message || "An error occurred while updating the cart."; + } finally { + loading.value = false; + } + }; - removeProductFromCart(product) { - this.cart.splice(this.cart.indexOf(product), 1); - }, - clearCart() { - this.cart.length = 0; - }, - }, - getters: { - getCartQuantity() { - if (!this.cart) { - console.error("Cart is undefined"); - return 0; - } - return this.cart.reduce((total, product) => total + product.quantity, 0); - }, + const removeProductFromCart = async (key) => { + return updateCartItemQuantity(key, 0); + }; - getCartTotal() { - const currencySymbol = useRuntimeConfig().public.currencySymbol || "kr"; - - const total = this.cart.reduce((total, product) => { - // Assuming product.price is a string that includes the currency symbol - const numericPrice = product.price - .replace(currencySymbol, "") - .replace(/[^0-9.]+/g, ""); + const clearCart = async () => { + loading.value = true; + error.value = null; + try { + for (const item of cart.value) { + await removeProductFromCart(item.key); + } + } catch (err) { + console.error("Error clearing cart:", err); + error.value = err.message || "An error occurred while clearing the cart."; + } finally { + loading.value = false; + } + }; - // Convert the cleaned string to a floating-point number - const price = parseFloat(numericPrice); + const cartQuantity = computed(() => { + return cart.value.reduce((total, item) => total + item.quantity, 0); + }); - const productTotal = price * product.quantity; + const cartSubtotal = computed(() => { + return cartTotals.value.subtotal || '0'; + }); - return total + productTotal; - }, 0); + const cartTotal = computed(() => { + return cartTotals.value.total || '0'; + }); - // Format the total with the currency symbol and return it - return `${currencySymbol} ${total.toFixed(2)}`; - }, - }, - persist: true, + return { + cart, + loading, + error, + cartTotals, + addToCart, + updateCartItemQuantity, + removeProductFromCart, + clearCart, + cartQuantity, + cartSubtotal, + cartTotal, + refetch, + }; }); From 57ddd719a13176df00942285b3f7d6200c2667a6 Mon Sep 17 00:00:00 2001 From: w3bdesign <45217974+w3bdesign@users.noreply.github.com> Date: Thu, 12 Sep 2024 06:26:35 +0200 Subject: [PATCH 3/7] . --- components/Cart/CartContents.vue | 57 ++++--------- components/Cart/CartItem.vue | 10 ++- components/Layout/LayoutCart.vue | 72 +++------------- components/Products/ProductsSingleProduct.vue | 26 +++--- store/useCart.js | 84 ++++++++++++++----- 5 files changed, 108 insertions(+), 141 deletions(-) diff --git a/components/Cart/CartContents.vue b/components/Cart/CartContents.vue index b1679adf..3aed0214 100644 --- a/components/Cart/CartContents.vue +++ b/components/Cart/CartContents.vue @@ -1,18 +1,18 @@ @@ -21,55 +21,28 @@ * Vue.js component for handling the logic of removing a product from the cart and updating the cart state. * * @module CartContents - * @param {Object} props - Object containing the component's properties. - * @param {Boolean} props.showCheckoutButton - Determines whether the checkout button should be shown or not. * @returns {Object} The Vue.js component object. */ -import GET_CART_QUERY from "@/apollo/queries/GET_CART_QUERY.gql"; -import UPDATE_CART_MUTATION from "@/apollo/mutations/UPDATE_CART_MUTATION.gql"; - +import { computed } from 'vue'; import { useCart } from "@/store/useCart"; +import CommonButton from '@/components/common/CommonButton.vue'; const cart = useCart(); -defineProps({ - showCheckoutButton: { type: Boolean, required: false, default: false }, -}); - -const { data } = await useAsyncQuery(GET_CART_QUERY); +const cartItems = computed(() => cart.cart); /** * Handles the removal of a product. * * @param {Object} product - The product to be removed. */ -const handleRemoveProduct = (product) => { - const updatedItems = []; - - const { key } = product; - - updatedItems.push({ - key, - quantity: 0, - }); - - const variables = { - input: { - items: updatedItems, - }, - }; - - cart.removeProductFromCart(product); - - const { mutate, onDone, onError } = useMutation(UPDATE_CART_MUTATION, { - variables, - }); - - mutate(variables); - - onDone(() => { - document.location = "/cart"; - }); +const handleRemoveProduct = async (product) => { + try { + await cart.removeProductFromCart(product.key); + } catch (error) { + console.error('Error removing product from cart:', error); + // You might want to show an error message to the user here + } }; diff --git a/components/Cart/CartItem.vue b/components/Cart/CartItem.vue index 7722cfb3..5d561fa8 100644 --- a/components/Cart/CartItem.vue +++ b/components/Cart/CartItem.vue @@ -15,7 +15,7 @@
Name:
- {{ product.product.node.name }} + {{ product.product.name }}
Quantity:
@@ -25,7 +25,7 @@
Subtotal:
- {{ formatPrice(`${product.total}`) }} + {{ formatPrice(product.total) }}
@@ -37,13 +37,15 @@ * @component CartItem * * @prop {Object} product - The product object containing information about the product. - * @prop {string} product.name - The name of the product. + * @prop {Object} product.product - The product details. + * @prop {string} product.product.name - The name of the product. * @prop {number} product.quantity - The quantity of the product. - * @prop {number} product.total - The subtotal of the product. + * @prop {string} product.total - The subtotal of the product. * * @emits CartItem#remove - Emitted when the remove button is clicked. */ +import { ref } from 'vue'; import { formatPrice } from "@/utils/functions"; const isRemoving = ref(false); diff --git a/components/Layout/LayoutCart.vue b/components/Layout/LayoutCart.vue index 3bb81e9f..ae88fcaa 100644 --- a/components/Layout/LayoutCart.vue +++ b/components/Layout/LayoutCart.vue @@ -1,14 +1,14 @@ diff --git a/components/Products/ProductsSingleProduct.vue b/components/Products/ProductsSingleProduct.vue index 64f6443b..eaffbf5a 100644 --- a/components/Products/ProductsSingleProduct.vue +++ b/components/Products/ProductsSingleProduct.vue @@ -54,9 +54,10 @@ @common-button-click="addProductToCart(data.product)" :is-loading="isLoading" > - ADD TO CART + ADD TO CART + +

{{ successMessage }}

@@ -75,10 +76,9 @@ * @prop {string} slug - The slug of the product to display. */ -import { ref, watch } from "vue"; +import { ref, watch, computed } from "vue"; import GET_SINGLE_PRODUCT_QUERY from "@/apollo/queries/GET_SINGLE_PRODUCT_QUERY.gql"; -import ADD_TO_CART_MUTATION from "@/apollo/mutations/ADD_TO_CART_MUTATION.gql"; import ProductImage from "@/components/Products/ProductImage.vue"; import ProductPrice from "@/components/Products/ProductPrice.vue"; @@ -92,6 +92,7 @@ const cart = useCart(); const isLoading = computed(() => cart.loading); const selectedVariation = ref(); // TODO Pass this value to addProductToCart() +const successMessage = ref(''); const props = defineProps({ id: { type: String, required: true }, @@ -119,12 +120,15 @@ watch( * @return {Promise} A Promise that resolves when the product has been successfully added to the cart. */ const addProductToCart = async (product) => { - await cart.addToCart(product); - - watchEffect(() => { - if (isLoading.value === false) { - window.location.reload(); - } - }); + try { + await cart.addToCart(product); + successMessage.value = 'Product added to cart successfully!'; + setTimeout(() => { + successMessage.value = ''; + }, 3000); + } catch (error) { + console.error('Error adding product to cart:', error); + // You might want to show an error message to the user here + } }; diff --git a/store/useCart.js b/store/useCart.js index 0d05db1e..387297ad 100644 --- a/store/useCart.js +++ b/store/useCart.js @@ -15,35 +15,49 @@ export const useCart = defineStore("cartState", () => { watch(cartResult, (newCartResult) => { if (newCartResult && newCartResult.cart) { - cart.value = newCartResult.cart.contents.nodes.map(item => ({ - key: item.key, - product: item.product.node, - variation: item.variation ? item.variation.node : null, - quantity: item.quantity, - total: item.total, - subtotal: item.subtotal, - subtotalTax: item.subtotalTax - })); - - cartTotals.value = { - subtotal: newCartResult.cart.subtotal, - subtotalTax: newCartResult.cart.subtotalTax, - shippingTax: newCartResult.cart.shippingTax, - shippingTotal: newCartResult.cart.shippingTotal, - total: newCartResult.cart.total, - totalTax: newCartResult.cart.totalTax, - feeTax: newCartResult.cart.feeTax, - feeTotal: newCartResult.cart.feeTotal, - discountTax: newCartResult.cart.discountTax, - discountTotal: newCartResult.cart.discountTotal - }; + updateCartState(newCartResult.cart); } }); + const updateCartState = (newCart) => { + cart.value = newCart.contents.nodes.map(item => ({ + key: item.key, + product: item.product.node, + variation: item.variation ? item.variation.node : null, + quantity: item.quantity, + total: item.total, + subtotal: item.subtotal, + subtotalTax: item.subtotalTax + })); + + cartTotals.value = { + subtotal: newCart.subtotal, + subtotalTax: newCart.subtotalTax, + shippingTax: newCart.shippingTax, + shippingTotal: newCart.shippingTotal, + total: newCart.total, + totalTax: newCart.totalTax, + feeTax: newCart.feeTax, + feeTotal: newCart.feeTotal, + discountTax: newCart.discountTax, + discountTotal: newCart.discountTotal + }; + }; + const addToCart = async (product, quantity = 1) => { loading.value = true; error.value = null; try { + // Optimistically update the local state + const newItem = { + key: Date.now().toString(), // Temporary key + product: product, + quantity: quantity, + total: (parseFloat(product.price) * quantity).toString(), + subtotal: (parseFloat(product.price) * quantity).toString(), + }; + cart.value.push(newItem); + const { mutate } = useMutation(ADD_TO_CART_MUTATION); await mutate({ input: { @@ -55,6 +69,8 @@ export const useCart = defineStore("cartState", () => { } catch (err) { console.error("Error adding to cart:", err); error.value = err.message || "An error occurred while adding to cart."; + // Revert the optimistic update + cart.value = cart.value.filter(item => item.key !== Date.now().toString()); } finally { loading.value = false; } @@ -64,6 +80,12 @@ export const useCart = defineStore("cartState", () => { loading.value = true; error.value = null; try { + // Optimistically update the local state + const itemIndex = cart.value.findIndex(item => item.key === key); + if (itemIndex !== -1) { + cart.value[itemIndex].quantity = quantity; + } + const { mutate } = useMutation(UPDATE_CART_MUTATION); await mutate({ input: { @@ -74,13 +96,29 @@ export const useCart = defineStore("cartState", () => { } catch (err) { console.error("Error updating cart:", err); error.value = err.message || "An error occurred while updating the cart."; + // Revert the optimistic update + await refetch(); } finally { loading.value = false; } }; const removeProductFromCart = async (key) => { - return updateCartItemQuantity(key, 0); + loading.value = true; + error.value = null; + try { + // Optimistically update the local state + cart.value = cart.value.filter(item => item.key !== key); + + await updateCartItemQuantity(key, 0); + } catch (err) { + console.error("Error removing product from cart:", err); + error.value = err.message || "An error occurred while removing the product from the cart."; + // Revert the optimistic update + await refetch(); + } finally { + loading.value = false; + } }; const clearCart = async () => { From f075d583a0b5870e69429fcb41d82ac7b7079cd3 Mon Sep 17 00:00:00 2001 From: w3bdesign <45217974+w3bdesign@users.noreply.github.com> Date: Thu, 12 Sep 2024 06:27:38 +0200 Subject: [PATCH 4/7] Update CartItem.vue --- components/Cart/CartItem.vue | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/components/Cart/CartItem.vue b/components/Cart/CartItem.vue index 5d561fa8..dc0a7f8a 100644 --- a/components/Cart/CartItem.vue +++ b/components/Cart/CartItem.vue @@ -41,6 +41,7 @@ * @prop {string} product.product.name - The name of the product. * @prop {number} product.quantity - The quantity of the product. * @prop {string} product.total - The subtotal of the product. + * @prop {string} product.key - The unique key for the cart item. * * @emits CartItem#remove - Emitted when the remove button is clicked. */ @@ -60,10 +61,11 @@ const props = defineProps({ const emit = defineEmits(["remove"]); /** - * Emits a "remove" event with the `product` prop as the payload. + * Emits a "remove" event with the product's key as the payload. */ const emitRemove = () => { - emit("remove", props.product); + isRemoving.value = true; + emit("remove", props.product.key); }; From f810b1d32ce2c61db159e38b989a0f218147175c Mon Sep 17 00:00:00 2001 From: w3bdesign <45217974+w3bdesign@users.noreply.github.com> Date: Thu, 12 Sep 2024 06:31:20 +0200 Subject: [PATCH 5/7] . --- components/Cart/CartContents.vue | 9 +++---- store/useCart.js | 40 ++++++-------------------------- 2 files changed, 12 insertions(+), 37 deletions(-) diff --git a/components/Cart/CartContents.vue b/components/Cart/CartContents.vue index 3aed0214..4ed2873a 100644 --- a/components/Cart/CartContents.vue +++ b/components/Cart/CartContents.vue @@ -34,14 +34,15 @@ const cartItems = computed(() => cart.cart); /** * Handles the removal of a product. * - * @param {Object} product - The product to be removed. + * @param {string} key - The key of the product to be removed. */ -const handleRemoveProduct = async (product) => { +const handleRemoveProduct = async (key) => { try { - await cart.removeProductFromCart(product.key); + await cart.removeProductFromCart(key); } catch (error) { console.error('Error removing product from cart:', error); - // You might want to show an error message to the user here + // Optionally, you can add a user-friendly notification here + // without exposing the error details } }; diff --git a/store/useCart.js b/store/useCart.js index 387297ad..99a91071 100644 --- a/store/useCart.js +++ b/store/useCart.js @@ -11,7 +11,7 @@ export const useCart = defineStore("cartState", () => { const error = ref(null); const cartTotals = ref({}); - const { result: cartResult, loading: cartLoading, refetch } = useQuery(GET_CART_QUERY, null, { fetchPolicy: 'network-only' }); + const { result: cartResult, loading: cartLoading, refetch: refetchCart } = useQuery(GET_CART_QUERY, null, { fetchPolicy: 'network-only' }); watch(cartResult, (newCartResult) => { if (newCartResult && newCartResult.cart) { @@ -48,16 +48,6 @@ export const useCart = defineStore("cartState", () => { loading.value = true; error.value = null; try { - // Optimistically update the local state - const newItem = { - key: Date.now().toString(), // Temporary key - product: product, - quantity: quantity, - total: (parseFloat(product.price) * quantity).toString(), - subtotal: (parseFloat(product.price) * quantity).toString(), - }; - cart.value.push(newItem); - const { mutate } = useMutation(ADD_TO_CART_MUTATION); await mutate({ input: { @@ -65,12 +55,9 @@ export const useCart = defineStore("cartState", () => { quantity: quantity, }, }); - await refetch(); + await refetchCart(); } catch (err) { console.error("Error adding to cart:", err); - error.value = err.message || "An error occurred while adding to cart."; - // Revert the optimistic update - cart.value = cart.value.filter(item => item.key !== Date.now().toString()); } finally { loading.value = false; } @@ -80,24 +67,16 @@ export const useCart = defineStore("cartState", () => { loading.value = true; error.value = null; try { - // Optimistically update the local state - const itemIndex = cart.value.findIndex(item => item.key === key); - if (itemIndex !== -1) { - cart.value[itemIndex].quantity = quantity; - } - const { mutate } = useMutation(UPDATE_CART_MUTATION); await mutate({ input: { items: [{ key, quantity }], }, }); - await refetch(); + await refetchCart(); } catch (err) { console.error("Error updating cart:", err); - error.value = err.message || "An error occurred while updating the cart."; - // Revert the optimistic update - await refetch(); + await refetchCart(); } finally { loading.value = false; } @@ -107,17 +86,12 @@ export const useCart = defineStore("cartState", () => { loading.value = true; error.value = null; try { - // Optimistically update the local state - cart.value = cart.value.filter(item => item.key !== key); - await updateCartItemQuantity(key, 0); } catch (err) { console.error("Error removing product from cart:", err); - error.value = err.message || "An error occurred while removing the product from the cart."; - // Revert the optimistic update - await refetch(); } finally { loading.value = false; + await refetchCart(); } }; @@ -130,9 +104,9 @@ export const useCart = defineStore("cartState", () => { } } catch (err) { console.error("Error clearing cart:", err); - error.value = err.message || "An error occurred while clearing the cart."; } finally { loading.value = false; + await refetchCart(); } }; @@ -160,6 +134,6 @@ export const useCart = defineStore("cartState", () => { cartQuantity, cartSubtotal, cartTotal, - refetch, + refetch: refetchCart, }; }); From b56198b9bbd277188bb7a17a625c718dcc185246 Mon Sep 17 00:00:00 2001 From: w3bdesign <45217974+w3bdesign@users.noreply.github.com> Date: Thu, 12 Sep 2024 06:40:02 +0200 Subject: [PATCH 6/7] . --- components/Cart/CartContents.vue | 23 +++++++++++++++++++-- components/Layout/LayoutCart.vue | 35 +++++++++++++++++++++++--------- nuxt.config.js | 2 +- plugins/cartUpdater.js | 16 +++++++++++++++ 4 files changed, 63 insertions(+), 13 deletions(-) create mode 100644 plugins/cartUpdater.js diff --git a/components/Cart/CartContents.vue b/components/Cart/CartContents.vue index 4ed2873a..cdea8f31 100644 --- a/components/Cart/CartContents.vue +++ b/components/Cart/CartContents.vue @@ -1,5 +1,11 @@