@lancom/shared 0.0.352 → 0.0.354

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 (34) hide show
  1. package/assets/js/utils/fonts-helper.js +2 -1
  2. package/assets/js/utils/product.js +10 -2
  3. package/components/checkout/cart/cart_entities_group/cart-entities-group.vue +1 -1
  4. package/components/checkout/cart/cart_entities_group/cart_entities_group_prints/cart_entities_group_print/cart-entities-group-print.vue +1 -1
  5. package/components/checkout/cart/cart_entities_group_table/cart_entities_group_tr/cart-entities-group-tr.vue +1 -1
  6. package/components/checkout/cart/cart_entity/cart_entity_prints/cart_entity_print/cart-entity-print.vue +1 -1
  7. package/components/common/client_settings_stock_country/client-settings-stock-country.vue +5 -2
  8. package/components/editor/editor_product_details/editor-product-details.vue +8 -2
  9. package/components/editor/editor_workspace/editor-workspace.scss +5 -1
  10. package/components/editor/editor_workspace/editor-workspace.vue +16 -6
  11. package/components/editor/editor_workspace/editor_workspace_side/editor-workspace-side.scss +8 -2
  12. package/components/editor/editor_workspace/editor_workspace_side/editor-workspace-side.vue +45 -14
  13. package/components/order/order_view/order_product_prints/order_product_print/order-product-print.vue +1 -1
  14. package/components/product/gallery/gallery.vue +9 -2
  15. package/components/product/product_colors_selector/product-colors-selector.vue +3 -2
  16. package/components/product/wizard/wizard_print_layers/wizard_print_layer/wizard-print-layer.vue +1 -1
  17. package/components/product/wizard/wizard_print_text_or_logo/wizard-print-text-or-logo.vue +1 -1
  18. package/components/product/wizard/wizard_print_type/wizard_print_area_print_type/wizard-print-area-print-type.vue +1 -1
  19. package/components/products/products_aside/products-aside.vue +15 -1
  20. package/components/products/products_link/products-link.vue +8 -0
  21. package/components/products/products_minimum_qty/products-minimum-qty.scss +39 -0
  22. package/components/products/products_minimum_qty/products-minimum-qty.vue +88 -0
  23. package/components/products/products_production_time/products-production-time.scss +39 -0
  24. package/components/products/products_production_time/products-production-time.vue +85 -0
  25. package/components/quotes/quote_view/quote_product_prints/quote_product_print/quote-product-print.vue +1 -1
  26. package/feeds/google-shopping.js +6 -4
  27. package/layouts/products.vue +0 -1
  28. package/middleware/page-info.js +16 -3
  29. package/mixins/meta-info.js +1 -1
  30. package/mixins/print-layer.js +2 -2
  31. package/mixins/product-view.js +35 -15
  32. package/package.json +1 -1
  33. package/store/cart.js +0 -2
  34. package/store/product.js +26 -2
@@ -65,7 +65,8 @@ export const getFormattedFont = alias => {
65
65
  export const getFontPath = alias => {
66
66
  const { PROD_CDN_URL, DEV_CDN_URL, CDN_DOMAIN } = Vue.$env;
67
67
  const { path } = getFont(alias) || {};
68
- return path && staticLink(`${CDN_DOMAIN || PROD_CDN_URL || DEV_CDN_URL || ''}${path}`);
68
+ const domain = process.env.IS_LOCAL ? '' : (CDN_DOMAIN || PROD_CDN_URL || DEV_CDN_URL || '');
69
+ return path && staticLink(`${domain}${path}`);
69
70
  };
70
71
 
71
72
  export const loadFont = alias => new Promise((resolve, reject) => {
@@ -110,10 +110,12 @@ function getCategoryPathFromRoute(routeParams) {
110
110
  }
111
111
 
112
112
  export function generateProductsLink($route, data, skipParams = false) {
113
- let { type, category, brand, text, sort } = data;
113
+ let { type, category, brand, text, sort, minimumQty, productionTime } = data;
114
114
  category = (category || category === null) ? generateCategoryPath(category) : getCategoryPathFromRoute($route?.params);
115
115
  type = (type || type === null) ? type : ($route?.params?.type || $route?.query?.type);
116
116
  brand = (brand || brand === null) ? brand : ($route?.params?.brand || $route?.query?.brand);
117
+ minimumQty = (minimumQty || minimumQty === null) ? minimumQty : ($route?.params?.minimumQty || $route?.query?.minimumQty);
118
+ productionTime = (productionTime || productionTime === null) ? productionTime : ($route?.params?.productionTime || $route?.query?.productionTime);
117
119
  text = (text || text === '') ? text : $route?.query?.text;
118
120
  sort = (sort || sort === '') ? sort : $route?.query?.sort;
119
121
 
@@ -125,6 +127,12 @@ export function generateProductsLink($route, data, skipParams = false) {
125
127
  if (sort) {
126
128
  params.push(`sort=${sort}`);
127
129
  }
130
+ if (minimumQty) {
131
+ params.push(`minimumQty=${minimumQty}`);
132
+ }
133
+ if (productionTime) {
134
+ params.push(`productionTime=${productionTime}`);
135
+ }
128
136
 
129
137
  const { page = 1 } = data;
130
138
  if (page > 1) {
@@ -132,7 +140,7 @@ export function generateProductsLink($route, data, skipParams = false) {
132
140
  }
133
141
 
134
142
  Object.keys({ ...(($route && $route.query) || {}), ...data }).forEach(key => {
135
- const rootKeys = ['text', 'sort', 'page', 'category', 'type', 'brand'];
143
+ const rootKeys = ['text', 'sort', 'page', 'category', 'type', 'brand', 'minimumQty', 'productionTime'];
136
144
  if (!rootKeys.includes(key)) {
137
145
  let items = ($route && $route.query[key]) ? $route.query[key].split(',') : [];
138
146
  items = (data[key] ? (items.includes(data[key]) ? items.filter(c => c !== data[key]) : [...items, data[key]]) : items).join(',');
@@ -50,7 +50,7 @@
50
50
  :key="`${entity.guid}_${index}`"
51
51
  class="lc_regular12 lc_grey1">
52
52
  <div>
53
- {{ print.printArea.name }} | {{ print.printSize.name }} | {{ print.printType.name }}
53
+ {{ print.printArea.name }} | {{ print.printSize.name }} | {{ print.printType ? print.printType.name : '' }}
54
54
  </div>
55
55
  </div>
56
56
  </div>
@@ -1,7 +1,7 @@
1
1
  <template>
2
2
  <tr>
3
3
  <td class="lc_body-small">
4
- {{ print.printType.name }}
4
+ {{ print.printType ? print.printType.name : '' }}
5
5
  </td>
6
6
  <td class="lc_body-small">
7
7
  {{ print.printArea.name }}
@@ -46,7 +46,7 @@
46
46
  :key="`${entity.guid}_${index}`"
47
47
  class="lc_regular12 lc_grey1">
48
48
  <div>
49
- {{ print.printArea.name }} | {{ print.printSize.name }} | {{ print.printType.name }}
49
+ {{ print.printArea.name }} | {{ print.printSize.name }} | {{ print.printType ? print.printType.name : '' }}
50
50
  </div>
51
51
  </div>
52
52
  </div>
@@ -3,7 +3,7 @@
3
3
  <tr>
4
4
  <td class="lc_body-small">
5
5
  <span class="hidden-md-and-up">Print Type</span>
6
- {{ print.printType.name }}
6
+ {{ print.printType ? print.printType.name : '' }}
7
7
  </td>
8
8
  <td class="lc_body-small">
9
9
  <span class="hidden-md-and-up">Location</span>
@@ -16,7 +16,7 @@
16
16
  </template>
17
17
 
18
18
  <script>
19
- import { mapGetters } from 'vuex';
19
+ import { mapGetters, mapMutations } from 'vuex';
20
20
 
21
21
  const Cookie = process.client ? require('js-cookie') : undefined;
22
22
 
@@ -44,9 +44,12 @@ export default {
44
44
  }
45
45
  },
46
46
  methods: {
47
+ ...mapMutations([
48
+ 'setStockCountry'
49
+ ]),
47
50
  selectStockCountry(stockCountry) {
48
51
  this.saveStockCountry(stockCountry);
49
- window.location.reload();
52
+ this.setStockCountry(stockCountry);
50
53
  },
51
54
  saveStockCountry(stockCountry) {
52
55
  const value = (stockCountry && stockCountry.isoCode !== 'world') ? stockCountry?._id : null
@@ -129,7 +129,9 @@
129
129
  <client-only>
130
130
  <gallery
131
131
  v-if="hasImages"
132
- :show-big-image="false" />
132
+ :show-big-image="false"
133
+ :image-types="['model']"
134
+ :show-filters="false" />
133
135
  </client-only>
134
136
  </div>
135
137
  <div
@@ -169,6 +171,7 @@ import ProductSideWithPrint from '@lancom/shared/components/common/product_side_
169
171
  import RichText from '@lancom/shared/components/common/rich-text';
170
172
  import QuoteRequestModal from '@lancom/shared/components/quotes/quote_request_modal/quote-request-modal';
171
173
  import Gallery from '@lancom/shared/components/product/gallery/gallery';
174
+ import { isValidImageType } from '@lancom/shared/assets/js/utils/colors';
172
175
 
173
176
  export default {
174
177
  name: 'EditorProductDetails',
@@ -216,7 +219,10 @@ export default {
216
219
  'country'
217
220
  ]),
218
221
  hasImages() {
219
- return this.images.length > 0;
222
+ return this.modelImages.length > 0;
223
+ },
224
+ modelImages() {
225
+ return this.images.filter(i => isValidImageType(i, 'model'));
220
226
  },
221
227
  previewPrintProduct() {
222
228
  return {
@@ -85,7 +85,11 @@
85
85
  position: fixed;
86
86
  right: 20px;
87
87
  top: 20px;
88
- font-size: 28px;
88
+ font-size: 18px;
89
+ display: flex;
90
+ .Btn__wrapper {
91
+ height: 40px;
92
+ }
89
93
  }
90
94
  &__side {
91
95
  position: absolute;
@@ -84,9 +84,14 @@
84
84
  </div>
85
85
  <div
86
86
  v-if="isZoomIn"
87
- class="EditorWorkspace__zoom-out"
88
- @click="zoomOut()">
89
- <i class="icon-cancel"></i>
87
+ class="EditorWorkspace__zoom-out">
88
+ <btn
89
+ btn-class="green"
90
+ btn-label="Save"
91
+ @onclick="zoomOut()" />
92
+ <btn
93
+ btn-label="Cancel"
94
+ @onclick="zoomOut(true)" />
90
95
  </div>
91
96
  </div>
92
97
  </client-only>
@@ -149,6 +154,7 @@ export default {
149
154
  ...mapGetters([
150
155
  'product',
151
156
  'selectedLayer',
157
+ 'selectedLayerCopy',
152
158
  'productDetailsLoaded',
153
159
  'selectedPrintArea',
154
160
  'editablePrintArea',
@@ -217,10 +223,14 @@ export default {
217
223
  'setEditableSide',
218
224
  'setSelectedPrintArea',
219
225
  'setEditablePrintArea',
220
- 'setSelectedLayer'
226
+ 'setSelectedLayer',
227
+ 'updateTemplateLayer'
221
228
  ]),
222
- zoomOut() {
223
- this.setSelectedLayer(null);
229
+ zoomOut(isCancel) {
230
+ if (isCancel) {
231
+ this.updateTemplateLayer(this.selectedLayerCopy);
232
+ }
233
+ setTimeout(() => this.setSelectedLayer(null), 100);
224
234
  },
225
235
  onWorkspaceChange({ size, fabricHelper }) {
226
236
  this.fabricHelper = fabricHelper;
@@ -6,14 +6,20 @@
6
6
  transition: background-color 0.5s ease;
7
7
  background-color: white;
8
8
  }
9
- &__toggle-wireframe {
9
+ &__toggle-bg {
10
10
  position: absolute;
11
+ top: 5px;
12
+ display: flex;
13
+ left: 0;
14
+ right: 0;
15
+ }
16
+ &__toggle-wireframe {
11
17
  z-index: 1001;
12
18
  background: gainsboro;
13
19
  padding: 6px;
14
- top: 5px;
15
20
  font-size: 14px;
16
21
  cursor: pointer;
22
+ margin-right: 5px;
17
23
  &--active {
18
24
  background: rgb(196, 226, 236);
19
25
  }
@@ -7,15 +7,29 @@
7
7
  }"
8
8
  v-click-outside="onOutsideClick"
9
9
  @mouseover="toogleBoundBox(true)"
10
- @mouseleave="toogleBoundBox(false)">
10
+ @mouseleave="toogleBoundBox(false)"
11
+ @mouseup="toggleOnpressImage(false)">
11
12
  <div
12
- v-if="hasWireframeImage"
13
- class="EditorWorkspaceSide__toggle-wireframe"
14
- :class="{
15
- 'EditorWorkspaceSide__toggle-wireframe--active': visibleWireframe
16
- }"
17
- @click.stop.prevent="toggleWireframeImage">
18
- {{ visibleWireframe ? 'Hide Wireframe' : 'Show Wireframe' }}
13
+ v-if="hasOnpressImage || hasWireframeImage"
14
+ class="EditorWorkspaceSide__toggle-bg">
15
+ <div
16
+ v-if="hasWireframeImage"
17
+ class="EditorWorkspaceSide__toggle-wireframe"
18
+ :class="{
19
+ 'EditorWorkspaceSide__toggle-wireframe--active': visibleWireframe
20
+ }"
21
+ @click.stop.prevent="toggleWireframeImage">
22
+ {{ visibleWireframe ? 'Hide Wireframe' : 'Show Wireframe' }}
23
+ </div>
24
+ <div
25
+ v-if="hasOnpressImage"
26
+ class="EditorWorkspaceSide__toggle-wireframe"
27
+ :class="{
28
+ 'EditorWorkspaceSide__toggle-wireframe--active': visibleOnpress
29
+ }"
30
+ @mousedown.stop.prevent="toggleOnpressImage(true)">
31
+ {{ visibleOnpress ? 'Hide On Press' : 'Show On Press' }}
32
+ </div>
19
33
  </div>
20
34
  <div
21
35
  v-if="deleteButtonPos"
@@ -149,6 +163,7 @@ export default {
149
163
  return {
150
164
  addedFromCanvas: false,
151
165
  visibleWireframe: false,
166
+ visibleOnpress: false,
152
167
  fabricHelper: null,
153
168
  redrawWithThrottle: throttle(this.redraw, 100, { trailing: false }),
154
169
  saveLayersAsImageWithDebounce: debounce(this.saveLayersAsImage, 400),
@@ -171,6 +186,7 @@ export default {
171
186
  'editableColor',
172
187
  'editableLayers',
173
188
  'selectedLayer',
189
+ 'selectedLayerCopy',
174
190
  'editorSize',
175
191
  'editModeSelectedLayer'
176
192
  ]),
@@ -180,9 +196,15 @@ export default {
180
196
  hasWireframeImage() {
181
197
  return !!this.wireframeImage;
182
198
  },
199
+ hasOnpressImage() {
200
+ return !!this.onpressImage;
201
+ },
183
202
  wireframeImage() {
184
203
  return this.images.find(i => i.types?.includes('wireframe') && i.types?.includes(this.side));
185
204
  },
205
+ onpressImage() {
206
+ return this.product.images?.find(i => i.types?.includes('on_press') && i.color === this.editableColor?._id);
207
+ },
186
208
  sideEditableLayers() {
187
209
  return this.editableLayers.filter(l => l.sideId === this.side);
188
210
  },
@@ -250,6 +272,9 @@ export default {
250
272
  visibleWireframe() {
251
273
  this.updateBackgroundImage();
252
274
  },
275
+ visibleOnpress() {
276
+ this.updateBackgroundImage();
277
+ },
253
278
  layers() {
254
279
  this.redrawWithThrottle();
255
280
  },
@@ -304,6 +329,9 @@ export default {
304
329
  toggleWireframeImage() {
305
330
  this.visibleWireframe = !this.visibleWireframe;
306
331
  },
332
+ toggleOnpressImage(state) {
333
+ this.visibleOnpress = state;
334
+ },
307
335
  adjustSelectedArtDPI() {
308
336
  const measure = this.fabricHelper.getEditorDPI();
309
337
  const scale = measure / 75;
@@ -402,19 +430,22 @@ export default {
402
430
  const isMedium = this.calcWorkspaceSize(true) <= 400;
403
431
  const type = this.side === 'front' ? COLORS_IMAGES_TYPES.FRONT : COLORS_IMAGES_TYPES.BACK;
404
432
  const wireframeImageUrl = this.visibleWireframe && (isMedium ? this.wireframeImage?.medium : this.wireframeImage?.large);
433
+ const onpressImageUrl = this.visibleOnpress && (isMedium ? this.onpressImage?.medium : this.onpressImage?.large);
405
434
  const getter = isMedium ? getMediumColorImage : getLargeColorImage;
406
- const imageUrl = wireframeImageUrl || getter(this.product, type, this.editableColor, false, ['wireframe']);
435
+ const imageUrl = wireframeImageUrl || onpressImageUrl || getter(this.product, type, this.editableColor, false, ['wireframe']);
407
436
  if (this.backgroundImage && this.backgroundImageUrl === imageUrl) {
408
437
  return this.fabricHelper.setBackgroundImage(this.backgroundImage);
409
438
  }
410
439
 
411
440
  this.backgroundImageLoaded = false;
441
+ this.backgroundImageUrl = imageUrl;
412
442
  fabric.Image.fromURL(imageUrl, image => {
413
- image.scale(this.editorSize.width / image.width);
414
- this.backgroundImage = image;
415
- this.backgroundImageUrl = imageUrl;
416
- this.backgroundImageLoaded = true;
417
- this.fabricHelper.setBackgroundImage(this.backgroundImage);
443
+ if (this.backgroundImageUrl === imageUrl) {
444
+ image.scale(this.editorSize.width / image.width);
445
+ this.backgroundImage = image;
446
+ this.backgroundImageLoaded = true;
447
+ this.fabricHelper.setBackgroundImage(this.backgroundImage);
448
+ }
418
449
  });
419
450
  },
420
451
  removeSelected() {
@@ -3,7 +3,7 @@
3
3
  <tr>
4
4
  <td class="lc_body-small">
5
5
  <span :class="responsive ? 'hidden-md-and-up' : 'hidden'">Print Type</span>
6
- {{ print.printType.name }}
6
+ {{ print.printType ? print.printType.name : '' }}
7
7
  </td>
8
8
  <td class="lc_body-small">
9
9
  <span :class="responsive ? 'hidden-md-and-up' : 'hidden'">Location</span>
@@ -88,7 +88,7 @@
88
88
  </button>
89
89
  </div>
90
90
  <div
91
- v-if="hasValidFilters"
91
+ v-if="showFilters && hasValidFilters"
92
92
  class="Gallery__filters">
93
93
  <div
94
94
  v-for="filter in validFilters"
@@ -127,6 +127,13 @@ export default {
127
127
  showBigImage: {
128
128
  type: Boolean,
129
129
  default: true
130
+ },
131
+ showFilters: {
132
+ type: Boolean,
133
+ default: true
134
+ },
135
+ imageTypes: {
136
+ type: Array
130
137
  }
131
138
  },
132
139
  data() {
@@ -160,7 +167,7 @@ export default {
160
167
  return this.validFilters.length > 1;
161
168
  },
162
169
  galleryImages() {
163
- return this.images.filter(i => !(i.types || []).includes('designer'));
170
+ return this.imageTypes ? this.images.filter(i => isValidImageTypes(i, this.imageTypes)) : this.images.filter(i => !(i.types || []).includes('designer'));
164
171
  },
165
172
  validFilters() {
166
173
  return this.filters.filter(filter => (this.galleryImages || []).some(i => this.isValidImageByFilter(filter, i)));
@@ -82,6 +82,7 @@
82
82
  </div>
83
83
  </div>
84
84
  <products-size-selector-color
85
+ :key="productDetailsKey"
85
86
  :with-gst="inclGSTFinal"
86
87
  class="ProductColorsSelector__section"
87
88
  :class="{
@@ -131,8 +132,8 @@ export default {
131
132
  }
132
133
  },
133
134
  computed: {
134
- ...mapGetters(['taxName']),
135
- ...mapGetters('product', ['editableColor', 'availableColors', 'visibleSteps', 'hasLayers', 'isPrintPricing', 'priceIncludeGST']),
135
+ ...mapGetters(['stockCountry', 'taxName']),
136
+ ...mapGetters('product', ['productDetailsKey', 'editableColor', 'availableColors', 'visibleSteps', 'hasLayers', 'isPrintPricing', 'priceIncludeGST']),
136
137
  selectedColor: {
137
138
  get() {
138
139
  return this.editableColor;
@@ -17,7 +17,7 @@
17
17
  <div class="lc_regular14 hidden-sm-and-up">
18
18
  Print type
19
19
  </div>
20
- {{ layerPrintType.name }}
20
+ {{ layerPrintType ? layerPrintType.name : '-' }}
21
21
  </td>
22
22
  <td>
23
23
  <div class="lc_regular14 hidden-sm-and-up">
@@ -107,7 +107,7 @@ export default {
107
107
  printArea: editablePrintArea._id,
108
108
  printSize: selectedPrintArea?._id,
109
109
  side: editablePrintArea.side,
110
- printType: selectedPrintType._id
110
+ printType: selectedPrintType?._id
111
111
  }
112
112
  };
113
113
  if (layer.type === 'text') {
@@ -95,7 +95,7 @@ export default {
95
95
  methods: {
96
96
  ...mapMutations('product', ['setSelectedPrintTypes']),
97
97
  isSelectedType(type) {
98
- return this.selectedPrintType && this.selectedPrintType._id === type._id;
98
+ return this.selectedPrintType && this.selectedPrintType?._id === type._id;
99
99
  },
100
100
  selectPrintType(type) {
101
101
  this.setSelectedPrintTypes({ printArea: this.editablePrintArea._id, type });
@@ -23,6 +23,16 @@
23
23
  :has-selected-icon="hasSelectedIcon"
24
24
  :selected-icon-circle="selectedIconCircle" />
25
25
  </div>
26
+ <div class="ProductsAside__item">
27
+ <products-production-time
28
+ :has-selected-icon="hasSelectedIcon"
29
+ :selected-icon-circle="selectedIconCircle" />
30
+ </div>
31
+ <div class="ProductsAside__item">
32
+ <products-minimum-qty
33
+ :has-selected-icon="hasSelectedIcon"
34
+ :selected-icon-circle="selectedIconCircle" />
35
+ </div>
26
36
  <div class="ProductsAside__item">
27
37
  <products-colors />
28
38
  </div>
@@ -44,6 +54,8 @@ import ProductsBrands from '@lancom/shared/components/products/products_brands/p
44
54
  import ProductsTags from '@lancom/shared/components/products/products_tags/products-tags';
45
55
  import ProductsAttributes from '@lancom/shared/components/products/products_attributes/products-attributes';
46
56
  import ProductsColors from '@lancom/shared/components/products/products_colors/products-colors';
57
+ import ProductsProductionTime from '@lancom/shared/components/products/products_production_time/products-production-time';
58
+ import ProductsMinimumQty from '@lancom/shared/components/products/products_minimum_qty/products-minimum-qty';
47
59
  import { generateProductsLink } from '@lancom/shared/assets/js/utils/product';
48
60
 
49
61
  export default {
@@ -53,7 +65,9 @@ export default {
53
65
  ProductsBrands,
54
66
  ProductsTags,
55
67
  ProductsAttributes,
56
- ProductsColors
68
+ ProductsColors,
69
+ ProductsProductionTime,
70
+ ProductsMinimumQty
57
71
  },
58
72
  props: {
59
73
  hasSelectedIcon: {
@@ -27,6 +27,12 @@ export default {
27
27
  color: {
28
28
  type: String
29
29
  },
30
+ productionTime: {
31
+ type: String
32
+ },
33
+ minimumQty: {
34
+ type: String
35
+ },
30
36
  attributes: {
31
37
  type: Object
32
38
  },
@@ -45,6 +51,8 @@ export default {
45
51
  brand: this.brand,
46
52
  tags: this.tags,
47
53
  colors: this.color,
54
+ productionTime: this.productionTime,
55
+ minimumQty: this.minimumQty,
48
56
  option: this.option,
49
57
  text: this.text,
50
58
  sort: this.sort,
@@ -0,0 +1,39 @@
1
+ @import "@/assets/scss/variables";
2
+
3
+ .ProductsMinimumQty {
4
+ &__header {
5
+ font-size: 22px;
6
+ line-height: 20px;
7
+ font-weight: 600;
8
+ margin-top: 3px;
9
+ margin-bottom: 24px;
10
+ color: $black;
11
+ }
12
+ &__item {
13
+ margin: 13px 0;
14
+ font-size: 15px;
15
+ font-weight: 400;
16
+ i {
17
+ font-size: 20px;
18
+ margin-right: 10px;
19
+ }
20
+ a {
21
+ color: $grey_1;
22
+ text-decoration: none;
23
+ display: flex;
24
+ align-items: center;
25
+ justify-content: space-between;
26
+ }
27
+ &--active a {
28
+ color: $green;
29
+ }
30
+ &-icon {
31
+ margin-right: 10px;
32
+ background-position: center;
33
+ background-repeat: no-repeat;
34
+ background-size: contain;
35
+ width: 20px;
36
+ height: 20px;
37
+ }
38
+ }
39
+ }
@@ -0,0 +1,88 @@
1
+ <template>
2
+ <div class="ProductsMinimumQty__wrapper">
3
+ <toggle-content label="Minimum Qty">
4
+ <div
5
+ v-for="minimumQtyRange in minimumQtyRanges"
6
+ :key="minimumQtyRange.alias"
7
+ class="ProductsMinimumQty__item"
8
+ :class="{
9
+ 'ProductsMinimumQty__item--active': isSelectedMinimumQty(minimumQtyRange)
10
+ }">
11
+ <products-link
12
+ class="ProductsMinimumQty__more"
13
+ :minimum-qty="isSelectedMinimumQty(minimumQtyRange) ? null : minimumQtyRange.alias">
14
+ <span>
15
+ {{ minimumQtyRange.name }}
16
+ </span>
17
+ <checked-icon
18
+ v-if="hasSelectedIcon"
19
+ :checked="isSelectedMinimumQty(minimumQtyRange)"
20
+ :circle="selectedIconCircle" />
21
+ </products-link>
22
+ </div>
23
+ </toggle-content>
24
+ </div>
25
+ </template>
26
+
27
+ <script>
28
+ import ToggleContent from '@lancom/shared/components/common/toggle-content';
29
+ import ProductsLink from '@lancom/shared/components/products/products_link/products-link';
30
+ import CheckedIcon from '@lancom/shared/components/common/checked-icon';
31
+
32
+ export default {
33
+ name: 'ProductsMinimumQty',
34
+ components: {
35
+ ProductsLink,
36
+ ToggleContent,
37
+ CheckedIcon
38
+ },
39
+ props: {
40
+ hasSelectedIcon: {
41
+ type: Boolean,
42
+ default: false
43
+ },
44
+ selectedIconCircle: {
45
+ type: Boolean,
46
+ default: false
47
+ }
48
+ },
49
+ data() {
50
+ return {
51
+ minimumQtyRanges: [{
52
+ alias: 'no-minimums',
53
+ name: 'No minimums'
54
+ }, {
55
+ alias: '6-or-less',
56
+ name: '6 or less'
57
+ }, {
58
+ alias: '12-or-less',
59
+ name: '12 or less'
60
+ }, {
61
+ alias: '25-or-less',
62
+ name: '25 or less'
63
+ }, {
64
+ alias: '50-or-less',
65
+ name: '50 or less'
66
+ }, {
67
+ alias: '100-or-less',
68
+ name: '100 or less'
69
+ }]
70
+ };
71
+ },
72
+ computed: {
73
+ selectedMinimumQties() {
74
+ const minimumQty = (this.$route.query.minimumQty || '').split(',');
75
+ return this.minimumQtyRanges.filter(minimumQtyRange => minimumQty.includes(minimumQtyRange.alias));
76
+ }
77
+ },
78
+ methods: {
79
+ isSelectedMinimumQty(minimumQty) {
80
+ return this.selectedMinimumQties.some(mq => mq.alias === minimumQty.alias);
81
+ }
82
+ }
83
+ };
84
+ </script>
85
+
86
+ <style lang="scss" scoped>
87
+ @import 'products-minimum-qty';
88
+ </style>
@@ -0,0 +1,39 @@
1
+ @import "@/assets/scss/variables";
2
+
3
+ .ProductsProductionTime {
4
+ &__header {
5
+ font-size: 22px;
6
+ line-height: 20px;
7
+ font-weight: 600;
8
+ margin-top: 3px;
9
+ margin-bottom: 24px;
10
+ color: $black;
11
+ }
12
+ &__item {
13
+ margin: 13px 0;
14
+ font-size: 15px;
15
+ font-weight: 400;
16
+ i {
17
+ font-size: 20px;
18
+ margin-right: 10px;
19
+ }
20
+ a {
21
+ color: $grey_1;
22
+ text-decoration: none;
23
+ display: flex;
24
+ align-items: center;
25
+ justify-content: space-between;
26
+ }
27
+ &--active a {
28
+ color: $green;
29
+ }
30
+ &-icon {
31
+ margin-right: 10px;
32
+ background-position: center;
33
+ background-repeat: no-repeat;
34
+ background-size: contain;
35
+ width: 20px;
36
+ height: 20px;
37
+ }
38
+ }
39
+ }
@@ -0,0 +1,85 @@
1
+ <template>
2
+ <div class="ProductsProductionTime__wrapper">
3
+ <toggle-content label="Production Time">
4
+ <div
5
+ v-for="produnctionRange in produnctionTimesRanges"
6
+ :key="produnctionRange.alias"
7
+ class="ProductsProductionTime__item"
8
+ :class="{
9
+ 'ProductsProductionTime__item--active': isSelectedProdunctionTime(produnctionRange)
10
+ }">
11
+ <products-link
12
+ class="ProductsProductionTime__more"
13
+ :production-time="isSelectedProdunctionTime(produnctionRange) ? null : produnctionRange.alias">
14
+ <span>
15
+ {{ produnctionRange.name }}
16
+ </span>
17
+ <checked-icon
18
+ v-if="hasSelectedIcon"
19
+ :checked="isSelectedProdunctionTime(produnctionRange)"
20
+ :circle="selectedIconCircle" />
21
+ </products-link>
22
+ </div>
23
+ </toggle-content>
24
+ </div>
25
+ </template>
26
+
27
+ <script>
28
+ import ToggleContent from '@lancom/shared/components/common/toggle-content';
29
+ import ProductsLink from '@lancom/shared/components/products/products_link/products-link';
30
+ import CheckedIcon from '@lancom/shared/components/common/checked-icon';
31
+
32
+ export default {
33
+ name: 'ProductsProductionTime',
34
+ components: {
35
+ ProductsLink,
36
+ ToggleContent,
37
+ CheckedIcon
38
+ },
39
+ props: {
40
+ hasSelectedIcon: {
41
+ type: Boolean,
42
+ default: false
43
+ },
44
+ selectedIconCircle: {
45
+ type: Boolean,
46
+ default: false
47
+ }
48
+ },
49
+ data() {
50
+ return {
51
+ produnctionTimesRanges: [{
52
+ alias: 'same-day',
53
+ name: 'Same day'
54
+ }, {
55
+ alias: '3-days',
56
+ name: 'Rush 3 day'
57
+ }, {
58
+ alias: '10-days',
59
+ name: '10 days'
60
+ }, {
61
+ alias: '2-weeks',
62
+ name: '2 weeks'
63
+ }, {
64
+ alias: '4-weeks',
65
+ name: '4 or more weeks'
66
+ }]
67
+ };
68
+ },
69
+ computed: {
70
+ selectedProdunctionTimes() {
71
+ const productionTime = (this.$route.query.productionTime || '').split(',');
72
+ return this.produnctionTimesRanges.filter(produnctionTimesRange => productionTime.includes(produnctionTimesRange.alias));
73
+ }
74
+ },
75
+ methods: {
76
+ isSelectedProdunctionTime(produnctionTime) {
77
+ return this.selectedProdunctionTimes.some(pt => pt.alias === produnctionTime.alias);
78
+ }
79
+ }
80
+ };
81
+ </script>
82
+
83
+ <style lang="scss" scoped>
84
+ @import 'products-production-time';
85
+ </style>
@@ -3,7 +3,7 @@
3
3
  <tr>
4
4
  <td class="lc_body-small">
5
5
  <span class="hidden-md-and-up">Print Type</span>
6
- {{ print.printType.name }}
6
+ {{ print.printType ? print.printType.name : '' }}
7
7
  </td>
8
8
  <td class="lc_body-small">
9
9
  <span class="hidden-md-and-up">Location</span>
@@ -59,15 +59,17 @@ async function googleShoppingFeed(axios, config, availableStores, country, isEdi
59
59
  .replace(/&middot;/, '·');
60
60
 
61
61
  let link = `https://${config.HOST_NAME}${generateProductLink(product, sp.color, isEditor)}`;
62
+ link = link.includes('?') ? `${link}&price=IT` : `${link}?price=IT`
62
63
  if (sp.multipackQty) {
63
- link = `${link}&multipack=${sp.SKU}`
64
+ link = link.includes('?') ? `${link}&multipack=${sp.SKU}` : `${link}?multipack=${sp.SKU}`;
64
65
  }
65
66
 
66
67
  const productWeight = +((product.weight || 0) * (sp.multipackQty || 1)).toFixed(3);
67
68
  const info = {
68
- title: { _text: sp.title || title },
69
- description: { _text: sp.description || description },
69
+ 'g:title': { _text: sp.title || title },
70
+ 'g:description': { _text: sp.description || description },
70
71
  link: { _text: link },
72
+ 'g:canonical_link': { _text: link.split('?')[0] },
71
73
  'g:id': { _text: sp.SKU },
72
74
  'g:item_group_id': { _text: product.SKU },
73
75
  'g:size': { _text: sp.size.name },
@@ -160,7 +162,7 @@ async function googleShoppingFeed(axios, config, availableStores, country, isEdi
160
162
 
161
163
  for (let label = 0; label <= 4; label++) {
162
164
  if (product[`feedCustomLabel${label}`]) {
163
- info[`custom_label_${label}`] = { _text: product[`feedCustomLabel${label}`] };
165
+ info[`g:custom_label_${label}`] = { _text: product[`feedCustomLabel${label}`] };
164
166
  }
165
167
  }
166
168
 
@@ -257,7 +257,6 @@
257
257
  }
258
258
 
259
259
  const image = getProductLargeCover(product, 'front') || getProductMediumCover(product, 'front');
260
- console.log('image: ', image, product.images);
261
260
  if (image) {
262
261
  schema.image = image;
263
262
  }
@@ -1,15 +1,28 @@
1
- export default async function ({ store, route, redirect }) {
1
+ export default async function ({ store, route, redirect, error }) {
2
2
  const matchedRoute = route.matched[route.matched.length - 1];
3
3
  if (matchedRoute) {
4
4
  const path = matchedRoute.path.replace(/\?/g, '');
5
5
  const data = {
6
6
  shop: store.getters.shop._id,
7
7
  route: path || '/',
8
- fullPath: route.fullPath
8
+ fullPath: route.path
9
9
  };
10
10
  const routeInfo = await store.dispatch('page/fetchRouteInfo', data);
11
11
  if (routeInfo?.type === 'redirect' && routeInfo.redirectTo) {
12
- redirect(301, routeInfo.redirectTo);
12
+ redirect(301, route.fullPath.replace(route.path, routeInfo.redirectTo));
13
+ }
14
+ } else {
15
+ const data = {
16
+ shop: store.getters.shop._id,
17
+ route: route.path || '/',
18
+ fullPath: route.path
19
+ };
20
+ const routeInfo = await store.dispatch('page/fetchRouteInfo', data);
21
+ if (routeInfo?.type === 'deleted') {
22
+ error({
23
+ statusCode: 410,
24
+ message: 'Page has been deleted'
25
+ });
13
26
  }
14
27
  }
15
28
  }
@@ -39,7 +39,7 @@ const metaInfo = {
39
39
  head() {
40
40
  const hasQueryParams = Object
41
41
  .keys(this.$route.query || {})
42
- .filter(key => !['color', 'store'].includes(`${key || ''}`.toLowerCase()))
42
+ .filter(key => !['color', 'store','price'].includes(`${key || ''}`.toLowerCase()))
43
43
  .length > 0;
44
44
 
45
45
  const { short_text: shortText, image, meta = {} } = this.routeInfo || {};
@@ -38,8 +38,8 @@ export default {
38
38
  return this.layerPrintSizes.find(({ _id }) => _id === this.layer.printSize);
39
39
  },
40
40
  layerPrintPricing() {
41
- const printArea = this.layerPrintType.printAreas.find(({ printSizes }) => printSizes.map(({ _id }) => _id).includes(this.layer.printSize)) || this.layerPrintType.printAreas[0];
42
- return printArea.printCost;
41
+ const printArea = this.layerPrintType?.printAreas.find(({ printSizes }) => printSizes.map(({ _id }) => _id).includes(this.layer.printSize)) || this.layerPrintType?.printAreas[0];
42
+ return printArea?.printCost;
43
43
  }
44
44
  }
45
45
  };
@@ -53,6 +53,9 @@ export default (IS_PRODUCT_PRESET_PRINT_PRICING, isEditor = false) => ({
53
53
  if (query.store) {
54
54
  await store.dispatch('product/updatePriceIncludeGST', true);
55
55
  }
56
+ if (query.price) {
57
+ store.commit('product/setPriceIncludeGST', query.price === 'IT');
58
+ }
56
59
  } catch (e) {
57
60
  console.log(e);
58
61
  }
@@ -77,9 +80,17 @@ export default (IS_PRODUCT_PRESET_PRINT_PRICING, isEditor = false) => ({
77
80
  preSetPrintPricing: false
78
81
  };
79
82
  },
83
+ watch: {
84
+ async stockCountryId(prev, curr) {
85
+ await this.loadProductStockDetails();
86
+ }
87
+ },
80
88
  computed: {
81
89
  ...mapGetters(['shop', 'gstTax', 'country', 'stockCountry', 'currency']),
82
90
  ...mapGetters('product', ['product', 'simpleProducts', 'productDetails', 'editableColor', 'images', 'preSetPrints', 'editorSize', 'printsPrice']),
91
+ stockCountryId() {
92
+ return this.stockCountry?._id || null;
93
+ },
83
94
  pageItemImage() {
84
95
  return this.mainProductImageSrc;
85
96
  },
@@ -112,21 +123,12 @@ export default (IS_PRODUCT_PRESET_PRINT_PRICING, isEditor = false) => ({
112
123
  this.fillBreadcrumbs();
113
124
  },
114
125
  async mounted() {
115
- const { slug } = this.$route.params;
116
- const { color, colour, multipack } = this.$route.query;
117
- const data = {
118
- shop: this.shop._id,
119
- slug,
120
- country: this.country?._id,
121
- stockCountry: this.stockCountry?._id,
122
- currency: this.currency?._id,
123
- defaultColor: color || colour
124
- };
125
126
  if (!this.product) {
126
- await this.fetchProduct(data);
127
+ const query = this.getProductQuery();
128
+ await this.fetchProduct(query);
127
129
  }
128
130
 
129
- await this.fetchProductDetails(data);
131
+ await this.loadProductStockDetails();
130
132
 
131
133
  try {
132
134
  if (this.preSetPrints?.length) {
@@ -168,7 +170,7 @@ export default (IS_PRODUCT_PRESET_PRINT_PRICING, isEditor = false) => ({
168
170
  }
169
171
 
170
172
  this.fillBreadcrumbs();
171
-
173
+ const { multipack } = this.$route.query;
172
174
  if (multipack) {
173
175
  const [SKU, qty] = multipack.split('-multipack-');
174
176
  const simpleProduct = this.simpleProducts.find(sp => sp.SKU === SKU);
@@ -189,9 +191,27 @@ export default (IS_PRODUCT_PRESET_PRINT_PRICING, isEditor = false) => ({
189
191
  this.clearProduct();
190
192
  },
191
193
  methods: {
192
- ...mapActions(['fetchProduct', 'fetchProductDetails', 'selectColor']),
193
- ...mapMutations(['clearProduct', 'clearTemplate', 'addTemplateLayer', 'setSimpleProductAmount']),
194
+ ...mapActions(['fetchProduct', 'fetchProductDetails', 'fetchProductStockDetails', 'selectColor']),
195
+ ...mapMutations(['clearProduct', 'clearTemplate', 'addTemplateLayer', 'setSimpleProductAmount', 'setPriceIncludeGST']),
194
196
  staticLink,
197
+ getProductQuery() {
198
+ const { slug } = this.$route.params;
199
+ const { color, colour } = this.$route.query;
200
+ const data = {
201
+ shop: this.shop._id,
202
+ slug,
203
+ country: this.country?._id,
204
+ stockCountry: this.stockCountry?._id,
205
+ currency: this.currency?._id,
206
+ defaultColor: color || colour
207
+ };
208
+
209
+ return data;
210
+ },
211
+ loadProductStockDetails() {
212
+ const query = this.getProductQuery();
213
+ this.fetchProductStockDetails(query);
214
+ },
195
215
  goToInfoTab(tab) {
196
216
  this.$refs.infoTabs.selectedTab = tab;
197
217
  const { $el } = this.$refs.infoTabs;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lancom/shared",
3
- "version": "0.0.352",
3
+ "version": "0.0.354",
4
4
  "description": "lancom common scripts",
5
5
  "author": "e.tokovenko <e.tokovenko@gmail.com>",
6
6
  "repository": {
package/store/cart.js CHANGED
@@ -165,7 +165,6 @@ export const actions = {
165
165
  try {
166
166
  commit('setCartPricingCalculating', true);
167
167
  const response = await api.calculateProductPrice(payload, shop._id);
168
- console.log('response: ', response);
169
168
  commit('setCartPricing', response);
170
169
  commit('setCartPricingError', null);
171
170
  } catch (e) {
@@ -237,7 +236,6 @@ export const mutations = {
237
236
  state.coupon = coupon;
238
237
  },
239
238
  setCartPricing(state, price) {
240
- console.log('setCartPricing: ', price);
241
239
  state.cartPricing = price;
242
240
  },
243
241
  setCartPricingCalculating(state, calculating) {
package/store/product.js CHANGED
@@ -13,6 +13,7 @@ export const state = () => ({
13
13
  multipack: null,
14
14
  calculatingPrice: false,
15
15
  loadingProductDetails: false,
16
+ productDetailsKey: Date.now(),
16
17
  images: [],
17
18
  priceIncludeGST: false,
18
19
  product: null,
@@ -35,6 +36,7 @@ export const state = () => ({
35
36
  isPrintPricing: false,
36
37
  editableColor: null,
37
38
  selectedLayer: null,
39
+ selectedLayerCopy: null,
38
40
  editablePrintArea: null,
39
41
  editablePrintAreas: [],
40
42
  selectedPrintType: null,
@@ -50,6 +52,7 @@ export const state = () => ({
50
52
  });
51
53
 
52
54
  export const getters = {
55
+ productDetailsKey: ({ productDetailsKey }) => productDetailsKey,
53
56
  loadingProductDetails: ({ loadingProductDetails }) => loadingProductDetails,
54
57
  multipack: ({ multipack }) => multipack,
55
58
  calculatingPrice: ({ calculatingPrice }) => calculatingPrice,
@@ -80,6 +83,7 @@ export const getters = {
80
83
  editableColor: ({ editableColor }) => editableColor,
81
84
  editableLayers: ({ template, editableColor, editablePrintArea }) => template.layers,
82
85
  selectedLayer: ({ selectedLayer }) => selectedLayer,
86
+ selectedLayerCopy: ({ selectedLayerCopy }) => selectedLayerCopy,
83
87
  editModeSelectedLayer: ({ editModeSelectedLayer }) => editModeSelectedLayer,
84
88
  selectedPrintAreas: ({ selectedPrintAreas }) => selectedPrintAreas,
85
89
  selectedPrintArea: ({ selectedPrintAreas, editablePrintArea }) =>
@@ -185,7 +189,8 @@ export const actions = {
185
189
  commit('setLoadingProductDetails', false);
186
190
 
187
191
  const catalogFrontColorId = getColorOfDefaultCatalogFrontImage(state.product);
188
- const editableColor = state.availableColors.find(c => c.alias === defaultColor) ||
192
+ const editableColor = state.availableColors.find(c => c._id === state.editableColor?._id) ||
193
+ state.availableColors.find(c => c.alias === defaultColor) ||
189
194
  state.availableColors.find(c => c._id === catalogFrontColorId) ||
190
195
  state.availableColors[0];
191
196
  commit('setEditableColor', editableColor);
@@ -203,6 +208,13 @@ export const actions = {
203
208
  images = [...imagesGroups.values()].reduce((images, group) => [...images, ...group], []);
204
209
  commit('setImages', images);
205
210
  },
211
+ async fetchProductStockDetails({ commit }, { shop, slug, country, stockCountry, currency }) {
212
+ const params = { country, currency, stockCountry };
213
+ commit('setLoadingProductDetails', true);
214
+ const response = await api.fetchProductDetails(shop, slug, params);
215
+ commit('setProductStockDetails', response);
216
+ commit('setLoadingProductDetails', false);
217
+ },
206
218
  async fetchPrintTypes({ commit }, { shop }) {
207
219
  const response = await api.fetchPrintTypes(shop);
208
220
  commit('setPrintTypes', response);
@@ -295,7 +307,7 @@ export const mutations = {
295
307
  setProductDetails(state, simpleProducts) {
296
308
  const { preSetPrints } = state;
297
309
  const [preSetPrint] = preSetPrints || [];
298
- simpleProducts = simpleProducts.filter(sp => !preSetPrint || (preSetPrint.colors || []).includes(sp.color._id) )
310
+ simpleProducts = simpleProducts.filter(sp => !preSetPrint || (preSetPrint.colors || []).includes(sp.color._id) );
299
311
  state.productDetails = { simpleProducts };
300
312
  const availableSizes = [];
301
313
  const sizesPerColor = simpleProducts.reduce((map, { color, size }) => {
@@ -319,6 +331,17 @@ export const mutations = {
319
331
  }
320
332
  }
321
333
  state.availableColors = sortByName(availableColors);
334
+ state.productDetailsKey = Date.now();
335
+ },
336
+ setProductStockDetails(state, simpleProducts) {
337
+ state.productDetails?.simpleProducts?.forEach(sp => {
338
+ const sp2 = simpleProducts.find(simpleProduct => simpleProduct.SKU === sp.SKU);
339
+ Vue.set(sp, 'quantityStock', sp2?.quantityStock || 0);
340
+ });
341
+ state.template?.simpleProducts?.forEach(sp => {
342
+ const sp2 = simpleProducts.find(simpleProduct => simpleProduct.SKU === sp.SKU);
343
+ Vue.set(sp, 'quantityStock', sp2?.quantityStock || 0);
344
+ });
322
345
  },
323
346
  clearProduct(state) {
324
347
  state.product = null;
@@ -358,6 +381,7 @@ export const mutations = {
358
381
  */
359
382
  setSelectedLayer(state, layer) {
360
383
  state.selectedLayer = layer;
384
+ state.selectedLayerCopy = JSON.parse(JSON.stringify(layer || null));
361
385
  },
362
386
  setEditModeSelectedLayer(state, editMode) {
363
387
  state.editModeSelectedLayer = editMode;