@lancom/shared 0.0.452 → 0.0.454

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.
@@ -18,7 +18,7 @@ if (process.client) {
18
18
  axiosApiInstance.interceptors.response.use(
19
19
  response => response,
20
20
  error => {
21
- if (error.response.status === 401) {
21
+ if (error.response?.status === 401) {
22
22
  removeAuthToken();
23
23
  const currentPath = window.location.pathname;
24
24
  if (currentPath && currentPath !== '/') {
@@ -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
  },
@@ -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 };
@@ -49,20 +49,21 @@ export function setupCustomControls(fabricHelper) {
49
49
  ['tl', 'bl', 'br'].forEach(key => {
50
50
  fabric.Object.prototype.controls[key].sizeX = 7;
51
51
  fabric.Object.prototype.controls[key].sizeY = 7;
52
+ fabric.Object.prototype.controls[key].actionName = 'scaling';
52
53
  });
53
54
 
54
55
  fabric.Object.prototype.controls.tr = new fabric.Control({
55
56
  x: 0.5,
56
57
  y: -0.5,
57
58
  cursorStyle: 'pointer',
58
- mouseUpHandler: function(eventData, transform) {
59
+ mouseUpHandler(eventData, transform) {
59
60
  const target = transform.target;
60
61
  fabricHelper.dispatch('removeLayer', target.layer);
61
62
  fabricHelper.editor.remove(target);
62
63
  fabricHelper.editor.renderAll();
63
64
  return true;
64
65
  },
65
- render: function(ctx, left, top, styleOverride, fabricObject) {
66
+ render(ctx, left, top, styleOverride, fabricObject) {
66
67
  const size = 10;
67
68
  ctx.save();
68
69
  ctx.translate(left, top);
@@ -91,7 +92,7 @@ export function setupCustomControls(fabricHelper) {
91
92
  cursorStyle: 'pointer',
92
93
  actionHandler: fabric.controlsUtils.rotationWithSnapping,
93
94
  actionName: 'rotate',
94
- render: function(ctx, left, top, styleOverride, fabricObject) {
95
+ render(ctx, left, top, styleOverride, fabricObject) {
95
96
  if (rotateImg) {
96
97
  renderIcon(ctx, left, top, styleOverride, fabricObject, rotateImg);
97
98
  }
@@ -106,7 +107,7 @@ export function setupCustomControls(fabricHelper) {
106
107
  offsetY: -30,
107
108
  offsetX: 30,
108
109
  cursorStyle: 'pointer',
109
- mouseUpHandler: function(eventData, transform) {
110
+ mouseUpHandler(eventData, transform) {
110
111
  const target = transform.target;
111
112
  const helper = fabricHelper;
112
113
 
@@ -134,7 +135,7 @@ export function setupCustomControls(fabricHelper) {
134
135
 
135
136
  return true;
136
137
  },
137
- render: function(ctx, left, top, styleOverride, fabricObject) {
138
+ render(ctx, left, top, styleOverride, fabricObject) {
138
139
  if (centerImg) {
139
140
  renderIcon(ctx, left, top, styleOverride, fabricObject, centerImg);
140
141
  } else {
@@ -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() {
@@ -275,13 +277,7 @@ export default class FabricHelper {
275
277
  object.on('rotating', () => {
276
278
  this.dispatch('setField', { field: 'angle', value: parseInt(object.angle) });
277
279
  });
278
- object.on('moved', () => {
279
- this.dispatch('setField', { field: 'top', value: Math.round(object.top) });
280
- this.dispatch('setField', { field: 'left', value: Math.round(object.left) });
281
- });
282
- object.on('moving', () => this.checkBoundingIntersection(object));
283
280
  object.on('scaling', () => {
284
- this.checkBoundingIntersection(object);
285
281
  if (object.type === 'art') {
286
282
  const measure = this.getEditorDPI();
287
283
  const originalRatio = object.originalSize.width / object.width;
@@ -290,17 +286,16 @@ export default class FabricHelper {
290
286
  }
291
287
  });
292
288
 
293
- object.on('scaled', () => {
294
- this.dispatch('setField', { field: 'boundingRect', value: object.getBoundingRect() });
295
- });
296
- object.on('moved', () => {
289
+ object.on('modified', () => {
290
+ this.dispatch('setField', { field: 'top', value: Math.round(object.top) });
291
+ this.dispatch('setField', { field: 'left', value: Math.round(object.left) });
297
292
  this.dispatch('setField', { field: 'boundingRect', value: object.getBoundingRect() });
298
293
  });
299
294
  /*
300
295
  ** TEXT OBJECT EVENTS
301
296
  */
302
297
  if (type === 'text') {
303
- object.on('scaled', target => {
298
+ object.on('modified', target => {
304
299
  const fontSize = Math.round(object.fontSize * object.scaleX);
305
300
  // const strokeWidth = Math.round(object.strokeWidth * object.scaleX);
306
301
  this.dispatch('setField', { field: 'fontSize', value: fontSize });
@@ -331,7 +326,7 @@ export default class FabricHelper {
331
326
  ** ART OBJECT EVENTS
332
327
  */
333
328
  if (type === 'art') {
334
- object.on('scaled', () => {
329
+ object.on('modified', () => {
335
330
  this.dispatch('setField', { field: 'scaleX', value: object.scaleX });
336
331
  this.dispatch('setField', { field: 'scaleY', value: object.scaleY });
337
332
  });
@@ -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 {
@@ -386,11 +386,12 @@ export default {
386
386
  });
387
387
  this.fabricHelper.on('removeLayer', (layer) => {
388
388
  setTimeout(() => {
389
- if (!this.editModeSelectedLayer) {
390
- this.removeTemplateLayer(layer);
391
- } else if (!layer.copy) {
392
- this.fabricHelper.dispatch('setField', { field: 'isEditMode', value: false });;
393
- }
389
+ this.removeTemplateLayer(layer);
390
+ // if (!this.editModeSelectedLayer) {
391
+ // this.removeTemplateLayer(layer);
392
+ // } else if (!layer.copy) {
393
+ // this.fabricHelper.dispatch('setField', { field: 'isEditMode', value: false });;
394
+ // }
394
395
  }, 100);
395
396
  });
396
397
  this.fabricHelper.on('outOfPrintArea', this.setOffsetWarningVisibility);
@@ -462,7 +463,7 @@ export default {
462
463
  async createTextLayer() {
463
464
  this.addedFromCanvas = true;
464
465
  window.scrollTo(0, 0);
465
- const layer = await this.createLayer({ type: 'text', isEditMode: this.isEditMode });
466
+ const layer = this.printAreaLayers?.length > 0 ? this.printAreaLayers[0] : (await this.createLayer({ type: 'text', isEditMode: this.isEditMode }));
466
467
  this.visibleWireframe = true;
467
468
  if (!this.isEditMode) {
468
469
  setTimeout(() => {
@@ -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
 
@@ -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
  }
@@ -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,7 +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
- return this.mainProductImage && staticLink(this.mainProductImage.extralarge || this.mainProductImage.large);
128
+ const image = (this.isEditor && this.mainProductImage?.extralarge) || this.mainProductImage?.large;
129
+ return image && staticLink(image);
129
130
  },
130
131
  mainProductImageStyles() {
131
132
  return this.mainProductImageSrc ? { 'background-image': `url(${this.mainProductImageSrc});` } : {};
@@ -342,7 +343,7 @@ export default (IS_PRODUCT_PRESET_PRINT_PRICING, isEditor = false) => ({
342
343
  if (brand) {
343
344
  productSchema.brand = {
344
345
  '@type': 'Brand',
345
- logo: staticLink(brand.logo),
346
+ logo: staticLink(brand.smallLogo || brand.logo),
346
347
  name: brand.name
347
348
  };
348
349
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lancom/shared",
3
- "version": "0.0.452",
3
+ "version": "0.0.454",
4
4
  "description": "lancom common scripts",
5
5
  "author": "e.tokovenko <e.tokovenko@gmail.com>",
6
6
  "repository": {
@@ -10,6 +10,7 @@
10
10
  "license": "ISC",
11
11
  "homepage": "https://bitbucket.org/simpletee/lancom-shared#readme",
12
12
  "dependencies": {
13
+ "axios": "0.21.4",
13
14
  "basic-auth": "^2.0.1",
14
15
  "lodash.get": "^4.4.2",
15
16
  "nuxt-client-init-module": "^0.3.0",