@lancom/shared 0.0.434 → 0.0.436

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 (30) hide show
  1. package/assets/js/models/product-layers.js +1 -1
  2. package/components/common/file_uploader.vue +19 -3
  3. package/components/common/payment/payment_card/googlepay/googlepay.vue +1 -1
  4. package/components/editor/editor_layers/editor-layers.scss +10 -0
  5. package/components/editor/editor_layers/editor-layers.vue +22 -13
  6. package/components/editor/editor_layers/editor_layer_forms/editor_layer_form_text/editor-layer-form-text.scss +11 -0
  7. package/components/editor/editor_layers/editor_layer_forms/editor_layer_form_text/editor-layer-form-text.vue +42 -18
  8. package/components/editor/editor_layers/editor_layers_toolbar/editor-layers-toolbar.vue +2 -3
  9. package/components/editor/editor_print_area_options/editor_print_area_option/editor-print-area-option.scss +5 -2
  10. package/components/editor/editor_product_details/editor-product-details.vue +1 -1
  11. package/components/editor/editor_workspace/editor-workspace.vue +6 -0
  12. package/components/editor/editor_workspace/editor_workspace_side/editor-workspace-side.scss +25 -2
  13. package/components/editor/editor_workspace/editor_workspace_side/editor-workspace-side.vue +48 -24
  14. package/components/email_link/email-link.vue +22 -7
  15. package/components/product/add-to-cart-btn.vue +5 -1
  16. package/components/product/editor_pricing/editor-pricing.scss +3 -0
  17. package/components/product/editor_pricing/editor-pricing.vue +59 -51
  18. package/components/product/layouts/product_colors_selector/product_colors_selector_options/product-colors-selector-options.scss +15 -3
  19. package/components/product/layouts/product_colors_selector/product_colors_selector_options/product-colors-selector-options.vue +9 -1
  20. package/components/product/product_check_delivery/product-check-delivery.vue +1 -1
  21. package/components/product/product_color_image/product-color-image.scss +1 -1
  22. package/components/product/product_pricing_tiers/product-pricing-tiers.vue +7 -3
  23. package/components/product/products_multipacks_size_selector_color/products-multipacks-size-selector-color.vue +1 -1
  24. package/components/product/products_size_selector_color/product_size_selector_color/product-size-selector-color.vue +35 -9
  25. package/feeds/google-shopping.js +1 -1
  26. package/mixins/add-to-cart.js +7 -5
  27. package/mixins/product-view.js +1 -1
  28. package/package.json +1 -1
  29. package/pages/checkout/order.vue +3 -2
  30. package/store/product.js +11 -2
@@ -26,7 +26,7 @@ export class Layer {
26
26
  cornerSize = 13;
27
27
  cornerStyle = 'circle';
28
28
  lockScalingFlip = true;
29
- padding = 0;
29
+ padding = 5;
30
30
  selectionDashArray = [10, 5];
31
31
  borderDashArray = [5, 3];
32
32
  cornerDashArray = [10, 5];
@@ -11,6 +11,7 @@
11
11
  <slot name="toggle" :uploading="uploading"></slot>
12
12
  <slot name="progress" :progress="progress"></slot>
13
13
  <input
14
+ :id="'fileUpload' + uniqueKey"
14
15
  ref="fileUploaderField"
15
16
  class="FileUploader__input"
16
17
  type="file"
@@ -20,6 +21,10 @@
20
21
  :required="required"
21
22
  :disabled="disabled || uploading"
22
23
  @change="fileChange" />
24
+ <label
25
+ :for="'fileUpload' + uniqueKey"
26
+ class="FileUploader__input-label">
27
+ </label>
23
28
  </div>
24
29
  </div>
25
30
  <div v-if="hasUploadLink">
@@ -59,14 +64,14 @@ export default {
59
64
  rendered: null,
60
65
  error: null,
61
66
  progress: 0,
62
- // timelineLite: new TimelineLite()
67
+ uniqueKey: Math.random()
63
68
  };
64
69
  },
65
70
  methods: {
66
71
  async onPastLink(event) {
67
72
  const url = event.target.value?.trim();
68
73
  if (!url) return;
69
-
74
+
70
75
  try {
71
76
  const response = await fetch(url);
72
77
  const blob = await response.blob();
@@ -167,7 +172,9 @@ export default {
167
172
  display: inline-block;
168
173
  position: relative;
169
174
  width: inherit;
175
+ cursor: pointer !important;
170
176
  &:before {
177
+ cursor: pointer !important;
171
178
  content: '';
172
179
  position: absolute;
173
180
  width: 100%;
@@ -209,10 +216,19 @@ export default {
209
216
  width: 100%;
210
217
  height: 100%;
211
218
  position: absolute;
212
- cursor: pointer;
213
219
  &.disabled {
214
220
  cursor: default;
215
221
  }
222
+
223
+ &-label {
224
+ opacity: 0;
225
+ cursor: pointer;
226
+ position: absolute;
227
+ top: 0;
228
+ right: 0;
229
+ bottom: 0;
230
+ left: 0;
231
+ }
216
232
  }
217
233
  }
218
234
  </style>
@@ -17,7 +17,7 @@
17
17
  </label>
18
18
  <input
19
19
  id="payment-amount"
20
- :value="`${amount} AUD`"
20
+ :value="`${amount} ${currency ? currency.isoCode : 'AUD'}`"
21
21
  name="payment-amount"
22
22
  placeholder="payment-amount"
23
23
  disabled
@@ -63,6 +63,16 @@
63
63
  }
64
64
  }
65
65
  }
66
+ &__controls {
67
+ display: flex;
68
+ gap: 20px;
69
+ .EditorPrintAreaOptions__wrapper {
70
+ padding: 0;
71
+ }
72
+ .Btn__wrapper {
73
+ height: 44px;
74
+ }
75
+ }
66
76
  }
67
77
  .EditorLayersGroup {
68
78
  &__wrapper {
@@ -21,7 +21,16 @@
21
21
  <i class="icon-arrow-left EditorLayers__close-editable-icon"></i>
22
22
  See all design layers
23
23
  </div>
24
- <div>
24
+ <div class="EditorLayers__controls">
25
+ <editor-print-area-options
26
+ v-if="selectedLayer"
27
+ :selected="selectedLayerPrintArea"
28
+ :side="selectedLayer.sideId"
29
+ :product="product"
30
+ :layers="editableLayers"
31
+ @select="handlePrintAreaSelect"
32
+ @option-mouseover="toogleBoundBox(true, $event)"
33
+ @option-mouseleave="toogleBoundBox(false, $event)" />
25
34
  <btn
26
35
  btn-class="green"
27
36
  btn-label="Save"
@@ -29,15 +38,6 @@
29
38
  </div>
30
39
  </div>
31
40
  </template>
32
- <template #after-canvas="{ invalid }">
33
- <editor-print-area-options
34
- v-if="selectedLayer"
35
- :selected="selectedLayerPrintArea"
36
- :side="selectedLayer.sideId"
37
- :product="product"
38
- :layers="editableLayers"
39
- @select="handlePrintAreaSelect" />
40
- </template>
41
41
  </component>
42
42
  </div>
43
43
  <div
@@ -163,7 +163,8 @@ export default {
163
163
  'selectedLayer',
164
164
  'editModeSelectedLayer',
165
165
  'editableSide',
166
- 'selectedPrintArea'
166
+ 'selectedPrintArea',
167
+ 'editablePrintArea'
167
168
  ]),
168
169
  editableLayerType() {
169
170
  return this.selectedLayer ? `editor-layer-form-${this.selectedLayer.type}` : null;
@@ -217,7 +218,8 @@ export default {
217
218
  'updateTemplateLayer',
218
219
  'setEditableSide',
219
220
  'setSelectedPrintArea',
220
- 'setEditablePrintArea'
221
+ 'setEditablePrintArea',
222
+ 'setPreviewPrintArea'
221
223
  ]),
222
224
  ...mapActions(['increaseLayersUpdatesCount']),
223
225
  openSelectedLayerGroup(selected) {
@@ -284,7 +286,14 @@ export default {
284
286
 
285
287
  const printSize = printArea.printSize._id;
286
288
  this.setLayerField({ field: 'printSize', value: printSize });
287
- console.log('handlePrintAreaSelect: ', option, this.selectedLayer);
289
+
290
+ this.toggleOpenedGroup(printArea, true);
291
+ },
292
+ toogleBoundBox(state, option) {
293
+ if (option) {
294
+ const printArea = state ? option.printArea : this.editablePrintArea;
295
+ this.setPreviewPrintArea(printArea);
296
+ }
288
297
  }
289
298
  }
290
299
  };
@@ -19,6 +19,17 @@
19
19
  }
20
20
  }
21
21
  }
22
+ &__placeholder {
23
+ position: absolute;
24
+ text-transform: uppercase;
25
+ font-size: 22px;
26
+ font-weight: 800;
27
+ left: 0;
28
+ right: 0;
29
+ text-align: center;
30
+ top: 50%;
31
+ z-index: 1;
32
+ }
22
33
  &__content {
23
34
  padding-top: 24px;
24
35
  }
@@ -7,6 +7,12 @@
7
7
  </slot>
8
8
 
9
9
  <div class="EditorLayerFormText__preview">
10
+ <div
11
+ v-if="isVisiblePlaceholder"
12
+ class="EditorLayerFormText__placeholder"
13
+ @click="editText()">
14
+ Add Text
15
+ </div>
10
16
  <canvas ref="previewCanvas"></canvas>
11
17
  </div>
12
18
 
@@ -171,19 +177,23 @@ import ColorPicker from '@lancom/shared/components/common/color-picker';
171
177
  import EditorLayerCommonFields from '../editor_layer_common_fields/editor-layer-common-fields';
172
178
  import FontFamilySelect from './font-family_select/font-family-select';
173
179
 
174
- const editableModel = (field, prop) => ({
175
- get() {
176
- return prop
177
- ? this[`${field}_options`].find(o => o[prop] === this.layer[field])
178
- : this.layer[field];
179
- },
180
- set(v) {
181
- const value = prop && Object.prototype.hasOwnProperty.call(v, prop) ? v[prop] : v;
182
- this.$emit('set-layer-field', { field, value });
183
- this.textObject[field] = value;
184
- this.fabricCanvas.renderAll();
185
- }
186
- });
180
+ const editableModel = function (field, prop) {
181
+ return {
182
+ get() {
183
+ return prop
184
+ ? this[`${field}_options`].find(o => o[prop] === this.layer[field])
185
+ : this.layer[field];
186
+ },
187
+ set(v) {
188
+ const value = prop && Object.prototype.hasOwnProperty.call(v, prop) ? v[prop] : v;
189
+ this.$emit('set-layer-field', { field, value });
190
+ if (field !== 'copy') {
191
+ this.textObject[field] = value;
192
+ }
193
+ this.fabricCanvas.renderAll();
194
+ }
195
+ };
196
+ };
187
197
  export default {
188
198
  name: 'EditorLayerFormText',
189
199
  components: {
@@ -249,6 +259,9 @@ export default {
249
259
  },
250
260
  fontFamily_options() {
251
261
  return availableFonts.map(({ label, alias }) => ({ label, value: alias }));
262
+ },
263
+ isVisiblePlaceholder() {
264
+ return !this.copy?.trim() && !this.isEditing;
252
265
  }
253
266
  },
254
267
  mounted() {
@@ -261,6 +274,14 @@ export default {
261
274
  }
262
275
  },
263
276
  methods: {
277
+ editText() {
278
+ if (!this.isEditing) {
279
+ this.fabricCanvas.setActiveObject(this.textObject);
280
+ this.textObject.enterEditing();
281
+ this.textObject.hiddenTextarea.focus();
282
+ this.isEditing = true;
283
+ }
284
+ },
264
285
  async selectFont({ value }) {
265
286
  const font = await loadFont(value);
266
287
  Object.keys(font).forEach(field => this.$emit('set-layer-field', {
@@ -288,7 +309,7 @@ export default {
288
309
  initFabricCanvas() {
289
310
  if (this.$refs.previewCanvas) {
290
311
  this.fabricCanvas = new fabric.Canvas(this.$refs.previewCanvas, {
291
- width: 300,
312
+ width: this.$refs.previewCanvas.parentElement.offsetWidth - 100,
292
313
  height: 200,
293
314
  uniScaleTransform: false
294
315
  });
@@ -311,6 +332,11 @@ export default {
311
332
  this.syncTextFromCanvas();
312
333
  }
313
334
  });
335
+ this.fabricCanvas.on('mouse:down', options => {
336
+ if (options.target === this.textObject) {
337
+ this.editText();
338
+ }
339
+ });
314
340
  },
315
341
  async renderLayer() {
316
342
  try {
@@ -347,12 +373,10 @@ export default {
347
373
 
348
374
  this.fabricCanvas.add(textObj);
349
375
  this.fabricCanvas.renderAll();
376
+
350
377
  this.textObject = textObj;
351
378
 
352
- this.$nextTick(() => {
353
- this.fabricCanvas.setActiveObject(textObj);
354
- textObj.enterEditing();
355
- });
379
+ this.editText();
356
380
  } catch (error) {
357
381
  console.error('Error rendering layer:', error);
358
382
  }
@@ -21,12 +21,11 @@
21
21
  </div>
22
22
 
23
23
  <file-uploader
24
+ class="EditorLayersToolbar__option"
24
25
  :multiple="false"
25
26
  :url="`image/editor/${shop._id}/${product._id}`"
26
27
  @onuploaded="handleUploaded">
27
- <div
28
- slot="toggle"
29
- class="EditorLayersToolbar__option">
28
+ <div slot="toggle">
30
29
  <i class="icon-layer-art EditorLayersToolbar__option-icon"></i>
31
30
  <div class="lc_black">
32
31
  Add art
@@ -17,6 +17,9 @@
17
17
  &.selected {
18
18
  background-color: $green;
19
19
  }
20
+ > * {
21
+ pointer-events: none;
22
+ }
20
23
  }
21
24
  &__info {
22
25
  text-align: center;
@@ -51,7 +54,7 @@
51
54
  }
52
55
  }
53
56
  }
54
-
57
+
55
58
  // TEE - FRONT
56
59
  @each $position in center, left, right {
57
60
  &.front.tee.#{$position} {
@@ -72,4 +75,4 @@
72
75
  background-image: url(./../../../../static/icons/print-area/sleeve-right_tee_right.svg);
73
76
  }
74
77
  }
75
- }
78
+ }
@@ -224,7 +224,7 @@ export default {
224
224
  const name = this.product.name;
225
225
  const color = this.availableColors?.find(c => c.alias === this.$route.query?.color);
226
226
  const size = this.availableSizes?.find(c => c.shortName === this.$route.query?.size);
227
- return `${name}${(color || size) ? ` | ${[color?.name, size?.shortName].filter(i => !!i).join(', ') || ''}` : ''}`;
227
+ return `${name}${(color || size) ? ` | ${[color?.name, size?.name].filter(i => !!i).join(', ') || ''}` : ''}`;
228
228
  },
229
229
  hasImages() {
230
230
  return this.modelImages.length > 0;
@@ -29,6 +29,7 @@
29
29
  <editor-workspace-side
30
30
  ref="editor"
31
31
  :key="side"
32
+ :is-edit-mode="isEditMode"
32
33
  :side="side"
33
34
  :print-area="editablePrintArea"
34
35
  :zoom-size="sideZoomSize"
@@ -65,6 +66,7 @@
65
66
  <editor-workspace-side
66
67
  ref="editor"
67
68
  :key="editableSide.id"
69
+ :is-edit-mode="isEditMode"
68
70
  :side="editableSide.id"
69
71
  :print-area="editablePrintArea"
70
72
  :print-area-size="selectedPrintArea"
@@ -153,6 +155,10 @@ export default {
153
155
  visibleBackgroundImage: {
154
156
  type: Boolean,
155
157
  default: true
158
+ },
159
+ isEditMode: {
160
+ type: Boolean,
161
+ default: true
156
162
  }
157
163
  },
158
164
  data() {
@@ -64,6 +64,24 @@
64
64
  bottom: 0;
65
65
  z-index: 1000;
66
66
  }
67
+ &__add-btn {
68
+ background-color: white;
69
+ opacity: 0.8;
70
+ transition: transform 0.3s ease, box-shadow 0.3s ease;
71
+ border-radius: 3px;
72
+ box-shadow: 0px 0px 0px rgba(75, 75, 75, 0.2);
73
+ margin: 3px 0;
74
+ padding: 5px;
75
+ min-width: 73px;
76
+ &:nth-child(3) {
77
+ padding: 5px 5px 3px 5px;
78
+ }
79
+ &:hover {
80
+ opacity: 1;
81
+ transform: translateY(-1px);
82
+ box-shadow: 0px 5px 15px rgba(75, 75, 75, 0.2);
83
+ }
84
+ }
67
85
  &__placeholder {
68
86
  position: absolute;
69
87
  z-index: 999;
@@ -72,6 +90,7 @@
72
90
  align-items: center;
73
91
  justify-content: center;
74
92
  flex-direction: column;
93
+ font-size: 12px;
75
94
 
76
95
  // @media (max-width: $bp-extra-small-max) {
77
96
  // font-size: 10px;
@@ -86,8 +105,8 @@
86
105
  letter-spacing: -0.5px;
87
106
  &-option {
88
107
  cursor: pointer;
89
- margin: 5px 0;
90
108
  text-transform: uppercase;
109
+ display: inline-block;
91
110
  span {
92
111
  &:last-child {
93
112
  display: none;
@@ -102,6 +121,10 @@
102
121
  }
103
122
  &-divider {
104
123
  color: $grey_1;
124
+ padding: 2px;
125
+ background-color: white;
126
+ border-radius: 2px;
127
+ opacity: 0.7;
105
128
  }
106
129
  &-progress {
107
130
  position: absolute;
@@ -185,4 +208,4 @@
185
208
  background-color: #EB5757;
186
209
  }
187
210
  }
188
- }
211
+ }
@@ -1,13 +1,12 @@
1
1
  <template>
2
2
  <div
3
+ v-click-outside="onOutsideClick"
3
4
  class="EditorWorkspaceSide__wrapper"
4
5
  :style="backgroundColor"
5
6
  :class="{
6
7
  'EditorWorkspaceSide__wrapper--zoom-in': isZoomed
7
8
  }"
8
- v-click-outside="onOutsideClick"
9
9
  @mouseover="toogleBoundBox(true)"
10
- @mouseleave="toogleBoundBox(false)"
11
10
  @mouseup="toggleOnpressImage(false)">
12
11
  <div
13
12
  v-if="hasOnpressImage || hasWireframeImage"
@@ -60,15 +59,23 @@
60
59
  ignore-document-click
61
60
  :style="positionPlaceholder">
62
61
  <div
63
- @click="createTextLayer"
64
- class="EditorWorkspaceSide__placeholder-option">
65
- <span>Type here</span>
62
+ class="EditorWorkspaceSide__placeholder-option"
63
+ :class="{
64
+ 'EditorWorkspaceSide__add-btn': !printAreaLayers.length
65
+ }"
66
+ @click="createTextLayer">
67
+ <span>
68
+ <i class="icon-layer-text"></i> Add Text
69
+ </span>
66
70
  <span style="margin-top: -0.5px;">Text</span>
67
71
  </div>
68
72
  <div class="EditorWorkspaceSide__placeholder-divider">
69
73
  or
70
74
  </div>
71
- <div>
75
+ <div
76
+ :class="{
77
+ 'EditorWorkspaceSide__add-btn': !printAreaLayers.length
78
+ }">
72
79
  <file-uploader
73
80
  :multiple="false"
74
81
  :url="`image/editor/${shop._id}/${product._id}`"
@@ -165,6 +172,10 @@ export default {
165
172
  visibleBackgroundImage: {
166
173
  type: Boolean,
167
174
  default: true
175
+ },
176
+ isEditMode: {
177
+ type: Boolean,
178
+ default: true
168
179
  }
169
180
  },
170
181
  data() {
@@ -199,7 +210,8 @@ export default {
199
210
  'editModeSelectedLayer',
200
211
  'offsetWarningVisible',
201
212
  'showRecommendationToUseLargerImage',
202
- 'showErrorAboutSmallImage'
213
+ 'showErrorAboutSmallImage',
214
+ 'previewPrintArea'
203
215
  ]),
204
216
  editableLayersCount() {
205
217
  return this.editableLayers?.length;
@@ -235,16 +247,22 @@ export default {
235
247
  },
236
248
  positionPlaceholder() {
237
249
  const { center, left, top, width, height } = this.fabricHelper.printAreaRect;
238
- // console.log('this.fabricHelper.printAreaRect: ', this.fabricHelper.printAreaRect);
239
250
  const ratio = this.calcWorkspaceSize() / this.editorSize.width;
240
- return this.printAreaLayers.length > 0 ? {
241
- top: `${(top + height) * ratio}px`,
242
- left: `${left * ratio}px`
243
- } : {
244
- top: `${top * ratio}px`,
245
- left: `${left * ratio}px`,
246
- width: `${Math.max(60, width * ratio)}px`,
247
- height: `${height * ratio}px`,
251
+ if (this.printAreaLayers.length > 0) {
252
+ return {
253
+ top: `${(top + height) * ratio}px`,
254
+ left: `${left * ratio}px`
255
+ };
256
+ }
257
+ const centerX = center?.x ?? (left + width / 2);
258
+ const centerY = center?.y ?? (top + height / 2);
259
+ const placeholderWidth = Math.max(100, width * ratio);
260
+ const placeholderHeight = height * ratio;
261
+ return {
262
+ top: `${(centerY * ratio) - (placeholderHeight / 2)}px`,
263
+ left: `${(centerX * ratio) - (placeholderWidth / 2)}px`,
264
+ width: `${placeholderWidth}px`,
265
+ height: `${placeholderHeight}px`
248
266
  };
249
267
  },
250
268
  printAreaIsSmall() {
@@ -284,6 +302,10 @@ export default {
284
302
  this.fabricHelper.setPrintArea(value, this.editorSize, this.product);
285
303
  this.redraw();
286
304
  },
305
+ previewPrintArea(value) {
306
+ this.fabricHelper.setPrintArea(value, this.editorSize, this.product);
307
+ this.redraw();
308
+ },
287
309
  zoomSize() {
288
310
  this.handleWorkspaceSize();
289
311
  }
@@ -346,15 +368,12 @@ export default {
346
368
  this.$emit('workspace', { size: this.calcWorkspaceSize(true), fabricHelper: this.fabricHelper });
347
369
  },
348
370
  initEventsListeners() {
349
- // document.addEventListener('mousedown', this.onDocumentStartClick);
350
371
  window.addEventListener('resize', this.onResize);
351
372
  },
352
373
  clearEventsListeners() {
353
- // document.removeEventListener('mousedown', this.onDocumentStartClick);
354
374
  window.removeEventListener('resize', this.onResize);
355
375
  },
356
376
  onDocumentStartClick({ target }) {
357
- // todo: replace by click outside plugin?
358
377
  console.log('target: ', target);
359
378
  const isCanvasElement = target.tagName === 'CANVAS';
360
379
  const isIgnoreClick = !!findParentByPredicate(target, element => element.hasAttribute('ignore-document-click'));
@@ -386,7 +405,7 @@ export default {
386
405
  });
387
406
  this.fabricHelper.on('setDeleteButtonPosition', this.setDeleteButtonPosition);
388
407
  this.fabricHelper.on('outOfPrintArea', this.setOffsetWarningVisibility);
389
- // todo center to print area
408
+
390
409
  this.updateBackgroundImage();
391
410
  this.redrawWithThrottle();
392
411
  },
@@ -451,11 +470,18 @@ export default {
451
470
  const image = this.fabricHelper.getLayersAsImage();
452
471
  this.setLayersThumbnail({ side: this.side, value: image });
453
472
  },
454
- createTextLayer() {
473
+ async createTextLayer() {
455
474
  this.addedFromCanvas = true;
456
475
  window.scrollTo(0, 0);
457
- this.createLayer({ type: 'text', isEditMode: true });
476
+ const layer = await this.createLayer({ type: 'text', isEditMode: this.isEditMode });
458
477
  this.visibleWireframe = true;
478
+ if (!this.isEditMode) {
479
+ setTimeout(() => {
480
+ this.setSelectedLayer(layer);
481
+ this.setEditModeSelectedLayer(true);
482
+ this.toogleBoundBox(true);
483
+ });
484
+ }
459
485
  },
460
486
  async handleUploaded({ url, size, fileName }) {
461
487
  window.scrollTo(0, 0);
@@ -477,8 +503,6 @@ export default {
477
503
  const area = state ? option.printArea : this.printArea;
478
504
  this.fabricHelper.setPrintArea(area, this.editorSize, this.product);
479
505
  }
480
-
481
- this.fabricHelper.background.toggleBorder(state);
482
506
  }
483
507
  }
484
508
  };
@@ -1,22 +1,37 @@
1
1
  <template>
2
- <a ref="link"></a>
2
+ <a
3
+ v-if="emailDisplaySSR"
4
+ :href="`mailto:${emailAddress}`">{{ emailAddress }}</a>
5
+ <a
6
+ v-else
7
+ ref="link"></a>
3
8
  </template>
4
-
9
+
5
10
  <script>
11
+ import { mapGetters } from 'vuex';
12
+
6
13
  export default {
7
14
  name: 'EmailLink',
8
15
  props: {
9
16
  email: {
10
- type: Array | String,
17
+ type: [Array, String],
11
18
  required: true
12
19
  }
13
20
  },
21
+ computed: {
22
+ ...mapGetters(['SETTINGS']),
23
+ emailAddress() {
24
+ return (Array.isArray(this.email) ? this.email : [this.email]).join('@');
25
+ },
26
+ emailDisplaySSR() {
27
+ return !!this.SETTINGS?.EMAIL_DISPLAY_SSR;
28
+ }
29
+ },
14
30
  mounted() {
15
- if (process.client) {
16
- const email = (Array.isArray(this.email) ? this.email : [this.email]).join('@');
31
+ if (!this.emailDisplaySSR) {
17
32
  const linkEl = this.$refs.link;
18
- linkEl.href = `mailto:${email}`;
19
- linkEl.textContent = email;
33
+ linkEl.href = `mailto:${this.emailAddress}`;
34
+ linkEl.textContent = this.emailAddress;
20
35
  }
21
36
  }
22
37
  };
@@ -2,7 +2,7 @@
2
2
  <btn
3
3
  :btn-class="btnClass"
4
4
  btn-label="Add to cart"
5
- :btn-disabled="addToCartDisabled"
5
+ :btn-disabled="!allowClick && addToCartDisabled"
6
6
  :btn-processing="addingToCart"
7
7
  @onclick="proceedToCard">
8
8
  <template slot="icon-before">
@@ -21,6 +21,10 @@ export default {
21
21
  btnClass: {
22
22
  type: String,
23
23
  default: 'green'
24
+ },
25
+ allowClick: {
26
+ type: Boolean,
27
+ default: false
24
28
  }
25
29
  }
26
30
  };
@@ -1,6 +1,9 @@
1
1
  @import "@/assets/scss/variables";
2
2
  .EditorPricing {
3
3
  &__main {
4
+ &-alerts-wrapper {
5
+ display: contents;
6
+ }
4
7
  &-alert {
5
8
  display: flex;
6
9
  align-items: center;
@@ -6,65 +6,68 @@
6
6
  class="EditorPricing__details">
7
7
  <editor-pricing-details />
8
8
  </div>
9
-
10
9
  <div
11
- v-if="addedToCart"
12
- class="EditorPricing__main-alert">
13
- <img src="~static/images/smile.svg" />
14
- Products have been added to cart
15
- </div>
10
+ v-if="visibleAlert"
11
+ class="EditorPricing__alerts-wrapper">
12
+ <div
13
+ v-if="addedToCart"
14
+ class="EditorPricing__main-alert">
15
+ <img src="~static/images/smile.svg" />
16
+ Products have been added to cart
17
+ </div>
16
18
 
17
- <div
18
- v-else-if="!hasUsedSimpleProducts && (!layers.length && isPrintPricing)"
19
- class="EditorPricing__main-alert">
20
- <img src="~static/images/sad.svg" />
21
- No products selected and no prints defined
22
- </div>
19
+ <div
20
+ v-else-if="!hasUsedSimpleProducts && (!layers.length && isPrintPricing)"
21
+ class="EditorPricing__main-alert">
22
+ <img src="~static/images/sad.svg" />
23
+ No products selected and no prints defined
24
+ </div>
23
25
 
24
- <div
25
- v-else-if="hasUsedSimpleProducts && ((!isValidPrintOnly || !layers.length && isPrintPricing))"
26
- class="EditorPricing__main-alert">
27
- <img src="~static/images/sad.svg" />
28
- No prints defined
29
- </div>
26
+ <div
27
+ v-else-if="hasUsedSimpleProducts && ((!isValidPrintOnly || !layers.length && isPrintPricing))"
28
+ class="EditorPricing__main-alert">
29
+ <img src="~static/images/sad.svg" />
30
+ No prints defined
31
+ </div>
30
32
 
31
- <div
32
- v-else-if="!hasUsedSimpleProducts && layers.length"
33
- class="EditorPricing__main-alert">
34
- <img src="~static/images/sad.svg" />
35
- <a
36
- href="#"
37
- @click.prevent.stop="$emit('choose-products')">
38
- Choose products here to continue
39
- </a>
40
- </div>
33
+ <div
34
+ v-else-if="!hasUsedSimpleProducts && layers.length"
35
+ class="EditorPricing__main-alert">
36
+ <img src="~static/images/sad.svg" />
37
+ <a
38
+ href="#"
39
+ @click.prevent.stop="$emit('choose-products')">
40
+ Please enter the quantity required
41
+ </a>
42
+ </div>
41
43
 
42
- <div
43
- v-else-if="!isValidMiltipackOrderQuantity"
44
- class="EditorPricing__main-alert">
45
- <img src="~static/images/sad.svg" />
46
- bulk pack {{ multipack.qty }} - add more x {{ multipack.qty - usedSimpleProductsQuantity }}
47
- </div>
44
+ <div
45
+ v-else-if="!isValidMiltipackOrderQuantity"
46
+ class="EditorPricing__main-alert">
47
+ <img src="~static/images/sad.svg" />
48
+ bulk pack {{ multipack.qty }} - add more x {{ multipack.qty - usedSimpleProductsQuantity }}
49
+ </div>
48
50
 
49
- <div
50
- v-else-if="!isValidOrderQuantity && isValidBigSizeOrderQuantity && minimumOrderQuantity > 1"
51
- class="EditorPricing__main-alert">
52
- <img src="~static/images/sad.svg" />
53
- Minimum order of these items must be {{ minimumOrderQuantity }} or more
54
- </div>
51
+ <div
52
+ v-else-if="!isValidOrderQuantity && isValidBigSizeOrderQuantity && minimumOrderQuantity > 1"
53
+ class="EditorPricing__main-alert">
54
+ <img src="~static/images/sad.svg" />
55
+ Minimum order of these items must be {{ minimumOrderQuantity }} or more
56
+ </div>
55
57
 
56
- <div
57
- v-else-if="!isValidBigSizeOrderQuantity"
58
- class="EditorPricing__main-alert">
59
- <img src="~static/images/sad.svg" />
60
- Minimum order of 5XL-7XL should be 50%
61
- </div>
58
+ <div
59
+ v-else-if="!isValidBigSizeOrderQuantity"
60
+ class="EditorPricing__main-alert">
61
+ <img src="~static/images/sad.svg" />
62
+ Minimum order of 5XL-7XL should be 50%
63
+ </div>
62
64
 
63
- <div
64
- v-else-if="isValidOrderQuantity"
65
- class="EditorPricing__main-alert">
66
- <img src="~static/images/smile.svg" />
67
- {{ hasPrintIssues ? 'Proceed with order, Will be in touch regarding print issue' : 'All good to go!' }}
65
+ <div
66
+ v-else-if="isValidOrderQuantity"
67
+ class="EditorPricing__main-alert">
68
+ <img src="~static/images/smile.svg" />
69
+ {{ hasPrintIssues ? 'Proceed with order, Will be in touch regarding print issue' : 'All good to go!' }}
70
+ </div>
68
71
  </div>
69
72
 
70
73
  <div
@@ -129,10 +132,15 @@ export default {
129
132
  hasPricing: {
130
133
  type: Boolean,
131
134
  default: true
135
+ },
136
+ visibleAlert: {
137
+ type: Boolean,
138
+ default: true
132
139
  }
133
140
  },
134
141
  data() {
135
142
  return {
143
+ clickedAddToCart: false,
136
144
  price: null,
137
145
  showDetails: false,
138
146
  calculatePriceWithDebounce: debounce(() => this.calculateProductPrice({ shop: this.shop, country: this.country, currency: this.currency }), 500)
@@ -45,6 +45,18 @@
45
45
  &-selected-icon {
46
46
  position: absolute;
47
47
  }
48
+ &>i {
49
+ position: absolute;
50
+ z-index: 3;
51
+ top: -3px;
52
+ right: -5px;
53
+ color: $purple;
54
+ background-color: white;
55
+ border-radius: 50%;
56
+ padding: 3px 0;
57
+ font-size: 15px;
58
+ box-shadow: 0 0 2px grey;
59
+ }
48
60
  }
49
61
  &__products-count {
50
62
  position: absolute;
@@ -58,11 +70,11 @@
58
70
  font-weight: bold;
59
71
  font-size: 12px;
60
72
  line-height: 16px;
61
- text-align: center;
73
+ text-align: center;
62
74
  span:not(:empty) {
63
75
  background-color: white;
64
76
  border-radius: 50%;
65
77
  width: 16px;
66
78
  }
67
- }
68
- }
79
+ }
80
+ }
@@ -7,6 +7,10 @@
7
7
  class="ProductColorsSelectorOptions__color"
8
8
  :class="{ selected: editableColor === color }"
9
9
  @click="toggleSelection(color)">
10
+ <i
11
+ v-if="isClearanceColor(color)"
12
+ v-tooltip="'Clearance'"
13
+ class="icon-percent"></i>
10
14
  <product-color-image
11
15
  :color="color"
12
16
  :lazy="true"
@@ -44,7 +48,8 @@ export default {
44
48
  'template',
45
49
  'editableColor',
46
50
  'simpleProducts',
47
- 'templateColors'
51
+ 'templateColors',
52
+ 'product'
48
53
  ])
49
54
  },
50
55
  mounted() {
@@ -67,6 +72,9 @@ export default {
67
72
  },
68
73
  countColorProducts({ _id }) {
69
74
  return this.simpleProducts.reduce((count, { color, amount }) => color._id === _id ? count + (amount || 0) : count, 0);
75
+ },
76
+ isClearanceColor(color) {
77
+ return this.product.colorsPricing?.some(c => c.colors?.includes(color._id) && c.clearance);
70
78
  }
71
79
  }
72
80
  };
@@ -14,7 +14,7 @@
14
14
  </div>
15
15
  <div class="ProductCheckDelivery__postcode">
16
16
  <postcode-select
17
- v-model="postcode"
17
+ v-model="suburb.postcode"
18
18
  :suburb="suburb"
19
19
  :only-postcode="true"
20
20
  label-text="Postcode"
@@ -5,4 +5,4 @@
5
5
  &.zoomedIn {
6
6
  background-size: 200%;
7
7
  }
8
- }
8
+ }
@@ -5,7 +5,7 @@
5
5
  </h3>
6
6
  <div class="ProductPricingTiers__tiers">
7
7
  <div
8
- v-for="(tier, index) in defaultSimpleProduct.pricing"
8
+ v-for="(tier, index) in visibleTiers"
9
9
  :key="index"
10
10
  class="ProductPricingTiers__tier"
11
11
  @click="selectTier(tier)">
@@ -41,8 +41,12 @@ export default {
41
41
  },
42
42
  mixins: [MultipackMixin],
43
43
  computed: {
44
- ...mapGetters('product', ['product', 'editableColor', 'priceIncludeGST', 'defaultSimpleProduct']),
45
- ...mapGetters(['pricingSettings', 'currency'])
44
+ ...mapGetters('product', ['product', 'editableColor', 'priceIncludeGST', 'isPrintPricing', 'printsPrice', 'defaultSimpleProduct']),
45
+ ...mapGetters(['pricingSettings', 'currency']),
46
+ visibleTiers() {
47
+ const pricing = this.defaultSimpleProduct?.pricing || [];
48
+ return pricing.map(p => ({ ...p, price: p.price + this.printsPrice }));
49
+ }
46
50
  },
47
51
  methods: {
48
52
  selectTier(tier) {
@@ -78,7 +78,7 @@ export default {
78
78
  return sortBySize(this.editableColorSimpleProducts);
79
79
  },
80
80
  pricing() {
81
- return this.defaultSimpleProduct.pricing;
81
+ return this.defaultSimpleProduct?.pricing || [];
82
82
  }
83
83
  },
84
84
  methods: {
@@ -46,16 +46,42 @@
46
46
  </div>
47
47
  </template>
48
48
  </v-popover>
49
- <span
49
+ <v-popover
50
50
  v-if="printsPrice > 0"
51
- class="lc_regular13">
52
- +
53
- <price
54
- :price="printsPrice"
55
- :with-gst="withGst" />
56
- </span>
51
+ trigger="hover"
52
+ :delay="{ show: 200, hide: 400 }"
53
+ popover-class="tooltip popover white"
54
+ class="ProductSizeSelectorColor__price ml-6"
55
+ style="display: inline-block;">
56
+ <span class="lc_regular13">
57
+ {{ productPrintsPrice | price(currency) }}
58
+ </span>
59
+ <template slot="popover">
60
+ <div>
61
+ <table class="lc_table bordered">
62
+ <thead>
63
+ <tr class="highlighted">
64
+ <th>Area</th>
65
+ <th>Size</th>
66
+ <th>Type</th>
67
+ <th>Price</th>
68
+ </tr>
69
+ </thead>
70
+ <tbody>
71
+ <tr
72
+ v-for="(print, index) in productPricingPrints"
73
+ :key="index">
74
+ <td>{{ print.printArea ? print.printArea.name : '' }}</td>
75
+ <td>{{ print.printSize ? print.printSize.name : '' }}</td>
76
+ <td>{{ print.printType ? print.printType.name : '' }}</td>
77
+ <td>{{ (withGst ? print.price : print.priceWithoutTax) | price(currency) }}</td>
78
+ </tr>
79
+ </tbody>
80
+ </table>
81
+ </div>
82
+ </template>
83
+ </v-popover>
57
84
  </div>
58
-
59
85
  </div>
60
86
  <div class="hidden-md-and-up">
61
87
  <div
@@ -126,7 +152,7 @@ export default {
126
152
  }
127
153
  },
128
154
  computed: {
129
- ...mapGetters('product', ['isPrintPricing', 'usedSimpleProductsQuantity', 'printsPrice']),
155
+ ...mapGetters('product', ['isPrintPricing', 'usedSimpleProductsQuantity', 'printsPrice', 'productPricingPrints']),
130
156
  ...mapGetters(['gstTax', 'currency']),
131
157
  currentQuantityStockLabel() {
132
158
  return this.currentQuantityStock > 100 ? '100+' : this.currentQuantityStock;
@@ -106,7 +106,7 @@ async function googleShoppingFeed(axios, config, availableStores, country, isEdi
106
106
  info['g:sale_price'] = { _text: `${(sp.clearancePrice)} ${sp.currencyCode || 'AUD'}` };
107
107
  }
108
108
 
109
- if (sp.storeCode) {
109
+ if (sp.storeCode && !config.IGNORE_STORE_CODE) {
110
110
  info['g:store_code'] = { _text: sp.storeCode };
111
111
  info['g:link_template'] = { _text: `${link}&store={store_code}` };
112
112
  if (availableStores) {
@@ -51,6 +51,7 @@ export default {
51
51
  ...mapMutations('layers', ['resetLayers']),
52
52
  async proceedToCard() {
53
53
  if (this.addingToCart || this.addToCartDisabled) {
54
+ this.$emit('proceed', false);
54
55
  return;
55
56
  }
56
57
  try {
@@ -65,20 +66,21 @@ export default {
65
66
  googleClickId: this.googleClickId
66
67
  });
67
68
  this.$toastr.s('Products successfully added to cart');
68
-
69
+
69
70
  this.clearTemplate(true);
70
-
71
+
71
72
  this.calculateCartPrice({ shop: this.shop, country: this.country, currency: this.currency });
72
-
73
+
73
74
  // this.resetLayers();
74
75
  // this.setIsPrintPricing(false);
75
76
  setTimeout(() => {
76
77
  this.addedToCart = true;
77
78
  });
78
- } catch (e) {
79
+ } catch (e) {
79
80
  } finally {
80
81
  this.addingToCart = false;
81
82
  }
83
+ this.$emit('proceed', true);
82
84
  }
83
85
  }
84
- };
86
+ };
@@ -289,7 +289,7 @@ export default (IS_PRODUCT_PRESET_PRINT_PRICING, isEditor = false) => ({
289
289
  url,
290
290
  availability: `https://schema.org/${availability}`,
291
291
  price: +tax(maxPrice, this.gstTax).toFixed(2),
292
- priceCurrency: 'AUD',
292
+ priceCurrency: this.currency?.isoCode || 'AUD',
293
293
  priceValidUntil: '31-12-2030',
294
294
  sku: sp.SKU,
295
295
  ...(brandAdditionalProductsRichSnippet.offers || {})
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lancom/shared",
3
- "version": "0.0.434",
3
+ "version": "0.0.436",
4
4
  "description": "lancom common scripts",
5
5
  "author": "e.tokovenko <e.tokovenko@gmail.com>",
6
6
  "repository": {
@@ -43,6 +43,7 @@ export default {
43
43
  }
44
44
  },
45
45
  computed: {
46
+ ...mapGetters(['currency']),
46
47
  ...mapGetters('cart', ['entitiesWithoutFreeProducts', 'entities', 'coupon', 'cartPricing'])
47
48
  },
48
49
  methods: {
@@ -50,7 +51,7 @@ export default {
50
51
  gtm.push({
51
52
  event: 'begin_checkout',
52
53
  value: this.cartPricing.totalPriceWithoutTax,
53
- currency: 'AUD',
54
+ currency: this.currency ? this.currency.isoCode : 'AUD',
54
55
  coupon: this.cartPricing.coupon?.code,
55
56
  items: this.entitiesWithoutFreeProducts.reduce((products, { guid: productGuid, product, simpleProducts }) => {
56
57
  return [
@@ -62,7 +63,7 @@ export default {
62
63
  item_name: product.name,
63
64
  item_brand: product.brand.name,
64
65
  price: this.cartPricing.products[productGuid]?.products[guid]?.totalPriceWithoutTax,
65
- currency: 'AUD',
66
+ currency: this.currency ? this.currency.isoCode : 'AUD',
66
67
  quantity: amount
67
68
  }))
68
69
  ];
package/store/product.js CHANGED
@@ -17,7 +17,7 @@ export const state = () => ({
17
17
  loadingProductDetails: false,
18
18
  productDetailsKey: Date.now(),
19
19
  images: [],
20
- priceIncludeGST: false,
20
+ priceIncludeGST: true,
21
21
  product: null,
22
22
  preSetPrints: null,
23
23
  loadError: null,
@@ -58,6 +58,7 @@ export const state = () => ({
58
58
  selectedPrintAreas: {},
59
59
  productPricing: null,
60
60
  selectedTab: null,
61
+ previewPrintArea: null,
61
62
  layersUpdatesCount: 0,
62
63
  editableSide: { id: 'front' }
63
64
  });
@@ -173,6 +174,10 @@ export const getters = {
173
174
  const pricePrintsPrice = pricing?.prints?.prints?.reduce((sum, print) => sum + (+print.priceWithoutTax || 0), 0);
174
175
  return pricePrintsPrice >= 0 ? pricePrintsPrice : maxPrintsPrice;
175
176
  },
177
+ productPricingPrints: ({ productPricing, product }) => {
178
+ const pricing = (productPricing?.products || {})[product._id];
179
+ return pricing?.prints?.prints || [];
180
+ },
176
181
  offsetWarningVisible: ({ offsetWarningVisible }) => offsetWarningVisible,
177
182
  showRecommendationToUseLargerImage: ({ selectedLayer }) => {
178
183
  return (
@@ -208,7 +213,8 @@ export const getters = {
208
213
  } else {
209
214
  return PRODUCT_STOCK_STATUS.NOT_IN_STOCK;
210
215
  }
211
- }
216
+ },
217
+ previewPrintArea: ({ previewPrintArea }) => previewPrintArea
212
218
  };
213
219
 
214
220
  export const actions = {
@@ -634,6 +640,9 @@ export const mutations = {
634
640
  },
635
641
  setCalculatingPrice(state, calculatingPrice) {
636
642
  state.calculatingPrice = calculatingPrice;
643
+ },
644
+ setPreviewPrintArea(state, previewPrintArea) {
645
+ state.previewPrintArea = previewPrintArea;
637
646
  }
638
647
  };
639
648