@lancom/shared 0.0.207 → 0.0.208

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.
@@ -30,6 +30,53 @@
30
30
  margin-right: 22px;
31
31
  }
32
32
  }
33
+ &__upload {
34
+ display: flex;
35
+ &--uploading {
36
+ pointer-events: none;
37
+ opacity: 0.7;
38
+ }
39
+ &-btn {
40
+ font-weight: bold;
41
+ font-size: 16px;
42
+ line-height: 22px;
43
+ text-transform: uppercase;
44
+ background-color: $green;
45
+ padding: 8px 14px;
46
+ &--disabled {
47
+ background-color: $gray;
48
+ }
49
+ &--uploaded {
50
+ opacity: 0.5;
51
+ }
52
+ }
53
+ &-info {
54
+ font-size: 12px;
55
+ line-height: 16px;
56
+ color: $black;
57
+ margin-left: 12px;
58
+ }
59
+ &-progress {
60
+ position: absolute;
61
+ left: 0;
62
+ top: 0;
63
+ right: 0;
64
+ bottom: 0;
65
+ }
66
+ &-file {
67
+ margin-top: 5px;
68
+ font-size: 12px;
69
+ a {
70
+ text-decoration: none;
71
+ color: $black;
72
+ }
73
+ }
74
+ }
75
+ &__error {
76
+ margin-top: 5px;
77
+ font-size: 12px;
78
+ color: $error;
79
+ }
33
80
  }
34
81
  ::v-deep .AddReview__content {
35
82
  input,
@@ -9,7 +9,7 @@
9
9
  tag="form"
10
10
  class="AddReview__form">
11
11
  <div class="row">
12
- <div class="col-sm-6 col-12">
12
+ <div class="col-sm-7 col-12">
13
13
  <validation-provider
14
14
  v-slot="{ errors }"
15
15
  tag="div"
@@ -40,7 +40,7 @@
40
40
  </span>
41
41
  </validation-provider>
42
42
  </div>
43
- <div class="col-sm-6 col-12">
43
+ <div class="col-sm-5 col-12">
44
44
  <validation-provider
45
45
  v-slot="{ errors }"
46
46
  tag="div"
@@ -72,8 +72,10 @@
72
72
  </validation-provider>
73
73
  </div>
74
74
  </div>
75
- <div class="row">
76
- <div class="col-12">
75
+ <div
76
+ class="row"
77
+ style="margin-top: -20px;">
78
+ <div class="col-sm-7 col-12">
77
79
  <validation-provider
78
80
  v-slot="{ errors }"
79
81
  tag="div"
@@ -103,6 +105,113 @@
103
105
  </span>
104
106
  </validation-provider>
105
107
  </div>
108
+ <div class="col-sm-5 col-12">
109
+ <div>
110
+ <validation-provider
111
+ v-slot="{ errors }"
112
+ tag="div"
113
+ name="Pro"
114
+ class="form-row">
115
+ <input
116
+ id="add-review-pro"
117
+ ref="pro"
118
+ v-model="form.pro"
119
+ name="pro"
120
+ type="text"
121
+ class="form-field"
122
+ :class="{
123
+ 'is-danger': errors.length,
124
+ filled: form.pro
125
+ }" />
126
+ <label
127
+ for="add-review-pro"
128
+ class="form-label label-inner">
129
+ Pro
130
+ </label>
131
+ <span
132
+ v-if="errors.length"
133
+ class="form-help is-danger">
134
+ {{ errors[0] }}
135
+ </span>
136
+ </validation-provider>
137
+ </div>
138
+ <div class="mt-10">
139
+ <validation-provider
140
+ v-slot="{ errors }"
141
+ tag="div"
142
+ name="Cons"
143
+ class="form-row">
144
+ <input
145
+ id="add-review-cons"
146
+ ref="cons"
147
+ v-model="form.cons"
148
+ name="cons"
149
+ type="text"
150
+ class="form-field"
151
+ :class="{
152
+ 'is-danger': errors.length,
153
+ filled: form.cons
154
+ }" />
155
+ <label
156
+ for="add-review-cons"
157
+ class="form-label label-inner">
158
+ Cons
159
+ </label>
160
+ <span
161
+ v-if="errors.length"
162
+ class="form-help is-danger">
163
+ {{ errors[0] }}
164
+ </span>
165
+ </validation-provider>
166
+ </div>
167
+ </div>
168
+ </div>
169
+ <div class="form-row">
170
+ <file-uploader
171
+ url="reviews/image"
172
+ :multiple="false"
173
+ :has-conversion-error-modal="false"
174
+ :show-error-message="false"
175
+ @onchange="handleUploadChange"
176
+ @onerror="handleUploadError"
177
+ @onuploaded="handleUploaded">
178
+ <template v-slot:toggle="{ uploading }">
179
+ <div
180
+ class="AddReview__upload"
181
+ :class="{
182
+ 'AddReview__upload--uploading': uploading
183
+ }">
184
+ <div
185
+ class="AddReview__upload-btn"
186
+ :class="{
187
+ 'AddReview__upload-btn--disabled': uploading,
188
+ 'AddReview__upload-btn--uploaded': form.image
189
+ }">
190
+ Upload
191
+ </div>
192
+ <div class="AddReview__upload-info">
193
+ <div>Note: accepted file types:</div>
194
+ <div><b>JPEG / PNG / AI / EPS / PDF</b></div>
195
+ </div>
196
+ </div>
197
+ </template>
198
+ <template v-slot:progress="{ progress }">
199
+ <div
200
+ v-if="progress"
201
+ class="AddReview__upload-progress">
202
+ <spinner background="black" />
203
+ </div>
204
+ </template>
205
+ </file-uploader>
206
+ <div
207
+ v-if="uploadError"
208
+ class="AddReview__error">
209
+ {{ uploadError }}
210
+ </div>
211
+ <product-review-image
212
+ v-if="form.image"
213
+ style="margin-top: 10px;"
214
+ :review="form" />
106
215
  </div>
107
216
  <div class="AddReview__mark">
108
217
  <div class="AddReview__mark-label">
@@ -128,14 +237,18 @@
128
237
  </template>
129
238
 
130
239
  <script>
240
+ import FileUploader from '@lancom/shared/components/common/file_uploader';
131
241
  import StarsMark from '@lancom/shared/components/common/stars-mark';
132
242
  import api from '@lancom/shared/assets/js/api';
133
243
  import { mapGetters } from 'vuex';
244
+ import ProductReviewImage from './../product_review/product_review_image/product-review-image';
134
245
 
135
246
  export default {
136
247
  name: 'AddReview',
137
248
  components: {
138
- StarsMark
249
+ FileUploader,
250
+ StarsMark,
251
+ ProductReviewImage
139
252
  },
140
253
  props: {
141
254
  product: {
@@ -145,11 +258,16 @@ export default {
145
258
  },
146
259
  data() {
147
260
  return {
261
+ uploadError: null,
262
+ processing: false,
148
263
  form: {
149
264
  name: '',
150
265
  email: '',
151
266
  text: '',
152
- mark: 4
267
+ pro: '',
268
+ cons: '',
269
+ mark: 4,
270
+ image: null
153
271
  }
154
272
  };
155
273
  },
@@ -157,6 +275,17 @@ export default {
157
275
  ...mapGetters(['shop'])
158
276
  },
159
277
  methods: {
278
+ handleUploaded(file) {
279
+ this.form.image = file;
280
+ },
281
+ handleUploadError(e) {
282
+ const { error, message } = e?.response?.data || {};
283
+ this.uploadError = error || message || 'Failed upload image';
284
+ },
285
+ handleUploadChange() {
286
+ this.uploadError = null;
287
+ this.form.image = null;
288
+ },
160
289
  async submit() {
161
290
  try {
162
291
  this.processing = true;
@@ -39,6 +39,20 @@ export default {
39
39
  ProductReview
40
40
  },
41
41
  mixins: [],
42
+ data() {
43
+ const reviews = this.product.reviews || [];
44
+ const [, mainReviewId] = (this.$route.hash || '').match(/review-([a-z0-9]+)/i) || [];
45
+ const mainReview = reviews.find(review => review._id === mainReviewId);
46
+ console.log('mainReviewId: ', mainReviewId);
47
+ return {
48
+ scrollInterval: null,
49
+ mainReview,
50
+ reviews: [
51
+ mainReview,
52
+ ...reviews.filter(review => review !== mainReview)
53
+ ].filter(review => !!review)
54
+ };
55
+ },
42
56
  props: {
43
57
  product: {
44
58
  type: Object,
@@ -48,11 +62,31 @@ export default {
48
62
  computed: {
49
63
  hasReviews() {
50
64
  return this.reviews.length > 0;
51
- },
52
- reviews() {
53
- return this.product.reviews || [];
54
65
  }
55
66
  },
67
+ mounted() {
68
+ setTimeout(() => {
69
+ if (this.mainReview) {
70
+ const [first, second] = document.querySelectorAll(`#review-${this.mainReview._id}`) || [];
71
+ const mainReviewEl = second || first;
72
+ let top = 0;
73
+ if (mainReviewEl) {
74
+ this.scrollInterval = setInterval(() => {
75
+ top += 50;
76
+ window.scroll(0, top);
77
+ const rect = mainReviewEl.getBoundingClientRect();
78
+ console.log('mainReviewEl: ', mainReviewEl, rect.top);
79
+ if (rect.top < 300) {
80
+ clearInterval(this.scrollInterval);
81
+ }
82
+ }, 50);
83
+ }
84
+ }
85
+ }, 100);
86
+ },
87
+ destroyed() {
88
+ clearInterval(this.scrollInterval);
89
+ },
56
90
  methods: {
57
91
  showAddReviewModal() {
58
92
  const params = {
@@ -37,6 +37,14 @@
37
37
  margin-bottom: 10px;
38
38
  }
39
39
  }
40
+ &__info {
41
+ font-size: 11px;
42
+ margin-top: 6px;
43
+ color: grey;
44
+ }
45
+ &__info-wrapper {
46
+ display: flex;
47
+ }
40
48
  &__readmore {
41
49
  font-weight: 800;
42
50
  font-size: 16px;
@@ -1,11 +1,36 @@
1
1
  <template>
2
- <div class="ProductReview__wrapper">
2
+ <div
3
+ :id="`review-${review._id}`"
4
+ class="ProductReview__wrapper">
3
5
  <div class="ProductReview__name">
4
6
  {{ review.name }}
5
7
  </div>
6
8
  <div class="ProductReview__mark">
7
9
  <stars-mark v-model="review.mark" :disabled="true" />
8
10
  </div>
11
+ <div
12
+ v-if="review.pro || review.cons || review.image"
13
+ class="ProductReview__info-wrapper">
14
+ <div
15
+ v-if="review.image"
16
+ style="margin-right: 10px; margin-top: 7px;">
17
+ <product-review-image
18
+ :review="review"
19
+ :size="50" />
20
+ </div>
21
+ <div>
22
+ <div
23
+ v-if="review.pro"
24
+ class="ProductReview__info">
25
+ Pro: {{ review.pro }}
26
+ </div>
27
+ <div
28
+ v-if="review.cons"
29
+ class="ProductReview__info">
30
+ Cons: {{ review.cons }}
31
+ </div>
32
+ </div>
33
+ </div>
9
34
  <div
10
35
  ref="text"
11
36
  class="ProductReview__text"
@@ -24,11 +49,13 @@
24
49
  <script>
25
50
  import StarsMark from '@lancom/shared/components/common/stars-mark';
26
51
  import { nl2p } from '@lancom/shared/assets/js/utils/filters';
52
+ import ProductReviewImage from './product_review_image/product-review-image';
27
53
 
28
54
  export default {
29
55
  name: 'ProductReviews',
30
56
  components: {
31
- StarsMark
57
+ StarsMark,
58
+ ProductReviewImage
32
59
  },
33
60
  props: {
34
61
  review: {
@@ -0,0 +1,54 @@
1
+ <template>
2
+ <div class="ProductReviewImage__wrapper">
3
+ <img
4
+ :src="review.image.small"
5
+ :style="{
6
+ width: `${size}px`
7
+ }"
8
+ @click="showImage">
9
+ </div>
10
+ </template>
11
+
12
+ <script>
13
+ import ImageViewer from '@lancom/shared/components/common/image_viewer/image-viewer';
14
+
15
+ export default {
16
+ name: 'ProductReviewImage',
17
+ props: {
18
+ review: {
19
+ type: Object,
20
+ required: true
21
+ },
22
+ size: {
23
+ type: Number,
24
+ default: 100
25
+ }
26
+ },
27
+ methods: {
28
+ showImage() {
29
+ this.$modal.show(
30
+ ImageViewer,
31
+ {
32
+ items: [{
33
+ src: this.review.image.large
34
+ }],
35
+ index: 0
36
+ },
37
+ {
38
+ name: 'image-viewer-modal',
39
+ root: this.$root,
40
+ width: '100%',
41
+ height: 'auto',
42
+ adaptive: true,
43
+ clickToClose: true,
44
+ transition: 'from-top-to-bottom'
45
+ }
46
+ );
47
+ }
48
+ }
49
+ };
50
+ </script>
51
+
52
+ <style lang="scss" scoped>
53
+ @import 'product-review-image.scss';
54
+ </style>
@@ -30,12 +30,12 @@
30
30
  <tr>
31
31
  <td>
32
32
  <div><b>QUOTE</b></div>
33
- <div>{{ quote.fullName }}</div>
34
- <div v-if="quote.company">{{ quote.company }}</div>
35
- <div v-if="quote.phone">{{ quote.phone }}</div>
36
- <div>{{ quote.email }}</div>
33
+ <div>{{ quote.address.fullName }}</div>
34
+ <div v-if="quote.address.company">{{ quote.address.company }}</div>
35
+ <div v-if="quote.address.phone">{{ quote.address.phone }}</div>
36
+ <div>{{ quote.address.email }}</div>
37
37
  <div>{{ quoteAddress }}</div>
38
- <div>{{ quote.additionalInfo }}</div>
38
+ <div>{{ quote.address.additionalInfo }}</div>
39
39
  </td>
40
40
  <td class="w-50">
41
41
  <div><b>DIRECT DEPOSIT DETAILS</b></div>
package/nuxt.config.js CHANGED
@@ -99,7 +99,7 @@ module.exports = (config, axios, { raygunClient, publicPath } = {}) => ({
99
99
  config.devtool = 'source-map';
100
100
  }
101
101
  },
102
- extractCSS: true,
102
+ extractCSS: !config.IS_LOCAL,
103
103
  },
104
104
  hooks: {
105
105
  render: {
@@ -111,12 +111,112 @@ module.exports = (config, axios, { raygunClient, publicPath } = {}) => ({
111
111
  },
112
112
  },
113
113
  feed: [{
114
+ path: '/pr-rev-au.xml',
115
+ async get() {
116
+ const { data } = await axios.get(`${config.LOCAL_API_URL}/feed/reviews?host=${config.HOST_NAME}`);
117
+
118
+ return {
119
+ _declaration: { _attributes: { version: "1.0", encoding: "utf-8" } },
120
+ feed: {
121
+ _attributes: {
122
+ 'xmlns:vc': 'http://www.w3.org/2007/XMLSchema-versioning',
123
+ 'xmlns:xsi': 'http://www.w3.org/2001/XMLSchema-instance',
124
+ 'xsi:noNamespaceSchemaLocation': 'http://www.google.com/shopping/reviews/schema/product/2.3/product_reviews.xsd'
125
+ },
126
+ version: { _text: '2.3' },
127
+ publisher: {
128
+ name: { _text: 'Workdepot Australia' },
129
+ favicon: { _text: 'https://www.workdepot.com.au/favicon.png' }
130
+ },
131
+ reviews: {
132
+ review: [
133
+ ...data
134
+ .filter(review => !!review.product)
135
+ .map(review => {
136
+ const { product } = review;
137
+ const productUrl = `https://${config.HOST_NAME}/${product.brand.alias}/${product.productType.alias}/${product.alias}`;
138
+ const item = {
139
+ review_id: { _text: review._id },
140
+ reviewer: {
141
+ name: {
142
+ _attributes: {
143
+ is_anonymous: 'false'
144
+ },
145
+ _text: review.name
146
+ }
147
+ },
148
+ review_timestamp: { _text: review.createdAt },
149
+ content: { _text: review.text },
150
+ review_url: {
151
+ _attributes: {
152
+ type: 'singleton'
153
+ },
154
+ _text: `${productUrl}#review-${review._id}`
155
+ },
156
+ ratings: {
157
+ overall: {
158
+ _attributes: {
159
+ min: 1,
160
+ max: 5
161
+ },
162
+ _text: review.mark
163
+ }
164
+ },
165
+ products: {
166
+ product: {
167
+ product_ids: {
168
+ // gtins: {
169
+ // gtin: { _text: '541710238425' }
170
+ // },
171
+ // mpns: {
172
+ // mpn: { _text: '60101-10000' }
173
+ // },
174
+ skus: {
175
+ sku: { _text: review.product.SKU }
176
+ },
177
+ brands: {
178
+ brand: { _text: review.product.brand.name }
179
+ },
180
+ // asins: {
181
+ // asin: { _text: 'B07YMJ57MB' }
182
+ // }
183
+ },
184
+ product_name: { _text: review.product.name },
185
+ product_url: { _text: productUrl }
186
+ }
187
+ }
188
+ };
189
+ if (review.pro) {
190
+ item.pros = {
191
+ pro: [{ _text: review.pro }]
192
+ };
193
+ }
194
+ if (review.cons) {
195
+ item.cons = {
196
+ con: [{ _text: review.cons }]
197
+ };
198
+ }
199
+ if (review.image) {
200
+ item.reviewer_images = {
201
+ reviewer_image: [{
202
+ url: { _text: review.image.large }
203
+ }]
204
+ };
205
+ }
206
+ return item;
207
+ })
208
+ ]
209
+ }
210
+ }
211
+ };
212
+ }
213
+ }, {
114
214
  path: '/google-shopping.xml',
115
215
  async get() {
116
216
  const { data } = await axios.get(`${config.LOCAL_API_URL}/feed/products?host=${config.HOST_NAME}`);
117
217
  const spliceFirstImage = images => (images || []).splice(0, 1)[0];
118
218
  const getImages = images => (images || []).length > 0 ? images : null;
119
- return {
219
+ const channel = {
120
220
  title: { _text: 'All products' },
121
221
  link: { _text: `https://${config.HOST_NAME}` },
122
222
  generator: { _text: config.HOST_NAME },
@@ -183,6 +283,18 @@ module.exports = (config, axios, { raygunClient, publicPath } = {}) => ({
183
283
  ];
184
284
  }, [])
185
285
  };
286
+
287
+ return {
288
+ _declaration: { _attributes: { version: "1.0", encoding: "utf-8" } },
289
+ rss: {
290
+ _attributes: { version: "2.0", 'xmlns:g': "http://base.google.com/ns/1.0" },
291
+ channel: {
292
+ lastBuildDate: { _text: new Date().toUTCString() },
293
+ docs: { _text: "https://validator.w3.org/feed/docs/rss2.html" },
294
+ ...channel
295
+ },
296
+ },
297
+ }
186
298
  }
187
299
  }],
188
300
  router: {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lancom/shared",
3
- "version": "0.0.207",
3
+ "version": "0.0.208",
4
4
  "description": "lancom common scripts",
5
5
  "author": "e.tokovenko <e.tokovenko@gmail.com>",
6
6
  "repository": {