@lancom/shared 0.0.419 → 0.0.421
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.
- package/assets/js/api/admin.js +5 -24
- package/assets/js/api/index.js +3 -13
- package/components/checkout/order/order-payment-information/order-payment-information.vue +1 -0
- package/components/common/coupon_select/coupon-select.vue +2 -12
- package/components/common/payment/payment_card/pinpayment/pinpayment.scss +10 -1
- package/components/common/payment/payment_card/pinpayment/pinpayment.vue +57 -33
- package/components/common/phone_input/phone-input.vue +3 -15
- package/components/common/postcode_select/postcode-select.vue +1 -3
- package/components/customer/customer_navigation_menu/customer-navigation-menu.vue +7 -11
- package/components/product/product.vue +12 -0
- package/components/product/product_reviews/product-reviews.vue +6 -6
- package/components/product/related_products/related-products.vue +4 -4
- package/package.json +1 -1
- package/plugins/vue-recaptcha.js +40 -12
- package/routes/index.js +0 -10
- package/store/index.js +3 -12
- package/store/product.js +3 -0
- package/assets/js/utils/share-promises/index.js +0 -76
- package/components/customer/customer_preferences_form/customer-preferences-form.scss +0 -12
- package/components/customer/customer_preferences_form/customer-preferences-form.vue +0 -87
- package/components/pages/customer/preferences/preferences.vue +0 -79
- package/pages/customer/preferences.vue +0 -33
package/assets/js/api/admin.js
CHANGED
|
@@ -3,12 +3,6 @@ import { _get, _post, _delete, _put } from './helpers';
|
|
|
3
3
|
import { sortSizes } from './../utils/sizes';
|
|
4
4
|
import { sortByName } from './../utils/filters';
|
|
5
5
|
|
|
6
|
-
const CACHE = {
|
|
7
|
-
PRINT_AREAS: 'print-areas',
|
|
8
|
-
PACKAGES: 'packages'
|
|
9
|
-
};
|
|
10
|
-
const promiseCache = new Map();
|
|
11
|
-
|
|
12
6
|
export default {
|
|
13
7
|
updateInventoryFromLink(link, warehouse) {
|
|
14
8
|
const url = `admin/products/inventory/link${warehouse ? `?warehouse=${warehouse}` : ''}`;
|
|
@@ -221,13 +215,8 @@ export default {
|
|
|
221
215
|
removeCategory(id) {
|
|
222
216
|
return _delete(`admin/categories/${id}`);
|
|
223
217
|
},
|
|
224
|
-
dropPackagesCache() {
|
|
225
|
-
promiseCache.delete(CACHE.PACKAGES);
|
|
226
|
-
},
|
|
227
218
|
fetchPackages() {
|
|
228
|
-
|
|
229
|
-
promiseCache.set(CACHE.PACKAGES, promise);
|
|
230
|
-
return promise;
|
|
219
|
+
return _get('admin/packages');
|
|
231
220
|
},
|
|
232
221
|
fetchPackageById(id) {
|
|
233
222
|
return _get(`admin/packages/${id}`);
|
|
@@ -671,16 +660,8 @@ export default {
|
|
|
671
660
|
removePrintType(id) {
|
|
672
661
|
return _delete(`admin/print-types/${id}`);
|
|
673
662
|
},
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
},
|
|
677
|
-
fetchPrintAreas(params) {
|
|
678
|
-
if (params) {
|
|
679
|
-
return _get('admin/print-areas', params);
|
|
680
|
-
}
|
|
681
|
-
const promise = promiseCache.get(CACHE.PRINT_AREAS) || _get('admin/print-areas');
|
|
682
|
-
promiseCache.set(CACHE.PRINT_AREAS, promise);
|
|
683
|
-
return promise;
|
|
663
|
+
fetchPrintAreas() {
|
|
664
|
+
return _get('admin/print-areas');
|
|
684
665
|
},
|
|
685
666
|
fetchPrintAreaById(id) {
|
|
686
667
|
return _get(`admin/print-areas/${id}`);
|
|
@@ -691,8 +672,8 @@ export default {
|
|
|
691
672
|
removePrintArea(id) {
|
|
692
673
|
return _delete(`admin/print-areas/${id}`);
|
|
693
674
|
},
|
|
694
|
-
fetchPrintSizes(
|
|
695
|
-
return _get('admin/print-sizes'
|
|
675
|
+
fetchPrintSizes() {
|
|
676
|
+
return _get('admin/print-sizes');
|
|
696
677
|
},
|
|
697
678
|
fetchPrintSizeById(id) {
|
|
698
679
|
return _get(`admin/print-sizes/${id}`);
|
package/assets/js/api/index.js
CHANGED
|
@@ -1,15 +1,10 @@
|
|
|
1
|
+
|
|
1
2
|
import { _get, _post, _put, _delete, _patch } from './helpers';
|
|
2
3
|
import adminApi from './admin';
|
|
3
4
|
import { unminifySimpleProducts } from './utils/simple-products';
|
|
4
5
|
import { unminifyProduct } from './utils/product';
|
|
5
|
-
import { getSharePromise } from './../utils/share-promises';
|
|
6
|
-
|
|
7
|
-
const shareRequestsPromises = getSharePromise(5 * 60 * 1000);
|
|
8
6
|
|
|
9
7
|
const api = {
|
|
10
|
-
clearAllSharePromises() {
|
|
11
|
-
return _post('clear-share-promises');
|
|
12
|
-
},
|
|
13
8
|
fetchClientSettings(shop, params) {
|
|
14
9
|
return _get(`shop/${shop}/client-settings`, params);
|
|
15
10
|
},
|
|
@@ -25,9 +20,6 @@ const api = {
|
|
|
25
20
|
saveCustomer(customer, shop) {
|
|
26
21
|
return customer._id ? _patch(`shop/${shop}/customer/${customer.accessToken}`, customer) : _post(`shop/${shop}/customer`, customer);
|
|
27
22
|
},
|
|
28
|
-
fetchCustomer(accessToken, shop) {
|
|
29
|
-
return _get(`shop/${shop}/customer/${accessToken}`);
|
|
30
|
-
},
|
|
31
23
|
authCustomer(customer, shop) {
|
|
32
24
|
return _post(`shop/${shop}/customer/login`, customer);
|
|
33
25
|
},
|
|
@@ -77,8 +69,7 @@ const api = {
|
|
|
77
69
|
return _get(`shop/${shop}/menus`);
|
|
78
70
|
},
|
|
79
71
|
fetchBanners(shop, params) {
|
|
80
|
-
|
|
81
|
-
return shareRequestsPromises.share(KEY, () => _get(`shop/${shop}/banners`, params));
|
|
72
|
+
return _get(`shop/${shop}/banners`, params);
|
|
82
73
|
},
|
|
83
74
|
fetchProductsKits(shop, params) {
|
|
84
75
|
return _get(`shop/${shop}/products-kit`, params);
|
|
@@ -93,8 +84,7 @@ const api = {
|
|
|
93
84
|
return _get(`shop/${shop}/news/${alias}?preview=${preview}`);
|
|
94
85
|
},
|
|
95
86
|
fetchNewsTags(shop, params) {
|
|
96
|
-
|
|
97
|
-
return shareRequestsPromises.share(KEY, () => _get(`shop/${shop}/news-tag`, params));
|
|
87
|
+
return _get(`shop/${shop}/news-tag`, params);
|
|
98
88
|
},
|
|
99
89
|
fetchNewsTag(shop, alias) {
|
|
100
90
|
return _get(`shop/${shop}/news-tag/${alias}`);
|
|
@@ -74,7 +74,7 @@
|
|
|
74
74
|
<div
|
|
75
75
|
v-else
|
|
76
76
|
class="lc_caption form-help is-danger">
|
|
77
|
-
Invalid coupon: Min Order for Coupon: {{ minOrderValue | price(currency) }}
|
|
77
|
+
Invalid coupon: Min Order for Coupon: {{ value.minOrderValue | price(currency) }}
|
|
78
78
|
</div>
|
|
79
79
|
<div v-if="hasQualifyingProducts" class="lc_caption mb-10">
|
|
80
80
|
<div v-if="noQualifyingProductsInCart">
|
|
@@ -129,18 +129,8 @@ export default {
|
|
|
129
129
|
hasQualifyingProductsInCart() {
|
|
130
130
|
return this.sumQualifyingProductsInCart > 0;
|
|
131
131
|
},
|
|
132
|
-
minOrderValue() {
|
|
133
|
-
const value = [
|
|
134
|
-
this.value,
|
|
135
|
-
...(this.value?.values || [])
|
|
136
|
-
]
|
|
137
|
-
.filter(v => v?.minOrderValue > 0)
|
|
138
|
-
.sort((a, b) => a.minOrderValue - b.minOrderValue)[0];
|
|
139
|
-
|
|
140
|
-
return value?.minOrderValue || 0;
|
|
141
|
-
},
|
|
142
132
|
isValidPricing() {
|
|
143
|
-
return !this.minOrderValue || this.pricing.coupon;
|
|
133
|
+
return !this.value?.minOrderValue || this.pricing.coupon;
|
|
144
134
|
},
|
|
145
135
|
model: {
|
|
146
136
|
get() {
|
|
@@ -10,6 +10,15 @@
|
|
|
10
10
|
background-color: #F4F4F4;
|
|
11
11
|
}
|
|
12
12
|
}
|
|
13
|
+
&__error {
|
|
14
|
+
font-weight: bold;
|
|
15
|
+
font-size: 14px;
|
|
16
|
+
padding: 18px 10px;
|
|
17
|
+
text-align: center;
|
|
18
|
+
color: $white;
|
|
19
|
+
background: #EA3434;
|
|
20
|
+
margin-top: 20px;
|
|
21
|
+
}
|
|
13
22
|
&__field-container {
|
|
14
23
|
width: 100%;
|
|
15
24
|
}
|
|
@@ -50,4 +59,4 @@
|
|
|
50
59
|
}
|
|
51
60
|
}
|
|
52
61
|
}
|
|
53
|
-
}
|
|
62
|
+
}
|
|
@@ -1,6 +1,11 @@
|
|
|
1
1
|
<template>
|
|
2
2
|
<div class="Payment__wrapper">
|
|
3
|
-
<div
|
|
3
|
+
<div
|
|
4
|
+
v-if="pinpaymentsError"
|
|
5
|
+
class="Payment__error">
|
|
6
|
+
{{ pinpaymentsError }}
|
|
7
|
+
</div>
|
|
8
|
+
<div v-else-if="(hasSpinner && processing) || loading">
|
|
4
9
|
<spinner />
|
|
5
10
|
</div>
|
|
6
11
|
<div
|
|
@@ -86,7 +91,6 @@
|
|
|
86
91
|
</template>
|
|
87
92
|
|
|
88
93
|
<script>
|
|
89
|
-
let timer = null;
|
|
90
94
|
let pinpaymentStartLoaded = false;
|
|
91
95
|
|
|
92
96
|
export default {
|
|
@@ -108,26 +112,20 @@ export default {
|
|
|
108
112
|
return {
|
|
109
113
|
processing: false,
|
|
110
114
|
loading: false,
|
|
115
|
+
waitPinpaymentTimer: null,
|
|
116
|
+
pinpaymentsError: null,
|
|
111
117
|
fields: null
|
|
112
118
|
};
|
|
113
119
|
},
|
|
114
120
|
async mounted() {
|
|
115
121
|
this.loading = true;
|
|
116
|
-
|
|
117
122
|
await this.loadPinpayments();
|
|
118
|
-
|
|
119
123
|
if (window.HostedFields) {
|
|
120
124
|
await this.initHostedPayment();
|
|
121
|
-
} else {
|
|
122
|
-
window.addEventListener('load', async () => {
|
|
123
|
-
if (!this.fields) {
|
|
124
|
-
await this.initHostedPayment();
|
|
125
|
-
}
|
|
126
|
-
});
|
|
127
125
|
}
|
|
128
126
|
},
|
|
129
127
|
destroyed() {
|
|
130
|
-
clearInterval(
|
|
128
|
+
clearInterval(this.waitPinpaymentTimer);
|
|
131
129
|
},
|
|
132
130
|
methods: {
|
|
133
131
|
initHostedPayment() {
|
|
@@ -207,30 +205,56 @@ export default {
|
|
|
207
205
|
},
|
|
208
206
|
async loadPinpayments() {
|
|
209
207
|
if (process.browser) {
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
} else {
|
|
224
|
-
let repeated = 0;
|
|
225
|
-
timer = setInterval(() => {
|
|
226
|
-
if (!!window.HostedFields || repeated++ > 40) {
|
|
227
|
-
clearInterval(timer);
|
|
228
|
-
resolve();
|
|
229
|
-
}
|
|
230
|
-
}, 500);
|
|
208
|
+
if (pinpaymentStartLoaded) {
|
|
209
|
+
await this.waitUntilLoadedPinpayment();
|
|
210
|
+
}
|
|
211
|
+
if (!window.HostedField) {
|
|
212
|
+
pinpaymentStartLoaded = true;
|
|
213
|
+
for (let index = 0; index < 10; index++) {
|
|
214
|
+
try {
|
|
215
|
+
await this.loadPinpaymentsJs();
|
|
216
|
+
break;
|
|
217
|
+
} catch (e) {
|
|
218
|
+
console.log('loadPinpayments:error ', e);
|
|
219
|
+
await new Promise(resolve => setTimeout(resolve, 500));
|
|
220
|
+
}
|
|
231
221
|
}
|
|
232
|
-
}
|
|
222
|
+
}
|
|
223
|
+
if (!window.HostedFields) {
|
|
224
|
+
this.pinpaymentsError = '"Credit Card" payment temporarily not available';
|
|
225
|
+
}
|
|
233
226
|
}
|
|
227
|
+
},
|
|
228
|
+
waitUntilLoadedPinpayment() {
|
|
229
|
+
return new Promise((resolve, reject) => {
|
|
230
|
+
let repeated = 0;
|
|
231
|
+
this.waitPinpaymentTimer = setInterval(() => {
|
|
232
|
+
if (!!window.HostedFields || repeated++ > 10) {
|
|
233
|
+
clearInterval(this.waitPinpaymentTimer);
|
|
234
|
+
resolve();
|
|
235
|
+
}
|
|
236
|
+
}, 500);
|
|
237
|
+
});
|
|
238
|
+
},
|
|
239
|
+
loadPinpaymentsJs() {
|
|
240
|
+
return (new Promise((resolve, reject) => {
|
|
241
|
+
const domElement = document.createElement('script');
|
|
242
|
+
domElement.type = 'text/javascript';
|
|
243
|
+
domElement.setAttribute('src', 'https://cdn.pinpayments.com/pin.hosted_fields.v1.js');
|
|
244
|
+
domElement.onload = () => {
|
|
245
|
+
setTimeout(() => {
|
|
246
|
+
if (window.HostedFields) {
|
|
247
|
+
resolve();
|
|
248
|
+
} else {
|
|
249
|
+
reject(new Error('Failed to Load JS'));
|
|
250
|
+
}
|
|
251
|
+
}, 500);
|
|
252
|
+
};
|
|
253
|
+
domElement.onerror = () => {
|
|
254
|
+
reject(new Error('Failed to Load JS'));
|
|
255
|
+
};
|
|
256
|
+
document.body.appendChild(domElement);
|
|
257
|
+
}));
|
|
234
258
|
}
|
|
235
259
|
}
|
|
236
260
|
};
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
<template>
|
|
2
2
|
<div>
|
|
3
|
-
<div
|
|
3
|
+
<div class="form-row">
|
|
4
4
|
<label
|
|
5
5
|
v-if="labelless"
|
|
6
|
-
|
|
6
|
+
class="form-label"
|
|
7
7
|
@click="$refs.phone.$el.focus()">
|
|
8
8
|
{{ displayLabelText }}
|
|
9
9
|
</label>
|
|
@@ -31,8 +31,8 @@
|
|
|
31
31
|
:disabled="disabled"
|
|
32
32
|
:name="'phone' + uniqueKey"
|
|
33
33
|
type="text"
|
|
34
|
+
class="form-field"
|
|
34
35
|
:class="{
|
|
35
|
-
[inputClassname]: true,
|
|
36
36
|
'is-danger': errors.length,
|
|
37
37
|
filled: model,
|
|
38
38
|
labelless
|
|
@@ -115,18 +115,6 @@ export default {
|
|
|
115
115
|
namePrefix: {
|
|
116
116
|
type: String,
|
|
117
117
|
default: ''
|
|
118
|
-
},
|
|
119
|
-
containerClassname: {
|
|
120
|
-
type: String,
|
|
121
|
-
default: 'form-row'
|
|
122
|
-
},
|
|
123
|
-
labelClassname: {
|
|
124
|
-
type: String,
|
|
125
|
-
default: 'form-label'
|
|
126
|
-
},
|
|
127
|
-
inputClassname: {
|
|
128
|
-
type: String,
|
|
129
|
-
default: 'form-field'
|
|
130
118
|
}
|
|
131
119
|
},
|
|
132
120
|
data() {
|
|
@@ -177,9 +177,7 @@ export default {
|
|
|
177
177
|
if (this.suburb) {
|
|
178
178
|
const option = this.createOptionFromSuburb(this.suburb);
|
|
179
179
|
this.selected = option;
|
|
180
|
-
|
|
181
|
-
await this.handleSearch(this.selected.value.trim());
|
|
182
|
-
}
|
|
180
|
+
await this.handleSearch(this.selected.value.trim())
|
|
183
181
|
}
|
|
184
182
|
},
|
|
185
183
|
methods: {
|
|
@@ -15,12 +15,6 @@
|
|
|
15
15
|
<script>
|
|
16
16
|
export default {
|
|
17
17
|
name: 'CustomerMenu',
|
|
18
|
-
props: {
|
|
19
|
-
activeMenuItem: {
|
|
20
|
-
type: String,
|
|
21
|
-
required: true
|
|
22
|
-
}
|
|
23
|
-
},
|
|
24
18
|
data() {
|
|
25
19
|
return {
|
|
26
20
|
menuItems: [{
|
|
@@ -35,12 +29,14 @@ export default {
|
|
|
35
29
|
label: 'Coupons',
|
|
36
30
|
key: 'coupons',
|
|
37
31
|
url: '/customer/coupons'
|
|
38
|
-
}, {
|
|
39
|
-
label: 'Preferences',
|
|
40
|
-
key: 'preferences',
|
|
41
|
-
url: '/customer/preferences'
|
|
42
32
|
}]
|
|
43
33
|
};
|
|
34
|
+
},
|
|
35
|
+
props: {
|
|
36
|
+
activeMenuItem: {
|
|
37
|
+
type: String,
|
|
38
|
+
required: true
|
|
39
|
+
}
|
|
44
40
|
}
|
|
45
41
|
};
|
|
46
42
|
</script>
|
|
@@ -72,5 +68,5 @@ export default {
|
|
|
72
68
|
background-color: $green;
|
|
73
69
|
}
|
|
74
70
|
}
|
|
75
|
-
}
|
|
71
|
+
}
|
|
76
72
|
</style>
|
|
@@ -32,6 +32,12 @@
|
|
|
32
32
|
<section class="Product__section">
|
|
33
33
|
<wizard-editor />
|
|
34
34
|
</section>
|
|
35
|
+
<!-- <section class="Product__section">
|
|
36
|
+
<product-colors-selector v-if="productDetails" />
|
|
37
|
+
</section> -->
|
|
38
|
+
<!-- <section class="Product__section">
|
|
39
|
+
<product-tier-prices :product="product" />
|
|
40
|
+
</section> -->
|
|
35
41
|
<section class="Product__section">
|
|
36
42
|
<product-fabric-and-size-info :product="product" />
|
|
37
43
|
</section>
|
|
@@ -53,17 +59,23 @@ import RelatedProducts from '@lancom/shared/components/product/related_products/
|
|
|
53
59
|
import Gallery from '@lancom/shared/components/product/gallery/gallery';
|
|
54
60
|
import { generateProductLink } from '@lancom/shared/assets/js/utils/product';
|
|
55
61
|
import ProductMainInfo from './layouts/product_main_info/product-main-info';
|
|
62
|
+
import ProductColorsSelector from './product_colors_selector/product-colors-selector';
|
|
63
|
+
import ProductTierPrices from './layouts/product_tier_prices/product-tier-prices';
|
|
56
64
|
import ProductFabricAndSizeInfo from './layouts/product_fabric_and_size_info/product-fabric-and-size-info';
|
|
57
65
|
import ProductPackaging from './layouts/product_packaging/product-packaging';
|
|
58
66
|
import ProductModelMeasurements from './layouts/product_model_measurements/product-model-measurements';
|
|
59
67
|
import ProductSizeTable from './layouts/product_size_table/product-size-table';
|
|
60
68
|
import WizardEditor from './wizard-editor/wizard-editor.vue';
|
|
61
69
|
|
|
70
|
+
|
|
71
|
+
// import ProductLabelsCustomization from './layouts/product_labels_customization/product-labels-customization';
|
|
62
72
|
|
|
63
73
|
export default {
|
|
64
74
|
name: 'Product',
|
|
65
75
|
components: {
|
|
66
76
|
ProductMainInfo,
|
|
77
|
+
ProductColorsSelector,
|
|
78
|
+
ProductTierPrices,
|
|
67
79
|
ProductFabricAndSizeInfo,
|
|
68
80
|
ProductPackaging,
|
|
69
81
|
ProductModelMeasurements,
|
|
@@ -41,12 +41,6 @@ export default {
|
|
|
41
41
|
ProductReview
|
|
42
42
|
},
|
|
43
43
|
mixins: [],
|
|
44
|
-
props: {
|
|
45
|
-
product: {
|
|
46
|
-
type: Object,
|
|
47
|
-
required: true
|
|
48
|
-
}
|
|
49
|
-
},
|
|
50
44
|
data() {
|
|
51
45
|
const reviews = this.product.reviews || [];
|
|
52
46
|
const [, mainReviewId] = (this.$route.hash || '').match(/review-([a-z0-9]+)/i) || [];
|
|
@@ -62,6 +56,12 @@ export default {
|
|
|
62
56
|
].filter(review => !!review)
|
|
63
57
|
};
|
|
64
58
|
},
|
|
59
|
+
props: {
|
|
60
|
+
product: {
|
|
61
|
+
type: Object,
|
|
62
|
+
required: true
|
|
63
|
+
}
|
|
64
|
+
},
|
|
65
65
|
computed: {
|
|
66
66
|
currentReview() {
|
|
67
67
|
return this.reviews[this.activeIndex];
|
|
@@ -36,15 +36,15 @@ export default {
|
|
|
36
36
|
products: []
|
|
37
37
|
};
|
|
38
38
|
},
|
|
39
|
-
async fetch() {
|
|
40
|
-
const { products } = await api.fetchRelatedProducts(this.shop._id, this.product.alias, { needShuffle: true });
|
|
41
|
-
this.products = products.splice(0, 6);
|
|
42
|
-
},
|
|
43
39
|
computed: {
|
|
44
40
|
...mapGetters(['shop']),
|
|
45
41
|
hasProducts() {
|
|
46
42
|
return this.products.length > 0;
|
|
47
43
|
}
|
|
44
|
+
},
|
|
45
|
+
async fetch() {
|
|
46
|
+
const { products } = await api.fetchRelatedProducts(this.shop._id, this.product.alias, { needShuffle: true });
|
|
47
|
+
this.products = products.splice(0, 6);
|
|
48
48
|
}
|
|
49
49
|
};
|
|
50
50
|
</script>
|
package/package.json
CHANGED
package/plugins/vue-recaptcha.js
CHANGED
|
@@ -1,29 +1,57 @@
|
|
|
1
1
|
import Vue from 'vue';
|
|
2
2
|
|
|
3
|
-
let
|
|
3
|
+
let recaptchaStartLoaded = false;
|
|
4
4
|
|
|
5
5
|
export default () => {
|
|
6
6
|
Vue.mixin({
|
|
7
|
+
data() {
|
|
8
|
+
return {
|
|
9
|
+
waitRecaptchaTimer: null,
|
|
10
|
+
recaptchaError: null
|
|
11
|
+
};
|
|
12
|
+
},
|
|
7
13
|
methods: {
|
|
8
14
|
async getRecaptcha(name) {
|
|
9
15
|
await this.preloadReCaptcha();
|
|
10
16
|
return process.env.IS_LOCAL === 'true' || await this.$recaptcha(name);
|
|
11
17
|
},
|
|
12
18
|
async preloadReCaptcha() {
|
|
19
|
+
if (recaptchaStartLoaded) {
|
|
20
|
+
await this.waitUntilLoadedRecaptcha();
|
|
21
|
+
}
|
|
22
|
+
|
|
13
23
|
if (!this.$recaptcha) {
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
}
|
|
23
|
-
}
|
|
24
|
+
recaptchaStartLoaded = true;
|
|
25
|
+
for (let index = 0; index < 10; index++) {
|
|
26
|
+
try {
|
|
27
|
+
await this.loadRecaptchaJs();
|
|
28
|
+
break;
|
|
29
|
+
} catch (e) {
|
|
30
|
+
console.log('loadRecaptcha:error ', e);
|
|
31
|
+
await new Promise(resolve => setTimeout(resolve, 500));
|
|
32
|
+
}
|
|
33
|
+
}
|
|
24
34
|
}
|
|
35
|
+
|
|
36
|
+
await this.$recaptchaLoaded();
|
|
37
|
+
|
|
38
|
+
if (!this.$recaptcha) {
|
|
39
|
+
this.recaptchaError = 'Recaptcha temporarily not available';
|
|
40
|
+
}
|
|
41
|
+
console.log('loadRecaptcha:result ', !!this.$recaptcha);
|
|
42
|
+
},
|
|
43
|
+
waitUntilLoadedRecaptcha() {
|
|
44
|
+
return new Promise((resolve, reject) => {
|
|
45
|
+
let repeated = 0;
|
|
46
|
+
this.waitRecaptchaTimer = setInterval(() => {
|
|
47
|
+
if (!!this.$recaptcha || repeated++ > 10) {
|
|
48
|
+
clearInterval(this.waitRecaptchaTimer);
|
|
49
|
+
resolve();
|
|
50
|
+
}
|
|
51
|
+
}, 500);
|
|
52
|
+
});
|
|
25
53
|
},
|
|
26
|
-
async
|
|
54
|
+
async loadRecaptchaJs() {
|
|
27
55
|
const { VueReCaptcha } = await import('vue-recaptcha-v3');
|
|
28
56
|
Vue.use(VueReCaptcha, {
|
|
29
57
|
siteKey: process.env.RECAPTCHA_KEY,
|
package/routes/index.js
CHANGED
|
@@ -122,11 +122,6 @@ module.exports = function(routes, resolve, config) {
|
|
|
122
122
|
path: '/customer/coupons',
|
|
123
123
|
component: resolve('@lancom/shared/pages/customer/coupons.vue'),
|
|
124
124
|
chunkName: 'pages/customer/coupons'
|
|
125
|
-
}, {
|
|
126
|
-
name: 'customer-preferences',
|
|
127
|
-
path: '/customer/preferences',
|
|
128
|
-
component: resolve('@lancom/shared/pages/customer/preferences.vue'),
|
|
129
|
-
chunkName: 'pages/customer/preferences'
|
|
130
125
|
}, {
|
|
131
126
|
name: 'customer-recovery',
|
|
132
127
|
path: '/customer/recovery',
|
|
@@ -142,11 +137,6 @@ module.exports = function(routes, resolve, config) {
|
|
|
142
137
|
path: '/customer/password/:token',
|
|
143
138
|
component: resolve('@lancom/shared/pages/customer/password/_token.vue'),
|
|
144
139
|
chunkName: 'pages/customer/password/reset'
|
|
145
|
-
}, {
|
|
146
|
-
name: 'customer-preferences',
|
|
147
|
-
path: '/customer/preferences',
|
|
148
|
-
component: resolve('@lancom/shared/pages/customer/preferences.vue'),
|
|
149
|
-
chunkName: 'pages/customer/preferences'
|
|
150
140
|
}, {
|
|
151
141
|
name: 'checkout-cart',
|
|
152
142
|
path: '/checkout/cart',
|
package/store/index.js
CHANGED
|
@@ -4,13 +4,9 @@ import { getAuthToken } from '@lancom/shared/assets/js/utils/auth';
|
|
|
4
4
|
import { getShopCountrySettings } from '@lancom/shared/assets/js/utils/shop';
|
|
5
5
|
import { MESSAGES, COUNTRIES_MESSAGES } from '@/messages';
|
|
6
6
|
import { SETTINGS, COUNTRIES_SETTINGS } from '@/settings';
|
|
7
|
-
import { getSharePromise, clearAllSharePromises } from '@lancom/shared/assets/js/utils/share-promises';
|
|
8
|
-
|
|
9
7
|
const cookieparser = process.server ? require('cookieparser') : undefined;
|
|
10
8
|
const CLOSED_NOTIFICATION = 'lancom-closed-notification-1.0';
|
|
11
9
|
|
|
12
|
-
const shareShopInfo = getSharePromise(60 * 1000);
|
|
13
|
-
|
|
14
10
|
export const state = () => ({
|
|
15
11
|
googleClickId: null,
|
|
16
12
|
stockCountry: null,
|
|
@@ -73,14 +69,9 @@ export const actions = {
|
|
|
73
69
|
await commit('setShop', shop);
|
|
74
70
|
}
|
|
75
71
|
},
|
|
76
|
-
async nuxtServerInit({ commit }, { req
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
await api.clearAllSharePromises();
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
const shop = await shareShopInfo.share(process.env.HOST_NAME, () => api.fetchShopByUrl(process.env.HOST_NAME));
|
|
83
|
-
const menus = await shareShopInfo.share(`menus_${shop._id}`, () => api.fetchMenus(shop._id));
|
|
72
|
+
async nuxtServerInit({ commit }, { req }) {
|
|
73
|
+
const shop = await api.fetchShopByUrl(process.env.HOST_NAME);
|
|
74
|
+
const menus = await api.fetchMenus(shop._id);
|
|
84
75
|
commit('setMenus', menus);
|
|
85
76
|
commit('setShop', shop);
|
|
86
77
|
// if (req.headers.cookie) {
|
package/store/product.js
CHANGED
|
@@ -80,6 +80,9 @@ export const getters = {
|
|
|
80
80
|
|| productDetails.simpleProducts.find(({ color }) => color._id === editableColor._id)
|
|
81
81
|
);
|
|
82
82
|
},
|
|
83
|
+
defaultColorSimpleProducts: ({ productDetails, editableColor }) => {
|
|
84
|
+
return editableColor && productDetails.simpleProducts.filter(({ color }) => color._id === editableColor._id);
|
|
85
|
+
},
|
|
83
86
|
availableColors: ({ availableColors }) => availableColors,
|
|
84
87
|
availableSizes: ({ availableSizes }) => availableSizes,
|
|
85
88
|
editableColor: ({ editableColor }) => editableColor,
|
|
@@ -1,76 +0,0 @@
|
|
|
1
|
-
const MAX_PROMISES_COUNT = 1000;
|
|
2
|
-
const DEFAULT_SHARE_LIFETIME = 10 * 1000;
|
|
3
|
-
|
|
4
|
-
export class SharePromises {
|
|
5
|
-
constructor(clearSharesTimeoutDelay) {
|
|
6
|
-
this.promises = new Map();
|
|
7
|
-
this.clearSharesTimeout = null;
|
|
8
|
-
this.clearSharesTimeoutDelay = clearSharesTimeoutDelay || DEFAULT_SHARE_LIFETIME;
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
startClearTimeout(key) {
|
|
12
|
-
const promise = this.promises.get(key);
|
|
13
|
-
if (promise) {
|
|
14
|
-
clearTimeout(promise.timeout);
|
|
15
|
-
promise.timeout = setTimeout(() => {
|
|
16
|
-
this.promises.delete(key);
|
|
17
|
-
}, this.clearSharesTimeoutDelay);
|
|
18
|
-
}
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
share(keyObj, promiseFactory) {
|
|
22
|
-
const key = `${keyObj}`;
|
|
23
|
-
let promise = this.promises.get(key);
|
|
24
|
-
if (!promise) {
|
|
25
|
-
this.checkMaxPromisesCount();
|
|
26
|
-
|
|
27
|
-
promise = {
|
|
28
|
-
promise: promiseFactory(),
|
|
29
|
-
timeout: null
|
|
30
|
-
};
|
|
31
|
-
|
|
32
|
-
this.promises.set(key, promise);
|
|
33
|
-
|
|
34
|
-
this.startClearTimeout(key);
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
promise.promise = promise.promise.then((result) => result);
|
|
38
|
-
|
|
39
|
-
return promise.promise;
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
checkMaxPromisesCount() {
|
|
43
|
-
if (this.promises.size >= MAX_PROMISES_COUNT) {
|
|
44
|
-
this.clear();
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
clear() {
|
|
49
|
-
this.promises.forEach(promise => {
|
|
50
|
-
clearTimeout(promise.timeout);
|
|
51
|
-
});
|
|
52
|
-
this.promises.clear();
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
clearPromise(keyObj) {
|
|
56
|
-
const key = `${keyObj}`;
|
|
57
|
-
let promise = this.promises.get(key);
|
|
58
|
-
if (promise) {
|
|
59
|
-
clearTimeout(promise.timeout);
|
|
60
|
-
this.promises.delete(key);
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
const allSharePromises = [];
|
|
66
|
-
export function getSharePromise(timeout) {
|
|
67
|
-
const sharePromise = new SharePromises(timeout);
|
|
68
|
-
allSharePromises.push(sharePromise);
|
|
69
|
-
return sharePromise;
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
export function clearAllSharePromises() {
|
|
73
|
-
for (const sharePromise of allSharePromises) {
|
|
74
|
-
sharePromise.clear();
|
|
75
|
-
}
|
|
76
|
-
}
|
|
@@ -1,87 +0,0 @@
|
|
|
1
|
-
<template>
|
|
2
|
-
<div class="form__wrapper CustomerFrom__wrapper">
|
|
3
|
-
<validation-observer ref="form">
|
|
4
|
-
<div class="mt-10">
|
|
5
|
-
<div
|
|
6
|
-
class="row"
|
|
7
|
-
style="margin-top: -20px">
|
|
8
|
-
<div class="col-12">
|
|
9
|
-
<div class="form-row">
|
|
10
|
-
<label class="form-label">
|
|
11
|
-
<checkbox v-model="customer.signUpForNewsletter" />
|
|
12
|
-
<span class="lc_regular12 lc__grey1">
|
|
13
|
-
Sign up for newsletter
|
|
14
|
-
</span>
|
|
15
|
-
</label>
|
|
16
|
-
</div>
|
|
17
|
-
</div>
|
|
18
|
-
</div>
|
|
19
|
-
</div>
|
|
20
|
-
<div class="form__footer mt-5">
|
|
21
|
-
<div
|
|
22
|
-
v-if="errorMessage"
|
|
23
|
-
class="CustomerForm__error">
|
|
24
|
-
{{ errorMessage }}
|
|
25
|
-
</div>
|
|
26
|
-
<div class="form-actions full">
|
|
27
|
-
<btn
|
|
28
|
-
class="btn btn-black"
|
|
29
|
-
:btn-processing="processing"
|
|
30
|
-
:btn-disabled="processing"
|
|
31
|
-
:btn-label="customer._id ? 'Save' : 'Create Account'"
|
|
32
|
-
@onclick="submit()">
|
|
33
|
-
</btn>
|
|
34
|
-
</div>
|
|
35
|
-
</div>
|
|
36
|
-
</validation-observer>
|
|
37
|
-
</div>
|
|
38
|
-
</template>
|
|
39
|
-
|
|
40
|
-
<script>
|
|
41
|
-
import { mapGetters } from 'vuex';
|
|
42
|
-
import api from '@lancom/shared/assets/js/api';
|
|
43
|
-
|
|
44
|
-
export default {
|
|
45
|
-
name: 'CustomerPreferencesForm',
|
|
46
|
-
props: {
|
|
47
|
-
customer: {
|
|
48
|
-
type: Object,
|
|
49
|
-
required: true
|
|
50
|
-
}
|
|
51
|
-
},
|
|
52
|
-
data() {
|
|
53
|
-
return {
|
|
54
|
-
errorMessage: null,
|
|
55
|
-
processing: false
|
|
56
|
-
};
|
|
57
|
-
},
|
|
58
|
-
computed: {
|
|
59
|
-
...mapGetters(['shop'])
|
|
60
|
-
},
|
|
61
|
-
methods: {
|
|
62
|
-
async submit() {
|
|
63
|
-
this.errorMessage = null;
|
|
64
|
-
|
|
65
|
-
const isValid = await this.$refs.form.validate();
|
|
66
|
-
if (!isValid) {
|
|
67
|
-
return;
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
try {
|
|
71
|
-
this.processing = true;
|
|
72
|
-
const result = await api.saveCustomer(this.customer, this.shop._id);
|
|
73
|
-
this.$emit('save', result);
|
|
74
|
-
} catch ({ response }) {
|
|
75
|
-
const { data } = response || {};
|
|
76
|
-
this.errorMessage = data && data.error;
|
|
77
|
-
} finally {
|
|
78
|
-
this.processing = false;
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
|
-
};
|
|
83
|
-
</script>
|
|
84
|
-
|
|
85
|
-
<style lang="scss" scoped>
|
|
86
|
-
@import 'customer-preferences-form.scss';
|
|
87
|
-
</style>
|
|
@@ -1,79 +0,0 @@
|
|
|
1
|
-
<template>
|
|
2
|
-
<div class="CustomerPreferences__wrapper">
|
|
3
|
-
<customer-preferences-form
|
|
4
|
-
v-if="customer"
|
|
5
|
-
ref="customerForm"
|
|
6
|
-
:customer="customer"
|
|
7
|
-
@save="onSave"
|
|
8
|
-
@customer-loaded="onCustomerLoaded" />
|
|
9
|
-
</div>
|
|
10
|
-
</template>
|
|
11
|
-
|
|
12
|
-
<script>
|
|
13
|
-
import api from '@lancom/shared/assets/js/api';
|
|
14
|
-
import { mapGetters, mapActions } from 'vuex';
|
|
15
|
-
import CustomerPreferencesForm from '@lancom/shared/components/customer/customer_preferences_form/customer-preferences-form';
|
|
16
|
-
|
|
17
|
-
export default {
|
|
18
|
-
name: 'CustomerPreferences',
|
|
19
|
-
components: {
|
|
20
|
-
CustomerPreferencesForm
|
|
21
|
-
},
|
|
22
|
-
data() {
|
|
23
|
-
return {
|
|
24
|
-
customer: null,
|
|
25
|
-
loading: false,
|
|
26
|
-
error: null
|
|
27
|
-
};
|
|
28
|
-
},
|
|
29
|
-
computed: {
|
|
30
|
-
...mapGetters(['shop']),
|
|
31
|
-
...mapGetters('auth', ['user'])
|
|
32
|
-
},
|
|
33
|
-
async created() {
|
|
34
|
-
await this.loadCustomerData();
|
|
35
|
-
},
|
|
36
|
-
methods: {
|
|
37
|
-
...mapActions('auth', ['update_user']),
|
|
38
|
-
async loadCustomerData() {
|
|
39
|
-
try {
|
|
40
|
-
this.processing = true;
|
|
41
|
-
const customer = await api.fetchCustomer(this.user.accessToken, this.shop._id);
|
|
42
|
-
console.log('customer: ', customer);
|
|
43
|
-
this.customer = customer;
|
|
44
|
-
} catch (e) {
|
|
45
|
-
console.log(e);
|
|
46
|
-
const errorMessage = e?.response?.data?.error;
|
|
47
|
-
this.$toastr.e(errorMessage || 'Customer failed to load');
|
|
48
|
-
} finally {
|
|
49
|
-
this.processing = false;
|
|
50
|
-
}
|
|
51
|
-
},
|
|
52
|
-
onSave({ customer: user, token }) {
|
|
53
|
-
this.$toastr.s('Customer successfully saved');
|
|
54
|
-
this.update_user({ user, token });
|
|
55
|
-
},
|
|
56
|
-
onCustomerLoaded(customer) {
|
|
57
|
-
this.customer = customer;
|
|
58
|
-
}
|
|
59
|
-
}
|
|
60
|
-
};
|
|
61
|
-
</script>
|
|
62
|
-
|
|
63
|
-
<style lang="scss" scoped>
|
|
64
|
-
.loading-message {
|
|
65
|
-
text-align: center;
|
|
66
|
-
padding: 2rem;
|
|
67
|
-
font-size: 1.1rem;
|
|
68
|
-
color: #666;
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
.error-message {
|
|
72
|
-
text-align: center;
|
|
73
|
-
padding: 2rem;
|
|
74
|
-
color: #d32f2f;
|
|
75
|
-
background-color: #ffebee;
|
|
76
|
-
border-radius: 4px;
|
|
77
|
-
margin: 1rem 0;
|
|
78
|
-
}
|
|
79
|
-
</style>
|
|
@@ -1,33 +0,0 @@
|
|
|
1
|
-
<template>
|
|
2
|
-
<div
|
|
3
|
-
:data-aos="aosFadeLeft"
|
|
4
|
-
class="CustomerPreferencesPage__wrapper">
|
|
5
|
-
<customer-preferences />
|
|
6
|
-
</div>
|
|
7
|
-
</template>
|
|
8
|
-
|
|
9
|
-
<script>
|
|
10
|
-
import metaInfo from '@lancom/shared/mixins/meta-info';
|
|
11
|
-
|
|
12
|
-
export default {
|
|
13
|
-
name: 'CustomerPreferencesPage',
|
|
14
|
-
components: {
|
|
15
|
-
CustomerPreferences: () => import('@lancom/shared/components/pages/customer/preferences/preferences')
|
|
16
|
-
},
|
|
17
|
-
mixins: [metaInfo]
|
|
18
|
-
};
|
|
19
|
-
</script>
|
|
20
|
-
|
|
21
|
-
<style lang="scss" scoped>
|
|
22
|
-
@import "@/assets/scss/variables";
|
|
23
|
-
|
|
24
|
-
.CustomerPreferencesPage {
|
|
25
|
-
&__wrapper {
|
|
26
|
-
margin: 100px auto;
|
|
27
|
-
width: 500px;
|
|
28
|
-
@media (max-width: $bp-extra-small-max) {
|
|
29
|
-
width: auto;
|
|
30
|
-
}
|
|
31
|
-
}
|
|
32
|
-
}
|
|
33
|
-
</style>
|