@lancom/shared 0.0.385 → 0.0.387

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.
@@ -21,6 +21,7 @@ $medium_gray: #B0B0BA !default;
21
21
  $light_gray: #F9F9FC;
22
22
  $link: #5D89A9;
23
23
  $error: #E44868;
24
+ $warning: #e48e48;
24
25
  $secondary_900: #20303C;
25
26
  $secondary_800: #324B5D;
26
27
  $secondary_700: #3D5D74;
@@ -35,7 +35,7 @@ export default {
35
35
  return this.isNotValidProductsQuantity || this.isNotValidStockQuantity || this.isNotValidPrintsQuantity || this.isNotValidPrintsBigSizeQuantity;
36
36
  },
37
37
  isNotValidShipping() {
38
- return (this.needToPickup && this.notValidProductsPickup.length > 0) || !this.suburb;
38
+ return this.needToPickup ? this.notValidProductsPickup.length > 0 : !this.suburb;
39
39
  },
40
40
  isNotValidProductsQuantity() {
41
41
  return this.notValidProductsQuantities.length > 0;
@@ -25,6 +25,14 @@ export default {
25
25
  productLink() {
26
26
  return generateProductLink(this.product, null, !!this.SETTINGS.CART_PRODUCT_LINK_TO_EDITOR);
27
27
  },
28
+ suppliersWithRates() {
29
+ return this.cartPricing?.shipping?.suppliersWithRates || [];
30
+ },
31
+ fulfillment() {
32
+ const supplierWithRates = this.suppliersWithRates.find(s => s?.brands?.some(b => b?._id === this.product?.brand?._id));
33
+ const selectedRate = supplierWithRates?.rates?.find(r => r.selected);
34
+ return selectedRate?.name;
35
+ },
28
36
  sumPrints() {
29
37
  return (this.entity.prints || []).length;
30
38
  },
@@ -40,6 +40,16 @@
40
40
  {{ product.brand.name }}
41
41
  </div>
42
42
  </div>
43
+ <div
44
+ v-if="fulfillment"
45
+ class="CartEntity__feature">
46
+ <div class="lc_title-small">
47
+ Fulfillment:
48
+ </div>
49
+ <div class="lc_body-small">
50
+ {{ fulfillment }}
51
+ </div>
52
+ </div>
43
53
  </div>
44
54
  <div>
45
55
  <cart-entity-color-simple-products
@@ -28,6 +28,8 @@
28
28
  </template>
29
29
 
30
30
  <script>
31
+ import { mapGetters } from 'vuex';
32
+ import { generateProductLink } from '@lancom/shared/assets/js/utils/product';
31
33
  import CartEntity from './../cart_entity/cart-entity';
32
34
 
33
35
  export default {
@@ -42,8 +44,9 @@ export default {
42
44
  }
43
45
  },
44
46
  computed: {
47
+ ...mapGetters(['SETTINGS']),
45
48
  productsKitLink() {
46
- return `/products-kit/${this.productsKitEntity.productsKit.alias}`;
49
+ return generateProductLink(this.productsKitEntity.productsKit, null, !!this.SETTINGS.CART_PRODUCT_LINK_TO_EDITOR);
47
50
  }
48
51
  },
49
52
  methods: {
@@ -40,4 +40,9 @@
40
40
  font-size: 13px;
41
41
  line-height: 17px;
42
42
  }
43
+ &__pickup-warnings {
44
+ color: $warning;
45
+ font-size: 13px;
46
+ line-height: 17px;
47
+ }
43
48
  }
@@ -2,7 +2,7 @@
2
2
  <div
3
3
  class="CartShipping__wrapper"
4
4
  :class="{
5
- 'CartShipping__wrapper--warning': (needToPickup && hasNotValidProductsPickup) || !suburb
5
+ 'CartShipping__wrapper--warning': needToPickup ? hasNotValidProductsPickup : !suburb
6
6
  }">
7
7
  <div class="lc_h4 lc_uppercase lc_black CartShipping__header">
8
8
  Order Shipping
@@ -18,6 +18,7 @@
18
18
  @select="$emit('suburb', $event)" />
19
19
  <div class="mt-10 mb-10">
20
20
  <checkbox
21
+ :disabled="!isAvailableForPickupSomeProducts"
21
22
  v-model="wantsToPickup"
22
23
  :dark="true"
23
24
  @change="calculatePrice()">
@@ -26,15 +27,21 @@
26
27
  </div>
27
28
  </checkbox>
28
29
  </div>
30
+ <div
31
+ v-if="!isAvailableForPickupSomeProducts"
32
+ class="CartShipping__pickup-warnings">
33
+ Pickup not available for current cart items
34
+ {{ notValidProductsPickupBySKU }}
35
+ </div>
29
36
  <div
30
37
  v-if="visiblePickupError && hasNotValidProductsPickup"
31
38
  class="CartShipping__pickup-errors">
32
- <div>Not all items in cart are available for pickup. Some items are available for delivery only</div>
39
+ <div>Not all items in cart are available for pickup. Some items are available for delivery only:</div>
33
40
  <ul>
34
41
  <li
35
- v-for="product in notValidProductsPickup"
42
+ v-for="product in notValidProductsPickupBySKU"
36
43
  :key="product._id">
37
- <b>{{ product.name }}. {{ product.size ? product.size.shortName : '' }}</b> order quantity: {{ product.amount }}. Available {{ product.warehouseQuantityStock }}
44
+ <b>{{ product.name }}({{ product.productSKU }})</b>
38
45
  </li>
39
46
  </ul>
40
47
  </div>
@@ -70,6 +77,7 @@ export default {
70
77
  computed: {
71
78
  ...mapGetters(['shop', 'SETTINGS', 'MESSAGES', 'country', 'currency', 'contacts']),
72
79
  ...mapGetters('cart', [
80
+ 'entities',
73
81
  'suburb',
74
82
  'notValidProductsPickup',
75
83
  'needToPickup',
@@ -95,6 +103,7 @@ export default {
95
103
  },
96
104
  set(needToPickup) {
97
105
  if (needToPickup && this.hasNotValidProductsPickup) {
106
+ this.setNeedToPickup(needToPickup);
98
107
  this.visiblePickupError = true;
99
108
  } else {
100
109
  // if (needToPickup) {
@@ -104,6 +113,22 @@ export default {
104
113
  this.visiblePickupError = false;
105
114
  }
106
115
  }
116
+ },
117
+ notValidProductsPickupBySKU() {
118
+ return groupProductsBySKU(this.notValidProductsPickup, 'productSKU');
119
+ },
120
+ isAvailableForPickupSomeProducts() {
121
+ const products = this.entities.map(({ product }) => product);
122
+ const grouped = groupProductsBySKU(products);
123
+ return this.notValidProductsPickupBySKU.length === 0 || this.notValidProductsPickupBySKU.length !== grouped?.length;
124
+ }
125
+ },
126
+ mounted() {
127
+ if (!this.isAvailableForPickupSomeProducts) {
128
+ this.wantsToPickup = false;
129
+ }
130
+ if (this.wantsToPickup && this.hasNotValidProductsPickup) {
131
+ this.visiblePickupError = true;
107
132
  }
108
133
  },
109
134
  methods: {
@@ -119,6 +144,19 @@ export default {
119
144
  }
120
145
  }
121
146
  };
147
+
148
+ function groupProductsBySKU(products, skuProp = 'SKU') {
149
+ const grouped = products.reduce((acc, product) => {
150
+ const existingProduct = acc.find(p => p[skuProp] === product[skuProp]);
151
+ if (existingProduct) {
152
+ existingProduct.amount += product.amount;
153
+ } else {
154
+ acc.push({ ...product });
155
+ }
156
+ return acc;
157
+ }, []);
158
+ return grouped;
159
+ }
122
160
  </script>
123
161
 
124
162
  <style lang="scss" scoped>
@@ -108,7 +108,7 @@ html[dir=rtl] {
108
108
  margin: 2px 0;
109
109
  &--disabled {
110
110
  pointer-events: none;
111
- opacity: .7;
111
+ opacity: .4;
112
112
  }
113
113
  }
114
114
 
@@ -15,8 +15,10 @@
15
15
  </div>
16
16
  </article>
17
17
  <products-kit-options
18
+ v-if="productsKit[optionsAttr]"
18
19
  class="ProductsKit__options"
19
- :products-kit="productsKit" />
20
+ :products-kit="productsKit"
21
+ :options-attr="optionsAttr" />
20
22
  <products-kit-cart
21
23
  class="ProductsKit__cart"
22
24
  :products-kit="productsKit" />
@@ -37,6 +39,10 @@ export default {
37
39
  productsKit: {
38
40
  type: Object,
39
41
  required: true
42
+ },
43
+ optionsAttr: {
44
+ type: String,
45
+ default: 'productsKitOptions'
40
46
  }
41
47
  }
42
48
  };
@@ -1,7 +1,7 @@
1
1
  <template>
2
2
  <div class="ProductsKitOptions__wrapper">
3
3
  <products-kit-option
4
- v-for="option in productsKit.options"
4
+ v-for="option in productsKit[optionsAttr]"
5
5
  :key="option._id"
6
6
  :products-kit="productsKit"
7
7
  :option="option"
@@ -22,6 +22,10 @@ export default {
22
22
  productsKit: {
23
23
  type: Object,
24
24
  required: true
25
+ },
26
+ optionsAttr: {
27
+ type: String,
28
+ default: 'productsKitOptions'
25
29
  }
26
30
  }
27
31
  };
@@ -36,6 +36,8 @@ export default (IS_PRODUCT_PRESET_PRINT_PRICING, isEditor = false) => ({
36
36
 
37
37
  const product = store.getters['product/product'];
38
38
 
39
+ await store.dispatch('productsKit/setProductsKit', product);
40
+
39
41
  if ((product.useTaxonomyUrl && !params.sku) || (!product.useTaxonomyUrl && params.sku)) {
40
42
  const redirectUrl = generateProductLink(product, data.defaultColor, isEditor);
41
43
  return redirect(301, redirectUrl);
@@ -91,6 +93,9 @@ export default (IS_PRODUCT_PRESET_PRINT_PRICING, isEditor = false) => ({
91
93
  computed: {
92
94
  ...mapGetters(['shop', 'gstTax', 'country', 'stockCountry', 'currency']),
93
95
  ...mapGetters('product', ['product', 'simpleProducts', 'productDetails', 'editableColor', 'images', 'preSetPrints', 'editorSize', 'printsPrice']),
96
+ hasProductsKitOptions() {
97
+ return this.product.productsKitOptions?.length > 0;
98
+ },
94
99
  stockCountryId() {
95
100
  return this.stockCountry?._id || null;
96
101
  },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lancom/shared",
3
- "version": "0.0.385",
3
+ "version": "0.0.387",
4
4
  "description": "lancom common scripts",
5
5
  "author": "e.tokovenko <e.tokovenko@gmail.com>",
6
6
  "repository": {
@@ -9,7 +9,11 @@
9
9
  btn-class="black"
10
10
  btn-label="EDIT CART"
11
11
  :btn-block="true"
12
- to="/checkout/cart" />
12
+ to="/checkout/cart">
13
+ <template #icon-before>
14
+ <i class="icon-edit"></i>
15
+ </template>
16
+ </btn>
13
17
  <cart-price-info
14
18
  class="mt-10"
15
19
  :disabled-rates="true"
package/routes/index.js CHANGED
@@ -169,16 +169,6 @@ module.exports = function(routes, resolve) {
169
169
  path: '/news/tag/:tag',
170
170
  component: resolve('@lancom/shared/pages/news/tag/_tag.vue'),
171
171
  chunkName: 'pages/news/tag'
172
- }, {
173
- name: 'products-kits',
174
- path: '/products-kits',
175
- component: resolve('@lancom/shared/pages/products-kit/index.vue'),
176
- chunkName: 'pages/products-kits'
177
- }, {
178
- name: 'products-kit-view',
179
- path: '/products-kit/:alias',
180
- component: resolve('@lancom/shared/pages/products-kit/_alias.vue'),
181
- chunkName: 'pages/products-kit/products-kit-view'
182
172
  }, {
183
173
  name: 'contact',
184
174
  path: '/contact',
@@ -6,7 +6,8 @@ const robots = fs.readFileSync(filePath);
6
6
 
7
7
  module.exports = function (req, res, next) {
8
8
  if (req.url === '/robots.txt') {
9
- if (req.headers.host?.includes('www1') && !req.headers['X-PULL'] && !req.headers['x-pull']) {
9
+ const isDeny = ['www1', 'dev.', 'dev1.'].some(key => req.headers.host?.includes(key));
10
+ if (isDeny && !req.headers['X-PULL'] && !req.headers['x-pull']) {
10
11
  res.setHeader('Content-Type', 'text/plain');
11
12
  res.end('User-agent: *\nDisallow: /');
12
13
  return;
package/store/cart.js CHANGED
@@ -28,7 +28,7 @@ const getEntitiesQuantity = entities => {
28
28
  const getProductsQuantities = entities => {
29
29
  const grouped = groupBy(entities, e => e.product._id);
30
30
  const quantities = Object.keys(grouped).map(_id => {
31
- const [{ prints, product: { name, minimumOrderQuantity, minimumPrintOrderQuantity } }] = grouped[_id];
31
+ const [{ prints, product: { name, minimumOrderQuantity, minimumPrintOrderQuantity, SKU } }] = grouped[_id];
32
32
  const minQty = (prints?.length > 0 ? minimumPrintOrderQuantity : minimumOrderQuantity) || minimumOrderQuantity;
33
33
  const simpleProducts = grouped[_id].reduce((simpleProducts, product) => {
34
34
  product.simpleProducts?.forEach(simpleProduct => {
@@ -43,7 +43,7 @@ const getProductsQuantities = entities => {
43
43
  });
44
44
  return simpleProducts;
45
45
  }, []);
46
- return { _id, name, minimumOrderQuantity: minQty, quantity: getEntitiesQuantity(grouped[_id]), simpleProducts };
46
+ return { _id, name, SKU, minimumOrderQuantity: minQty, quantity: getEntitiesQuantity(grouped[_id]), simpleProducts };
47
47
  });
48
48
  return quantities;
49
49
  };
@@ -101,6 +101,7 @@ export const getters = {
101
101
  ...simpleProducts,
102
102
  ...product?.simpleProducts?.map(sp => ({
103
103
  name: product.name,
104
+ productSKU: product.SKU,
104
105
  ...sp
105
106
  }))
106
107
  ]
@@ -109,7 +110,6 @@ export const getters = {
109
110
  const notValid = simpleProducts.filter(sp => sp.warehouseQuantityStock < sp.amount)
110
111
 
111
112
  return notValid;
112
-
113
113
  },
114
114
  notValidStockQuantities(state, { quantities }) {
115
115
  return (quantities?.stock || []).filter(({ maxQuantity, quantity }) => quantity > maxQuantity);
@@ -190,7 +190,7 @@ export const actions = {
190
190
  await api.saveCart(payload, shop._id);
191
191
  commit('setEntities', entities);
192
192
  },
193
- async calculateCartPrice({ state: { suburb, entities, coupon, cartPricing }, getters: { needToPickupWithoutErrors }, commit }, { shop, country, currency }) {
193
+ async calculateCartPrice({ state: { suburb, entities, coupon, cartPricing }, getters: { needToPickup }, commit }, { shop, country, currency }) {
194
194
  let savedSuppliersWithRates = null;
195
195
  try {
196
196
  savedSuppliersWithRates = JSON.parse(localStorage.getItem(SUPPLIERS_WITH_RATES_KEY));
@@ -200,7 +200,7 @@ export const actions = {
200
200
  console.log('calculateCartPrice:payload: ', payload)
201
201
  try {
202
202
  commit('setCartPricingCalculating', true);
203
- const response = await api.calculateProductPrice({ ...payload, needToPickup: needToPickupWithoutErrors }, shop._id);
203
+ const response = await api.calculateProductPrice({ ...payload, needToPickup }, shop._id);
204
204
  commit('setCartPricing', response);
205
205
  commit('setCartPricingError', null);
206
206
  } catch (e) {
@@ -41,7 +41,7 @@ export const getters = {
41
41
  selectedOptionsSizes: ({ selectedOptionsSizes }) => selectedOptionsSizes,
42
42
  isAllOptionsSelected: ({ productsKit, selectedOptionsSimpleProducts, selectedOptionsColors, selectedOptionsSizes }) => {
43
43
  const selectedSimpleProducts = getSelectedSimpleProducts(selectedOptionsSimpleProducts, selectedOptionsColors, selectedOptionsSizes);
44
- return productsKit.options?.length === selectedSimpleProducts?.length;
44
+ return productsKit.productsKitOptions?.length === selectedSimpleProducts?.length;
45
45
  },
46
46
  selectedSimpleProducts: ({ selectedOptionsSimpleProducts, selectedOptionsColors, selectedOptionsSizes }) => getSelectedSimpleProducts(selectedOptionsSimpleProducts, selectedOptionsColors, selectedOptionsSizes),
47
47
  maxOrderQty: ({ selectedOptionsSimpleProducts, selectedOptionsColors, selectedOptionsSizes }) => {
@@ -53,7 +53,7 @@ export const getters = {
53
53
  },
54
54
  cartEntities: ({ amount, productsKit, selectedOptionsProducts, selectedOptionsSimpleProducts, selectedOptionsColors, selectedOptionsSizes }) => {
55
55
  const productsKitGuid = generateGUID();
56
- const entities = productsKit.options
56
+ const entities = productsKit.productsKitOptions
57
57
  .map(option => {
58
58
  const product = selectedOptionsProducts[option._id];
59
59
  if (product) {
@@ -87,6 +87,9 @@ export const actions = {
87
87
  });
88
88
  }
89
89
  },
90
+ setProductsKit({ commit }, productsKit) {
91
+ commit('setProductsKit', productsKit);
92
+ },
90
93
  async selectOptionProduct({ commit }, { option, product, country, currency, stockCountry }) {
91
94
  const params = { country, currency, stockCountry };
92
95
  const simpleProducts = await api.fetchProductDetails(null, product.alias, params);
@@ -104,7 +107,7 @@ export const actions = {
104
107
  },
105
108
  clearOptions({ commit, state }) {
106
109
  const { productsKit } = state;
107
- productsKit.options?.forEach(option => {
110
+ productsKit.productsKitOptions?.forEach(option => {
108
111
  commit('clearOption', option);
109
112
  });
110
113
  },
@@ -117,7 +120,7 @@ export const actions = {
117
120
  async calculateProductsKitPrice({ state, commit }, { shop, country, currency }) {
118
121
  commit('setCalculatingPrice', true);
119
122
  const { amount, selectedOptionsProducts, productsKit, selectedOptionsSimpleProducts, selectedOptionsColors, selectedOptionsSizes } = state;
120
- const entities = productsKit.options
123
+ const entities = productsKit.productsKitOptions
121
124
  .map(option => {
122
125
  const product = selectedOptionsProducts[option._id];
123
126
  if (product) {
@@ -1,103 +0,0 @@
1
- <template>
2
- <div class="ProductsKitPage__wrapper">
3
- <breadcrumbs :items="breadcrumbs" />
4
- <transition name="from-right-to-left" appear>
5
- <products-kit :products-kit="productsKit" />
6
- </transition>
7
- <transition
8
- v-if="hasProductsKitsList"
9
- name="from-left-to-right"
10
- appear>
11
- <div class="content-inner">
12
- <h2 class="lc_h2 lc_black ProductsKitPage__list-title">
13
- You might also like
14
- </h2>
15
- <products-kits-list
16
- class="ProductsKitPage__list"
17
- :productsKits="productsKitsList" />
18
- </div>
19
- </transition>
20
- </div>
21
- </template>
22
- <script>
23
- import metaInfo from '@lancom/shared/mixins/meta-info';
24
-
25
- export default {
26
- name: 'ProductsKitPage',
27
- components: {
28
- Breadcrumbs: () => import('@lancom/shared/components/common/breadcrumbs/breadcrumbs'),
29
- ProductsKit: () => import('@lancom/shared/components/products_kit/products_kit/products-kit'),
30
- ProductsKitsList: () => import('@lancom/shared/components/products_kit/products_kits_list/products-kits-list')
31
- },
32
- mixins: [metaInfo],
33
- async asyncData({ params, store, error, redirect }) {
34
- console.log('asyncData: ', 1);
35
- try {
36
- const { shop, country, currency } = store.getters;
37
- const data = {
38
- shop: shop._id,
39
- alias: params.alias,
40
- country: country?._id,
41
- currency: currency?._id
42
- };
43
- await store.dispatch('productsKit/fetchProductsKit', data);
44
- } catch (e) {
45
- console.log(e);
46
- }
47
- const productsKit = store.getters['productsKit/productsKit'];
48
- const loadError = store.getters['productsKit/loadError'];
49
-
50
- // console.log('loadError: ', loadError);
51
- if (loadError?.redirectLink) {
52
- return redirect(301, loadError?.redirectLink);
53
- } else if (loadError) {
54
- error(loadError);
55
- }
56
- return {
57
- productsKit
58
- };
59
- },
60
- data() {
61
- return {
62
- productsKit: null,
63
- productsKits: []
64
- };
65
- },
66
- computed: {
67
- hasProductsKitsList() {
68
- return this.productsKitsList.length > 0;
69
- },
70
- productsKitsList() {
71
- return this.productsKits.filter(i => i.alias !== this.productsKit.alias).slice(0, 3);
72
- },
73
- breadcrumbs() {
74
- const item = this.productsKit ? { text: this.productsKit.name } : null;
75
- return [{
76
- to: '/products-kits',
77
- text: 'Products Kits'
78
- }, item].filter(i => !!i);
79
- },
80
- pageItemType() {
81
- return 'products-kits';
82
- }
83
- },
84
- getRoute() {
85
- return '/shop/:shop/products-kits/:alias';
86
- }
87
- };
88
- </script>
89
-
90
- <style lang="scss" scoped>
91
- .ProductsKitPage{
92
- &__wrapper {
93
- margin-top: 30px;
94
- }
95
- &__list-title {
96
- margin-top: 20px;
97
- text-align: center;
98
- }
99
- &__list {
100
- padding: 20px 0;
101
- }
102
- }
103
- </style>
@@ -1,59 +0,0 @@
1
- <template>
2
- <div class="ProductsKitsPage__wrapper view-transition">
3
- <div class="content-inner">
4
- <breadcrumbs :items="breadcrumbs" />
5
- <products-kits-list
6
- class="ProductsKitsPage__list"
7
- :products-kits="productsKits"
8
- :count="count"
9
- :page="page"
10
- :per-page="perPage" />
11
- </div>
12
- </div>
13
- </template>
14
-
15
- <script>
16
- import metaInfo from '@lancom/shared/mixins/meta-info';
17
- import api from '@lancom/shared/assets/js/api';
18
-
19
- const KITS_PER_PAGE = 18;
20
-
21
- export default {
22
- name: 'ProductPage',
23
- components: {
24
- Breadcrumbs: () => import('@lancom/shared/components/common/breadcrumbs/breadcrumbs'),
25
- ProductsKitsList: () => import('@lancom/shared/components/products_kit/products_kits_list/products-kits-list')
26
- },
27
- mixins: [metaInfo],
28
- async asyncData({ store, params, query }) {
29
- const { _id } = store.getters.shop;
30
- const kitsQuery = { ...params, ...query, limit: KITS_PER_PAGE };
31
- const { productsKits, count, page, perPage } = await api.fetchProductsKits(_id, kitsQuery);
32
- return { productsKits, count, page, perPage };
33
- },
34
- data() {
35
- return {
36
- productsKits: [],
37
- count: null,
38
- page: null,
39
- perPage: null,
40
- breadcrumbs: [{
41
- text: 'Products Kits'
42
- }]
43
- };
44
- },
45
- computed: {
46
- pageItemType() {
47
- return 'products-kits';
48
- }
49
- }
50
- };
51
- </script>
52
-
53
- <style lang="scss" scoped>
54
- .ProductsKitsPage {
55
- &__wrapper {
56
- padding: 30px 0;
57
- }
58
- }
59
- </style>