@ecomplus/storefront-components 1.0.0-beta.17 → 1.0.0-beta.171

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/CHANGELOG.md +1224 -131
  2. package/README.md +9 -4
  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 +19 -14
  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 +2 -2
  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 +56 -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 +100 -24
  57. package/src/html/ShippingCalculator.html +53 -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 +192 -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 +21 -2
  73. package/src/js/DiscountApplier.js +132 -42
  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 +14 -2
  80. package/src/js/LoginModal.js +17 -4
  81. package/src/js/PaymentOption.js +28 -1
  82. package/src/js/PointsApplier.js +110 -0
  83. package/src/js/ProductCard.js +97 -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 +157 -33
  91. package/src/js/ShippingLine.js +21 -5
  92. package/src/js/TheAccount.js +87 -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 +66 -28
  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 +30 -1
  121. package/src/scss/ShippingLine.scss +20 -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,40 +1,67 @@
1
1
  import {
2
+ i19addToFavorites,
2
3
  i19buy,
3
4
  i19close,
5
+ i19days,
4
6
  i19discountOf,
5
- i19inStock,
7
+ i19endsIn,
8
+ i19freeShippingFrom,
6
9
  i19loadProductErrorMsg,
10
+ i19offer,
7
11
  i19only,
8
12
  i19outOfStock,
9
13
  i19paymentOptions,
14
+ i19perUnit,
15
+ i19productionDeadline,
16
+ i19removeFromFavorites,
10
17
  i19retry,
11
- i19selectVariation,
12
- i19unavailable
18
+ i19selectVariationMsg,
19
+ i19unavailable,
20
+ i19units,
21
+ i19unitsInStock,
22
+ i19workingDays
13
23
  } from '@ecomplus/i18n'
14
24
 
15
25
  import {
16
26
  i18n,
27
+ randomObjectId as genRandomObjectId,
17
28
  name as getName,
18
29
  inStock as checkInStock,
19
30
  onPromotion as checkOnPromotion,
20
31
  price as getPrice,
32
+ img as getImg,
21
33
  variationsGrids as getVariationsGrids,
22
- specValueByText as getSpecValueByText
34
+ specTextValue as getSpecTextValue,
35
+ specValueByText as getSpecValueByText,
36
+ formatMoney,
37
+ $ecomConfig
23
38
  } from '@ecomplus/utils'
24
39
 
25
40
  import { store, modules } from '@ecomplus/client'
41
+ import EcomSearch from '@ecomplus/search-engine'
26
42
  import ecomCart from '@ecomplus/shopping-cart'
43
+ import { isMobile } from '@ecomplus/storefront-twbs'
44
+ import lozad from 'lozad'
45
+ import sortApps from './helpers/sort-apps'
46
+ import addIdleCallback from './helpers/add-idle-callback'
47
+ import scrollToElement from './helpers/scroll-to-element'
48
+ import { Portal } from '@linusborg/vue-simple-portal'
49
+ import ALink from '../ALink.vue'
27
50
  import AAlert from '../AAlert.vue'
51
+ import APicture from '../APicture.vue'
28
52
  import APrices from '../APrices.vue'
53
+ import AShare from '../AShare.vue'
29
54
  import ProductVariations from '../ProductVariations.vue'
55
+ import KitProductVariations from '../KitProductVariations.vue'
30
56
  import ProductGallery from '../ProductGallery.vue'
57
+ import QuantitySelector from '../QuantitySelector.vue'
31
58
  import ShippingCalculator from '../ShippingCalculator.vue'
32
59
  import PaymentOption from '../PaymentOption.vue'
60
+ import ecomPassport from '@ecomplus/passport-client'
61
+ import { toggleFavorite, checkFavorite } from './helpers/favorite-products'
33
62
 
34
- const storefront = typeof window === 'object' && window.storefront
35
- const getContextBody = () => storefront
36
- ? storefront.context && storefront.context.body
37
- : {}
63
+ const storefront = (typeof window === 'object' && window.storefront) || {}
64
+ const getContextBody = () => (storefront.context && storefront.context.body) || {}
38
65
  const getContextId = () => getContextBody()._id
39
66
 
40
67
  const sanitizeProductBody = body => {
@@ -42,6 +69,8 @@ const sanitizeProductBody = body => {
42
69
  delete product.body_html
43
70
  delete product.body_text
44
71
  delete product.specifications
72
+ delete product.inventory_records
73
+ delete product.price_change_records
45
74
  return product
46
75
  }
47
76
 
@@ -49,10 +78,16 @@ export default {
49
78
  name: 'TheProduct',
50
79
 
51
80
  components: {
81
+ Portal,
82
+ ALink,
52
83
  AAlert,
84
+ APicture,
53
85
  APrices,
86
+ AShare,
87
+ KitProductVariations,
54
88
  ProductVariations,
55
89
  ProductGallery,
90
+ QuantitySelector,
56
91
  ShippingCalculator,
57
92
  PaymentOption
58
93
  },
@@ -70,13 +105,47 @@ export default {
70
105
  default: 'h1'
71
106
  },
72
107
  buyText: String,
108
+ galleryColClassName: {
109
+ type: String,
110
+ default: 'col-12 col-md-6'
111
+ },
112
+ hasPromotionTimer: Boolean,
113
+ hasStickyBuyButton: {
114
+ type: Boolean,
115
+ default: true
116
+ },
117
+ hasQuantitySelector: Boolean,
73
118
  canAddToCart: {
74
119
  type: Boolean,
75
120
  default: true
76
121
  },
122
+ hasBuyButton: {
123
+ type: Boolean,
124
+ default: true
125
+ },
77
126
  lowQuantityToWarn: {
78
127
  type: Number,
79
128
  default: 12
129
+ },
130
+ maxVariationOptionsBtns: Number,
131
+ isQuickview: Boolean,
132
+ paymentAppsSort: {
133
+ type: Array,
134
+ default () {
135
+ return window.ecomPaymentApps || []
136
+ }
137
+ },
138
+ quoteLink: String,
139
+ isSSR: Boolean,
140
+ ecomPassport: {
141
+ type: Object,
142
+ default () {
143
+ return ecomPassport
144
+ }
145
+ },
146
+ accountUrl: {
147
+ type: String,
148
+ default: '/app/#/account/'
80
149
  }
81
150
  },
82
151
 
@@ -86,23 +155,41 @@ export default {
86
155
  fixedPrice: null,
87
156
  selectedVariationId: null,
88
157
  currentGalleyImg: 1,
158
+ isOnCart: false,
159
+ qntToBuy: 1,
160
+ isStickyBuyVisible: false,
161
+ isFavorite: false,
89
162
  hasClickedBuy: false,
90
163
  hasLoadError: false,
91
- paymentGateways: []
164
+ paymentOptions: [],
165
+ customizations: [],
166
+ kitItems: [],
167
+ currentTimer: null
92
168
  }
93
169
  },
94
170
 
95
171
  computed: {
172
+ i19addToFavorites: () => i18n(i19addToFavorites),
96
173
  i19close: () => i18n(i19close),
174
+ i19days: () => i18n(i19days),
97
175
  i19discountOf: () => i18n(i19discountOf),
98
- i19inStock: () => i18n(i19inStock),
176
+ i19endsIn: () => i18n(i19endsIn),
177
+ i19freeShippingFrom: () => i18n(i19freeShippingFrom),
99
178
  i19loadProductErrorMsg: () => i18n(i19loadProductErrorMsg),
179
+ i19offer: () => i18n(i19offer),
100
180
  i19only: () => i18n(i19only),
101
181
  i19outOfStock: () => i18n(i19outOfStock),
102
182
  i19paymentOptions: () => i18n(i19paymentOptions),
183
+ i19perUnit: () => i18n(i19perUnit),
184
+ i19productionDeadline: () => i18n(i19productionDeadline),
185
+ i19removeFromFavorites: () => i18n(i19removeFromFavorites),
103
186
  i19retry: () => i18n(i19retry),
104
- i19selectVariation: () => i18n(i19selectVariation),
187
+ i19selectVariationMsg: () => i18n(i19selectVariationMsg),
105
188
  i19unavailable: () => i18n(i19unavailable),
189
+ i19quoteProduct: () => 'Cotar produto',
190
+ i19units: () => i18n(i19units).toLowerCase(),
191
+ i19unitsInStock: () => i18n(i19unitsInStock),
192
+ i19workingDays: () => i18n(i19workingDays),
106
193
 
107
194
  selectedVariation () {
108
195
  return this.selectedVariationId
@@ -118,6 +205,22 @@ export default {
118
205
  return checkInStock(this.body)
119
206
  },
120
207
 
208
+ isWithoutPrice () {
209
+ return !getPrice(this.body)
210
+ },
211
+
212
+ isVariationInStock () {
213
+ return checkInStock(this.selectedVariationId ? this.selectedVariation : this.body)
214
+ },
215
+
216
+ isLogged () {
217
+ return ecomPassport.checkAuthorization()
218
+ },
219
+
220
+ thumbnail () {
221
+ return getImg(this.body, null, 'small')
222
+ },
223
+
121
224
  productQuantity () {
122
225
  if (this.selectedVariation.quantity) {
123
226
  return this.selectedVariation.quantity
@@ -137,13 +240,55 @@ export default {
137
240
 
138
241
  discount () {
139
242
  const { body } = this
140
- return checkOnPromotion(body)
141
- ? Math.round(((body.base_price - getPrice(body)) * 100) / body.base_price)
243
+ const priceValue = this.fixedPrice || getPrice(body)
244
+ return checkOnPromotion(body) || (body.price > priceValue)
245
+ ? Math.round(((body.base_price - priceValue) * 100) / body.base_price)
142
246
  : 0
143
247
  },
144
248
 
249
+ isOnSale () {
250
+ const { body } = this
251
+ return this.hasPromotionTimer &&
252
+ checkOnPromotion(body) &&
253
+ body.price_effective_date &&
254
+ body.price_effective_date.end &&
255
+ Date.now() < new Date(body.price_effective_date.end).getTime()
256
+ },
257
+
258
+ ghostProductForPrices () {
259
+ const prices = {}
260
+ ;['price', 'base_price'].forEach(field => {
261
+ let price = this.selectedVariation[field] || this.body[field]
262
+ if (price !== undefined) {
263
+ this.customizations.forEach(customization => {
264
+ if (customization.add_to_price) {
265
+ price += this.getAdditionalPrice(customization.add_to_price)
266
+ }
267
+ })
268
+ }
269
+ prices[field] = price
270
+ })
271
+ const ghostProduct = { ...this.body }
272
+ if (this.selectedVariationId) {
273
+ Object.assign(ghostProduct, this.selectedVariation)
274
+ delete ghostProduct.variations
275
+ }
276
+ return {
277
+ ...ghostProduct,
278
+ ...prices
279
+ }
280
+ },
281
+
145
282
  hasVariations () {
146
283
  return this.body.variations && this.body.variations.length
284
+ },
285
+
286
+ isKit () {
287
+ return this.body.kit_composition && this.body.kit_composition.length
288
+ },
289
+
290
+ isKitWithVariations () {
291
+ return this.kitItems.some(item => item.variations && item.variations.length)
147
292
  }
148
293
  },
149
294
 
@@ -152,15 +297,19 @@ export default {
152
297
  getSpecValueByText,
153
298
 
154
299
  setBody (data) {
155
- this.body = data
300
+ this.body = {
301
+ ...data,
302
+ body_html: '',
303
+ body_text: '',
304
+ inventory_records: []
305
+ }
156
306
  this.$emit('update:product', data)
157
307
  },
158
308
 
159
309
  fetchProduct (isRetry = false) {
160
- const { storeId, productId } = this
161
- store({
310
+ const { productId } = this
311
+ return store({
162
312
  url: `/products/${productId}.json`,
163
- storeId,
164
313
  axiosConfig: {
165
314
  timeout: isRetry ? 2500 : 6000
166
315
  }
@@ -168,7 +317,7 @@ export default {
168
317
  .then(({ data }) => {
169
318
  this.setBody(data)
170
319
  if (getContextId() === productId) {
171
- storefront.context.body = data
320
+ storefront.context.body = this.body
172
321
  }
173
322
  this.hasLoadError = false
174
323
  })
@@ -188,6 +337,63 @@ export default {
188
337
  })
189
338
  },
190
339
 
340
+ getAdditionalPrice ({ type, addition }) {
341
+ return type === 'fixed'
342
+ ? addition
343
+ : getPrice(this.body) * addition / 100
344
+ },
345
+
346
+ formatAdditionalPrice (addToPrice) {
347
+ if (addToPrice && addToPrice.addition) {
348
+ return formatMoney(this.getAdditionalPrice(addToPrice))
349
+ }
350
+ return ''
351
+ },
352
+
353
+ setCustomizationOption (customization, text) {
354
+ const index = this.customizations.findIndex(({ _id }) => _id === customization._id)
355
+ if (text) {
356
+ if (index > -1) {
357
+ this.customizations[index].option = { text }
358
+ } else {
359
+ this.customizations.push({
360
+ _id: customization._id,
361
+ label: customization.label,
362
+ add_to_price: customization.add_to_price,
363
+ option: { text }
364
+ })
365
+ }
366
+ } else if (index > -1) {
367
+ this.customizations.splice(index, 1)
368
+ }
369
+ },
370
+
371
+ showVariationPicture (variation) {
372
+ if (variation.picture_id) {
373
+ const pictureIndex = this.body.pictures.findIndex(({ _id }) => {
374
+ return _id === variation.picture_id
375
+ })
376
+ this.currentGalleyImg = pictureIndex + 1
377
+ }
378
+ },
379
+
380
+ handleGridOption ({ gridId, gridIndex, optionText }) {
381
+ if (gridIndex === 0) {
382
+ const variation = this.body.variations.find(variation => {
383
+ return getSpecTextValue(variation, gridId) === optionText
384
+ })
385
+ if (variation) {
386
+ this.showVariationPicture(variation)
387
+ }
388
+ }
389
+ },
390
+
391
+ toggleFavorite () {
392
+ if (this.isLogged) {
393
+ this.isFavorite = toggleFavorite(this.body._id, this.ecomPassport)
394
+ }
395
+ },
396
+
191
397
  buy () {
192
398
  this.hasClickedBuy = true
193
399
  const product = sanitizeProductBody(this.body)
@@ -199,9 +405,19 @@ export default {
199
405
  return
200
406
  }
201
407
  }
202
- this.$emit('buy', { product, variationId })
408
+ const customizations = [...this.customizations]
409
+ this.$emit('buy', { product, variationId, customizations })
203
410
  if (this.canAddToCart) {
204
- ecomCart.addProduct(product, variationId)
411
+ ecomCart.addProduct({ ...product, customizations }, variationId, this.qntToBuy)
412
+ }
413
+ this.isOnCart = true
414
+ },
415
+
416
+ buyOrScroll () {
417
+ if (this.hasVariations || this.isKit) {
418
+ scrollToElement(this.$refs.actions)
419
+ } else {
420
+ this.buy()
205
421
  }
206
422
  }
207
423
  },
@@ -212,18 +428,20 @@ export default {
212
428
  if (this.hasClickedBuy) {
213
429
  this.hasClickedBuy = false
214
430
  }
215
- if (this.selectedVariation.picture_id) {
216
- const pictureIndex = this.body.pictures.findIndex(({ _id }) => {
217
- return _id === this.selectedVariation.picture_id
218
- })
219
- this.currentGalleyImg = pictureIndex + 1
220
- }
431
+ const { pathname } = window.location
432
+ const searchParams = new URLSearchParams(window.location.search)
433
+ searchParams.set('variation_id', variationId)
434
+ window.history.pushState({
435
+ pathname,
436
+ params: searchParams.toString()
437
+ }, '', `${pathname}?${searchParams.toString()}`)
438
+ this.showVariationPicture(this.selectedVariation)
221
439
  }
222
440
  },
223
441
 
224
442
  fixedPrice (price) {
225
- if (price > 0) {
226
- const fetchPaymentOptions = () => {
443
+ if (price > 0 && !this.isQuickview) {
444
+ addIdleCallback(() => {
227
445
  modules({
228
446
  url: '/list_payments.json',
229
447
  method: 'POST',
@@ -234,35 +452,161 @@ export default {
234
452
  items: [{
235
453
  ...sanitizeProductBody(this.body),
236
454
  product_id: this.body._id
237
- }]
455
+ }],
456
+ currency_id: this.body.currency_id || $ecomConfig.get('currency'),
457
+ currency_symbol: this.body.currency_symbol || $ecomConfig.get('currency_symbol')
238
458
  }
239
459
  })
240
460
  .then(({ data }) => {
241
- this.paymentGateways = data.result
242
- .reduce((paymentGateways, { validated, response }) => {
243
- return validated
244
- ? paymentGateways.concat(response.payment_gateways)
245
- : paymentGateways
246
- }, []).sort((a, b) => {
247
- return a.discount && !b.discount ? -1 : 1
461
+ if (Array.isArray(this.paymentAppsSort) && this.paymentAppsSort.length) {
462
+ sortApps(data.result, this.paymentAppsSort)
463
+ }
464
+ this.paymentOptions = data.result
465
+ .reduce((paymentOptions, appResult) => {
466
+ if (appResult.validated) {
467
+ paymentOptions.push({
468
+ app_id: appResult.app_id,
469
+ ...appResult.response
470
+ })
471
+ }
472
+ return paymentOptions
473
+ }, [])
474
+ .sort((a, b) => {
475
+ return a.discount_option && a.discount_option.value &&
476
+ !(b.discount_option && b.discount_option.value)
477
+ ? -1
478
+ : 1
248
479
  })
249
480
  })
250
481
  .catch(console.error)
251
- }
252
- if (typeof window.requestIdleCallback === 'function') {
253
- window.requestIdleCallback(fetchPaymentOptions)
254
- } else {
255
- setTimeout(fetchPaymentOptions, 500)
256
- }
482
+ })
257
483
  }
484
+ },
485
+
486
+ isKit: {
487
+ handler (isKit) {
488
+ if (isKit && !this.kitItems.length) {
489
+ const kitComposition = this.body.kit_composition
490
+ const ecomSearch = new EcomSearch()
491
+ ecomSearch
492
+ .setPageSize(kitComposition.length)
493
+ .setProductIds(kitComposition.map(({ _id }) => _id))
494
+ .fetch(true)
495
+ .then(() => {
496
+ ecomSearch.getItems().forEach(product => {
497
+ const { quantity } = kitComposition.find(({ _id }) => _id === product._id)
498
+ const addKitItem = variationId => {
499
+ const item = ecomCart.parseProduct(product, variationId, quantity)
500
+ if (quantity) {
501
+ item.min_quantity = item.max_quantity = quantity
502
+ } else {
503
+ item.quantity = 0
504
+ }
505
+ this.kitItems.push({
506
+ ...item,
507
+ _id: genRandomObjectId()
508
+ })
509
+ }
510
+ addKitItem()
511
+ })
512
+ })
513
+ .catch(console.error)
514
+ }
515
+ },
516
+ immediate: true
258
517
  }
259
518
  },
260
519
 
261
520
  created () {
521
+ const presetQntToBuy = () => {
522
+ this.qntToBuy = this.body.min_quantity || 1
523
+ }
262
524
  if (this.product) {
263
525
  this.body = this.product
526
+ if (this.isSSR) {
527
+ this.fetchProduct().then(presetQntToBuy)
528
+ } else {
529
+ presetQntToBuy()
530
+ }
264
531
  } else {
265
- this.fetchProduct()
532
+ this.fetchProduct().then(presetQntToBuy)
533
+ }
534
+ this.isFavorite = checkFavorite(this.body._id || this.productId, this.ecomPassport)
535
+ },
536
+
537
+ mounted () {
538
+ if (this.$refs.sticky && !this.isWithoutPrice) {
539
+ let isBodyPaddingSet = false
540
+ const setStickyBuyObserver = (isToVisible = true) => {
541
+ const $anchor = this.$refs[isToVisible ? 'sticky' : 'buy']
542
+ if (!$anchor) {
543
+ return
544
+ }
545
+ const $tmpDiv = document.createElement('div')
546
+ $anchor.parentNode.insertBefore($tmpDiv, $anchor)
547
+ if (isToVisible) {
548
+ $tmpDiv.style.position = 'absolute'
549
+ $tmpDiv.style.bottom = isMobile ? '-1600px' : '-1000px'
550
+ }
551
+ const obs = lozad($tmpDiv, {
552
+ rootMargin: '100px',
553
+ threshold: 0,
554
+ load: () => {
555
+ this.isStickyBuyVisible = isToVisible
556
+ if (isToVisible && !isBodyPaddingSet) {
557
+ this.$nextTick(() => {
558
+ const stickyHeight = this.$refs.sticky.offsetHeight
559
+ document.body.style.paddingBottom = `${(stickyHeight + 4)}px`
560
+ isBodyPaddingSet = true
561
+ })
562
+ }
563
+ $tmpDiv.remove()
564
+ setTimeout(() => {
565
+ const createObserver = function () {
566
+ setStickyBuyObserver(!isToVisible)
567
+ document.removeEventListener('scroll', createObserver)
568
+ }
569
+ document.addEventListener('scroll', createObserver)
570
+ }, 100)
571
+ }
572
+ })
573
+ obs.observe()
574
+ }
575
+ setStickyBuyObserver()
576
+ }
577
+ if (this.isOnSale) {
578
+ const promotionDate = new Date(this.body.price_effective_date.end)
579
+ const now = Date.now()
580
+ if (promotionDate.getTime() > now) {
581
+ let targetDate
582
+ const dayMs = 24 * 60 * 60 * 1000
583
+ const daysBetween = Math.floor((promotionDate.getTime() - now) / dayMs)
584
+ if (daysBetween > 2) {
585
+ targetDate = new Date()
586
+ targetDate.setHours(23, 59, 59, 999)
587
+ } else {
588
+ targetDate = promotionDate
589
+ }
590
+ const formatTime = (number) => number < 10 ? `0${number}` : number
591
+ const getRemainingTime = () => {
592
+ const distance = targetDate.getTime() - Date.now()
593
+ const days = Math.floor(distance / dayMs)
594
+ const hours = Math.floor((distance % dayMs) / (1000 * 60 * 60))
595
+ const minutes = Math.floor((distance % (1000 * 60 * 60)) / (1000 * 60))
596
+ const seconds = Math.floor((distance % (1000 * 60)) / 1000)
597
+ return (days > 0 ? `${formatTime(days)}:` : '') +
598
+ `${formatTime(hours)}:${formatTime(minutes)}:${formatTime(seconds)}`
599
+ }
600
+ this.currentTimer = setInterval(() => {
601
+ this.$refs.timer.innerHTML = getRemainingTime()
602
+ }, 1000)
603
+ }
604
+ }
605
+ },
606
+
607
+ destroyed () {
608
+ if (this.currentTimer) {
609
+ clearInterval(this.currentTimer)
266
610
  }
267
611
  }
268
612
  }
@@ -0,0 +1,7 @@
1
+ export default fn => {
2
+ if (typeof window === 'object' && typeof window.requestIdleCallback === 'function') {
3
+ window.requestIdleCallback(fn)
4
+ } else {
5
+ setTimeout(fn, 500)
6
+ }
7
+ }
@@ -0,0 +1,3 @@
1
+ export default $form => typeof $form.reportValidity === 'function'
2
+ ? $form.reportValidity()
3
+ : $form.checkValidity()
@@ -0,0 +1,24 @@
1
+ import _ecomPassport from '@ecomplus/passport-client'
2
+
3
+ const toggleFavorite = (productId, ecomPassport = _ecomPassport) => {
4
+ const customer = ecomPassport.getCustomer()
5
+ const favorites = customer.favorites || []
6
+ const isFavorite = checkFavorite(productId, ecomPassport)
7
+
8
+ if (!isFavorite) {
9
+ favorites.push(productId)
10
+ } else {
11
+ const favIndex = favorites.indexOf(productId)
12
+ favorites.splice(favIndex, 1)
13
+ }
14
+
15
+ ecomPassport.requestApi('/me.json', 'patch', { favorites })
16
+ return !isFavorite
17
+ }
18
+
19
+ const checkFavorite = (productId, ecomPassport) => {
20
+ const { favorites } = ecomPassport.getCustomer()
21
+ return favorites && favorites.includes(productId)
22
+ }
23
+
24
+ export { toggleFavorite, checkFavorite }
@@ -0,0 +1,10 @@
1
+ export default ($el, top = 0) => {
2
+ while ($el.offsetParent) {
3
+ top += $el.offsetTop
4
+ $el = $el.offsetParent
5
+ }
6
+ return window.scroll({
7
+ top,
8
+ behavior: 'smooth'
9
+ })
10
+ }
@@ -0,0 +1,14 @@
1
+ export default (results, order) => {
2
+ return results.sort((a, b) => {
3
+ if (a.app_id === b.app_id) {
4
+ return 0
5
+ }
6
+ const indexA = order.indexOf(a.app_id)
7
+ const indexB = order.indexOf(b.app_id)
8
+ return indexA > -1
9
+ ? indexB > -1
10
+ ? indexA < indexB ? -1 : 1
11
+ : indexA > -1 ? -1 : 1
12
+ : indexB > -1 ? 1 : 0
13
+ })
14
+ }
@@ -0,0 +1,21 @@
1
+ export default (resource, field) => new Promise(resolve => {
2
+ const storefront = typeof window === 'object' && window.storefront
3
+ if (storefront) {
4
+ const getStorefrontInfo = () => {
5
+ let data = storefront.info && storefront.info[resource]
6
+ if (data) {
7
+ if (field) {
8
+ data = data[field]
9
+ }
10
+ if (data && Object.keys(data).length) {
11
+ resolve(data)
12
+ return true
13
+ }
14
+ }
15
+ return false
16
+ }
17
+ if (!getStorefrontInfo()) {
18
+ storefront.on(`info:${resource}`, getStorefrontInfo)
19
+ }
20
+ }
21
+ })
@@ -9,9 +9,11 @@
9
9
  opacity: .3;
10
10
  height: 100%;
11
11
  min-height: 50px;
12
+ font-size: 0;
12
13
  }
13
14
 
14
15
  img {
16
+ width: auto;
15
17
  max-width: 100%;
16
18
  height: auto;
17
19
  margin: 0 auto;