@lancom/shared 0.0.453 → 0.0.456

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.
@@ -3,6 +3,7 @@ import { _get, _post, _put, _delete, _patch } from './helpers';
3
3
  import adminApi from './admin';
4
4
  import { unminifySimpleProducts } from './utils/simple-products';
5
5
  import { unminifyProduct } from './utils/product';
6
+ import { createCachedFunction } from './../utils/cache';
6
7
 
7
8
  const api = {
8
9
  fetchClientSettings(shop, params) {
@@ -93,12 +94,12 @@ const api = {
93
94
  const url = shop ? `shop/${shop}/products/${alias}/simple-products` : `products/${alias}/simple-products`;
94
95
  return unminifySimpleProducts(await _get(url, params));
95
96
  },
96
- fetchRelatedProducts(shop, alias, params) {
97
+ fetchRelatedProducts: createCachedFunction((shop, alias, params) => {
97
98
  return _get(`shop/${shop}/products/${alias}/related-products`, params);
98
- },
99
- fetchOtherProducts(shop, alias, params) {
99
+ }, 5000),
100
+ fetchOtherProducts: createCachedFunction((shop, alias, params) => {
100
101
  return _get(`shop/${shop}/products/${alias}/other-products`, params);
101
- },
102
+ }, 5000),
102
103
  fetchHelpMessages(shop, group) {
103
104
  return _get(`shop/${shop}/help-messages/${group}`);
104
105
  },
@@ -3,7 +3,8 @@ import debounce from 'lodash.debounce';
3
3
 
4
4
  class Breakpoints {
5
5
  screens = {
6
- mini: 448,
6
+ mini: 449,
7
+ xs: 450,
7
8
  sm: 768,
8
9
  md: 1024,
9
10
  lg: 1280,
@@ -48,7 +49,7 @@ class Breakpoints {
48
49
 
49
50
 
50
51
  isMini(val) {
51
- return val < this.screens.mini;
52
+ return val <= this.screens.mini;
52
53
  }
53
54
 
54
55
  isXs(val) {
@@ -0,0 +1,33 @@
1
+ const createCachedFunction = (fn, ttl = 30000) => {
2
+ const cache = new Map();
3
+ const inFlight = new Map();
4
+
5
+ return (...args) => {
6
+ const cacheKey = JSON.stringify(args);
7
+ const cached = cache.get(cacheKey);
8
+ const now = Date.now();
9
+
10
+ if (cached && now - cached.timestamp < ttl) {
11
+ return Promise.resolve(cached.data);
12
+ }
13
+
14
+ const existing = inFlight.get(cacheKey);
15
+ if (existing) {
16
+ return existing;
17
+ }
18
+
19
+ const promise = fn(...args).then(data => {
20
+ cache.set(cacheKey, { data, timestamp: now });
21
+ inFlight.delete(cacheKey);
22
+ return data;
23
+ }).catch(error => {
24
+ inFlight.delete(cacheKey);
25
+ throw error;
26
+ });
27
+
28
+ inFlight.set(cacheKey, promise);
29
+ return promise;
30
+ };
31
+ };
32
+
33
+ export { createCachedFunction };
@@ -137,17 +137,19 @@ export default class FabricHelper {
137
137
  }
138
138
 
139
139
  setPrintArea(printArea, size, product) {
140
- this.printAreaRect = getPrintAreaByName({
141
- printArea: printArea?.parentPrintArea || printArea?._id,
142
- printSize: printArea?.printSize,
143
- printAreaOffsets: printArea?.printAreaOffsets,
144
- editorWidth: size.width,
145
- editorHeight: size.height
146
- }, product, true);
147
- if (this.background) {
148
- this.background.setBoundingRect(this.printAreaRect);
140
+ if (printArea) {
141
+ this.printAreaRect = getPrintAreaByName({
142
+ printArea: printArea?.parentPrintArea || printArea?._id,
143
+ printSize: printArea?.printSize,
144
+ printAreaOffsets: printArea?.printAreaOffsets,
145
+ editorWidth: size.width,
146
+ editorHeight: size.height
147
+ }, product, true);
148
+ if (this.background) {
149
+ this.background.setBoundingRect(this.printAreaRect);
150
+ }
151
+ this.addBoundingArea();
149
152
  }
150
- this.addBoundingArea();
151
153
  }
152
154
 
153
155
  addBoundingArea() {
@@ -179,6 +179,7 @@ export default {
179
179
  &s--primary &.disabled,
180
180
  &s--secondary &.disabled {
181
181
  pointer-events: none;
182
+ opacity: 0.7;
182
183
  }
183
184
  &s--large &.short,
184
185
  &s--primary &.short,
@@ -43,7 +43,7 @@
43
43
  </div>
44
44
  </div>
45
45
  <div
46
- v-if="fabricHelper && !editModeSelectedLayer"
46
+ v-if="fabricHelper && fabricHelper.printAreaRect && !editModeSelectedLayer"
47
47
  class="EditorWorkspaceSide__placeholder"
48
48
  :class="{
49
49
  tighten: !isZoomed && printAreaIsSmall,
@@ -233,7 +233,7 @@ export default {
233
233
  return layers;
234
234
  },
235
235
  positionPlaceholder() {
236
- const { center, left, top, width, height } = this.fabricHelper.printAreaRect;
236
+ const { center, left, top, width, height } = this.fabricHelper.printAreaRect || {};
237
237
  const ratio = this.calcWorkspaceSize() / this.editorSize.width;
238
238
  if (this.printAreaLayers.length > 0) {
239
239
  return {
@@ -451,7 +451,7 @@ export default {
451
451
  this.removeTemplateLayer(this.selectedLayer);
452
452
  },
453
453
  onOutsideClick() {
454
- if (this.addedFromCanvas && this.selectedLayer?.type === 'text' && !this.selectedLayer.copy) {
454
+ if (this.addedFromCanvas && this.selectedLayer?.type === 'text' && !this.selectedLayer.copy) {
455
455
  this.removeTemplateLayer(this.selectedLayer);
456
456
  }
457
457
  this.addedFromCanvas = false;
@@ -8,6 +8,13 @@
8
8
  background: white;
9
9
  left: 0;
10
10
  right: 0;
11
+ // min-height: 64px;
12
+ box-shadow: none !important;
13
+ border-radius: 0;
14
+ border: none;
15
+ .EditorPricing__clearance-messages {
16
+ display: none;
17
+ }
11
18
  }
12
19
  &__menu {
13
20
  position: fixed;
@@ -117,4 +124,4 @@
117
124
  overflow-y: auto;
118
125
  overflow-x: hidden;
119
126
  }
120
- }
127
+ }
@@ -79,7 +79,7 @@
79
79
  style="margin-top: 40px;" />
80
80
  <editor-layers
81
81
  v-show="currentTab === 'layers'"
82
- :hasPricing="false" />
82
+ :has-pricing="false" />
83
83
  </div>
84
84
  </div>
85
85
  </div>
@@ -101,6 +101,12 @@ const { mapMutations, mapGetters } = createNamespacedHelpers('product');
101
101
  export default {
102
102
  name: 'MobileEditorProductDetails',
103
103
  mixins: [addToCartMixin],
104
+ props: {
105
+ isEditMode: {
106
+ type: Boolean,
107
+ default: true
108
+ }
109
+ },
104
110
  components: {
105
111
  EditorWorkspace,
106
112
  EditorLayers,
@@ -156,14 +162,14 @@ export default {
156
162
 
157
163
  const prevTab = this.currentTab;
158
164
 
159
- if (this.isOpen) {
165
+ if (this.isOpen && this.isEditMode) {
160
166
  this.hide(true);
161
167
  }
162
168
 
163
169
  this.$nextTick(() => {
164
170
  this.isOpen = true;
165
171
 
166
- if(tab === 'layers') {
172
+ if (!this.isEditMode && tab === 'layers') {
167
173
  this.setEditModeSelectedLayer(false);
168
174
  }
169
175
 
@@ -0,0 +1,35 @@
1
+ .ClearanceProducts {
2
+ &__head {
3
+ display: flex;
4
+ align-items: center;
5
+ justify-content: space-between;
6
+ margin: 4px 0 16px;
7
+ gap: 16px;
8
+ }
9
+
10
+ &__title {
11
+ color: #0A0A0A;
12
+ font-size: 14px;
13
+ font-style: normal;
14
+ font-weight: 600;
15
+ line-height: 20px;
16
+ }
17
+
18
+ &__all {
19
+ color: #194BB3;
20
+ text-align: center;
21
+ font-size: 12px;
22
+ font-style: normal;
23
+ font-weight: 600;
24
+ line-height: 16px;
25
+ text-decoration: underline;
26
+ &:hover {
27
+ text-decoration: none;
28
+ }
29
+ }
30
+ &__list {
31
+ display: grid;
32
+ gap: 14px;
33
+ }
34
+ }
35
+
@@ -0,0 +1,85 @@
1
+ <template>
2
+ <section
3
+ class="ClearanceProducts__wrapper">
4
+ <div class="ClearanceProducts__head">
5
+ <h2 class="ClearanceProducts__title">
6
+ Clearance Product
7
+ </h2>
8
+ <a
9
+ href="/c/clearance"
10
+ class="ClearanceProducts__all">
11
+ View all clearance products
12
+ </a>
13
+ </div>
14
+ <div class="ClearanceProducts__list">
15
+ <clearance-product
16
+ v-for="item in products"
17
+ :key="item._id"
18
+ :product="item"
19
+ :to-editor="toEditor"
20
+ class="ClearanceProducts__product" />
21
+ </div>
22
+ </section>
23
+ </template>
24
+
25
+ <script>
26
+ import { mapGetters } from 'vuex';
27
+ import api from '@lancom/shared/assets/js/api';
28
+ import ClearanceProduct from './clearance_product/clearance-product';
29
+
30
+ export default {
31
+ name: 'ClearanceProducts',
32
+ components: {
33
+ ClearanceProduct
34
+ },
35
+ props: {
36
+ product: {
37
+ type: Object,
38
+ required: true
39
+ },
40
+ toEditor: {
41
+ type: Boolean,
42
+ default: false
43
+ }
44
+ },
45
+ data() {
46
+ return {
47
+ products: [],
48
+ loading: false
49
+ };
50
+ },
51
+ computed: {
52
+ ...mapGetters(['shop', 'country', 'currency'])
53
+ },
54
+ mounted() {
55
+ this.loadProducts();
56
+ },
57
+ methods: {
58
+ isClearanceColor(color) {
59
+ return color?.pricing?.some(c => c.clearance);
60
+ },
61
+ hasClearanceColors(product) {
62
+ return (product.colors || []).some(color => this.isClearanceColor(color));
63
+ },
64
+ async loadProducts() {
65
+ try {
66
+ this.loading = true;
67
+ const { products } = await api.fetchRelatedProducts(this.shop._id, this.product.alias, {
68
+ needShuffle: false,
69
+ country: this.country?._id,
70
+ currency: this.currency?._id
71
+ });
72
+ const clearanceProducts = products.filter(p => p.isClearance || this.hasClearanceColors(p));
73
+ this.products = clearanceProducts.slice(0, 1);
74
+ } catch (e) {
75
+ } finally {
76
+ this.loading = false;
77
+ }
78
+ }
79
+ }
80
+ };
81
+ </script>
82
+
83
+ <style lang="scss" scoped>
84
+ @import 'clearance-products';
85
+ </style>
@@ -0,0 +1,82 @@
1
+ .ClearanceProduct {
2
+ &__wrapper {
3
+ display: flex;
4
+ gap: 12px;
5
+ padding: 12px;
6
+ border-radius: 8px;
7
+ border: 1px solid #E5E5E5;
8
+ background: #FFF;
9
+ }
10
+
11
+ &__img {
12
+ width: 64px;
13
+ height: 64px;
14
+ object-fit: contain;
15
+ border-radius: 4px;
16
+ }
17
+
18
+ &__body {
19
+ display: flex;
20
+ flex-direction: column;
21
+ gap: 4px;
22
+ flex: 1;
23
+ }
24
+
25
+ &__row {
26
+ display: flex;
27
+ gap: 8px;
28
+ }
29
+
30
+ &__title {
31
+ color: #0A0A0A;
32
+ font-size: 14px;
33
+ font-style: normal;
34
+ font-weight: 600;
35
+ line-height: 20px;
36
+ text-decoration: none;
37
+ &:hover {
38
+ text-decoration: underline;
39
+ }
40
+ }
41
+
42
+ &__sku {
43
+ color: #666;
44
+ font-size: 12px;
45
+ font-style: normal;
46
+ font-weight: 400;
47
+ line-height: 16px;
48
+ }
49
+
50
+ &__clearance-label {
51
+ color: #D97706;
52
+ font-size: 12px;
53
+ font-style: normal;
54
+ font-weight: 600;
55
+ line-height: 16px;
56
+ }
57
+
58
+ &__price {
59
+ display: flex;
60
+ align-items: center;
61
+ gap: 8px;
62
+ margin-top: auto;
63
+ }
64
+
65
+ &__price-current {
66
+ color: #0A0A0A;
67
+ font-size: 14px;
68
+ font-style: normal;
69
+ font-weight: 600;
70
+ line-height: 20px;
71
+ }
72
+
73
+ &__price-old {
74
+ color: #999;
75
+ font-size: 12px;
76
+ font-style: normal;
77
+ font-weight: 400;
78
+ line-height: 16px;
79
+ text-decoration: line-through;
80
+ }
81
+ }
82
+
@@ -0,0 +1,56 @@
1
+ <template>
2
+ <div class="ClearanceProduct__wrapper">
3
+ <a
4
+ v-if="productСover"
5
+ :href="productLink">
6
+ <img
7
+ :src="productСover"
8
+ :alt="product.name"
9
+ class="ClearanceProduct__img" />
10
+ </a>
11
+ <div class="ClearanceProduct__body">
12
+ <div class="ClearanceProduct__row">
13
+ <a
14
+ :href="productLink"
15
+ class="ClearanceProduct__title">
16
+ {{ product.name }}
17
+ </a>
18
+ </div>
19
+ <div
20
+ v-if="product.SKU"
21
+ class="ClearanceProduct__sku">
22
+ sku: {{ product.SKU }}
23
+ </div>
24
+ <div class="ClearanceProduct__price">
25
+ <span class="ClearanceProduct__price-current">
26
+ <price
27
+ :price="currentColorMaxPrice"
28
+ :with-gst="priceIncludeGST" />
29
+ </span>
30
+ </div>
31
+ </div>
32
+ </div>
33
+ </template>
34
+
35
+ <script>
36
+ import productPreview from '@lancom/shared/mixins/product-preview';
37
+ import Price from '@lancom/shared/components/common/price';
38
+
39
+ export default {
40
+ name: 'ClearanceProduct',
41
+ components: {
42
+ Price
43
+ },
44
+ mixins: [productPreview],
45
+ props: {
46
+ toEditor: {
47
+ type: Boolean,
48
+ default: false
49
+ }
50
+ }
51
+ };
52
+ </script>
53
+
54
+ <style lang="scss" scoped>
55
+ @import 'clearance-product';
56
+ </style>
@@ -4,15 +4,19 @@
4
4
  <h2 class="OtherProducts__title">
5
5
  Other Products
6
6
  </h2>
7
- <a href="#" class="OtherProducts__all">
7
+ <a
8
+ v-if="categoryLink"
9
+ :href="categoryLink"
10
+ class="OtherProducts__all">
8
11
  View all products in this category
9
12
  </a>
10
13
  </div>
11
14
  <div class="OtherProducts__list">
12
15
  <other-product
13
- v-for="product in products"
14
- :key="product._id"
15
- :product="product"
16
+ v-for="item in products"
17
+ :key="item._id"
18
+ :product="item"
19
+ :to-editor="toEditor"
16
20
  class="OtherProducts__product" />
17
21
  </div>
18
22
  </section>
@@ -35,7 +39,7 @@ export default {
35
39
  },
36
40
  toEditor: {
37
41
  type: Boolean,
38
- default: true
42
+ default: false
39
43
  },
40
44
  limit: {
41
45
  type: Number,
@@ -49,21 +53,44 @@ export default {
49
53
  };
50
54
  },
51
55
  computed: {
52
- ...mapGetters(['shop', 'country', 'currency'])
56
+ ...mapGetters(['shop', 'country', 'currency']),
57
+ categoryLink() {
58
+ const category = this.product.category;
59
+ if (!category) {
60
+ return null;
61
+ }
62
+ const categories = [];
63
+ let current = category;
64
+ while (current) {
65
+ categories.unshift(current.alias);
66
+ current = current.parent;
67
+ }
68
+ return categories.length ? `/c/${categories.join('/')}` : null;
69
+ }
53
70
  },
54
71
  mounted() {
55
72
  this.loadProducts();
56
73
  },
57
74
  methods: {
75
+ isClearanceColor(color) {
76
+ return color?.pricing?.some(c => c.clearance);
77
+ },
78
+ hasClearanceColors(product) {
79
+ return (product.colors || []).some(color => this.isClearanceColor(color));
80
+ },
58
81
  async loadProducts() {
59
82
  try {
60
83
  this.loading = true;
61
- const condition = {
84
+ const { products } = await api.fetchRelatedProducts(this.shop._id, this.product.alias, {
85
+ needShuffle: false,
62
86
  country: this.country?._id,
63
- currency: this.currency?._id,
64
- limit: this.limit
65
- };
66
- this.products = (await api.fetchOtherProducts(this.shop._id, this.product.alias, condition)).products;
87
+ currency: this.currency?._id
88
+ });
89
+ this.products = products.sort((a, b) => {
90
+ const aIsClearance = a.isClearance || this.hasClearanceColors(a);
91
+ const bIsClearance = b.isClearance || this.hasClearanceColors(b);
92
+ return aIsClearance === bIsClearance ? 0 : aIsClearance ? -1 : 1;
93
+ });
67
94
  } catch (e) {
68
95
  } finally {
69
96
  this.loading = false;
@@ -21,28 +21,10 @@
21
21
  class="OtherProduct__sku">
22
22
  sku: {{ product.SKU }}
23
23
  </div>
24
- <div
25
- v-if="product.isClearance"
26
- class="OtherProduct__price">
27
- <span
28
- v-if="product.oldPrice"
29
- class="OtherProduct__price-old">
30
- <price
31
- :price="minPrice"
32
- :with-gst="priceIncludeGST" />
33
- </span>
34
- <span class="OtherProduct__price-current">
35
- <price
36
- :price="product.minPrice"
37
- :with-gst="priceIncludeGST" />
38
- </span>
39
- </div>
40
- <div
41
- v-else
42
- class="OtherProduct__price">
24
+ <div class="OtherProduct__price">
43
25
  <span class="OtherProduct__price-current">
44
26
  <price
45
- :price="minPrice"
27
+ :price="currentColorMaxPrice"
46
28
  :with-gst="priceIncludeGST" />
47
29
  </span>
48
30
  </div>
@@ -59,7 +41,13 @@ export default {
59
41
  components: {
60
42
  Price
61
43
  },
62
- mixins: [productPreview]
44
+ mixins: [productPreview],
45
+ props: {
46
+ toEditor: {
47
+ type: Boolean,
48
+ default: false
49
+ }
50
+ }
63
51
  };
64
52
  </script>
65
53
 
@@ -1,3 +1,5 @@
1
+ @import "@/assets/scss/variables";
2
+
1
3
  .ProductPricingTiers {
2
4
  &__title {
3
5
  color: #0A0A0A;
@@ -21,6 +23,9 @@
21
23
  box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.10), 0 2px 4px -2px rgba(0, 0, 0, 0.10);
22
24
  cursor: pointer;
23
25
  transition: all 0.3s ease;
26
+ @media (max-width: $bp-extra-small-max) {
27
+ width: 132px;
28
+ }
24
29
  &:hover {
25
30
  transform: translateY(-3px);
26
31
  box-shadow: 0 4px 8px -1px rgba(0, 0, 0, 0.30), 0 2px 4px -2px rgba(0, 0, 0, 0.30);
@@ -35,11 +40,20 @@
35
40
  font-weight: 600;
36
41
  line-height: 28px;
37
42
  text-align: center;
43
+ @media (max-width: $bp-extra-small-max) {
44
+ padding: 8px 0;
45
+ font-size: 12px;
46
+ line-height: 20px;
47
+ }
38
48
  span {
39
49
  color: #194BB3;
40
50
  font-size: 18px;
41
51
  font-weight: 700;
42
52
  line-height: 28px;
53
+ @media (max-width: $bp-extra-small-max) {
54
+ font-size: 14px;
55
+ line-height: 20px;
56
+ }
43
57
  }
44
58
  }
45
59
  &-price {
@@ -50,11 +64,21 @@
50
64
  margin-top: 6px;
51
65
  padding: 4px;
52
66
  text-align: center;
67
+ @media (max-width: $bp-extra-small-max) {
68
+ font-size: 10px;
69
+ line-height: 16px;
70
+ margin-top: 4px;
71
+ padding: 2px;
72
+ }
53
73
  span {
54
74
  color: #194BB3;
55
75
  font-size: 12px;
56
76
  font-weight: 700;
57
77
  line-height: 20px;
78
+ @media (max-width: $bp-extra-small-max) {
79
+ font-size: 10px;
80
+ line-height: 16px;
81
+ }
58
82
  }
59
83
  }
60
84
  }
@@ -37,11 +37,15 @@ export default {
37
37
  };
38
38
  },
39
39
  async fetch() {
40
- const { products } = await api.fetchRelatedProducts(this.shop._id, this.product.alias, { needShuffle: true });
40
+ const { products } = await api.fetchRelatedProducts(this.shop._id, this.product.alias, {
41
+ needShuffle: false,
42
+ country: this.country?._id,
43
+ currency: this.currency?._id
44
+ });
41
45
  this.products = products.splice(0, 6);
42
46
  },
43
47
  computed: {
44
- ...mapGetters(['shop']),
48
+ ...mapGetters(['shop', 'country', 'currency']),
45
49
  hasProducts() {
46
50
  return this.products.length > 0;
47
51
  }
@@ -34,6 +34,13 @@
34
34
  z-index: 200;
35
35
  transition: opacity .16s cubic-bezier(0.23, 1, 0.32, 1);
36
36
  }
37
+ &__close {
38
+ position: absolute;
39
+ right: 10px;
40
+ top: 40px;
41
+ font-weight: bold;
42
+ font-size: 20px;
43
+ }
37
44
  }
38
45
 
39
46
  .TheAside__container,
@@ -2,6 +2,7 @@ import { mapGetters } from 'vuex';
2
2
  import { getColorBackgroundStyle, getProductMediumCover, getBgStyle, getProductHoverCover } from '@lancom/shared/assets/js/utils/colors';
3
3
  import { staticLink } from '@lancom/shared/assets/js/utils/filters';
4
4
  import { generateProductLink } from '@lancom/shared/assets/js/utils/product';
5
+ import { sortSizes } from '@lancom/shared/assets/js/utils/sizes';
5
6
 
6
7
  const loadHolder = {
7
8
  canLoadImages: true,
@@ -129,6 +130,68 @@ const productPreview = {
129
130
  case 'child':
130
131
  return 'icon-baby';
131
132
  }
133
+ },
134
+
135
+ colors() {
136
+ const colors = this.product.colors?.filter(c => !!c) || [];
137
+ const sortedColors = colors.sort((a, b) => {
138
+ const aIsClearance = this.isClearanceColor(a);
139
+ const bIsClearance = this.isClearanceColor(b);
140
+ return aIsClearance === bIsClearance ? 0 : aIsClearance ? -1 : 1;
141
+ });
142
+ return this.full ? sortedColors : sortedColors.slice(0, this.maxVisibleColors);
143
+ },
144
+ hasSizes() {
145
+ return this.sizes.length > 0;
146
+ },
147
+ sizes() {
148
+ const sizes = sortSizes(this.product.sizes || []);
149
+ return this.full ? sizes : sizes.slice(0, this.maxVisibleSizes);
150
+ },
151
+ hiddenSizesCount() {
152
+ const sizes = this.product.sizes || [];
153
+ return sizes.length - this.sizes.length;
154
+ },
155
+ isVisibleShowMore() {
156
+ const colors = this.product.colors || [];
157
+ return (colors.length > this.maxVisibleColors) && !this.full;
158
+ },
159
+ mainColor() {
160
+ return this.currentColor || this.colorWithMaxPrice;
161
+ },
162
+ colorWithMaxPrice() {
163
+ const colorWithMaxPrice = this.product.colors?.reduce((max, current) => {
164
+ const currentMaxPrice = current?.maxPrice || 0;
165
+ const maxPrice = max?.maxPrice || 0;
166
+ return (!max || (current && currentMaxPrice > maxPrice)) ? current : max;
167
+ }, null);
168
+ return colorWithMaxPrice;
169
+ },
170
+ visibleTiers() {
171
+ const pricing = (this.full ? this.mainColor?.pricing : this.mainColor?.pricing?.slice(0, this.maxVisibleTiers)) || [];
172
+ const printsPrice = this.product.minPrintsPrice || 0;
173
+ return pricing.map(p => ({ ...p, price: p.price + printsPrice }));
174
+ },
175
+ currentColorMaxPrice() {
176
+ const printsPrice = this.product.maxPrintsPrice || 0;
177
+ const productPrice = this.mainColor?.maxPrice || 0;
178
+ return productPrice + printsPrice;
179
+ },
180
+ currentColorMaxPriceWithoutClearance() {
181
+ const printsPrice = this.product.maxPrintsPrice || 0;
182
+ const productPrice = this.mainColor?.maxPriceWithoutClearance || 0;
183
+ return productPrice + printsPrice;
184
+ },
185
+ minClearanceColorPrice() {
186
+ const clearanceColors = this.product.colors?.filter(c => this.isClearanceColor(c)) || [];
187
+ const minClearanceColor = clearanceColors.reduce((min, current) => {
188
+ const currentMinPrice = current?.minPrice || 0;
189
+ const minPrice = min?.minPrice || 0;
190
+ return (!min || (current && currentMinPrice < minPrice)) ? current : min;
191
+ }, null);
192
+ const printsPrice = this.product.minPrintsPrice || 0;
193
+ const productPrice = minClearanceColor?.minPrice || 0;
194
+ return minClearanceColor ? productPrice + printsPrice : 0;
132
195
  }
133
196
  },
134
197
  mounted() {
@@ -125,8 +125,8 @@ export default (IS_PRODUCT_PRESET_PRINT_PRICING, isEditor = false) => ({
125
125
  return (thumbProductImages.length > 0 ? thumbProductImages : this.images).slice(0, 6);
126
126
  },
127
127
  mainProductImageSrc() {
128
- const image = (this.isEditor && this.mainProductImage.extralarge) || this.mainProductImage.large;
129
- return this.mainProductImage && staticLink(image);
128
+ const image = (this.isEditor && this.mainProductImage?.extralarge) || this.mainProductImage?.large;
129
+ return image && staticLink(image);
130
130
  },
131
131
  mainProductImageStyles() {
132
132
  return this.mainProductImageSrc ? { 'background-image': `url(${this.mainProductImageSrc});` } : {};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lancom/shared",
3
- "version": "0.0.453",
3
+ "version": "0.0.456",
4
4
  "description": "lancom common scripts",
5
5
  "author": "e.tokovenko <e.tokovenko@gmail.com>",
6
6
  "repository": {