@ecomplus/storefront-components 1.0.0-beta.19 → 1.0.0-beta.191

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (125) hide show
  1. package/.version +0 -0
  2. package/CHANGELOG.md +1333 -135
  3. package/all.js +3 -1
  4. package/dist/1.storefront-components.min.js +2 -0
  5. package/dist/1.storefront-components.min.js.map +1 -0
  6. package/dist/2.storefront-components.min.js +5 -0
  7. package/dist/2.storefront-components.min.js.map +1 -0
  8. package/dist/3.storefront-components.min.js +5 -0
  9. package/dist/3.storefront-components.min.js.map +1 -0
  10. package/dist/storefront-components.min.js +33 -12
  11. package/dist/storefront-components.min.js.map +1 -1
  12. package/package.json +17 -12
  13. package/src/APagination.vue +2 -0
  14. package/src/AShare.vue +2 -0
  15. package/src/AccountAddresses.vue +3 -0
  16. package/src/AccountForm.vue +3 -0
  17. package/src/AccountPoints.vue +3 -0
  18. package/src/BuyTogether.vue +3 -0
  19. package/src/EarnPointsProgress.vue +3 -0
  20. package/src/ItemCustomizations.vue +2 -0
  21. package/src/KitProductVariations.vue +3 -0
  22. package/src/PointsApplier.vue +2 -0
  23. package/src/ProductQuickview.vue +3 -0
  24. package/src/QuantitySelector.vue +3 -0
  25. package/src/RecommendedItems.vue +3 -0
  26. package/src/ShippingLine.vue +1 -0
  27. package/src/TheCart.vue +3 -0
  28. package/src/html/APagination.html +90 -0
  29. package/src/html/APrices.html +24 -4
  30. package/src/html/AShare.html +31 -0
  31. package/src/html/AccountAddresses.html +90 -0
  32. package/src/html/AccountForm.html +269 -0
  33. package/src/html/AccountPoints.html +39 -0
  34. package/src/html/AddressForm.html +9 -7
  35. package/src/html/BuyTogether.html +60 -0
  36. package/src/html/CartItem.html +86 -38
  37. package/src/html/CartQuickview.html +28 -5
  38. package/src/html/DiscountApplier.html +3 -3
  39. package/src/html/EarnPointsProgress.html +28 -0
  40. package/src/html/InputDate.html +1 -1
  41. package/src/html/InputDocNumber.html +1 -0
  42. package/src/html/InputPhone.html +1 -1
  43. package/src/html/InstantSearch.html +3 -3
  44. package/src/html/ItemCustomizations.html +14 -0
  45. package/src/html/KitProductVariations.html +92 -0
  46. package/src/html/LoginBlock.html +34 -32
  47. package/src/html/LoginModal.html +9 -4
  48. package/src/html/PaymentOption.html +7 -5
  49. package/src/html/PointsApplier.html +26 -0
  50. package/src/html/ProductCard.html +68 -8
  51. package/src/html/ProductGallery.html +21 -3
  52. package/src/html/ProductQuickview.html +64 -0
  53. package/src/html/ProductVariations.html +30 -3
  54. package/src/html/QuantitySelector.html +85 -0
  55. package/src/html/RecommendedItems.html +48 -0
  56. package/src/html/SearchEngine.html +101 -24
  57. package/src/html/ShippingCalculator.html +84 -3
  58. package/src/html/ShippingLine.html +5 -2
  59. package/src/html/TheAccount.html +43 -9
  60. package/src/html/TheCart.html +156 -0
  61. package/src/html/TheProduct.html +416 -138
  62. package/src/js/APagination.js +74 -0
  63. package/src/js/APicture.js +27 -7
  64. package/src/js/APrices.js +80 -41
  65. package/src/js/AShare.js +83 -0
  66. package/src/js/AccountAddresses.js +201 -0
  67. package/src/js/AccountForm.js +312 -0
  68. package/src/js/AccountPoints.js +63 -0
  69. package/src/js/AddressForm.js +80 -35
  70. package/src/js/BuyTogether.js +246 -0
  71. package/src/js/CartItem.js +67 -14
  72. package/src/js/CartQuickview.js +20 -1
  73. package/src/js/DiscountApplier.js +181 -50
  74. package/src/js/EarnPointsProgress.js +77 -0
  75. package/src/js/InputDate.js +8 -8
  76. package/src/js/InputDocNumber.js +20 -0
  77. package/src/js/ItemCustomizations.js +10 -0
  78. package/src/js/KitProductVariations.js +218 -0
  79. package/src/js/LoginBlock.js +47 -6
  80. package/src/js/LoginModal.js +18 -10
  81. package/src/js/PaymentOption.js +28 -1
  82. package/src/js/PointsApplier.js +110 -0
  83. package/src/js/ProductCard.js +115 -11
  84. package/src/js/ProductGallery.js +32 -12
  85. package/src/js/ProductQuickview.js +72 -0
  86. package/src/js/ProductVariations.js +76 -19
  87. package/src/js/QuantitySelector.js +175 -0
  88. package/src/js/RecommendedItems.js +178 -0
  89. package/src/js/SearchEngine.js +185 -55
  90. package/src/js/ShippingCalculator.js +176 -35
  91. package/src/js/ShippingLine.js +44 -5
  92. package/src/js/TheAccount.js +97 -6
  93. package/src/js/TheCart.js +146 -0
  94. package/src/js/TheProduct.js +387 -43
  95. package/src/js/helpers/add-idle-callback.js +7 -0
  96. package/src/js/helpers/check-form-validity.js +3 -0
  97. package/src/js/helpers/favorite-products.js +24 -0
  98. package/src/js/helpers/scroll-to-element.js +10 -0
  99. package/src/js/helpers/sort-apps.js +14 -0
  100. package/src/js/helpers/wait-storefront-info.js +21 -0
  101. package/src/scss/APicture.scss +2 -0
  102. package/src/scss/APrices.scss +13 -1
  103. package/src/scss/AccountAddresses.scss +27 -0
  104. package/src/scss/AccountForm.scss +5 -0
  105. package/src/scss/AccountPoints.scss +17 -0
  106. package/src/scss/BuyTogether.scss +38 -0
  107. package/src/scss/CartItem.scss +17 -1
  108. package/src/scss/EarnPointsProgress.scss +6 -0
  109. package/src/scss/InstantSearch.scss +1 -0
  110. package/src/scss/KitProductVariations.scss +72 -0
  111. package/src/scss/LoginBlock.scss +5 -0
  112. package/src/scss/PaymentOption.scss +10 -1
  113. package/src/scss/ProductCard.scss +63 -25
  114. package/src/scss/ProductGallery.scss +4 -2
  115. package/src/scss/ProductQuickview.scss +36 -0
  116. package/src/scss/ProductVariations.scss +20 -4
  117. package/src/scss/QuantitySelector.scss +39 -0
  118. package/src/scss/RecommendedItems.scss +28 -0
  119. package/src/scss/SearchEngine.scss +9 -5
  120. package/src/scss/ShippingCalculator.scss +42 -1
  121. package/src/scss/ShippingLine.scss +24 -0
  122. package/src/scss/TheAccount.scss +4 -0
  123. package/src/scss/TheCart.scss +54 -0
  124. package/src/scss/TheProduct.scss +146 -1
  125. package/webpack.config.js +20 -6
@@ -1,4 +1,5 @@
1
1
  import {
2
+ i19addToFavorites,
2
3
  i19buy,
3
4
  i19connectionErrorProductMsg,
4
5
  i19outOfStock,
@@ -13,11 +14,27 @@ import {
13
14
  price as getPrice
14
15
  } from '@ecomplus/utils'
15
16
 
17
+ import Vue from 'vue'
16
18
  import { store } from '@ecomplus/client'
17
19
  import ecomCart from '@ecomplus/shopping-cart'
18
20
  import ALink from '../ALink.vue'
19
21
  import APicture from '../APicture.vue'
20
22
  import APrices from '../APrices.vue'
23
+ import ecomPassport from '@ecomplus/passport-client'
24
+ import { toggleFavorite, checkFavorite } from './helpers/favorite-products'
25
+
26
+ const getExternalHtml = (varName, product) => {
27
+ if (typeof window === 'object') {
28
+ varName = `productCard${varName}Html`
29
+ const html = typeof window[varName] === 'function'
30
+ ? window[varName](product)
31
+ : window[varName]
32
+ if (typeof html === 'string') {
33
+ return html
34
+ }
35
+ }
36
+ return undefined
37
+ }
21
38
 
22
39
  export default {
23
40
  name: 'ProductCard',
@@ -37,27 +54,67 @@ export default {
37
54
  default: 'h3'
38
55
  },
39
56
  buyText: String,
57
+ transitionClass: {
58
+ type: String,
59
+ default: 'animated fadeIn'
60
+ },
40
61
  canAddToCart: {
41
62
  type: Boolean,
42
63
  default: true
43
64
  },
44
- isLoaded: Boolean
65
+ ecomPassport: {
66
+ type: Object,
67
+ default () {
68
+ return ecomPassport
69
+ }
70
+ },
71
+ accountUrl: {
72
+ type: String,
73
+ default: '/app/#/account/'
74
+ },
75
+ isLoaded: Boolean,
76
+ installmentsOption: Object,
77
+ discountOption: Object,
78
+ stamps: {
79
+ type: Array,
80
+ default () {
81
+ return window.productCardStamps || []
82
+ }
83
+ }
45
84
  },
46
85
 
47
86
  data () {
48
87
  return {
49
88
  body: {},
50
89
  isLoading: false,
90
+ isWaitingBuy: false,
51
91
  isHovered: false,
92
+ isFavorite: false,
52
93
  error: ''
53
94
  }
54
95
  },
55
96
 
56
97
  computed: {
98
+ i19addToFavorites: () => i18n(i19addToFavorites),
57
99
  i19outOfStock: () => i18n(i19outOfStock),
58
100
  i19unavailable: () => i18n(i19unavailable),
59
- buyHtml: () => typeof window === 'object' && window.productCardBuyHtml,
60
- footerHtml: () => typeof window === 'object' && window.productCardFooterHtml,
101
+ i19uponRequest: () => 'Sob consulta',
102
+
103
+ isWithoutPrice () {
104
+ return !getPrice(this.body)
105
+ },
106
+
107
+ ratingHtml () {
108
+ return getExternalHtml('Rating', this.body)
109
+ },
110
+
111
+ buyHtml () {
112
+ return getExternalHtml('Buy', this.body)
113
+ },
114
+
115
+ footerHtml () {
116
+ return getExternalHtml('Footer', this.body)
117
+ },
61
118
 
62
119
  name () {
63
120
  return getName(this.body)
@@ -77,11 +134,27 @@ export default {
77
134
  return this.body.available && this.body.visible && this.isInStock
78
135
  },
79
136
 
137
+ isLogged () {
138
+ return ecomPassport.checkAuthorization()
139
+ },
140
+
80
141
  discount () {
81
142
  const { body } = this
82
143
  return checkOnPromotion(body)
83
144
  ? Math.round(((body.base_price - getPrice(body)) * 100) / body.base_price)
84
145
  : 0
146
+ },
147
+
148
+ validStamps () {
149
+ if (!Array.isArray(this.stamps)) return []
150
+ return this.stamps.filter((stamp) => {
151
+ if (stamp && stamp.img) {
152
+ if (Array.isArray(stamp.skus) && stamp.skus.length) {
153
+ return stamp.skus.includes(this.body.sku)
154
+ }
155
+ }
156
+ return false
157
+ })
85
158
  }
86
159
  },
87
160
 
@@ -90,13 +163,15 @@ export default {
90
163
  this.body = Object.assign({}, data)
91
164
  delete this.body.body_html
92
165
  delete this.body.body_text
166
+ delete this.body.inventory_records
167
+ delete this.body.price_change_records
168
+ this.isFavorite = checkFavorite(this.body._id, this.ecomPassport)
93
169
  },
94
170
 
95
171
  fetchItem () {
96
172
  if (this.productId) {
97
173
  this.isLoading = true
98
- const { storeId, productId } = this
99
- store({ url: `/products/${productId}.json`, storeId })
174
+ store({ url: `/products/${this.productId}.json` })
100
175
  .then(({ data }) => {
101
176
  this.$emit('update:product', data)
102
177
  this.setBody(data)
@@ -114,16 +189,45 @@ export default {
114
189
  }
115
190
  },
116
191
 
192
+ toggleFavorite () {
193
+ if (this.isLogged) {
194
+ this.isFavorite = toggleFavorite(this.body._id, this.ecomPassport)
195
+ }
196
+ },
197
+
117
198
  buy () {
118
199
  const product = this.body
119
200
  this.$emit('buy', { product })
120
201
  if (this.canAddToCart) {
121
- const { variations, slug } = product
122
- if (variations && variations.length) {
123
- window.location = `/${slug}`
124
- } else {
125
- ecomCart.addProduct(product)
126
- }
202
+ this.isWaitingBuy = true
203
+ store({ url: `/products/${product._id}.json` })
204
+ .then(({ data }) => {
205
+ const selectFields = ['variations', 'customizations', 'kit_composition']
206
+ for (let i = 0; i < selectFields.length; i++) {
207
+ const selectOptions = data[selectFields[i]]
208
+ if (selectOptions && selectOptions.length) {
209
+ return import('../ProductQuickview.vue')
210
+ .then(quickview => {
211
+ new Vue({
212
+ render: h => h(quickview.default, {
213
+ props: {
214
+ product: data
215
+ }
216
+ })
217
+ }).$mount(this.$refs.quickview)
218
+ })
219
+ }
220
+ }
221
+ const { quantity, price } = data
222
+ ecomCart.addProduct({ ...product, quantity, price })
223
+ })
224
+ .catch(err => {
225
+ console.error(err)
226
+ window.location = `/${product.slug}`
227
+ })
228
+ .finally(() => {
229
+ this.isWaitingBuy = false
230
+ })
127
231
  }
128
232
  }
129
233
  },
@@ -18,8 +18,6 @@ import {
18
18
 
19
19
  import ecomCart from '@ecomplus/shopping-cart'
20
20
  import Glide from '@glidejs/glide'
21
- import * as PhotoSwipe from 'photoswipe'
22
- import * as psUi from 'photoswipe/dist/photoswipe-ui-default'
23
21
  import APicture from '../APicture.vue'
24
22
 
25
23
  export default {
@@ -43,7 +41,7 @@ export default {
43
41
  video: Object,
44
42
  videoAspectRatio: {
45
43
  type: String,
46
- default: '4by3'
44
+ default: '16by9'
47
45
  },
48
46
  canAddToCart: {
49
47
  type: Boolean,
@@ -62,7 +60,8 @@ export default {
62
60
  rewind: false
63
61
  }
64
62
  }
65
- }
63
+ },
64
+ isSSR: Boolean
66
65
  },
67
66
 
68
67
  data () {
@@ -70,7 +69,9 @@ export default {
70
69
  glide: null,
71
70
  pswp: null,
72
71
  activeIndex: null,
73
- isSliderMoved: false
72
+ isSliderMoved: false,
73
+ elFirstPicture: null,
74
+ zoomLinkStyle: null
74
75
  }
75
76
  },
76
77
 
@@ -86,7 +87,8 @@ export default {
86
87
 
87
88
  localPictures () {
88
89
  return this.pictures && this.pictures.length
89
- ? this.pictures : (this.product.pictures || [])
90
+ ? this.pictures
91
+ : (this.product.pictures || [])
90
92
  },
91
93
 
92
94
  videoSrc () {
@@ -162,13 +164,23 @@ export default {
162
164
  },
163
165
 
164
166
  openZoom (index) {
165
- if (!this.pswd) {
166
- this.pswp = new PhotoSwipe(this.$refs.pswp, psUi, this.pswpItems, {
167
- ...this.pswpOptions,
168
- index
167
+ this.zoomLinkStyle = 'cursor: wait'
168
+ return import(/* webpackPrefetch: true */ 'photoswipe')
169
+ .then(pack => {
170
+ const PhotoSwipe = pack.default
171
+ return import(/* webpackPrefetch: true */ 'photoswipe/dist/photoswipe-ui-default').then(pack => {
172
+ const psUi = pack.default
173
+ this.pswp = new PhotoSwipe(this.$refs.pswp, psUi, this.pswpItems, {
174
+ ...this.pswpOptions,
175
+ index
176
+ })
177
+ this.pswp.init()
178
+ })
179
+ })
180
+ .catch(console.error)
181
+ .finally(() => {
182
+ this.zoomLinkStyle = null
169
183
  })
170
- }
171
- this.pswp.init()
172
184
  },
173
185
 
174
186
  buy () {
@@ -203,6 +215,14 @@ export default {
203
215
  },
204
216
 
205
217
  mounted () {
218
+ if (this.isSSR) {
219
+ this.elFirstPicture = document.querySelector('#product-gallery .product__picture')
220
+ if (this.elFirstPicture) {
221
+ this.$nextTick(() => {
222
+ this.$refs.firstPicture[0].appendChild(this.elFirstPicture)
223
+ })
224
+ }
225
+ }
206
226
  const glide = new Glide(this.$refs.glide, this.glideOptions)
207
227
  glide.on('run', () => {
208
228
  this.moveSlider(glide.index)
@@ -0,0 +1,72 @@
1
+ import {
2
+ i19close,
3
+ i19seeMoreInfo
4
+ } from '@ecomplus/i18n'
5
+
6
+ import { i18n } from '@ecomplus/utils'
7
+ import { Portal } from '@linusborg/vue-simple-portal'
8
+ import TheProduct from '../TheProduct.vue'
9
+ import ABackdrop from '../ABackdrop.vue'
10
+
11
+ export default {
12
+ name: 'ProductQuickView',
13
+
14
+ components: {
15
+ Portal,
16
+ TheProduct,
17
+ ABackdrop
18
+ },
19
+
20
+ props: {
21
+ productId: String,
22
+ product: Object,
23
+ portalId: {
24
+ type: String,
25
+ default: 'quickview'
26
+ }
27
+ },
28
+
29
+ data () {
30
+ return {
31
+ productName: '',
32
+ productLink: '',
33
+ isVisible: false
34
+ }
35
+ },
36
+
37
+ computed: {
38
+ i19close: () => i18n(i19close),
39
+ i19seeMoreInfo: () => i18n(i19seeMoreInfo)
40
+ },
41
+
42
+ methods: {
43
+ setProduct ({ name, slug }) {
44
+ this.productName = name
45
+ this.productLink = `/${slug}`
46
+ },
47
+
48
+ hide () {
49
+ this.isVisible = false
50
+ setTimeout(() => {
51
+ if (!this.isVisible) {
52
+ this.$destroy()
53
+ }
54
+ }, 450)
55
+ }
56
+ },
57
+
58
+ created () {
59
+ let portal = document.getElementById(this.portalId)
60
+ if (!portal) {
61
+ portal = document.createElement('div')
62
+ portal.setAttribute('id', this.portalId)
63
+ document.body.appendChild(portal)
64
+ } else {
65
+ portal.innerHTML = ''
66
+ }
67
+ if (this.product) {
68
+ this.setProduct(this.product)
69
+ }
70
+ this.isVisible = true
71
+ }
72
+ }
@@ -1,5 +1,10 @@
1
1
  import {
2
- inStock as checkStock,
2
+ i19select,
3
+ i19selectVariation
4
+ } from '@ecomplus/i18n'
5
+
6
+ import {
7
+ i18n,
3
8
  specValueByText as getSpecValueByText,
4
9
  specTextValue as getSpecTextValue,
5
10
  variationsGrids as getVariationsGrids,
@@ -32,7 +37,31 @@ export default {
32
37
  data () {
33
38
  return {
34
39
  selectedOptions: {},
35
- filteredGrids: getVariationsGrids(this.product, null, true)
40
+ filteredGrids: {}
41
+ }
42
+ },
43
+
44
+ computed: {
45
+ i19select: () => i18n(i19select),
46
+ i19selectVariation: () => i18n(i19selectVariation),
47
+
48
+ variationsGrids () {
49
+ return getVariationsGrids(this.product)
50
+ },
51
+
52
+ orderedGrids () {
53
+ return Object.keys(this.variationsGrids)
54
+ },
55
+
56
+ variationFromUrl () {
57
+ if (typeof window === 'object') {
58
+ const urlParams = new URLSearchParams(window.location.search)
59
+ const variationId = urlParams.get('variation_id')
60
+ if (variationId) {
61
+ return variationId
62
+ }
63
+ }
64
+ return null
36
65
  }
37
66
  },
38
67
 
@@ -68,6 +97,11 @@ export default {
68
97
  selectOption (optionText, grid, gridIndex) {
69
98
  const { product, selectedOptions, orderedGrids } = this
70
99
  this.$set(selectedOptions, grid, optionText)
100
+ this.$emit('select-option', {
101
+ gridId: grid,
102
+ gridIndex,
103
+ optionText
104
+ })
71
105
  const filterGrids = {}
72
106
  for (let i = 0; i <= gridIndex; i++) {
73
107
  const grid = orderedGrids[i]
@@ -78,21 +112,21 @@ export default {
78
112
  const nextFilteredGrids = getVariationsGrids(product, filterGrids, true)
79
113
  for (let i = gridIndex + 1; i < orderedGrids.length; i++) {
80
114
  const grid = orderedGrids[i]
81
- this.filteredGrids[grid] = nextFilteredGrids[grid]
115
+ const options = nextFilteredGrids[grid]
116
+ this.filteredGrids[grid] = options
117
+ if (selectedOptions[grid] && !options.includes(selectedOptions[grid])) {
118
+ this.$set(selectedOptions, grid, undefined)
119
+ }
82
120
  }
83
121
  const variations = product.variations.slice(0)
84
122
  for (let i = 0; i < variations.length; i++) {
85
123
  const variation = variations[i]
86
- if (!checkStock(variation)) {
87
- variations.splice(i, 1)
88
- } else {
89
- const { specifications } = variation
90
- for (const grid in specifications) {
91
- if (selectedOptions[grid] !== getSpecTextValue(variation, grid)) {
92
- variations.splice(i, 1)
93
- i--
94
- break
95
- }
124
+ const { specifications } = variation
125
+ for (const grid in specifications) {
126
+ if (selectedOptions[grid] !== getSpecTextValue(variation, grid)) {
127
+ variations.splice(i, 1)
128
+ i--
129
+ break
96
130
  }
97
131
  }
98
132
  }
@@ -100,13 +134,36 @@ export default {
100
134
  }
101
135
  },
102
136
 
103
- computed: {
104
- variationsGrids () {
105
- return getVariationsGrids(this.product)
106
- },
137
+ watch: {
138
+ 'product.variations': {
139
+ handler () {
140
+ this.filteredGrids = getVariationsGrids(this.product, null, true)
141
+ },
142
+ deep: true,
143
+ immediate: true
144
+ }
145
+ },
107
146
 
108
- orderedGrids () {
109
- return Object.keys(this.variationsGrids)
147
+ mounted () {
148
+ if (this.variationFromUrl && Array.isArray(this.product.variations)) {
149
+ const selectedVariation = this.product.variations.find(variation => variation._id === this.variationFromUrl)
150
+ if (selectedVariation) {
151
+ const { specifications } = selectedVariation
152
+ const specs = Object.keys(specifications)
153
+ const nextSpec = (specIndex = 0) => {
154
+ const spec = specs[specIndex]
155
+ if (specs[specIndex] && specifications[spec] && specifications[spec].length === 1) {
156
+ const specText = specifications[spec][0].text
157
+ if (this.variationsGrids[spec].find(option => option === specText)) {
158
+ this.$nextTick(() => {
159
+ this.selectOption(specText, spec, this.orderedGrids.indexOf(spec))
160
+ nextSpec(specIndex + 1)
161
+ })
162
+ }
163
+ }
164
+ }
165
+ nextSpec()
166
+ }
110
167
  }
111
168
  }
112
169
  }
@@ -0,0 +1,175 @@
1
+ import {
2
+ i19buyKit,
3
+ i19maxQuantity,
4
+ i19minQuantity
5
+ } from '@ecomplus/i18n'
6
+
7
+ import { i18n } from '@ecomplus/utils'
8
+
9
+ import ecomCart from '@ecomplus/shopping-cart'
10
+ import ALink from '../ALink.vue'
11
+ import AAlert from '../AAlert.vue'
12
+
13
+ export default {
14
+ name: 'QuantitySelector',
15
+
16
+ components: {
17
+ ALink,
18
+ AAlert
19
+ },
20
+
21
+ props: {
22
+ items: {
23
+ type: Array,
24
+ required: true
25
+ },
26
+ min: {
27
+ type: Number,
28
+ default: 1
29
+ },
30
+ max: Number,
31
+ slug: String,
32
+ buyText: String,
33
+ kitProductId: String,
34
+ kitName: String,
35
+ kitPrice: Number,
36
+ canAddToCart: {
37
+ type: Boolean,
38
+ default: true
39
+ },
40
+ hasBuyButton: {
41
+ type: Boolean,
42
+ default: true
43
+ }
44
+ },
45
+
46
+ data () {
47
+ return {
48
+ selectedQnts: this.items.reduce((selectedQnts, item) => {
49
+ selectedQnts[item._id] = item.quantity || 0
50
+ return selectedQnts
51
+ }, {}),
52
+ hasMinAlert: false,
53
+ hasMaxAlert: false,
54
+ alertVariant: 'warning'
55
+ }
56
+ },
57
+
58
+ computed: {
59
+ i19maxQuantity: () => i18n(i19maxQuantity),
60
+ i19minQuantity: () => i18n(i19minQuantity),
61
+
62
+ totalQuantity () {
63
+ let total = 0
64
+ const { selectedQnts } = this
65
+ Object.keys(selectedQnts).forEach(key => {
66
+ if (selectedQnts[key]) {
67
+ total += selectedQnts[key]
68
+ }
69
+ })
70
+ return total
71
+ },
72
+
73
+ remainingQuantity () {
74
+ return this.max
75
+ ? this.max - this.totalQuantity
76
+ : 9999999
77
+ },
78
+
79
+ strBuy () {
80
+ return this.buyText || i18n(i19buyKit)
81
+ }
82
+ },
83
+
84
+ methods: {
85
+ checkInStock (item) {
86
+ const maxQuantity = item.max_quantity
87
+ return typeof maxQuantity === 'number' && maxQuantity >= 0
88
+ ? maxQuantity
89
+ : 9999999
90
+ },
91
+
92
+ changeQnt (item, qntDiff, ev) {
93
+ const { selectedQnts, remainingQuantity } = this
94
+ const lastQnt = selectedQnts[item._id]
95
+ let newQnt
96
+ if (qntDiff) {
97
+ newQnt = selectedQnts[item._id] + qntDiff
98
+ } else if (ev) {
99
+ selectedQnts[item._id] = ev.target.value.replace(/\D/g, '')
100
+ newQnt = parseInt(selectedQnts[item._id], 10)
101
+ }
102
+ if (this.items.length === 1 && this.min > newQnt) {
103
+ newQnt = this.min
104
+ }
105
+ if (newQnt > 0) {
106
+ if (item.min_quantity > newQnt) {
107
+ newQnt = item.min_quantity
108
+ } else {
109
+ const itemMaxQnt = item.max_quantity !== undefined ? item.max_quantity : 9999999
110
+ const maxQnt = Math.min(itemMaxQnt, lastQnt + remainingQuantity)
111
+ if (maxQnt < newQnt) {
112
+ this.alertVariant = 'info'
113
+ this.hasMaxAlert = true
114
+ newQnt = maxQnt
115
+ }
116
+ }
117
+ selectedQnts[item._id] = newQnt
118
+ } else {
119
+ selectedQnts[item._id] = 0
120
+ }
121
+ this.$emit('set-quantity', {
122
+ item,
123
+ quantity: selectedQnts[item._id]
124
+ })
125
+ },
126
+
127
+ buy () {
128
+ this.alertVariant = 'warning'
129
+ if (this.totalQuantity >= this.min) {
130
+ if (this.max === undefined || this.totalQuantity <= this.max) {
131
+ const items = []
132
+ const composition = this.items.map(item => ({
133
+ _id: item.product_id,
134
+ variation_id: item.variation_id,
135
+ quantity: this.selectedQnts[item._id]
136
+ }))
137
+ this.items.forEach(item => {
138
+ const quantity = this.selectedQnts[item._id]
139
+ if (quantity > 0) {
140
+ const newItem = { ...item, quantity }
141
+ delete newItem.customizations
142
+ if (this.kitProductId) {
143
+ newItem.kit_product = {
144
+ _id: this.kitProductId,
145
+ name: this.kitName,
146
+ pack_quantity: this.totalQuantity,
147
+ price: this.kitPrice,
148
+ composition
149
+ }
150
+ }
151
+ if (this.slug) {
152
+ newItem.slug = this.slug
153
+ }
154
+ items.push(newItem)
155
+ if (this.canAddToCart) {
156
+ ecomCart.addItem(newItem)
157
+ }
158
+ }
159
+ })
160
+ this.$emit('buy', { items })
161
+ } else {
162
+ this.hasMaxAlert = true
163
+ }
164
+ } else {
165
+ this.hasMinAlert = true
166
+ }
167
+ }
168
+ },
169
+
170
+ created () {
171
+ if (this.max < this.items.length) {
172
+ this.items.forEach(item => this.changeQnt(item))
173
+ }
174
+ }
175
+ }