@lancom/shared 0.0.475 → 0.0.476

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.
@@ -296,8 +296,8 @@ export default {
296
296
  removeUser(id) {
297
297
  return _delete(`admin/user/${id}`);
298
298
  },
299
- fetchCustomers() {
300
- return _get('admin/customers');
299
+ fetchCustomers(params) {
300
+ return _get('admin/customers', params);
301
301
  },
302
302
  fetchCustomerById(id) {
303
303
  return _get(`admin/customer/${id}`);
@@ -398,6 +398,18 @@ export default {
398
398
  removeContact(id) {
399
399
  return _delete(`admin/contact/${id}`);
400
400
  },
401
+ fetchTradeAccountRequests(params) {
402
+ return _get('admin/trade-account', params);
403
+ },
404
+ fetchTradeAccountRequestById(id) {
405
+ return _get(`admin/trade-account/${id}`);
406
+ },
407
+ saveTradeAccountRequest(item) {
408
+ return _put(`admin/trade-account/${item._id}`, item);
409
+ },
410
+ removeTradeAccountRequest(id) {
411
+ return _delete(`admin/trade-account/${id}`);
412
+ },
401
413
  fetchLeadById(id) {
402
414
  return _get(`admin/lead/${id}`);
403
415
  },
@@ -147,6 +147,9 @@ const api = {
147
147
  contactUs(data, shop) {
148
148
  return _post(`shop/${shop}/contact`, data);
149
149
  },
150
+ requestTradeAccount(data, shop) {
151
+ return _post(`shop/${shop}/trade-account`, data);
152
+ },
150
153
  fetchQuoteById(id, params) {
151
154
  return _get(`quotes/${id}`, params);
152
155
  },
@@ -1,17 +1,23 @@
1
1
 
2
2
  import { staticLink } from './filters';
3
3
  import { COLORS_IMAGES_TYPES } from './../constants/colors';
4
+ import { getCloudflareImageSrc } from './image';
4
5
 
5
6
  export const isValidImageType = (i, type) => i.type === type || (i.types || []).includes(type);
6
7
 
7
8
  export const isValidImageTypes = (i, types) => types.some(type => isValidImageType(i, type));
8
9
 
9
10
  export const getColorImage = (product = {}, size = 'small', type, color, allowAnyColor = false, excludeType = []) => {
11
+ const image = getColorImageObject(product, type, color, allowAnyColor, excludeType);
12
+ return image && ((image[size] && staticLink(image[size])) || getCloudflareImageSrc(image.cloudflareImageId, size));
13
+ };
14
+
15
+ export const getColorImageObject = (product = {}, type, color, allowAnyColor = false, excludeType = []) => {
10
16
  const excludeTypes = (excludeType && Array.isArray(excludeType) ? excludeType : [excludeType]) || [];
11
17
  const validImages = (product.images || []).filter(i => !excludeTypes.some(type => isValidImageType(i, type)));
12
18
  const colorImage = color && validImages.find(i => isValidImageType(i, type || COLORS_IMAGES_TYPES.FRONT) && (color?._id === i.color || color?._id === i.color?._id));
13
19
  const image = colorImage || validImages.find(i => isValidImageType(i, type || COLORS_IMAGES_TYPES.FRONT) && (allowAnyColor || !i.color));
14
- return image && image[size] && staticLink(image[size]);
20
+ return image;
15
21
  };
16
22
 
17
23
  export const getProductCover = (product = {}, size = 'small', type = COLORS_IMAGES_TYPES.FRONT, color, allowAnyColor = false) => {
@@ -49,6 +55,20 @@ export const getProductMediumCover = (product, type, color) => getProductCover(p
49
55
  export const getProductLargeCover = (product, type, color) => getProductCover(product, 'large', type, color);
50
56
  export const getProductOriginCover = (product, type, color) => getProductCover(product, 'origin', type, color);
51
57
 
58
+ export const getProductCoverObject = (product, type = COLORS_IMAGES_TYPES.FRONT, color) =>
59
+ (color && getColorImageObject(product, `catalog_${type}`, color)) ||
60
+ (color && getColorImageObject(product, type, color)) ||
61
+ getColorImageObject(product, `catalog_${type}`, null, true) ||
62
+ getColorImageObject(product, type, color) ||
63
+ getColorImageObject(product, type, null, true) ||
64
+ null;
65
+
66
+ export const getProductHoverCoverObject = (product, currentColor) =>
67
+ getColorImageObject(product, 'catalog_hover', currentColor) ||
68
+ (currentColor && getColorImageObject(product, 'back', currentColor)) ||
69
+ getColorImageObject(product, 'catalog_hover', null, true) ||
70
+ null;
71
+
52
72
  export const getBgStyle = img => img && ({ 'background-image': `url("${img}")` });
53
73
 
54
74
  export function getColorBackgroundStyle(color, skipPattern, originBackground) {
@@ -11,7 +11,7 @@ export function getCloudflareImageSrc(cloudflareImageId, variant = IMAGE_VARIANT
11
11
  }
12
12
 
13
13
  export function getCloudflareImageSrcset(cloudflareImageId) {
14
- const lines = (cloudflareImageId || '').split("\n").map(l => l.trim()).filter(Boolean);
14
+ const lines = (cloudflareImageId || '').split(/\n/g).map(l => l.trim()).filter(Boolean);
15
15
  if (lines.length < 2) {
16
16
  return null;
17
17
  }
@@ -5,7 +5,8 @@
5
5
  :alt="alt"
6
6
  :loading="loading"
7
7
  :decoding="decoding"
8
- :class="imgClass" />
8
+ :class="imgClass"
9
+ @load="$emit('load', $event)" />
9
10
  </template>
10
11
 
11
12
  <script>
@@ -45,13 +46,19 @@ export default {
45
46
  },
46
47
  computed: {
47
48
  resolvedSrc() {
48
- if (this.overrideSrc) return this.overrideSrc;
49
- if (!this.image) return null;
49
+ if (this.overrideSrc) {
50
+ return this.overrideSrc;
51
+ }
52
+ if (!this.image) {
53
+ return null;
54
+ }
50
55
  const { large, medium, small, thumb, origin, cloudflareImageId } = this.image;
51
- return this.image[this.size] || large || medium || small || thumb || origin || getCloudflareImageSrc(cloudflareImageId);
56
+ return this.image[this.size] || large || medium || small || thumb || origin || getCloudflareImageSrc(cloudflareImageId, this.size);
52
57
  },
53
58
  resolvedSrcset() {
54
- if (this.overrideSrc) return null;
59
+ if (this.overrideSrc) {
60
+ return null;
61
+ }
55
62
  return getCloudflareImageSrcset(this.image?.cloudflareImageId);
56
63
  }
57
64
  }
@@ -0,0 +1,524 @@
1
+ <template>
2
+ <div class="TradeAccountRequest__wrapper">
3
+ <div
4
+ v-if="!submitted"
5
+ class="TradeAccountRequest__form-container">
6
+ <slot name="header">
7
+ <div class="TradeAccountRequest__header">
8
+ <h1 class="TradeAccountRequest__title">
9
+ Request Trade Account
10
+ </h1>
11
+ <p class="TradeAccountRequest__description">
12
+ For businesses making regular purchases, a 30-day trade account offers a seamless and efficient way to order your workwear and supplies.
13
+ Please complete the form below to apply.
14
+ </p>
15
+ <p class="TradeAccountRequest__note">
16
+ <strong>Please note:</strong> 30-day trade accounts are available for businesses with a minimum monthly spend of $500.
17
+ </p>
18
+ </div>
19
+ </slot>
20
+
21
+ <validation-observer
22
+ ref="form"
23
+ v-slot="{ handleSubmit, errors: submittedErrors }"
24
+ tag="form"
25
+ class="TradeAccountRequest__form">
26
+ <div class="TradeAccountRequest__section-title">
27
+ Contact Details
28
+ </div>
29
+ <div class="row">
30
+ <div class="col-sm-6 col-12">
31
+ <validation-provider
32
+ v-slot="{ errors }"
33
+ tag="div"
34
+ name="First Name"
35
+ rules="required"
36
+ class="form-row">
37
+ <label class="form-label">First Name</label>
38
+ <input
39
+ v-model="form.firstName"
40
+ type="text"
41
+ class="form-field labelless"
42
+ placeholder="First name"
43
+ :class="{ 'is-danger': errors.length, filled: form.firstName }" />
44
+ <span
45
+ v-if="errors.length"
46
+ class="form-help is-danger">
47
+ {{ errors[0] }}
48
+ </span>
49
+ </validation-provider>
50
+ </div>
51
+ <div class="col-sm-6 col-12">
52
+ <validation-provider
53
+ v-slot="{ errors }"
54
+ tag="div"
55
+ name="Last Name"
56
+ rules="required"
57
+ class="form-row">
58
+ <label class="form-label">Last Name</label>
59
+ <input
60
+ v-model="form.lastName"
61
+ type="text"
62
+ class="form-field labelless"
63
+ placeholder="Last name"
64
+ :class="{ 'is-danger': errors.length, filled: form.lastName }" />
65
+ <span
66
+ v-if="errors.length"
67
+ class="form-help is-danger">
68
+ {{ errors[0] }}
69
+ </span>
70
+ </validation-provider>
71
+ </div>
72
+ </div>
73
+
74
+ <div class="row">
75
+ <div class="col-sm-6 col-12">
76
+ <validation-provider
77
+ v-slot="{ errors }"
78
+ tag="div"
79
+ name="Phone"
80
+ rules="required"
81
+ class="form-row">
82
+ <label class="form-label">Phone Number</label>
83
+ <input
84
+ v-model="form.phone"
85
+ type="tel"
86
+ class="form-field labelless"
87
+ placeholder="0400 000 000"
88
+ :class="{ 'is-danger': errors.length, filled: form.phone }" />
89
+ <span
90
+ v-if="errors.length"
91
+ class="form-help is-danger">
92
+ {{ errors[0] }}
93
+ </span>
94
+ </validation-provider>
95
+ </div>
96
+ <div class="col-sm-6 col-12">
97
+ <validation-provider
98
+ v-slot="{ errors }"
99
+ tag="div"
100
+ name="Email"
101
+ rules="required|email"
102
+ class="form-row">
103
+ <label class="form-label">Email Address</label>
104
+ <input
105
+ v-model="form.email"
106
+ type="email"
107
+ class="form-field labelless"
108
+ placeholder="your@email.com.au"
109
+ :class="{ 'is-danger': errors.length, filled: form.email }" />
110
+ <span
111
+ v-if="errors.length"
112
+ class="form-help is-danger">
113
+ {{ errors[0] }}
114
+ </span>
115
+ </validation-provider>
116
+ </div>
117
+ </div>
118
+
119
+ <div class="TradeAccountRequest__section-title">
120
+ Business Information
121
+ </div>
122
+ <div class="row">
123
+ <div class="col-sm-6 col-12">
124
+ <validation-provider
125
+ v-slot="{ errors }"
126
+ tag="div"
127
+ name="Company Name"
128
+ rules="required"
129
+ class="form-row">
130
+ <label class="form-label">Company Name</label>
131
+ <input
132
+ v-model="form.businessName"
133
+ type="text"
134
+ class="form-field labelless"
135
+ placeholder="Your company name"
136
+ :class="{ 'is-danger': errors.length, filled: form.businessName }" />
137
+ <span
138
+ v-if="errors.length"
139
+ class="form-help is-danger">
140
+ {{ errors[0] }}
141
+ </span>
142
+ </validation-provider>
143
+ </div>
144
+ <div class="col-sm-6 col-12">
145
+ <validation-provider
146
+ v-slot="{ errors }"
147
+ tag="div"
148
+ name="ABN"
149
+ class="form-row">
150
+ <label class="form-label">ABN</label>
151
+ <input
152
+ v-model="form.abn"
153
+ type="text"
154
+ class="form-field labelless"
155
+ placeholder="Australian Business Number"
156
+ :class="{ 'is-danger': errors.length, filled: form.abn }" />
157
+ <span
158
+ v-if="errors.length"
159
+ class="form-help is-danger">
160
+ {{ errors[0] }}
161
+ </span>
162
+ </validation-provider>
163
+ </div>
164
+ </div>
165
+
166
+ <div class="row">
167
+ <div class="col-sm-6 col-12">
168
+ <validation-provider
169
+ v-slot="{ errors }"
170
+ tag="div"
171
+ name="Industry"
172
+ class="form-row">
173
+ <label class="form-label">Industry / Business Type</label>
174
+ <input
175
+ v-model="form.industry"
176
+ type="text"
177
+ class="form-field labelless"
178
+ placeholder="e.g. Construction, Hospitality, Healthcare"
179
+ :class="{ 'is-danger': errors.length, filled: form.industry }" />
180
+ <span
181
+ v-if="errors.length"
182
+ class="form-help is-danger">
183
+ {{ errors[0] }}
184
+ </span>
185
+ </validation-provider>
186
+ </div>
187
+ <div class="col-sm-6 col-12">
188
+ <validation-provider
189
+ v-slot="{ errors }"
190
+ tag="div"
191
+ name="Company Size"
192
+ class="form-row">
193
+ <label class="form-label">Company Size</label>
194
+ <input
195
+ v-model="form.companySize"
196
+ type="text"
197
+ class="form-field labelless"
198
+ placeholder="e.g. 1–10, 11–50, 50+"
199
+ :class="{ 'is-danger': errors.length, filled: form.companySize }" />
200
+ <span
201
+ v-if="errors.length"
202
+ class="form-help is-danger">
203
+ {{ errors[0] }}
204
+ </span>
205
+ </validation-provider>
206
+ </div>
207
+ </div>
208
+
209
+ <div class="TradeAccountRequest__section-title">
210
+ Delivery Address
211
+ </div>
212
+ <div class="row">
213
+ <div class="col-12">
214
+ <validation-provider
215
+ v-slot="{ errors }"
216
+ tag="div"
217
+ name="Street Address"
218
+ class="form-row">
219
+ <label class="form-label">Street Address</label>
220
+ <input
221
+ v-model="form.streetAddress"
222
+ type="text"
223
+ class="form-field labelless"
224
+ placeholder="Street address"
225
+ :class="{ 'is-danger': errors.length, filled: form.streetAddress }" />
226
+ <span
227
+ v-if="errors.length"
228
+ class="form-help is-danger">
229
+ {{ errors[0] }}
230
+ </span>
231
+ </validation-provider>
232
+ </div>
233
+ </div>
234
+ <div class="row">
235
+ <div class="col-12">
236
+ <div class="form-row">
237
+ <postcode-select
238
+ :suburb="form.suburb"
239
+ :required="false"
240
+ :labelless="true"
241
+ placeholder="Suburb"
242
+ @select="handleSuburbChange" />
243
+ </div>
244
+ </div>
245
+ </div>
246
+
247
+ <div class="TradeAccountRequest__section-title">
248
+ Additional Information
249
+ </div>
250
+ <div class="row">
251
+ <div class="col-12">
252
+ <div class="form-row">
253
+ <label class="form-label">Have you purchased from us before?</label>
254
+ <div class="TradeAccountRequest__radio-group">
255
+ <label class="TradeAccountRequest__radio-label">
256
+ <input
257
+ v-model="form.previousCustomer"
258
+ type="radio"
259
+ :value="true" />
260
+ Yes
261
+ </label>
262
+ <label class="TradeAccountRequest__radio-label">
263
+ <input
264
+ v-model="form.previousCustomer"
265
+ type="radio"
266
+ :value="false" />
267
+ No
268
+ </label>
269
+ </div>
270
+ </div>
271
+ </div>
272
+ </div>
273
+
274
+ <div class="row">
275
+ <div class="col-12">
276
+ <validation-provider
277
+ v-slot="{ errors }"
278
+ tag="div"
279
+ name="Credit Line Request"
280
+ class="form-row">
281
+ <label class="form-label">Credit Line Request</label>
282
+ <textarea
283
+ v-model="form.creditLineRequest"
284
+ class="form-field labelless"
285
+ rows="4"
286
+ placeholder="Please describe your credit line requirements and any additional information about your business needs."
287
+ :class="{ 'is-danger': errors.length, filled: form.creditLineRequest }" />
288
+ <span
289
+ v-if="errors.length"
290
+ class="form-help is-danger">
291
+ {{ errors[0] }}
292
+ </span>
293
+ </validation-provider>
294
+ </div>
295
+ </div>
296
+
297
+ <div
298
+ v-if="errorMessage"
299
+ class="TradeAccountRequest__error">
300
+ {{ errorMessage }}
301
+ </div>
302
+
303
+ <div class="form-actions full">
304
+ <btn
305
+ btn-class="green"
306
+ btn-label="Submit Application"
307
+ :btn-disabled="submitting"
308
+ :btn-processing="submitting"
309
+ :btn-block="true"
310
+ @onclick="handleSubmit(submit)" />
311
+ </div>
312
+ </validation-observer>
313
+ </div>
314
+
315
+ <div
316
+ v-else
317
+ class="TradeAccountRequest__success">
318
+ <div class="TradeAccountRequest__success-icon">
319
+
320
+ </div>
321
+ <h2 class="TradeAccountRequest__success-title">
322
+ Received!
323
+ </h2>
324
+ <p class="TradeAccountRequest__success-text">
325
+ Thank you for applying for a trade account. We've received your application
326
+ (ref: <strong>{{ submittedCode }}</strong>) and will be in touch within 1–2 business days.
327
+ </p>
328
+ <btn
329
+ to="/"
330
+ btn-class="green"
331
+ btn-label="Continue Shopping" />
332
+ </div>
333
+ </div>
334
+ </template>
335
+
336
+ <script>
337
+ import { mapGetters } from 'vuex';
338
+ import api from '@lancom/shared/assets/js/api';
339
+ import PostcodeSelect from '@lancom/shared/components/common/postcode_select/postcode-select';
340
+ import Btn from '@lancom/shared/components/common/btn';
341
+
342
+ export default {
343
+ name: 'TradeAccountRequest',
344
+ components: {
345
+ PostcodeSelect,
346
+ Btn
347
+ },
348
+ data() {
349
+ return {
350
+ submitted: false,
351
+ submitting: false,
352
+ submittedCode: null,
353
+ errorMessage: null,
354
+ form: {
355
+ businessName: '',
356
+ firstName: '',
357
+ lastName: '',
358
+ email: '',
359
+ phone: '',
360
+ abn: '',
361
+ industry: '',
362
+ companySize: '',
363
+ streetAddress: '',
364
+ suburb: null,
365
+ state: '',
366
+ postcode: '',
367
+ previousCustomer: null,
368
+ creditLineRequest: ''
369
+ }
370
+ };
371
+ },
372
+ computed: {
373
+ ...mapGetters(['shop'])
374
+ },
375
+ methods: {
376
+ handleSuburbChange(suburb) {
377
+ this.form.suburb = suburb;
378
+ this.form.state = suburb?.state || '';
379
+ this.form.postcode = suburb?.postcode || '';
380
+ },
381
+ async submit() {
382
+ this.errorMessage = null;
383
+ this.submitting = true;
384
+ try {
385
+ let recaptchaToken = null;
386
+ if (this.$recaptcha) {
387
+ recaptchaToken = await this.$recaptcha('trade_account');
388
+ }
389
+ const payload = {
390
+ ...this.form,
391
+ suburb: this.form.suburb?.locality || this.form.suburb?.city || '',
392
+ recaptchaToken
393
+ };
394
+ const result = await api.requestTradeAccount(payload, this.shop._id);
395
+ this.submittedCode = result.code;
396
+ this.submitted = true;
397
+ } catch (e) {
398
+ this.errorMessage = 'Something went wrong. Please try again or contact us directly.';
399
+ } finally {
400
+ this.submitting = false;
401
+ }
402
+ }
403
+ }
404
+ };
405
+ </script>
406
+
407
+ <style lang="scss">
408
+ .TradeAccountRequest {
409
+ &__form-container {
410
+ background: #fff;
411
+ box-shadow: 0 6px 30px rgba(0, 0, 0, 0.08);
412
+ border-radius: 8px;
413
+ padding: 48px 40px 56px;
414
+
415
+ @media (max-width: 576px) {
416
+ padding: 32px 20px 40px;
417
+ }
418
+ }
419
+
420
+ &__header {
421
+ margin-bottom: 36px;
422
+ }
423
+
424
+ &__title {
425
+ font-size: 28px;
426
+ font-weight: 700;
427
+ margin-bottom: 12px;
428
+ text-transform: uppercase;
429
+ letter-spacing: 0.02em;
430
+ }
431
+
432
+ &__description {
433
+ font-size: 15px;
434
+ color: #555;
435
+ margin-bottom: 10px;
436
+ line-height: 1.6;
437
+ }
438
+
439
+ &__note {
440
+ font-size: 14px;
441
+ color: #777;
442
+ background: #f8f8f8;
443
+ border-left: 3px solid #ccc;
444
+ padding: 10px 14px;
445
+ border-radius: 0 4px 4px 0;
446
+ }
447
+
448
+ &__section-title {
449
+ font-size: 13px;
450
+ font-weight: 700;
451
+ text-transform: uppercase;
452
+ letter-spacing: 0.08em;
453
+ color: #999;
454
+ margin: 28px 0 12px;
455
+ padding-bottom: 8px;
456
+ border-bottom: 1px solid #eee;
457
+ }
458
+
459
+ &__radio-group {
460
+ display: flex;
461
+ gap: 24px;
462
+ margin-top: 4px;
463
+ }
464
+
465
+ &__radio-label {
466
+ display: flex;
467
+ align-items: center;
468
+ gap: 8px;
469
+ font-size: 14px;
470
+ color: #333;
471
+ cursor: pointer;
472
+
473
+ input[type="radio"] {
474
+ margin: 0;
475
+ cursor: pointer;
476
+ }
477
+ }
478
+
479
+ &__error {
480
+ margin-top: 16px;
481
+ padding: 12px 16px;
482
+ background: #fff5f5;
483
+ border: 1px solid #f5c6c6;
484
+ border-radius: 4px;
485
+ color: #c0392b;
486
+ font-size: 14px;
487
+ }
488
+
489
+ &__success {
490
+ text-align: center;
491
+ padding: 80px 40px;
492
+ background: #fff;
493
+ box-shadow: 0 6px 30px rgba(0, 0, 0, 0.08);
494
+ border-radius: 8px;
495
+ }
496
+
497
+ &__success-icon {
498
+ width: 72px;
499
+ height: 72px;
500
+ border-radius: 50%;
501
+ background: #38c88d;
502
+ color: #fff;
503
+ font-size: 36px;
504
+ line-height: 72px;
505
+ margin: 0 auto 24px;
506
+ }
507
+
508
+ &__success-title {
509
+ font-size: 26px;
510
+ font-weight: 700;
511
+ margin-bottom: 16px;
512
+ }
513
+
514
+ &__success-text {
515
+ font-size: 15px;
516
+ color: #555;
517
+ margin-bottom: 32px;
518
+ line-height: 1.6;
519
+ max-width: 500px;
520
+ margin-left: auto;
521
+ margin-right: auto;
522
+ }
523
+ }
524
+ </style>
@@ -1,5 +1,5 @@
1
1
  import { mapGetters } from 'vuex';
2
- import { getColorBackgroundStyle, getProductMediumCover, getBgStyle, getProductHoverCover } from '@lancom/shared/assets/js/utils/colors';
2
+ import { getColorBackgroundStyle, getProductMediumCover, getBgStyle, getProductHoverCover, getProductCoverObject, getProductHoverCoverObject } from '@lancom/shared/assets/js/utils/colors';
3
3
  import { staticLink } from '@lancom/shared/assets/js/utils/filters';
4
4
  import { generateProductLink } from '@lancom/shared/assets/js/utils/product';
5
5
  import { sortSizes } from '@lancom/shared/assets/js/utils/sizes';
@@ -99,6 +99,12 @@ const productPreview = {
99
99
  productСoverHover() {
100
100
  return getProductHoverCover(this.product, this.currentColor) || getProductMediumCover(this.product, 'back', this.currentColor);
101
101
  },
102
+ productСoverImage() {
103
+ return getProductCoverObject(this.product, 'front', this.currentColor);
104
+ },
105
+ productСoverHoverImage() {
106
+ return getProductHoverCoverObject(this.product, this.currentColor);
107
+ },
102
108
  productСoverBg() {
103
109
  return getBgStyle(this.productСover);
104
110
  },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lancom/shared",
3
- "version": "0.0.475",
3
+ "version": "0.0.476",
4
4
  "description": "lancom common scripts",
5
5
  "author": "e.tokovenko <e.tokovenko@gmail.com>",
6
6
  "repository": {