@lancom/shared 0.0.280 → 0.0.282

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 (95) hide show
  1. package/assets/js/api/admin.js +3 -0
  2. package/assets/js/api/index.js +7 -4
  3. package/assets/js/models/print-area.js +8 -4
  4. package/assets/js/utils/colors.js +1 -1
  5. package/assets/js/utils/fabric-helper.js +5 -18
  6. package/assets/js/utils/filters.js +2 -2
  7. package/assets/js/utils/product.js +10 -1
  8. package/assets/scss/_common.scss +10 -0
  9. package/components/asides/contact_us/contact-us.vue +9 -2
  10. package/components/asides/menu/menu.vue +1 -25
  11. package/components/checkout/cart/cart.mixin.js +3 -3
  12. package/components/checkout/cart/cart.scss +8 -109
  13. package/components/checkout/cart/cart.vue +84 -46
  14. package/components/checkout/cart/cart_entity/cart_entity_color_simple_products/cart_entity_color_simple_product/cart-entity-color-simple-product.vue +7 -4
  15. package/components/checkout/cart/cart_price_info/cart-price-info.vue +4 -5
  16. package/components/checkout/cart/cart_pricing/cart-pricing.scss +34 -0
  17. package/components/checkout/cart/cart_pricing/cart-pricing.vue +112 -0
  18. package/components/checkout/cart/cart_shipments_pricing/cart-shipments-pricing.vue +2 -2
  19. package/components/checkout/order/address-form/address-form.scss +16 -0
  20. package/components/checkout/order/address-form/address-form.vue +199 -91
  21. package/components/checkout/order/order-billing-information/order-billing-information.scss +1 -1
  22. package/components/checkout/order/order-payment-information/order-payment-information.vue +15 -5
  23. package/components/checkout/order/order.vue +2 -1
  24. package/components/common/client_settings/client-settings.scss +6 -0
  25. package/components/common/client_settings/client-settings.vue +9 -1
  26. package/components/common/payment/payment_card/stripe/stripe.vue +1 -0
  27. package/components/common/payment/payment_success/payment-success.vue +8 -1
  28. package/components/common/postcode_select/postcode-select.vue +24 -12
  29. package/components/common/price.vue +1 -1
  30. package/components/common/pricing_table/pricing-table.vue +3 -2
  31. package/components/common/tabs.vue +17 -8
  32. package/components/editor/editor.scss +6 -0
  33. package/components/editor/editor.vue +9 -25
  34. package/components/editor/editor_layers/editor-layers.scss +18 -0
  35. package/components/editor/editor_layers/editor-layers.vue +76 -20
  36. package/components/editor/editor_layers/editor_layers_layer/editor-layers-layer.vue +11 -4
  37. package/components/editor/editor_print_area_options/editor-print-area-options.vue +6 -1
  38. package/components/editor/editor_print_area_options/editor_print_area_option/editor-print-area-option.vue +2 -2
  39. package/components/editor/editor_product_details/editor-product-details.scss +8 -2
  40. package/components/editor/editor_product_details/editor-product-details.vue +22 -25
  41. package/components/editor/editor_wizard/editor-wizard.vue +2 -1
  42. package/components/editor/editor_workspace/editor-workspace.vue +7 -3
  43. package/components/editor/editor_workspace/editor_workspace_side/editor-workspace-side.vue +17 -20
  44. package/components/editor/mobile_editor_product_details/mobile-editor-product-details.scss +2 -2
  45. package/components/modals/cart_modal/cart-modal.vue +1 -1
  46. package/components/modals/order_modal/order-modal.vue +6 -0
  47. package/components/modals/payment_modal/payment-modal.vue +4 -3
  48. package/components/order/order_payment/order-payment.vue +6 -6
  49. package/components/product/editor_pricing/editor-pricing.scss +75 -0
  50. package/components/product/editor_pricing/editor-pricing.vue +197 -0
  51. package/components/product/editor_pricing/editor_pricing_details/editor-pricing-details.scss +0 -0
  52. package/components/product/editor_pricing/editor_pricing_details/editor-pricing-details.vue +29 -0
  53. package/components/product/editor_pricing/editor_pricing_details/editor_pricing_details_prints/editor-pricing-details-prints.scss +41 -0
  54. package/components/product/editor_pricing/editor_pricing_details/editor_pricing_details_prints/editor-pricing-details-prints.vue +118 -0
  55. package/components/product/editor_pricing/editor_pricing_details/editor_pricing_details_products/editor-pricing-details-products.scss +41 -0
  56. package/components/product/editor_pricing/editor_pricing_details/editor_pricing_details_products/editor-pricing-details-products.vue +102 -0
  57. package/components/product/product.vue +1 -1
  58. package/components/product/product_colors_selector/product-colors-selector.scss +117 -0
  59. package/components/product/product_colors_selector/product-colors-selector.vue +188 -0
  60. package/components/product/product_multipacks_carousel/product-multipacks-carousel.vue +3 -1
  61. package/components/product/product_size_selector/product_size_selector_color/product_size_selector_color_cell/product-size-selector-color-cell.vue +4 -5
  62. package/components/product/products_size_selector_color/product_size_selector_color/product-size-selector-color.scss +58 -0
  63. package/components/product/products_size_selector_color/product_size_selector_color/product-size-selector-color.vue +128 -0
  64. package/components/product/products_size_selector_color/products-size-selector-color.scss +12 -0
  65. package/components/product/products_size_selector_color/products-size-selector-color.vue +43 -0
  66. package/components/product/wizard/wizard_print_layers/wizard_print_layer/wizard-print-layer.vue +4 -34
  67. package/components/product/wizard/wizard_print_size/wizard_print_area_print_size/wizard-print-area-print-size.vue +2 -1
  68. package/components/product/wizard/wizard_print_text_or_logo/wizard-print-text-or-logo.vue +22 -1
  69. package/components/product/wizard/wizard_print_type/wizard_print_area_print_type/wizard-print-area-print-type.vue +2 -1
  70. package/components/products/product_list/product-list.scss +2 -2
  71. package/components/products/product_list_product/product-list-product.scss +7 -11
  72. package/components/products/product_list_product/product-list-product.vue +7 -15
  73. package/components/the_aside/the-aside.vue +1 -0
  74. package/components/the_navbar/the-navbar.scss +1 -1
  75. package/feeds/google-shopping.js +5 -5
  76. package/layouts/error.vue +39 -0
  77. package/layouts/products.vue +386 -0
  78. package/mixins/add-to-cart.js +64 -0
  79. package/mixins/payment.js +2 -1
  80. package/mixins/print-layer.js +45 -0
  81. package/mixins/product-preview.js +1 -1
  82. package/mixins/product-view.js +313 -0
  83. package/nuxt.config.js +0 -5
  84. package/package.json +1 -1
  85. package/pages/checkout/cart.vue +40 -0
  86. package/pages/checkout/order.vue +72 -0
  87. package/pages/customer/create.vue +33 -0
  88. package/pages/customer/password/_token.vue +79 -0
  89. package/pages/customer/recovery.vue +33 -0
  90. package/pages/customer/settings.vue +33 -0
  91. package/pages/customer/signin.vue +33 -0
  92. package/routes/index.js +35 -0
  93. package/store/cart.js +15 -6
  94. package/store/order.js +2 -2
  95. package/store/product.js +5 -12
@@ -0,0 +1,386 @@
1
+ <template>
2
+ <div
3
+ class="Products"
4
+ :class="{
5
+ 'has-notification': notificationBar.enabled
6
+ }">
7
+ <LazyHydrate when-idle>
8
+ <the-navbar />
9
+ </LazyHydrate>
10
+ <the-changes-saved-indicator />
11
+ <div class="content Products__wrapper">
12
+ <div v-if="loadError">
13
+ <error :error="loadError" />
14
+ </div>
15
+ <div
16
+ v-else
17
+ class="content-inner extra">
18
+ <breadcrumbs :items="breadcrumbs" />
19
+ <div class="Products__info">
20
+ <h1 class="Products__name">
21
+ {{ routeName }}
22
+ </h1>
23
+ <div class="Products__filters">
24
+ <products-filters @open="openAside" />
25
+ </div>
26
+ </div>
27
+ <div class="Products__content">
28
+ <div class="Products__aside">
29
+ <breakpoint
30
+ name="md"
31
+ mode="up">
32
+ <products-aside />
33
+ </breakpoint>
34
+ </div>
35
+ <div class="Products__list">
36
+ <products-catalog class="Products__catalog" />
37
+ </div>
38
+ </div>
39
+ <div
40
+ v-if="routeInfo && routeInfo.text"
41
+ class="Products__text">
42
+ <LazyHydrate never>
43
+ <static-page
44
+ :visible-title="false"
45
+ :item="routeInfo" />
46
+ </LazyHydrate>
47
+ </div>
48
+ </div>
49
+ </div>
50
+ <LazyHydrate on-interaction>
51
+ <the-footer />
52
+ </LazyHydrate>
53
+ <modals-container />
54
+ <the-aside />
55
+ </div>
56
+ </template>
57
+
58
+ <script>
59
+ import debounce from 'lodash.debounce';
60
+ import { mapActions, mapGetters, mapMutations } from 'vuex';
61
+ import gtm from '@lancom/shared/assets/js/utils/gtm';
62
+ import metaInfo from '@lancom/shared/mixins/meta-info';
63
+ import { generateProductsLink, generateProductLink } from '@lancom/shared/assets/js/utils/product';
64
+ import { getProductLargeCover } from '@lancom/shared/assets/js/utils/colors';
65
+ import LazyHydrate from 'vue-lazy-hydration';
66
+ import TheNavbar from '@/components/the_navbar/the-navbar';
67
+ import TheChangesSavedIndicator from '@lancom/shared/components/the_changes_saved_indicator/the-changes-saved-indicator';
68
+ import Breadcrumbs from '@lancom/shared/components/common/breadcrumbs/breadcrumbs';
69
+ import TheFooter from '@/components/the_footer/the-footer';
70
+ import TheAside from '@lancom/shared/components/the_aside/the-aside';
71
+ import ProductsAside from '@/components/products/products_aside/products-aside';
72
+ import ProductsFilters from '@lancom/shared/components/products/products_filters/products-filters';
73
+ import ProductsCatalog from '@/components/products/products_catalog/products-catalog';
74
+ import StaticPage from '@lancom/shared/components/static_page/static-page';
75
+ import Error from './error';
76
+
77
+ export default {
78
+ name: 'ProductsLayout',
79
+ components: {
80
+ LazyHydrate,
81
+ TheNavbar,
82
+ Breadcrumbs,
83
+ TheFooter,
84
+ TheAside,
85
+ ProductsAside,
86
+ ProductsFilters,
87
+ ProductsCatalog,
88
+ Error,
89
+ StaticPage,
90
+ TheChangesSavedIndicator
91
+ },
92
+ mixins: [metaInfo],
93
+ middleware: ['page-info'],
94
+ async fetch() {
95
+ // this.setPlaceholder(true);
96
+ await this.loadProducts();
97
+ },
98
+ computed: {
99
+ ...mapGetters('page', ['routeInfo']),
100
+ ...mapGetters(['notificationBar', 'shop', 'country', 'currency']),
101
+ ...mapGetters('products', ['brands', 'types', 'tags', 'loadError', 'count', 'products', 'minPrice', 'maxPrice']),
102
+ currentBrand() {
103
+ return this.findByRouteParam(this.brands, 'brand');
104
+ },
105
+ currentTag() {
106
+ return this.findByRouteParam(this.tags, 'category');
107
+ },
108
+ currentProductType() {
109
+ return this.findByRouteParam(this.types, 'type');
110
+ },
111
+ breadcrumbs() {
112
+ const breadcrumbs = [{
113
+ to: '/products',
114
+ text: 'Products'
115
+ }];
116
+
117
+ if (this.currentProductType) {
118
+ breadcrumbs.push({
119
+ to: generateProductsLink(this.$route, {
120
+ type: this.currentProductType.alias,
121
+ brand: null,
122
+ category: null
123
+ }),
124
+ text: this.currentProductType.name
125
+ });
126
+ }
127
+
128
+ if (this.currentTag) {
129
+ breadcrumbs.push({
130
+ to: generateProductsLink(this.$route, {
131
+ type: this.currentProductType?.alias,
132
+ category: this.currentTag?.alias,
133
+ brand: null
134
+ }),
135
+ text: this.currentTag.name
136
+ });
137
+ }
138
+
139
+ if (this.currentBrand) {
140
+ breadcrumbs.push({
141
+ to: generateProductsLink(this.$route, {
142
+ type: this.currentProductType?.alias,
143
+ category: this.currentTag?.alias,
144
+ brand: this.currentBrand?.alias
145
+ }),
146
+ text: this.currentBrand.name
147
+ });
148
+ }
149
+
150
+ return breadcrumbs;
151
+ },
152
+ routeName() {
153
+ if (this.routeInfo && this.routeInfo.textTitle) {
154
+ return this.routeInfo.textTitle;
155
+ }
156
+
157
+ const items = ['Products'];
158
+ if (this.currentProductType) {
159
+ items.push(this.currentProductType.name);
160
+ }
161
+ if (this.currentTag) {
162
+ items.push(this.currentTag.name);
163
+ }
164
+ if (this.currentBrand) {
165
+ items.push(this.currentBrand.name);
166
+ }
167
+ return items.join(' / ');
168
+ }
169
+ },
170
+ watch: {
171
+ '$route.params': {
172
+ handler() {
173
+ this.loadProductsWithDebounce();
174
+ },
175
+ deep: true
176
+ },
177
+ '$route.query': {
178
+ handler() {
179
+ this.loadProductsWithDebounce();
180
+ },
181
+ deep: true
182
+ }
183
+ },
184
+ mounted() {
185
+ this.loadState();
186
+ this.logGtm();
187
+ // setTimeout(() => this.loadProducts(), 3000);
188
+ },
189
+ jsonld() {
190
+ const productsSchema = {
191
+ '@context': 'http://schema.org',
192
+ '@type': 'OfferCatalog',
193
+ name: this.pageTitle,
194
+ numberOfItems: this.count,
195
+ itemListOrder: 'ItemListUnordered',
196
+ itemListElement: this.products.map(product => {
197
+ const schema = {
198
+ '@type': 'Product',
199
+ name: product.name,
200
+ url: `https://${process.env.HOST_NAME}${generateProductLink(product)}`,
201
+ sku: product.SKU,
202
+ offers: {
203
+ '@type': 'AggregateOffer',
204
+ offerCount: 1,
205
+ name: product.name,
206
+ highPrice: product.minPrice,
207
+ lowPrice: product.maxPrice,
208
+ priceCurrency: 'AUD',
209
+ availability: 'InStock'
210
+ }
211
+ };
212
+ if (product.brand) {
213
+ schema.brand = {
214
+ '@type': 'Organization',
215
+ name: product.brand.name
216
+ };
217
+ }
218
+
219
+ const image = getProductLargeCover(product, 'front');
220
+ if (image) {
221
+ schema.image = image;
222
+ }
223
+
224
+ return schema;
225
+ })
226
+ };
227
+
228
+ const breadcrumbSchema = {
229
+ '@context': 'https://schema.org',
230
+ '@type': 'BreadcrumbList',
231
+ itemListElement: [
232
+ {
233
+ '@type': 'ListItem',
234
+ position: 1,
235
+ name: 'Home',
236
+ item: `https://${process.env.HOST_NAME}/`
237
+ },
238
+ ...this.breadcrumbs
239
+ .map((b, index) => ({
240
+ '@type': 'ListItem',
241
+ position: index + 2,
242
+ name: b.text,
243
+ item: b.to ? `https://${process.env.HOST_NAME}${b.to}` : undefined
244
+ }))
245
+ ]
246
+ };
247
+
248
+ return [productsSchema, breadcrumbSchema];
249
+ },
250
+ methods: {
251
+ ...mapActions([
252
+ 'loadState'
253
+ ]),
254
+ ...mapActions('products', [
255
+ 'fetchProducts'
256
+ ]),
257
+ ...mapMutations('products', [
258
+ 'setPlaceholder'
259
+ ]),
260
+ logGtm() {
261
+ if (process.client) {
262
+ if (this.$route.query.text) {
263
+ gtm.viewSearchResult(this.$route.query.text);
264
+ }
265
+ gtm.viewItemList(this.products);
266
+ }
267
+ },
268
+ loadProductsWithDebounce: debounce(function () {
269
+ if (/^\/products/.test(this.$route.path)) {
270
+ this.loadProducts();
271
+ }
272
+
273
+ this.$aside.hide();
274
+ }, 100),
275
+ async loadProducts(placeholder) {
276
+ const params = {
277
+ ...this.$route.params,
278
+ ...this.$route.query,
279
+ country: this.country?._id,
280
+ currency: this.currency?._id,
281
+ placeholder
282
+ };
283
+ try {
284
+ await this.fetchProducts({ params, shop: this.shop._id });
285
+ this.setPlaceholder(placeholder);
286
+ setTimeout(() => this.logGtm());
287
+ } catch ({ response }) {
288
+ if (process.server) {
289
+ this.$nuxt.context.res.statusCode = this.loadError?.statusCode || 500;
290
+ }
291
+ }
292
+ },
293
+ findByRouteParam(list, param) {
294
+ return list.find(i => i.alias === this.$route.params[param]);
295
+ },
296
+ fillWithItemData(text = '') {
297
+ text = this.currentBrand ? text.replace(/{{brandName}}/g, this.currentBrand.name) : text;
298
+ text = this.currentProductType ? text.replace(/{{typeName}}/g, this.currentProductType.name) : text;
299
+ text = this.currentTag ? text.replace(/{{categoryName}}/g, this.currentTag.name) : text;
300
+ return text;
301
+ },
302
+ async openAside() {
303
+ const ProductsAside = await import('@/components/products/products_aside/products-aside');
304
+ this.$aside.show(ProductsAside.default);
305
+ }
306
+ }
307
+ };
308
+ </script>
309
+ <style lang="scss" scoped>
310
+ @import "@/assets/scss/variables";
311
+
312
+ .Products {
313
+ margin-top: 40px;
314
+ &__text {
315
+ margin-top: 30px;
316
+ }
317
+ &__content {
318
+ background: $white;
319
+ padding-left: 24px;
320
+ display: flex;
321
+ justify-content: space-between;
322
+ @media (max-width: $bp-small-max) {
323
+ padding-left: 0px;
324
+ }
325
+ }
326
+ &__filters {
327
+ position: relative;
328
+ z-index: 2;
329
+ @media (max-width: $bp-medium-max) {
330
+ display: flex;
331
+ width: 100%;
332
+ justify-content: flex-end;
333
+ margin-top: 20px;
334
+ }
335
+ }
336
+ &__aside {
337
+ width: 230px;
338
+ flex-grow: 1;
339
+ flex-shrink: 0;
340
+ @media (max-width: $bp-small-max) {
341
+ display: none;
342
+ }
343
+ }
344
+ &__list {
345
+ width: 100%;
346
+ }
347
+ &__info {
348
+ display: flex;
349
+ justify-content: space-between;
350
+ align-items: center;
351
+ margin: 25px 0;
352
+ @media (max-width: $bp-medium-max) {
353
+ flex-direction: column;
354
+ justify-content: start;
355
+ align-items: flex-start;
356
+ margin: 5px 0;
357
+ }
358
+ }
359
+ &__name {
360
+ font-weight: 800;
361
+ font-size: 36px;
362
+ line-height: 49px;
363
+ color: $black;
364
+ text-transform: uppercase;
365
+ @media (max-width: $bp-medium-max) {
366
+ font-size: 20px;
367
+ line-height: 29px;
368
+ }
369
+ }
370
+ }
371
+ ::v-deep .ProductsFilters {
372
+ &__wrapper {
373
+ margin-bottom: 20px;
374
+
375
+ input,
376
+ .multiselect__tags {
377
+ border-radius: 0px !important;
378
+ }
379
+ }
380
+ &__search {
381
+ margin-left: 16px;
382
+ width: 500px;
383
+ }
384
+ }
385
+ </style>
386
+
@@ -0,0 +1,64 @@
1
+ import { mapGetters, mapActions, mapMutations } from 'vuex';
2
+ import { generateCartProducts } from '@lancom/shared/assets/js/utils/product';
3
+
4
+ export default {
5
+ data() {
6
+ return {
7
+ addedToCart: false
8
+ };
9
+ },
10
+ computed: {
11
+ ...mapGetters(['shop', 'country', 'currency']),
12
+ ...mapGetters('product', [
13
+ 'product',
14
+ 'usedSimpleProducts',
15
+ 'usedSimpleProductsQuantity',
16
+ 'usedBigSizeSimpleProductsQuantity',
17
+ 'productPricing',
18
+ 'layers',
19
+ 'template',
20
+ 'isPrintPricing',
21
+ 'minimumOrderQuantity',
22
+ 'calculatingPrice',
23
+ 'multipack'
24
+ ]),
25
+ isValidOrderQuantity() {
26
+ return this.isValidBigSizeOrderQuantity && this.isValidMiltipackOrderQuantity && (!this.minimumOrderQuantity || this.usedSimpleProductsQuantity >= this.minimumOrderQuantity);
27
+ },
28
+ isValidBigSizeOrderQuantity() {
29
+ return this.usedSimpleProductsQuantity ? ((this.usedBigSizeSimpleProductsQuantity / this.usedSimpleProductsQuantity * 100) <= 50) : true;
30
+ },
31
+ isValidMiltipackOrderQuantity() {
32
+ return !this.multipack || this.multipack.qty <= this.usedSimpleProductsQuantity;
33
+ },
34
+ addToCartDisabled() {
35
+ return !((this.template.layers.length || !this.isPrintPricing) && this.usedSimpleProducts.length) || this.calculatingPrice || !this.isValidOrderQuantity;
36
+ }
37
+ },
38
+ methods: {
39
+ ...mapActions('cart', ['addToCart', 'calculateCartPrice']),
40
+ ...mapMutations('product', ['clearTemplate', 'setIsPrintPricing']),
41
+ ...mapMutations('layers', ['resetLayers']),
42
+ async proceedToCard() {
43
+ const entities = generateCartProducts(this.product, this.template.simpleProducts, this.template.layers, this.isPrintPricing);
44
+ await this.addToCart({
45
+ entities,
46
+ shop: this.shop,
47
+ pricing: this.productPricing,
48
+ country: this.country,
49
+ currency: this.currency
50
+ });
51
+ this.$toastr.s('Products successfully added to cart');
52
+
53
+ this.clearTemplate(true);
54
+
55
+ this.calculateCartPrice({ shop: this.shop, country: this.country });
56
+
57
+ // this.resetLayers();
58
+ // this.setIsPrintPricing(false);
59
+ setTimeout(() => {
60
+ this.addedToCart = true;
61
+ });
62
+ }
63
+ }
64
+ };
package/mixins/payment.js CHANGED
@@ -16,7 +16,7 @@ export default {
16
16
  };
17
17
  },
18
18
  computed: {
19
- ...mapGetters(['shop', 'country', 'payment']),
19
+ ...mapGetters(['shop', 'country', 'payment', 'currency']),
20
20
  ...mapGetters('order', ['orderData', 'card']),
21
21
  isVisibleChargeMessage() {
22
22
  return this.isSuccessOrderCharge || this.isFailedOrderCharge;
@@ -49,6 +49,7 @@ export default {
49
49
  shop: this.shop._id,
50
50
  country: this.country,
51
51
  payment: this.payment,
52
+ currency: this.currency?.isoCode,
52
53
  recaptchaToken: await this.getRecaptcha('order_payment')
53
54
  };
54
55
  await this.submitPayment(data);
@@ -0,0 +1,45 @@
1
+ import { mapGetters } from 'vuex';
2
+ import { getAllPrintAreas } from '@lancom/shared/assets/js/utils/product';
3
+
4
+ export default {
5
+ props: {
6
+ layer: {
7
+ type: Object,
8
+ required: true
9
+ }
10
+ },
11
+ computed: {
12
+ ...mapGetters('product', ['product']),
13
+ side() {
14
+ return this.layer.sideId;
15
+ },
16
+ type() {
17
+ return this.layer.type;
18
+ },
19
+ productPrintAreas() {
20
+ return getAllPrintAreas(this.product);
21
+ },
22
+ layerPrintArea() {
23
+ return this.productPrintAreas.find(({ _id }) => _id === this.layer.printArea);
24
+ },
25
+ layerPrintSizes() {
26
+ const { printSize, sizes = [] } = this.layerPrintArea;
27
+ return [
28
+ printSize,
29
+ ...sizes
30
+ .map(({ printSize }) => printSize)
31
+ .filter(({ _id }) => _id !== printSize._id)
32
+ ];
33
+ },
34
+ layerPrintType() {
35
+ return this.product.printTypes.find(({ _id }) => _id === this.layer.printType);
36
+ },
37
+ layerPrintSize() {
38
+ return this.layerPrintSizes.find(({ _id }) => _id === this.layer.printSize);
39
+ },
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;
43
+ }
44
+ }
45
+ };
@@ -73,7 +73,7 @@ const productPreview = {
73
73
  },
74
74
  productBrand() {
75
75
  const { brand } = this.product;
76
- return brand && staticLink(brand.logo);
76
+ return brand?.logo && staticLink(brand.logo);
77
77
  },
78
78
  hasTags() {
79
79
  return this.product?.tags.length > 0;