diff --git a/components/CartItem.vue b/components/CartItem.vue
index e558882a..0e96bc4f 100644
--- a/components/CartItem.vue
+++ b/components/CartItem.vue
@@ -96,6 +96,13 @@
/>
+
+
+
+
+ {{ $t('cart.exceedsStockLevel') }}
+
+
@@ -124,6 +131,8 @@ export default {
itemEditorIsVisible: false,
quantity: 1,
maxQuantity: 99,
+ adjustmentError: false,
+ validateCartStock: false,
}
},
@@ -143,6 +152,7 @@ export default {
this.maxQuantity = maxQuantity
this.quantity = item.quantity
+ this.validateCartStock = $swell.settings.get('cart.validateStock')
},
computed: {
diff --git a/components/QuickAdd.vue b/components/QuickAdd.vue
index 65752b8f..ff8c9202 100644
--- a/components/QuickAdd.vue
+++ b/components/QuickAdd.vue
@@ -37,6 +37,26 @@
+
+
+
+ {{
+ $t('products.preview.quickAdd.outOfStock')
+ }}
+
+
+
= optionInputs.length
) {
- this.addToCart()
-
// Hide options when adding to cart
this.quickAddIsActive = false
+
+ // Check if product/variant is in stock before adding to cart
+ if (!this.checkHasStock()) {
+ this.addToCartError = 'Out of stock'
+ return
+ }
+
+ // Add to cart
+ this.addToCart()
} else {
// Move onto next option if available
this.quickAddIndex++
@@ -216,19 +244,43 @@ export default {
}
},
+ // Check if current variation has stock
+ checkHasStock() {
+ const { product, variation } = this
+ return (
+ !product.stockTracking ||
+ product.stockPurchasable ||
+ ((variation.stockStatus !== 'out_of_stock' || variation.stockStatus) &&
+ variation.stockLevel > 0)
+ )
+ },
+
// Add product to cart with selected options
- addToCart() {
- // Emit event to show updating label
- this.$emit('adding-to-cart')
-
- // Emit event to hide quick add button if keep-alive is active
- this.$emit('keep-alive', false)
-
- this.$store.dispatch('addCartItem', {
- productId: this.variation.id,
- quantity: 1,
- options: this.optionState,
- })
+ async addToCart() {
+ try {
+ await this.$store.dispatch('addCartItem', {
+ productId: this.variation.id,
+ quantity: 1,
+ options: this.optionState,
+ })
+
+ // Close popup when product has been added to cart
+ this.$emit('click-close')
+ } catch (err) {
+ let errorMessage
+ switch (err.message) {
+ case 'invalid_stock':
+ errorMessage = this.$t('cart.exceedsStockLevel')
+ break
+ default:
+ break
+ }
+
+ this.$store.dispatch('showNotification', {
+ message: errorMessage,
+ type: 'error',
+ })
+ }
},
},
diff --git a/components/QuickViewPopup.vue b/components/QuickViewPopup.vue
index 2c731e3a..3b54e149 100644
--- a/components/QuickViewPopup.vue
+++ b/components/QuickViewPopup.vue
@@ -182,7 +182,6 @@
-
.gradient {
- @apply w-full h-12;
+ @apply w-full absolute top-0 h-6 transform -translate-y-full;
background: rgb(255, 255, 255);
background: linear-gradient(
0deg,
diff --git a/components/StockStatus.vue b/components/StockStatus.vue
index 88ee4490..72f24abf 100644
--- a/components/StockStatus.vue
+++ b/components/StockStatus.vue
@@ -8,6 +8,12 @@
{{
$t(status.label)
}}
+
+ • {{ $t('products.slug.stockRemaining', { n: stockLevel }) }}
+
{{
@@ -54,6 +60,14 @@ export default {
type: String,
default: 'out_of_stock',
},
+ stockLevel: {
+ type: Number,
+ default: 0,
+ },
+ showStockLevel: {
+ type: Boolean,
+ default: false,
+ },
},
computed: {
diff --git a/components/TheFooter.vue b/components/TheFooter.vue
index e6ff026b..099e4d1c 100644
--- a/components/TheFooter.vue
+++ b/components/TheFooter.vue
@@ -128,7 +128,6 @@
>Swell
-
@@ -352,6 +354,7 @@ export default {
selectedPurchaseOption: undefined,
productBenefits: [],
enableSocialSharing: false,
+ showStockLevel: false,
activeDropdownUID: null,
}
},
@@ -399,6 +402,7 @@ export default {
this.relatedProducts = relatedProducts
this.productBenefits = get(product, 'content.productBenefits', [])
this.enableSocialSharing = get(product, 'content.enableSocialSharing')
+ this.showStockLevel = get(product, 'content.showStockLevel')
this.enableQuantity = get(product, 'content.enableQuantity')
this.maxQuantity = maxQuantity
},
@@ -530,17 +534,25 @@ export default {
},
// Add product to cart with selected options
- addToCart() {
- // Touch and validate all fields
- this.$v.$touch()
- if (this.$v.$invalid) return // return if invalid
-
- this.$store.dispatch('addCartItem', {
- productId: this.variation.id,
- quantity: this.quantity || 1,
- options: this.optionState,
- purchaseOption: this.selectedPurchaseOption,
- })
+ async addToCart() {
+ try {
+ // Touch and validate all fields
+ this.$v.$touch()
+ if (this.$v.$invalid) return // return if invalid
+ await this.$store.dispatch('addCartItem', {
+ productId: this.variation.id,
+ quantity: this.quantity || 1,
+ options: this.optionState,
+ purchaseOption: this.selectedPurchaseOption,
+ })
+ } catch (err) {
+ if (err.message === 'invalid_stock') {
+ this.$store.dispatch('showNotification', {
+ message: this.$t('cart.exceedsStockLevel'),
+ type: 'error',
+ })
+ }
+ }
},
// Update an option value based on user input
diff --git a/store/index.js b/store/index.js
index e9e5f600..183ea4f5 100644
--- a/store/index.js
+++ b/store/index.js
@@ -29,6 +29,72 @@ export const actions = {
}
},
+ /**
+ * Check if a product is in stock to be added/modified within the cart
+ * @property {Object} item - The product or cart item
+ * @property {string} id - The cart item id
+ * @property {number} quantityToAdd - The quantity to add to cart
+ */
+ async checkCartItemHasStock({ state }, { item, id }) {
+ // Get cart items
+ const items = state.cart?.items
+
+ let cartItem
+ let stockPurchasable
+ let stockTracking
+ let stockLevel
+ let product
+ let currentQuantity = 0
+
+ const quantityToAdd = item ? item.quantity : 1
+
+ if (item) {
+ product = await this.$swell.products.get(item.productId)
+ } else if (id) {
+ product = await this.$swell.products.get(id)
+ }
+
+ if (!product) throw new Error('Product in cart could not be found.')
+
+ if (items) {
+ let variant
+ // If a product item is provided
+ if (item) variant = this.$swell.products.variation(product, item.options)
+ cartItem = items.find((item) => {
+ if (id) {
+ return item.id === id
+ } else if (item) {
+ return item.variant
+ ? item.variantId === variant?.variantId
+ : item.productId === variant?.id
+ }
+ return null
+ })
+ }
+
+ // Get stock availability of cart item
+ if (cartItem) {
+ stockPurchasable = cartItem.product.stockPurchasable
+ stockTracking = cartItem.product.stockTracking
+ stockLevel = cartItem.product.stockLevel
+ // If variant, get respective stock level
+ if (cartItem.variant) {
+ stockLevel = cartItem.variant.stockLevel
+ }
+ currentQuantity = cartItem.quantity
+ } else {
+ // Get stock availability of product to be added
+ stockPurchasable = product.stockPurchasable
+ stockTracking = product.stockTracking
+ stockLevel = product.stockLevel
+ }
+
+ // If product is purchasable out of stock or doesn't track stock, allow add to cart
+ if (stockPurchasable || !stockTracking) return true
+ if (currentQuantity + quantityToAdd > stockLevel) return false
+ return true
+ },
+
/**
* Product to be added to cart
* @type {Object} Product
@@ -49,6 +115,24 @@ export const actions = {
commit('setState', { key: 'cartIsUpdating', value: true })
try {
+ // Check if validate stock on add is active
+ const validateCartStock = this.$swell.settings.get('cart.validateStock')
+
+ if (validateCartStock) {
+ try {
+ const cartItemHasStock = await dispatch('checkCartItemHasStock', {
+ item,
+ })
+
+ if (!cartItemHasStock) {
+ commit('setState', { key: 'cartIsUpdating', value: false })
+ throw new Error('invalid_stock')
+ }
+ } catch (err) {
+ throw new Error(err.message)
+ }
+ }
+
// Make Swell API call
const cart = await this.$swell.cart.addItem(item)
// Replace cart state
@@ -82,6 +166,7 @@ export const actions = {
})
}
} catch (err) {
+ if (err.message === 'invalid_stock') throw new Error('invalid_stock')
dispatch('handleError', err)
}