@lancom/shared 0.0.336 → 0.0.338

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 (35) hide show
  1. package/assets/js/utils/colors.js +13 -11
  2. package/assets/js/utils/fabric/selection-style.js +1 -1
  3. package/assets/js/utils/prints.js +1 -1
  4. package/components/checkout/cart/cart.vue +2 -2
  5. package/components/checkout/cart/cart_entity/cart_entity_color_simple_products/cart-entity-color-simple-products.vue +2 -0
  6. package/components/checkout/cart/cart_pricing/cart-pricing.vue +4 -1
  7. package/components/checkout/order/order-success/order-success.vue +1 -1
  8. package/components/common/client_settings/client-settings.scss +4 -4
  9. package/components/common/client_settings/client-settings.vue +5 -1
  10. package/components/common/pricing_table/pricing-table.scss +3 -0
  11. package/components/common/product_side_with_print/product-side-with-print.scss +3 -0
  12. package/components/common/product_side_with_print/product-side-with-print.vue +5 -1
  13. package/components/customer/customer_menu/customer-menu.scss +9 -3
  14. package/components/customer/customer_menu/customer-menu.vue +26 -3
  15. package/components/editor/editor.vue +22 -0
  16. package/components/editor/editor_layers/editor-layers.vue +2 -1
  17. package/components/editor/editor_product_details/editor-product-details.scss +4 -1
  18. package/components/editor/editor_product_details/editor-product-details.vue +3 -1
  19. package/components/editor/editor_workspace/editor_workspace_side/editor-workspace-side.scss +56 -20
  20. package/components/editor/editor_workspace/editor_workspace_side/editor-workspace-side.vue +77 -14
  21. package/components/errors/404.vue +1 -1
  22. package/components/errors/500.vue +1 -1
  23. package/components/product/editor_pricing/editor-pricing.scss +3 -0
  24. package/components/product/editor_pricing/editor-pricing.vue +13 -9
  25. package/components/products/brands/brands.scss +90 -0
  26. package/components/products/brands/brands.vue +65 -0
  27. package/components/products/products_autocomplete/products-autocomplete.scss +3 -0
  28. package/components/products/products_autocomplete/products-autocomplete.vue +7 -7
  29. package/components/products/products_catalog/products-catalog.vue +1 -2
  30. package/components/subscribe/subscribe.vue +1 -1
  31. package/feeds/google-shopping.js +3 -2
  32. package/mixins/add-to-cart.js +3 -0
  33. package/package.json +1 -1
  34. package/store/product.js +2 -2
  35. package/store/products.js +1 -1
@@ -6,9 +6,11 @@ export const isValidImageType = (i, type) => i.type === type || (i.types || []).
6
6
 
7
7
  export const isValidImageTypes = (i, types) => types.some(type => isValidImageType(i, type));
8
8
 
9
- export const getColorImage = (product = {}, size = 'small', type, color, allowAnyColor = false) => {
10
- const colorImage = color && (product.images || []).find(i => isValidImageType(i, type || COLORS_IMAGES_TYPES.FRONT) && (color?._id === i.color || color?._id === i.color?._id));
11
- const image = colorImage || (product.images || []).find(i => isValidImageType(i, type || COLORS_IMAGES_TYPES.FRONT) && (allowAnyColor || !i.color));
9
+ export const getColorImage = (product = {}, size = 'small', type, color, allowAnyColor = false, excludeType = []) => {
10
+ const excludeTypes = (excludeType && Array.isArray(excludeType) ? excludeType : [excludeType]) || [];
11
+ const validImages = (product.images || []).filter(i => !excludeTypes.some(type => isValidImageType(i, type)));
12
+ const colorImage = color && validImages.find(i => isValidImageType(i, type || COLORS_IMAGES_TYPES.FRONT) && (color?._id === i.color || color?._id === i.color?._id));
13
+ const image = colorImage || validImages.find(i => isValidImageType(i, type || COLORS_IMAGES_TYPES.FRONT) && (allowAnyColor || !i.color));
12
14
  return image && staticLink(image[size]);
13
15
  };
14
16
 
@@ -35,11 +37,11 @@ export const getProductCover = (product = {}, size = 'small', type = COLORS_IMAG
35
37
  getFunc(product, type, null, true);
36
38
  };
37
39
 
38
- export const getFillColorImage = (product, type, color, allowAnyColor) => getColorImage(product, 'color', type, color, allowAnyColor);
39
- export const getSmallColorImage = (product, type, color, allowAnyColor) => getColorImage(product, 'small', type, color, allowAnyColor);
40
- export const getMediumColorImage = (product, type, color, allowAnyColor) => getColorImage(product, 'medium', type, color, allowAnyColor);
41
- export const getLargeColorImage = (product, type, color, allowAnyColor) => getColorImage(product, 'large', type, color, allowAnyColor);
42
- export const getOriginColorImage = (product, type, color, allowAnyColor) => getColorImage(product, 'origin', type, color, allowAnyColor);
40
+ export const getFillColorImage = (product, type, color, allowAnyColor, excludeType) => getColorImage(product, 'color', type, color, allowAnyColor, excludeType);
41
+ export const getSmallColorImage = (product, type, color, allowAnyColor, excludeType) => getColorImage(product, 'small', type, color, allowAnyColor, excludeType);
42
+ export const getMediumColorImage = (product, type, color, allowAnyColor, excludeType) => getColorImage(product, 'medium', type, color, allowAnyColor, excludeType);
43
+ export const getLargeColorImage = (product, type, color, allowAnyColor, excludeType) => getColorImage(product, 'large', type, color, allowAnyColor, excludeType);
44
+ export const getOriginColorImage = (product, type, color, allowAnyColor, excludeType) => getColorImage(product, 'origin', type, color, allowAnyColor, excludeType);
43
45
 
44
46
  export const getProductFillCover = (product, type, color) => getProductCover(product, 'color', type, color);
45
47
  export const getProductSmallCover = (product, type, color) => getProductCover(product, 'small', type, color);
@@ -49,12 +51,12 @@ export const getProductOriginCover = (product, type, color) => getProductCover(p
49
51
 
50
52
  export const getBgStyle = img => img && ({ 'background-image': `url("${img}")` });
51
53
 
52
- export function getColorBackgroundStyle(color, skipPattern) {
53
- const { rgb, pattern, name } = color || {};
54
+ export function getColorBackgroundStyle(color, skipPattern, originBackground) {
55
+ const { rgb, pattern, patternOrigin, name } = color || {};
54
56
 
55
57
  if (pattern && !skipPattern) {
56
58
  return {
57
- 'background-image': `url("${staticLink(pattern)}")`
59
+ 'background-image': `url("${staticLink((originBackground && patternOrigin) || pattern)}")`
58
60
  };
59
61
  }
60
62
 
@@ -4,7 +4,7 @@ let rotate;
4
4
 
5
5
  export function loadRotateImage(cb = Function.prototype) {
6
6
  const image = new Image();
7
- image.src = '/icons/rotate.png';
7
+ image.src = '/icons/rotate.png';
8
8
  image.onload = () => {
9
9
  rotate = image;
10
10
  cb();
@@ -61,7 +61,7 @@ export function getPrintsFromLayers(layers, product) {
61
61
  prints.set(printAreaKey, data);
62
62
  layer.printGuid = data.guid;
63
63
  } else {
64
- const print = prints.get(printArea);
64
+ const print = prints.get(printAreaKey);
65
65
  layer.printGuid = print.guid;
66
66
  print.layers.push(generatePrintLayer(layer));
67
67
  prints.set(printAreaKey, print);
@@ -92,10 +92,10 @@ export default {
92
92
  filters: { price },
93
93
  mixins: [CartMixin],
94
94
  computed: {
95
- ...mapGetters(['MESSAGES', 'SETTINGS', 'currency']),
95
+ ...mapGetters(['MESSAGES', 'SETTINGS', 'currency', 'country']),
96
96
  ...mapGetters('cart', ['isEmpty', 'cartPricingError', 'cartPricing', 'cartPricingCalculating', 'entities']),
97
97
  onlyPostcode() {
98
- return !!this.SETTINGS.CART_ONLY_POSTCODE;
98
+ return !!this.SETTINGS.CART_ONLY_POSTCODE && this.country?.isoCode === 'UK';
99
99
  },
100
100
  postcodeLabel() {
101
101
  return this.onlyPostcode ? 'Postcode' : (this.MESSAGES.CART_POSTCODE || 'Postcode');
@@ -6,6 +6,7 @@
6
6
  @click="showImage(images, 0)">
7
7
  <product-side-with-print
8
8
  :product="group"
9
+ :fill-background="true"
9
10
  :default-preview="defaultPreview"
10
11
  side="front"
11
12
  size="medium" />
@@ -17,6 +18,7 @@
17
18
  <product-side-with-print
18
19
  :product="group"
19
20
  :default-preview="defaultPreview"
21
+ :fill-background="true"
20
22
  side="back"
21
23
  size="medium" />
22
24
  </div>
@@ -58,7 +58,7 @@
58
58
  COUPON, {{ pricing.coupon.code }}:
59
59
  </div>
60
60
  <div class="lc_body-large lc_gray-main">
61
- {{ pricing.coupon.totalPrice | price(currency) }}
61
+ {{ couponTotal | price(currency) }}
62
62
  </div>
63
63
  </div>
64
64
  <div class="CartPricing__info CartPricing__info--gray">
@@ -97,6 +97,9 @@ export default {
97
97
  },
98
98
  computed: {
99
99
  ...mapGetters(['currency', 'taxName']),
100
+ couponTotal() {
101
+ return -this.pricing.totalsWithoutCoupon.totalPrice + this.pricing.totalPrice;
102
+ },
100
103
  hasSuppliersWithRates() {
101
104
  return this.suppliersWithRates.length > 0;
102
105
  },
@@ -2,7 +2,7 @@
2
2
  <div class="OrderSuccess__wrapper">
3
3
  <div class="OrderSuccess__card">
4
4
  <div class="OrderSuccess__card-icon">
5
- <img src="/images/success.svg" />
5
+ <img src="~static/images/success.svg" />
6
6
  </div>
7
7
  <div class="OrderSuccess__card-head">
8
8
  RECEIVED!
@@ -8,7 +8,7 @@
8
8
  min-width: 72px;
9
9
  width: 33%;
10
10
  height: 100%;
11
- border-radius: 2px;
11
+ border-radius: 5px;
12
12
  box-shadow: $elevation1;
13
13
  }
14
14
  &__value {
@@ -52,15 +52,15 @@
52
52
  &__dropdown {
53
53
  position: absolute;
54
54
  z-index: 101;
55
- top: 40px;
56
- width: 220px;
55
+ top: 35px;
56
+ width: 250px;
57
57
  margin-top: 20px;
58
58
  margin-left: -55px;
59
59
  // transform: translateY(-50%);
60
60
  padding: 15px;
61
61
  box-shadow: 0px 4px 121px 0px rgba(145, 136, 188, 0.34);
62
62
  background-color: white;
63
- border-radius: 12px;
63
+ border-radius: 5px;
64
64
  }
65
65
  &__backdrop {
66
66
  top: 0;
@@ -18,7 +18,8 @@
18
18
  </div>
19
19
  <div
20
20
  v-if="isOpen"
21
- class="ClientSettings__dropdown">
21
+ class="ClientSettings__dropdown"
22
+ v-click-outside="close">
22
23
  <div>
23
24
  <div
24
25
  class="ClientSettings__field"
@@ -112,6 +113,9 @@ export default {
112
113
  }
113
114
  },
114
115
  methods: {
116
+ close() {
117
+ this.isOpen = false;
118
+ },
115
119
  toggleVisibleCountries() {
116
120
  this.visibleCountries = !this.visibleCountries;
117
121
  },
@@ -3,4 +3,7 @@
3
3
  table {
4
4
  min-width: 200px;
5
5
  background-color: $white;
6
+ tr th {
7
+ overflow: hidden;
8
+ }
6
9
  }
@@ -2,6 +2,9 @@
2
2
  position: relative;
3
3
  width: 100%;
4
4
  height: 100%;
5
+ background-position: center;
6
+ background-size: contain;
7
+ background-repeat: no-repeat;
5
8
  &.preview {
6
9
  cursor: pointer;
7
10
  }
@@ -49,6 +49,10 @@ export default {
49
49
  type: Boolean,
50
50
  default: false
51
51
  },
52
+ originBackground: {
53
+ type: Boolean,
54
+ default: false
55
+ },
52
56
  priviewProducts: {
53
57
  type: Array,
54
58
  default: null
@@ -76,7 +80,7 @@ export default {
76
80
  return [...this.priviewProducts.reduce(predicate, new Map()).values()];
77
81
  },
78
82
  colorBackground() {
79
- return this.fillBackground && getColorBackgroundStyle(this.product.color);
83
+ return this.fillBackground && getColorBackgroundStyle(this.product.color, false, this.originBackground);
80
84
  },
81
85
  imageBackground() {
82
86
  return this.image && this.getImageBackground(this.image);
@@ -3,6 +3,11 @@
3
3
  .CustomerMenu {
4
4
  &__wrapper {
5
5
  position: relative;
6
+ .Btn {
7
+ &__wrapper {
8
+ height: 43px;
9
+ }
10
+ }
6
11
  }
7
12
  &__inner {
8
13
  min-width: 72px;
@@ -18,7 +23,7 @@
18
23
  display: flex;
19
24
  align-items: center;
20
25
  background-color: rgba(125, 106, 239, 0.07);
21
- padding: 10px 10px 5px 10px;
26
+ padding: 4px;
22
27
  border-radius: 20px;
23
28
  &--divider {
24
29
  display: block;
@@ -60,7 +65,7 @@
60
65
  padding: 15px;
61
66
  box-shadow: 0px 4px 121px 0px rgba(145, 136, 188, 0.34);
62
67
  background-color: white;
63
- border-radius: 12px;
68
+ border-radius: 5px;
64
69
  }
65
70
  &__backdrop {
66
71
  top: 0;
@@ -95,7 +100,8 @@
95
100
  display: flex;
96
101
  align-items: center;
97
102
  font-size: 16px;
98
- color: $purple;
103
+ color: $black;
104
+ text-decoration: none !important;
99
105
 
100
106
  &-checked {
101
107
  margin-right: 10px;
@@ -21,24 +21,47 @@
21
21
  </div>
22
22
  <div
23
23
  v-if="isOpen"
24
- class="CustomerMenu__dropdown">
24
+ class="CustomerMenu__dropdown"
25
+ v-click-outside="close">
25
26
  <div>
26
- <a href="/customer/settings" class="CustomerMenu__field-item">Settings</a>
27
+ <a
28
+ href="/customer/settings"
29
+ class="CustomerMenu__field-item">
30
+ Settings
31
+ </a>
27
32
  </div>
28
33
  <div>
29
- <a href="/customer/orders" class="CustomerMenu__field-item">Orders</a>
34
+ <a
35
+ href="/customer/orders"
36
+ class="CustomerMenu__field-item">
37
+ Orders
38
+ </a>
39
+ </div>
40
+ <div>
41
+ <a
42
+ href="#"
43
+ class="CustomerMenu__field-item"
44
+ @click="auth_logout">Logout</a>
30
45
  </div>
31
46
  </div>
32
47
  </div>
33
48
  </template>
34
49
 
35
50
  <script>
51
+ import { mapActions } from 'vuex';
52
+
36
53
  export default {
37
54
  name: 'CustomerMenu',
38
55
  data() {
39
56
  return {
40
57
  isOpen: false,
41
58
  };
59
+ },
60
+ methods: {
61
+ ...mapActions('auth', ['auth_logout']),
62
+ close() {
63
+ this.isOpen = false;
64
+ }
42
65
  }
43
66
  };
44
67
  </script>
@@ -14,6 +14,7 @@
14
14
  <template v-slot:default="{ currentTab }">
15
15
  <component
16
16
  :is="editorCurrentTabComponent(currentTab)"
17
+ @choose-products="chooseProducts()"
17
18
  v-if="currentTab">
18
19
  </component>
19
20
  </template>
@@ -25,7 +26,13 @@
25
26
  btn-label="Start again"
26
27
  btn-class="white"
27
28
  @onclick="showResetAllConfirm" />
29
+ <btn
30
+ v-if="isValidPrintOnly && needToAddProducts"
31
+ btn-label="Enter products"
32
+ btn-class="green"
33
+ @onclick="chooseProducts()" />
28
34
  <v-popover
35
+ v-else
29
36
  ref="popover"
30
37
  trigger="hover"
31
38
  :delay="{ hide: 400, show: 400 }"
@@ -210,6 +217,21 @@ export default {
210
217
  if (this.$refs.popover) {
211
218
  this.$refs.popover.hide();
212
219
  }
220
+ },
221
+ chooseProducts() {
222
+ this.selectTab('product');
223
+ setTimeout(() => {
224
+ const element = document.getElementById('EditorProductDetails');
225
+ if (element) {
226
+ const offset = 100;
227
+ const elementPosition = element.getBoundingClientRect().top;
228
+ const offsetPosition = elementPosition + window.pageYOffset - offset;
229
+ window.scrollTo({
230
+ top: offsetPosition,
231
+ behavior: 'smooth'
232
+ });
233
+ }
234
+ }, 250);
213
235
  }
214
236
  }
215
237
  };
@@ -79,7 +79,8 @@
79
79
  <editor-pricing
80
80
  v-if="hasPricing"
81
81
  class="EditorLayers__pricing"
82
- :has-cart-btn="false" />
82
+ :has-cart-btn="false"
83
+ @choose-products="$emit('choose-products')" />
83
84
  </div>
84
85
  </template>
85
86
 
@@ -58,13 +58,16 @@
58
58
  display: inline-block;
59
59
  }
60
60
  &__prints-table {
61
- width: 400px;
61
+ width: 450px;
62
62
 
63
63
  thead tr,
64
64
  tbody td:first-child {
65
65
  background-color: $grey_4 !important;
66
66
  text-align: center;
67
67
  }
68
+ tr th {
69
+ font-size: 12px;
70
+ }
68
71
  }
69
72
  &__available-warning {
70
73
  background-color: $error;
@@ -111,6 +111,7 @@
111
111
  :side="side"
112
112
  :default-preview="false"
113
113
  :fill-background="true"
114
+ :origin-background="true"
114
115
  size="large" />
115
116
  <div
116
117
  class="EditorProductDetails__product-sides-toggle"
@@ -119,7 +120,8 @@
119
120
  </div>
120
121
  </div>
121
122
  <div
122
- v-if="productDetailsLoaded">
123
+ v-if="productDetailsLoaded"
124
+ id="EditorProductDetails">
123
125
  <div class="EditorProductDetails__section">
124
126
  <product-colors-selector
125
127
  :has-another-print-btn="false"
@@ -58,35 +58,71 @@
58
58
  }
59
59
  &__placeholder {
60
60
  position: absolute;
61
- cursor: pointer;
62
61
  z-index: 999;
63
- background-image: url(../../../../static/images/type_here.png);
64
- background-size: contain;
65
- background-repeat: no-repeat;
66
- background-position: center;
67
-
68
- --placeholder-width: 140px;
69
- --placeholder-height: 50px;
62
+ text-align: center;
63
+ display: flex;
64
+ align-items: center;
65
+ justify-content: center;
66
+ flex-direction: column;
70
67
 
71
68
  @media (max-width: $bp-extra-small-max) {
72
- --placeholder-width: 70px;
73
- --placeholder-height: 25px;
69
+ font-size: 10px;
74
70
  }
75
71
 
76
72
  &.tighten {
77
- --placeholder-width: 70px;
78
- --placeholder-height: 25px;
79
-
73
+ font-size: 10px;
80
74
  @media (max-width: $bp-extra-small-max) {
81
- --placeholder-width: 35px;
82
- --placeholder-height: 12.5px;
75
+ font-size: 8px;
76
+ }
77
+ }
78
+ letter-spacing: -0.5px;
79
+ &-option {
80
+ cursor: pointer;
81
+ margin: 5px 0;
82
+ text-transform: uppercase;
83
+ span {
84
+ &:last-child {
85
+ display: none;
86
+ }
87
+ &:first-child {
88
+ display: inline;
89
+ }
90
+ }
91
+ }
92
+ &-divider {
93
+ color: $grey_1;
94
+ }
95
+ &-progress {
96
+ position: absolute;
97
+ top: 0;
98
+ left: 0;
99
+ width: 100%;
100
+ height: 100%;
101
+ background-color: $white_high_emphasis;
102
+ opacity: .7;
103
+ &-indicator {
104
+ position: absolute;
105
+ top: 0;
106
+ left: 0;
107
+ width: 0;
108
+ height: 100%;
109
+ background-color: $green;
110
+ }
111
+ }
112
+ &.side {
113
+ width: 20px;
114
+ .EditorWorkspaceSide__placeholder-option span {
115
+ &:last-child {
116
+ display: inline;
117
+ }
118
+ &:first-child {
119
+ display: none;
120
+ }
121
+ }
122
+ .EditorWorkspaceSide__placeholder-divider {
123
+ display: none;
83
124
  }
84
125
  }
85
-
86
- width: var(--placeholder-width);
87
- height: var(--placeholder-height);
88
- margin-left: calc(var(--placeholder-width) / 2 * (-1));
89
- margin-top: calc(var(--placeholder-height) / 2 * (-1));
90
126
  }
91
127
  &__alert {
92
128
  &-container {
@@ -5,6 +5,7 @@
5
5
  :class="{
6
6
  'EditorWorkspaceSide__wrapper--zoom-in': isZoomed
7
7
  }"
8
+ v-click-outside="onOutsideClick"
8
9
  @mouseover="toogleBoundBox(true)"
9
10
  @mouseleave="toogleBoundBox(false)">
10
11
  <div
@@ -32,12 +33,50 @@
32
33
  </div>
33
34
  </div>
34
35
  <div
35
- v-if="!printAreaLayers.length && fabricHelper"
36
+ v-if="fabricHelper"
36
37
  class="EditorWorkspaceSide__placeholder"
37
- :class="{ tighten: printAreaIsSmall }"
38
+ :class="{
39
+ tighten: printAreaIsSmall,
40
+ side: printAreaLayers.length
41
+ }"
38
42
  ignore-document-click
39
- :style="positionPlaceholder"
40
- @click="createTextLayer">
43
+ :style="positionPlaceholder">
44
+ <div
45
+ @click="createTextLayer"
46
+ class="EditorWorkspaceSide__placeholder-option">
47
+ <span>Type here</span>
48
+ <span>T</span>
49
+ </div>
50
+ <div class="EditorWorkspaceSide__placeholder-divider">
51
+ or
52
+ </div>
53
+ <div>
54
+ <file-uploader
55
+ :multiple="false"
56
+ :url="`image/editor/${shop._id}/${product._id}`"
57
+ @onuploaded="handleUploaded">
58
+ <template #toggle>
59
+ <div class="EditorWorkspaceSide__placeholder-option">
60
+ <span>
61
+ <i class="icon-picture"></i> Add art
62
+ </span>
63
+ <span>
64
+ <i class="icon-picture"></i>
65
+ </span>
66
+ </div>
67
+ </template>
68
+ <template v-slot:progress="{ progress }">
69
+ <div
70
+ v-if="progress"
71
+ class="EditorWorkspaceSide__placeholder-progress">
72
+ <div
73
+ :style="{ width: `${progress}%`}"
74
+ class="EditorWorkspaceSide__placeholder-progress-indicator"></div>
75
+ <spinner v-if="progress >= 100" background="white" style="margin-top: 50px" />
76
+ </div>
77
+ </template>
78
+ </file-uploader>
79
+ </div>
41
80
  </div>
42
81
  <div
43
82
  v-show="isVisibleOverlay"
@@ -85,9 +124,13 @@ import FabricHelper from '@lancom/shared/assets/js/utils/fabric-helper';
85
124
  import { findParentByPredicate } from '@lancom/shared/assets/js/utils/dom';
86
125
  import Breakpoints from '@lancom/shared/assets/js/utils/breakpoints';
87
126
  import { getColorBackgroundStyle } from '@lancom/shared/assets/js/utils/colors';
127
+ import FileUploader from '@lancom/shared/components/common/file_uploader';
88
128
 
89
129
  export default {
90
130
  name: 'EditorWorkspaceSide',
131
+ components: {
132
+ FileUploader
133
+ },
91
134
  props: {
92
135
  side: {
93
136
  type: String,
@@ -105,6 +148,7 @@ export default {
105
148
  },
106
149
  data() {
107
150
  return {
151
+ addedFromCanvas: false,
108
152
  visibleWireframe: false,
109
153
  fabricHelper: null,
110
154
  redrawWithThrottle: throttle(this.redraw, 100, { trailing: false }),
@@ -120,6 +164,7 @@ export default {
120
164
  };
121
165
  },
122
166
  computed: {
167
+ ...mapGetters(['shop']),
123
168
  ...mapGetters('product', [
124
169
  'product',
125
170
  'layers',
@@ -137,7 +182,7 @@ export default {
137
182
  return !!this.wireframeImage;
138
183
  },
139
184
  wireframeImage() {
140
- return this.images.find(i => i.types?.includes('designer') && i.types?.includes(this.side));
185
+ return this.images.find(i => i.types?.includes('wireframe') && i.types?.includes(this.side));
141
186
  },
142
187
  sideEditableLayers() {
143
188
  return this.editableLayers.filter(l => l.sideId === this.side);
@@ -154,26 +199,32 @@ export default {
154
199
  } : null;
155
200
  },
156
201
  positionPlaceholder() {
157
- const { center } = this.fabricHelper.printAreaRect;
202
+ const { center, left, top, width, height } = this.fabricHelper.printAreaRect;
203
+ // console.log('this.fabricHelper.printAreaRect: ', this.fabricHelper.printAreaRect);
158
204
  const ratio = this.calcWorkspaceSize() / this.editorSize.width;
159
- return {
160
- top: `${center.top * ratio}px`,
161
- left: `${center.left * ratio}px`
205
+ return this.printAreaLayers.length > 0 ? {
206
+ top: `${top * ratio}px`,
207
+ left: `${(left + width) * ratio + 4}px`
208
+ } : {
209
+ top: `${top * ratio}px`,
210
+ left: `${left * ratio}px`,
211
+ width: `${Math.max(60, width * ratio)}px`,
212
+ height: `${height * ratio}px`,
162
213
  };
163
214
  },
164
215
  showRecommendationToUseLargerImage() {
165
216
  return (
166
217
  this.selectedLayer &&
167
218
  this.selectedLayer.type === 'art' &&
168
- this.selectedLayer.dpi >= 38 &&
169
- this.selectedLayer.dpi <= 120
219
+ this.selectedLayer.dpi >= 75 &&
220
+ this.selectedLayer.dpi <= 100
170
221
  );
171
222
  },
172
223
  showErrorAboutSmallImage() {
173
224
  return (
174
225
  this.selectedLayer &&
175
226
  this.selectedLayer.type === 'art' &&
176
- this.selectedLayer.dpi < 38
227
+ this.selectedLayer.dpi < 75
177
228
  );
178
229
  },
179
230
  printAreaIsSmall() {
@@ -338,7 +389,7 @@ export default {
338
389
  updateBackgroundImage() {
339
390
  const type = this.side === 'front' ? COLORS_IMAGES_TYPES.FRONT : COLORS_IMAGES_TYPES.BACK;
340
391
  const wireframeImageUrl = this.visibleWireframe && this.wireframeImage?.large;
341
- const imageUrl = wireframeImageUrl || getLargeColorImage(this.product, type, this.editableColor);
392
+ const imageUrl = wireframeImageUrl || getLargeColorImage(this.product, type, this.editableColor, false, ['wireframe']);
342
393
  if (this.backgroundImage && this.backgroundImageUrl === imageUrl) {
343
394
  return this.fabricHelper.setBackgroundImage(this.backgroundImage);
344
395
  }
@@ -355,20 +406,32 @@ export default {
355
406
  removeSelected() {
356
407
  this.removeTemplateLayer(this.selectedLayer);
357
408
  },
409
+ onOutsideClick() {
410
+ if (this.addedFromCanvas && this.selectedLayer?.type === 'text' && !this.selectedLayer.copy) {
411
+ this.removeTemplateLayer(this.selectedLayer);
412
+ }
413
+ this.addedFromCanvas = false;
414
+ },
358
415
  saveLayersAsImage() {
359
416
  const image = this.fabricHelper.getLayersAsImage();
360
417
  this.setLayersThumbnail({ side: this.side, value: image });
361
418
  },
362
419
  createTextLayer() {
420
+ this.addedFromCanvas = true;
363
421
  window.scrollTo(0, 0);
364
422
  this.createLayer({ type: 'text', isEditMode: true });
365
423
  this.visibleWireframe = true;
366
424
  },
425
+ async handleUploaded({ url, size, fileName }) {
426
+ window.scrollTo(0, 0);
427
+ await this.createLayer({ type: 'art', url, size, fileName });
428
+ this.visibleWireframe = true;
429
+ },
367
430
  setDeleteButtonPosition(pos) {
368
431
  this.deleteButtonPos = pos;
369
432
  },
370
433
  setOffsetWarningVisibility(visible) {
371
- // this.offsetWarningVisible = visible;
434
+ this.offsetWarningVisible = visible;
372
435
  },
373
436
  toogleBoundBox(state, option) {
374
437
  if (!this.fabricHelper || this.breakpoints.is === 'xs') {
@@ -1,7 +1,7 @@
1
1
  <template>
2
2
  <div class="error-404">
3
3
  <div class="mt-10 mb-10 image">
4
- <img src="/images/tee.svg" width="250px" />
4
+ <img src="~static/images/tee.svg" width="250px" />
5
5
  <div class="code">
6
6
  404
7
7
  </div>
@@ -1,7 +1,7 @@
1
1
  <template>
2
2
  <div class="error-500">
3
3
  <div class="mt-10 mb-10 image">
4
- <img src="/images/tee.svg" width="250px" />
4
+ <img src="~static/images/tee.svg" width="250px" />
5
5
  <div class="code">
6
6
  500
7
7
  </div>
@@ -19,6 +19,9 @@
19
19
  img {
20
20
  margin-right: 10px;
21
21
  }
22
+ a {
23
+ color: $gray_main;
24
+ }
22
25
  }
23
26
  }
24
27
  &__footer {
@@ -10,56 +10,60 @@
10
10
  <div
11
11
  v-if="addedToCart"
12
12
  class="EditorPricing__main-alert">
13
- <img src="/images/smile.svg" />
13
+ <img src="~static/images/smile.svg" />
14
14
  Products have been added to cart
15
15
  </div>
16
16
 
17
17
  <div
18
18
  v-else-if="!hasUsedSimpleProducts && (!layers.length && isPrintPricing)"
19
19
  class="EditorPricing__main-alert">
20
- <img src="/images/sad.svg" />
20
+ <img src="~static/images/sad.svg" />
21
21
  No products selected and no prints defined
22
22
  </div>
23
23
 
24
24
  <div
25
25
  v-else-if="hasUsedSimpleProducts && ((!isValidPrintOnly || !layers.length && isPrintPricing))"
26
26
  class="EditorPricing__main-alert">
27
- <img src="/images/sad.svg" />
27
+ <img src="~static/images/sad.svg" />
28
28
  No prints defined
29
29
  </div>
30
30
 
31
31
  <div
32
32
  v-else-if="!hasUsedSimpleProducts && layers.length"
33
33
  class="EditorPricing__main-alert">
34
- <img src="/images/sad.svg" />
35
- No products selected to view price
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>
36
40
  </div>
37
41
 
38
42
  <div
39
43
  v-else-if="!isValidMiltipackOrderQuantity"
40
44
  class="EditorPricing__main-alert">
41
- <img src="/images/sad.svg" />
45
+ <img src="~static/images/sad.svg" />
42
46
  bulk pack {{ multipack.qty }} - add more x {{ multipack.qty - usedSimpleProductsQuantity }}
43
47
  </div>
44
48
 
45
49
  <div
46
50
  v-else-if="!isValidOrderQuantity && isValidBigSizeOrderQuantity && minimumOrderQuantity > 1"
47
51
  class="EditorPricing__main-alert">
48
- <img src="/images/sad.svg" />
52
+ <img src="~static/images/sad.svg" />
49
53
  Minimum order of these items must be {{ minimumOrderQuantity }} or more
50
54
  </div>
51
55
 
52
56
  <div
53
57
  v-else-if="!isValidBigSizeOrderQuantity"
54
58
  class="EditorPricing__main-alert">
55
- <img src="/images/sad.svg" />
59
+ <img src="~static/images/sad.svg" />
56
60
  Minimum order of 5XL-7XL should be 50%
57
61
  </div>
58
62
 
59
63
  <div
60
64
  v-else-if="isValidOrderQuantity"
61
65
  class="EditorPricing__main-alert">
62
- <img src="/images/smile.svg" />
66
+ <img src="~static/images/smile.svg" />
63
67
  All good to go!
64
68
  </div>
65
69
 
@@ -0,0 +1,90 @@
1
+ @import "@/assets/scss/variables";
2
+
3
+ .Brands {
4
+ &__main {
5
+ position: relative;
6
+ display: flex;
7
+ justify-content: space-between;
8
+ @media (max-width: $bp-extra-small-max) {
9
+ margin-top: 40px;
10
+ }
11
+ @media (max-width: $bp-small-max) {
12
+ flex-direction: column;
13
+ align-items: center;
14
+ }
15
+ }
16
+ &__items {
17
+ display: flex;
18
+ flex-wrap: wrap;
19
+ justify-content: space-between;
20
+ margin-top: 40px;
21
+ @media (max-width: $bp-medium-max) {
22
+ margin-top: 20px;
23
+ }
24
+ @media (max-width: $bp-extra-small-max) {
25
+ justify-content: space-around;
26
+ }
27
+ }
28
+ &__brand-item {
29
+ width: 280px;
30
+ height: 160px;
31
+ @media (max-width: $bp-medium-max) {
32
+ width: 240px;
33
+ height: 140px;
34
+ }
35
+ @media (max-width: $bp-small-max) {
36
+ width: 200px;
37
+ height: 120px;
38
+ }
39
+ @media (max-width: $bp-extra-small-max) {
40
+ width: 180px;
41
+ height: 90px;
42
+ }
43
+ @media (max-width: $bp-mini-max) {
44
+ width: 155px;
45
+ }
46
+ background-color: rgba(242, 242, 247, 1);
47
+ margin: 8px 0;
48
+ text-indent: -9999px;
49
+ display: flex;
50
+ align-items: center;
51
+ justify-content: center;
52
+ &-logo {
53
+ display: block;
54
+ width: 153px;
55
+ height: 100px;
56
+ @media (max-width: $bp-extra-small-max) {
57
+ width: 123px;
58
+ height: 70px;
59
+ }
60
+ background-position: center;
61
+ background-repeat: no-repeat;
62
+ background-size: contain;
63
+ }
64
+ }
65
+ &__control {
66
+ margin-top: 10px;
67
+ position: absolute;
68
+ top: 0;
69
+ right: 0;
70
+ a {
71
+ font-size: 24px;
72
+ font-weight: 700;
73
+ line-height: 31.2px;
74
+ letter-spacing: -0.005em;
75
+ color: $purple;
76
+ }
77
+ }
78
+ &__brands-image {
79
+ margin-top: 80px;
80
+ @media (max-width: $bp-medium-max) {
81
+ margin-top: 70px;
82
+ }
83
+ @media (max-width: $bp-small-max) {
84
+ margin-bottom: 70px;
85
+ }
86
+ img {
87
+ width: 100%;
88
+ }
89
+ }
90
+ }
@@ -0,0 +1,65 @@
1
+ <template>
2
+ <div class="Brands__wrapper content-inner">
3
+ <div class="Brands__main">
4
+ <div
5
+ data-aos="fade-left"
6
+ class="Brands__items-wrapper Brands__brands">
7
+ <div class="Brands__title lc_h2">
8
+ Brands
9
+ </div>
10
+ <div class="Brands__items">
11
+ <nuxt-link
12
+ v-for="brand in brands"
13
+ :key="brand._id"
14
+ :to="brandLink(brand)"
15
+ :title="brand.name"
16
+ class="Brands__brand-item">
17
+ <span
18
+ class="Brands__brand-item-logo"
19
+ :style="{
20
+ 'background-image': brandBackgroundImage(brand)
21
+ }">
22
+ </span>
23
+ </nuxt-link>
24
+ </div>
25
+ </div>
26
+ </div>
27
+ </div>
28
+ </template>
29
+
30
+ <script>
31
+ import { mapActions, mapGetters } from 'vuex';
32
+ import { generateProductsLink } from '@lancom/shared/assets/js/utils/product';
33
+ import { staticLink } from '@lancom/shared/assets/js/utils/filters';
34
+
35
+ export default {
36
+ name: 'Brands',
37
+ fetch() {
38
+ return Promise.all([
39
+ this.fetchBrands(this.shop._id)
40
+ ]);
41
+ },
42
+ computed: {
43
+ ...mapGetters(['shop']),
44
+ ...mapGetters('products', [
45
+ 'brands'
46
+ ])
47
+ },
48
+ methods: {
49
+ ...mapActions('products', [
50
+ 'fetchBrands'
51
+ ]),
52
+ brandLink({ alias: brand }) {
53
+ return generateProductsLink(null, { brand });
54
+ },
55
+ brandBackgroundImage(brand) {
56
+ const link = staticLink(brand.logo);
57
+ return link ? `url("${link}")` : null;
58
+ }
59
+ }
60
+ };
61
+ </script>
62
+
63
+ <style lang="scss" scoped>
64
+ @import 'brands';
65
+ </style>
@@ -28,6 +28,9 @@
28
28
  font-size: 13px;
29
29
  text-decoration: none;
30
30
  }
31
+ a:hover {
32
+ font-weight: bold;
33
+ }
31
34
  }
32
35
  &-all {
33
36
  border-top: 1px solid $gray_main;
@@ -28,13 +28,6 @@
28
28
  <spinner v-if="searching" />
29
29
  </div>
30
30
  <div v-if="hasProducts">
31
- <div class="ProductsAutocomplete__result-item">
32
- <a
33
- class="ProductsAutocomplete__result-item-all"
34
- :href="fullSearchLink">
35
- click to see all results
36
- </a>
37
- </div>
38
31
  <products-autocomplete-item
39
32
  v-for="product of products"
40
33
  :key="product._id"
@@ -42,6 +35,13 @@
42
35
  :product="product"
43
36
  class="ProductsAutocomplete__result-item"
44
37
  @select="$emit('select', product)" />
38
+ <div class="ProductsAutocomplete__result-item">
39
+ <a
40
+ class="ProductsAutocomplete__result-item-all"
41
+ :href="fullSearchLink">
42
+ more
43
+ </a>
44
+ </div>
45
45
  </div>
46
46
  </div>
47
47
  </div>
@@ -50,8 +50,7 @@ export default {
50
50
  },
51
51
  set(page) {
52
52
  const link = generateProductsLink(this.$route, { page });
53
- this.$router.push(link);
54
- window.scrollTo(0, 0);
53
+ window.location.href = link;
55
54
  }
56
55
  }
57
56
  }
@@ -10,7 +10,7 @@
10
10
  </slot>
11
11
  <slot name="description">
12
12
  <div class="Subscribe__description lc_regular16">
13
- Sign up for new product relesses, free design rescurrces, and a chance to win 36 free custom printed shirts.
13
+ Sign up for new product releases, free design resources, tips to grow your brand and promo sale events
14
14
  </div>
15
15
  </slot>
16
16
  </div>
@@ -209,7 +209,7 @@ function generateProductLink(product, color, toEditor) {
209
209
  return `${link}${color ? `?color=${color.alias || color}` : ''}`;
210
210
  }
211
211
 
212
- export const staticLink = (link, config) => {
212
+ const staticLink = (link, config) => {
213
213
  const { PROD_S3_BUCKET, DEV_S3_BUCKET, PROD_CDN_URL, DEV_CDN_URL, STATIC_URL } = config || {};
214
214
  if ((link || '').startsWith('http')) {
215
215
  if (PROD_S3_BUCKET && PROD_CDN_URL) {
@@ -228,5 +228,6 @@ export const staticLink = (link, config) => {
228
228
  module.exports = {
229
229
  googleShoppingFeed,
230
230
  googleLocalShoppingFeed,
231
- generateProductLink
231
+ generateProductLink,
232
+ staticLink
232
233
  };
@@ -38,6 +38,9 @@ export default {
38
38
  isValidMiltipackOrderQuantity() {
39
39
  return !this.multipack || this.multipack.qty <= this.usedSimpleProductsQuantity;
40
40
  },
41
+ needToAddProducts() {
42
+ return !this.usedSimpleProducts.length;
43
+ },
41
44
  addToCartDisabled() {
42
45
  return !((this.template.layers.length || !this.isPrintPricing) && this.usedSimpleProducts.length) || this.calculatingPrice || !this.isValidOrderQuantity || !this.isValidPrintOnly;
43
46
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lancom/shared",
3
- "version": "0.0.336",
3
+ "version": "0.0.338",
4
4
  "description": "lancom common scripts",
5
5
  "author": "e.tokovenko <e.tokovenko@gmail.com>",
6
6
  "repository": {
package/store/product.js CHANGED
@@ -370,7 +370,7 @@ export const mutations = {
370
370
  layer
371
371
  ];
372
372
  state.template = { ...state.template, layers };
373
- if (layers.length === 1 && !state.product.printOnly) {
373
+ if (layers.length >= 1) {
374
374
  // const printType = state.product.printTypes[0];
375
375
  // state.selectedPrintType = printType;
376
376
  state.isPrintPricing = true;
@@ -382,7 +382,7 @@ export const mutations = {
382
382
  if (state.selectedLayer && state.selectedLayer.createdAt === layer.createdAt) {
383
383
  state.selectedLayer = null;
384
384
  }
385
- if (state.template.layers.length === 0 && !state.product.printOnly) {
385
+ if (state.template.layers.length === 0) {
386
386
  // state.selectedPrintType = null;
387
387
  state.isPrintPricing = false;
388
388
  }
package/store/products.js CHANGED
@@ -38,7 +38,7 @@ export const getters = {
38
38
 
39
39
  export const actions = {
40
40
  async fetchBrands({ commit }, shop) {
41
- const brands = await api.fetchProductsBrands(shop, { limit: 10 });
41
+ const brands = await api.fetchProductsBrands(shop, { limit: 100 });
42
42
  commit('setBrands', brands);
43
43
  },
44
44
  async fetchTypes({ commit }, shop) {