@lancom/shared 0.0.394 → 0.0.396
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/index.js +3 -0
- package/assets/js/utils/gtm.js +3 -3
- package/assets/js/utils/phone.js +134 -0
- package/assets/js/utils/product.js +1 -1
- package/components/asides/contact_us/contact-us.vue +1 -1
- package/components/asides/offer_screen_printing/offer-screen-printing.vue +1 -1
- package/components/checkout/order/address-form/address-form.vue +1 -1
- package/components/checkout/order/order-payment-information/order-payment-information.vue +9 -0
- package/components/common/payment/payment_card/payment-card.vue +7 -0
- package/components/common/payment/payment_card/paypal/paypal.scss +8 -0
- package/components/common/payment/payment_card/paypal/paypal.vue +16 -0
- package/components/common/phone_input/phone-input.vue +28 -7
- package/components/customer/customer_form/customer-form.vue +1 -1
- package/components/modals/failed_conversion_modal/failed-conversion-modal.vue +1 -1
- package/components/modals/order_modal/order-modal.vue +1 -1
- package/components/products/children_categories/children-categories.scss +65 -0
- package/components/products/children_categories/children-categories.vue +43 -0
- package/components/quotes/quote_request/quote-request.vue +2 -2
- package/feeds/google-shopping.js +4 -4
- package/layouts/products.vue +28 -10
- package/nuxt.config.js +2 -1
- package/package.json +1 -1
- package/routes/index.js +14 -11
- package/store/products.js +14 -0
package/assets/js/api/index.js
CHANGED
|
@@ -41,6 +41,9 @@ const api = {
|
|
|
41
41
|
fetchCustomerCoupons(customer, shop) {
|
|
42
42
|
return _get(`shop/${shop}/customer/${customer._id}/coupons`);
|
|
43
43
|
},
|
|
44
|
+
fetchChildrenCategories(category, params) {
|
|
45
|
+
return _get(`category/${category}/children`, params);
|
|
46
|
+
},
|
|
44
47
|
searchProducts(shop, text, save) {
|
|
45
48
|
return _get(`shop/${shop}/products/search`, { text, save });
|
|
46
49
|
},
|
package/assets/js/utils/gtm.js
CHANGED
|
@@ -47,7 +47,7 @@ const gtm = {
|
|
|
47
47
|
currency: currency?.isoCode || 'AUD',
|
|
48
48
|
items: (products || []).map(product => {
|
|
49
49
|
return {
|
|
50
|
-
id: product.SKU,
|
|
50
|
+
id: product.SKU,
|
|
51
51
|
item_name: product.name,
|
|
52
52
|
google_business_vertical: 'retail',
|
|
53
53
|
price: product.minPrice
|
|
@@ -111,10 +111,10 @@ const gtm = {
|
|
|
111
111
|
|
|
112
112
|
const { shippingAddress } = order;
|
|
113
113
|
if (shippingAddress) {
|
|
114
|
-
const { fullName, email, phone, city, state, postcode, country, addressLine1, addressLine2 } = shippingAddress;
|
|
114
|
+
const { fullName, email, phone, phoneE164, city, state, postcode, country, addressLine1, addressLine2 } = shippingAddress;
|
|
115
115
|
const [first_name, last_name] = fullName.split(' ');
|
|
116
116
|
event.email = email;
|
|
117
|
-
event.phone_number= phone;
|
|
117
|
+
event.phone_number = phoneE164 || phone;
|
|
118
118
|
event.address = {
|
|
119
119
|
first_name,
|
|
120
120
|
last_name,
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @typedef {Object} PhoneNumberValidationResult
|
|
3
|
+
* @property {string} e164Number
|
|
4
|
+
* @property {boolean} isValid
|
|
5
|
+
* @property {boolean} supportsSms
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Transforms a phone number to E.164 format if valid for the specified country.
|
|
10
|
+
* @param {string|null|undefined} originalInput
|
|
11
|
+
* @param {string} country
|
|
12
|
+
* @param {string} [postcode]
|
|
13
|
+
* @returns {PhoneNumberValidationResult}
|
|
14
|
+
*/
|
|
15
|
+
export function formatNumberToE164(originalInput, country) {
|
|
16
|
+
const defaultFailureResult = {
|
|
17
|
+
e164Number: '',
|
|
18
|
+
isValid: false,
|
|
19
|
+
supportsSms: false
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
if (originalInput === null || originalInput === undefined) {
|
|
23
|
+
return defaultFailureResult;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const countryCodeMap = {
|
|
27
|
+
AU: '+61',
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
const normalizedCountry = country.toUpperCase();
|
|
31
|
+
const dialingCode = countryCodeMap[normalizedCountry];
|
|
32
|
+
|
|
33
|
+
let cleanedInput = originalInput.trim().replace(/\s+/g, '').replace(/[-()]/g, '');
|
|
34
|
+
|
|
35
|
+
if (cleanedInput === '') {
|
|
36
|
+
return defaultFailureResult;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
if (normalizedCountry !== 'AU' || !dialingCode) {
|
|
40
|
+
return defaultFailureResult;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
if (
|
|
44
|
+
cleanedInput.startsWith('1300') ||
|
|
45
|
+
(cleanedInput.startsWith('13') && cleanedInput.length > 2 && cleanedInput.length <= 6) ||
|
|
46
|
+
cleanedInput.startsWith('1800') ||
|
|
47
|
+
(cleanedInput.startsWith('180') && cleanedInput.length > 3 && cleanedInput.length <= 7)
|
|
48
|
+
) {
|
|
49
|
+
return defaultFailureResult;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
if (
|
|
53
|
+
cleanedInput.startsWith(dialingCode.substring(1)) &&
|
|
54
|
+
cleanedInput.length === dialingCode.length - 1 + 9
|
|
55
|
+
) {
|
|
56
|
+
const numPartAfterCountryCode = cleanedInput.substring(dialingCode.length - 1);
|
|
57
|
+
const firstDigitNumPart = numPartAfterCountryCode.charAt(0);
|
|
58
|
+
if (
|
|
59
|
+
/^\d+$/.test(numPartAfterCountryCode) &&
|
|
60
|
+
numPartAfterCountryCode.length === 9 &&
|
|
61
|
+
['2', '3', '4', '7', '8'].includes(firstDigitNumPart)
|
|
62
|
+
) {
|
|
63
|
+
return {
|
|
64
|
+
e164Number: dialingCode + numPartAfterCountryCode,
|
|
65
|
+
isValid: true,
|
|
66
|
+
supportsSms: firstDigitNumPart === '4',
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
return defaultFailureResult;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
if (cleanedInput.startsWith(dialingCode)) {
|
|
73
|
+
let numPartAfterDialingCodeRaw = cleanedInput.substring(dialingCode.length);
|
|
74
|
+
if (numPartAfterDialingCodeRaw.startsWith('0')) {
|
|
75
|
+
numPartAfterDialingCodeRaw = numPartAfterDialingCodeRaw.substring(1);
|
|
76
|
+
}
|
|
77
|
+
const firstDigitNumPartFinal = numPartAfterDialingCodeRaw.charAt(0);
|
|
78
|
+
if (
|
|
79
|
+
/^\d+$/.test(numPartAfterDialingCodeRaw) &&
|
|
80
|
+
numPartAfterDialingCodeRaw.length === 9 &&
|
|
81
|
+
['2', '3', '4', '7', '8'].includes(firstDigitNumPartFinal)
|
|
82
|
+
) {
|
|
83
|
+
return {
|
|
84
|
+
e164Number: dialingCode + numPartAfterDialingCodeRaw,
|
|
85
|
+
isValid: true,
|
|
86
|
+
supportsSms: firstDigitNumPartFinal === '4',
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
return defaultFailureResult;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
if (cleanedInput.startsWith('04')) {
|
|
93
|
+
const mobilePart = cleanedInput.substring(2);
|
|
94
|
+
if (/^\d+$/.test(mobilePart) && mobilePart.length === 8) {
|
|
95
|
+
return {
|
|
96
|
+
e164Number: dialingCode + '4' + mobilePart,
|
|
97
|
+
isValid: true,
|
|
98
|
+
supportsSms: true,
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
return defaultFailureResult;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
if (cleanedInput.startsWith('0') && cleanedInput.length === 10) {
|
|
105
|
+
const areaCode = cleanedInput.charAt(1);
|
|
106
|
+
const numPart = cleanedInput.substring(2);
|
|
107
|
+
if (
|
|
108
|
+
['2', '3', '7', '8'].includes(areaCode) &&
|
|
109
|
+
/^\d+$/.test(numPart) &&
|
|
110
|
+
numPart.length === 8
|
|
111
|
+
) {
|
|
112
|
+
return {
|
|
113
|
+
e164Number: dialingCode + areaCode + numPart,
|
|
114
|
+
isValid: true,
|
|
115
|
+
supportsSms: false,
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
return defaultFailureResult;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
if (cleanedInput.length === 9 && /^\d+$/.test(cleanedInput)) {
|
|
122
|
+
const firstDigit = cleanedInput.charAt(0);
|
|
123
|
+
if (['2', '3', '4', '7', '8'].includes(firstDigit)) {
|
|
124
|
+
return {
|
|
125
|
+
e164Number: dialingCode + cleanedInput,
|
|
126
|
+
isValid: true,
|
|
127
|
+
supportsSms: firstDigit === '4',
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
return defaultFailureResult;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
return defaultFailureResult;
|
|
134
|
+
}
|
|
@@ -187,7 +187,7 @@ export function generateProductLink(product, color, toEditor = false, size) {
|
|
|
187
187
|
const categoryPath = generateCategoryPath(product.category);
|
|
188
188
|
if (categoryPath) {
|
|
189
189
|
const categoryBaseLink = `/c/${categoryPath}/${product.alias}/${product.SKU.toLowerCase()}`;
|
|
190
|
-
link = toEditor ? `${categoryBaseLink}.html` : `${categoryBaseLink}
|
|
190
|
+
link = toEditor ? `${categoryBaseLink}.html` : `${categoryBaseLink}.html`;
|
|
191
191
|
}
|
|
192
192
|
}
|
|
193
193
|
const query = [];
|
|
@@ -42,6 +42,14 @@
|
|
|
42
42
|
Apple Pay
|
|
43
43
|
</span>
|
|
44
44
|
</label>
|
|
45
|
+
<label
|
|
46
|
+
class="form-label OrderPaymentInformation__checkbox"
|
|
47
|
+
@click="updatePaymentType('paypal')">
|
|
48
|
+
<checked-icon :checked="paymentMethod === 'paypal'" />
|
|
49
|
+
<span class="lc_regular12 lc__grey1 OrderPaymentInformation__checkbox-label">
|
|
50
|
+
Paypal
|
|
51
|
+
</span>
|
|
52
|
+
</label>
|
|
45
53
|
</div>
|
|
46
54
|
<div
|
|
47
55
|
v-if="paymentMethod === 'deposit'"
|
|
@@ -80,6 +88,7 @@
|
|
|
80
88
|
:key="paymentMethod"
|
|
81
89
|
:google="paymentMethod === 'google'"
|
|
82
90
|
:apple="paymentMethod === 'apple'"
|
|
91
|
+
:paypal="paymentMethod === 'paypal'"
|
|
83
92
|
:amount="cartPricing.totalPrice"
|
|
84
93
|
:order="orderData"
|
|
85
94
|
@inited="initedCard">
|
|
@@ -17,6 +17,7 @@ import { mapGetters } from 'vuex';
|
|
|
17
17
|
export default {
|
|
18
18
|
name: 'PaymentCard',
|
|
19
19
|
components: {
|
|
20
|
+
Paypal: () => import('./paypal/paypal'),
|
|
20
21
|
Applepay: () => import('./applepay/applepay'),
|
|
21
22
|
Googlepay: () => import('./googlepay/googlepay'),
|
|
22
23
|
Pinpayment: () => import('./pinpayment/pinpayment'),
|
|
@@ -36,6 +37,9 @@ export default {
|
|
|
36
37
|
},
|
|
37
38
|
apple: {
|
|
38
39
|
type: Boolean
|
|
40
|
+
},
|
|
41
|
+
paypal: {
|
|
42
|
+
type: Boolean
|
|
39
43
|
}
|
|
40
44
|
},
|
|
41
45
|
computed: {
|
|
@@ -47,6 +51,9 @@ export default {
|
|
|
47
51
|
if (this.apple) {
|
|
48
52
|
return 'applepay';
|
|
49
53
|
}
|
|
54
|
+
if (this.paypal) {
|
|
55
|
+
return 'paypal';
|
|
56
|
+
}
|
|
50
57
|
return this.payment?.type || 'stripe-payment';
|
|
51
58
|
}
|
|
52
59
|
},
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
<template>
|
|
2
2
|
<div>
|
|
3
|
+
<div>{{ item }}</div>
|
|
3
4
|
<div class="form-row">
|
|
4
5
|
<label
|
|
5
6
|
v-if="labelless"
|
|
@@ -63,6 +64,7 @@
|
|
|
63
64
|
|
|
64
65
|
<script>
|
|
65
66
|
import Multiselect from 'vue-multiselect';
|
|
67
|
+
import { formatNumberToE164 } from '@lancom/shared/assets/js/utils/phone';
|
|
66
68
|
|
|
67
69
|
export default {
|
|
68
70
|
name: 'PhoneInput',
|
|
@@ -73,8 +75,9 @@ export default {
|
|
|
73
75
|
codeCountry: {
|
|
74
76
|
type: Object
|
|
75
77
|
},
|
|
76
|
-
|
|
77
|
-
type:
|
|
78
|
+
item: {
|
|
79
|
+
type: Object,
|
|
80
|
+
required: true
|
|
78
81
|
},
|
|
79
82
|
labelText: {
|
|
80
83
|
type: String
|
|
@@ -86,6 +89,14 @@ export default {
|
|
|
86
89
|
type: String,
|
|
87
90
|
default: 'Phone'
|
|
88
91
|
},
|
|
92
|
+
phoneProperty: {
|
|
93
|
+
type: String,
|
|
94
|
+
default: 'phone'
|
|
95
|
+
},
|
|
96
|
+
phoneE164Property: {
|
|
97
|
+
type: String,
|
|
98
|
+
default: 'phoneE164'
|
|
99
|
+
},
|
|
89
100
|
copyFrom: {
|
|
90
101
|
type: Boolean,
|
|
91
102
|
default: false
|
|
@@ -120,16 +131,26 @@ export default {
|
|
|
120
131
|
},
|
|
121
132
|
model: {
|
|
122
133
|
get() {
|
|
123
|
-
return this.
|
|
134
|
+
return this.item[this.phoneProperty];
|
|
124
135
|
},
|
|
125
136
|
set(phone) {
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
// this.$emit('input', `${this.code}${phone}`);
|
|
137
|
+
this.$set(this.item, this.phoneProperty, phone);
|
|
138
|
+
this.updatePhoneE164();
|
|
139
|
+
|
|
130
140
|
this.$emit('input', phone);
|
|
131
141
|
}
|
|
132
142
|
}
|
|
143
|
+
},
|
|
144
|
+
mounted() {
|
|
145
|
+
this.updatePhoneE164();
|
|
146
|
+
},
|
|
147
|
+
methods: {
|
|
148
|
+
updatePhoneE164() {
|
|
149
|
+
const code = this.codeCountry?.isoCode || this.codeCountry || 'AU';
|
|
150
|
+
const result = formatNumberToE164(this.model, code);
|
|
151
|
+
const e164Number = result?.isValid ? result.e164Number : null;
|
|
152
|
+
this.$set(this.item, this.phoneE164Property, e164Number);
|
|
153
|
+
}
|
|
133
154
|
}
|
|
134
155
|
};
|
|
135
156
|
</script>
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
@import "@/assets/scss/variables";
|
|
2
|
+
|
|
3
|
+
.ChildrenCategories {
|
|
4
|
+
&__wrapper {
|
|
5
|
+
display: flex;
|
|
6
|
+
flex-wrap: wrap;
|
|
7
|
+
margin-top: 20px;
|
|
8
|
+
@media (max-width: $bp-small-max) {
|
|
9
|
+
justify-content: center;
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
.CategoryCard {
|
|
15
|
+
width: 270px;
|
|
16
|
+
margin: 0 0 15px 15px;
|
|
17
|
+
position: relative;
|
|
18
|
+
transition: transform 0.3s ease, box-shadow 0.3s ease;
|
|
19
|
+
border-radius: 5px;
|
|
20
|
+
overflow: hidden;
|
|
21
|
+
background-color: white;
|
|
22
|
+
box-shadow: 0px 0px 0px rgba(75, 75, 75, 0.2);
|
|
23
|
+
|
|
24
|
+
&:hover {
|
|
25
|
+
transform: translateY(-5px);
|
|
26
|
+
box-shadow: 0px 5px 15px rgba(75, 75, 75, 0.2);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
&__link {
|
|
30
|
+
display: block;
|
|
31
|
+
text-decoration: none;
|
|
32
|
+
color: inherit;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
&__image-wrapper {
|
|
36
|
+
width: 100%;
|
|
37
|
+
height: 300px;
|
|
38
|
+
overflow: hidden;
|
|
39
|
+
position: relative;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
&__image {
|
|
43
|
+
width: 100%;
|
|
44
|
+
height: 100%;
|
|
45
|
+
object-fit: cover;
|
|
46
|
+
transition: transform 0.3s ease;
|
|
47
|
+
|
|
48
|
+
&:hover {
|
|
49
|
+
transform: scale(1.05);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
&__name {
|
|
54
|
+
padding: 15px;
|
|
55
|
+
font-size: 16px;
|
|
56
|
+
font-weight: 600;
|
|
57
|
+
color: $grey_1;
|
|
58
|
+
text-align: center;
|
|
59
|
+
background-color: white;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
@media (max-width: $bp-extra-small-max) {
|
|
63
|
+
margin: 0 auto 25px auto;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div class="ChildrenCategories__wrapper">
|
|
3
|
+
<div
|
|
4
|
+
v-for="category in childrenCategories"
|
|
5
|
+
:key="category._id"
|
|
6
|
+
class="CategoryCard">
|
|
7
|
+
<a
|
|
8
|
+
:href="generateProductsLink($route, { category }, true)"
|
|
9
|
+
class="CategoryCard__link">
|
|
10
|
+
<div class="CategoryCard__image-wrapper">
|
|
11
|
+
<img
|
|
12
|
+
v-if="category.image"
|
|
13
|
+
:src="category.image.medium"
|
|
14
|
+
:alt="category.name"
|
|
15
|
+
class="CategoryCard__image" />
|
|
16
|
+
</div>
|
|
17
|
+
<div class="CategoryCard__name">
|
|
18
|
+
{{ category.name }}
|
|
19
|
+
</div>
|
|
20
|
+
</a>
|
|
21
|
+
</div>
|
|
22
|
+
</div>
|
|
23
|
+
</template>
|
|
24
|
+
|
|
25
|
+
<script>
|
|
26
|
+
import { mapGetters } from 'vuex';
|
|
27
|
+
import { generateProductsLink } from '@lancom/shared/assets/js/utils/product';
|
|
28
|
+
|
|
29
|
+
export default {
|
|
30
|
+
name: 'ChildrenCategories',
|
|
31
|
+
computed: {
|
|
32
|
+
...mapGetters('products', ['childrenCategories'])
|
|
33
|
+
},
|
|
34
|
+
methods: {
|
|
35
|
+
generateProductsLink
|
|
36
|
+
}
|
|
37
|
+
};
|
|
38
|
+
</script>
|
|
39
|
+
|
|
40
|
+
<style lang="scss" scoped>
|
|
41
|
+
@import 'children-categories';
|
|
42
|
+
</style>
|
|
43
|
+
|
|
@@ -37,7 +37,7 @@
|
|
|
37
37
|
</validation-provider>
|
|
38
38
|
<phone-input
|
|
39
39
|
class="form-row"
|
|
40
|
-
|
|
40
|
+
:item="quote.address" />
|
|
41
41
|
<validation-provider
|
|
42
42
|
v-slot="{ errors }"
|
|
43
43
|
tag="div"
|
|
@@ -427,7 +427,7 @@ export default {
|
|
|
427
427
|
}))
|
|
428
428
|
};
|
|
429
429
|
})
|
|
430
|
-
}
|
|
430
|
+
}
|
|
431
431
|
};
|
|
432
432
|
const body = {
|
|
433
433
|
recaptchaToken,
|
package/feeds/google-shopping.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
async function googleLocalShoppingFeed(axios, config, availableStores, country) {
|
|
2
2
|
const result = await googleShoppingFeed(axios, config, availableStores, country, false, { allowSameDayDelivery: true });
|
|
3
|
-
|
|
3
|
+
|
|
4
4
|
const fields = [
|
|
5
5
|
'g:availability',
|
|
6
6
|
'g:id',
|
|
@@ -59,7 +59,7 @@ async function googleShoppingFeed(axios, config, availableStores, country, isEdi
|
|
|
59
59
|
const description = `${product.description || product.fabricInfoShort || product.name || ''}`
|
|
60
60
|
.replace(/ /g, ' ')
|
|
61
61
|
.replace(/·/, '·');
|
|
62
|
-
|
|
62
|
+
|
|
63
63
|
let link = `https://${config.HOST_NAME}${generateProductLink(product, sp.color, isEditor)}`;
|
|
64
64
|
link = link.includes('?') ? `${link}&size=${sp.size?.shortName}` : `${link}?size=${sp.size?.shortName}`
|
|
65
65
|
link = link.includes('?') ? `${link}&price=IT` : `${link}?price=IT`
|
|
@@ -186,7 +186,7 @@ async function googleShoppingFeed(axios, config, availableStores, country, isEdi
|
|
|
186
186
|
];
|
|
187
187
|
}, [])
|
|
188
188
|
};
|
|
189
|
-
|
|
189
|
+
|
|
190
190
|
return {
|
|
191
191
|
_declaration: { _attributes: { version: "1.0", encoding: "utf-8" } },
|
|
192
192
|
rss: {
|
|
@@ -221,7 +221,7 @@ function generateProductLink(product, color, toEditor) {
|
|
|
221
221
|
const categoryPath = generateCategoryPath(product.category);
|
|
222
222
|
if (categoryPath) {
|
|
223
223
|
const categoryBaseLink = `/c/${categoryPath}/${product.alias}/${product.SKU.toLowerCase()}`;
|
|
224
|
-
link = toEditor ? `${categoryBaseLink}.html` : `${categoryBaseLink}
|
|
224
|
+
link = toEditor ? `${categoryBaseLink}.html` : `${categoryBaseLink}.html`;
|
|
225
225
|
}
|
|
226
226
|
}
|
|
227
227
|
return `${link}${color ? `?color=${color.alias || color}` : ''}`;
|
package/layouts/products.vue
CHANGED
|
@@ -20,12 +20,16 @@
|
|
|
20
20
|
<h1 class="Products__name">
|
|
21
21
|
{{ routeName }}
|
|
22
22
|
</h1>
|
|
23
|
-
<div
|
|
23
|
+
<div
|
|
24
|
+
v-if="!visiblChildrenCategories"
|
|
25
|
+
class="Products__filters">
|
|
24
26
|
<products-filters @open="openAside" />
|
|
25
27
|
</div>
|
|
26
28
|
</div>
|
|
27
29
|
<div class="Products__content">
|
|
28
|
-
<div
|
|
30
|
+
<div
|
|
31
|
+
v-if="!visiblChildrenCategories"
|
|
32
|
+
class="Products__aside">
|
|
29
33
|
<breakpoint
|
|
30
34
|
name="md"
|
|
31
35
|
mode="up">
|
|
@@ -33,7 +37,10 @@
|
|
|
33
37
|
</breakpoint>
|
|
34
38
|
</div>
|
|
35
39
|
<div class="Products__list">
|
|
36
|
-
<
|
|
40
|
+
<children-categories v-if="visiblChildrenCategories" />
|
|
41
|
+
<products-catalog
|
|
42
|
+
v-else
|
|
43
|
+
class="Products__catalog" />
|
|
37
44
|
</div>
|
|
38
45
|
</div>
|
|
39
46
|
<div
|
|
@@ -57,21 +64,22 @@
|
|
|
57
64
|
|
|
58
65
|
<script>
|
|
59
66
|
import debounce from 'lodash.debounce';
|
|
67
|
+
import LazyHydrate from 'vue-lazy-hydration';
|
|
60
68
|
import { mapActions, mapGetters, mapMutations } from 'vuex';
|
|
61
69
|
import gtm from '@lancom/shared/assets/js/utils/gtm';
|
|
62
70
|
import metaInfo from '@lancom/shared/mixins/meta-info';
|
|
63
71
|
import { generateProductsLink, generateProductLink } from '@lancom/shared/assets/js/utils/product';
|
|
64
72
|
import { getProductLargeCover, getProductMediumCover } from '@lancom/shared/assets/js/utils/colors';
|
|
65
|
-
import LazyHydrate from 'vue-lazy-hydration';
|
|
66
|
-
import TheNavbar from '@/components/the_navbar/the-navbar';
|
|
67
73
|
import TheChangesSavedIndicator from '@lancom/shared/components/the_changes_saved_indicator/the-changes-saved-indicator';
|
|
68
74
|
import Breadcrumbs from '@lancom/shared/components/common/breadcrumbs/breadcrumbs';
|
|
69
|
-
import
|
|
75
|
+
import StaticPage from '@lancom/shared/components/static_page/static-page';
|
|
70
76
|
import TheAside from '@lancom/shared/components/the_aside/the-aside';
|
|
71
|
-
import ProductsAside from '@/components/products/products_aside/products-aside';
|
|
72
77
|
import ProductsFilters from '@lancom/shared/components/products/products_filters/products-filters';
|
|
78
|
+
import ChildrenCategories from '@lancom/shared/components/products/children_categories/children-categories';
|
|
79
|
+
import ProductsAside from '@/components/products/products_aside/products-aside';
|
|
80
|
+
import TheNavbar from '@/components/the_navbar/the-navbar';
|
|
81
|
+
import TheFooter from '@/components/the_footer/the-footer';
|
|
73
82
|
import ProductsCatalog from '@/components/products/products_catalog/products-catalog';
|
|
74
|
-
import StaticPage from '@lancom/shared/components/static_page/static-page';
|
|
75
83
|
import Error from './error';
|
|
76
84
|
|
|
77
85
|
export default {
|
|
@@ -85,6 +93,7 @@
|
|
|
85
93
|
ProductsAside,
|
|
86
94
|
ProductsFilters,
|
|
87
95
|
ProductsCatalog,
|
|
96
|
+
ChildrenCategories,
|
|
88
97
|
Error,
|
|
89
98
|
StaticPage,
|
|
90
99
|
TheChangesSavedIndicator
|
|
@@ -93,6 +102,11 @@
|
|
|
93
102
|
middleware: ['page-info'],
|
|
94
103
|
async fetch() {
|
|
95
104
|
await this.loadProducts();
|
|
105
|
+
|
|
106
|
+
if (this.page === 1 && this.products.length === 0) {
|
|
107
|
+
await this.fetchChildrenCategories(this.currentCategory);
|
|
108
|
+
}
|
|
109
|
+
|
|
96
110
|
if (process.server) {
|
|
97
111
|
this.$root.context?.res?.setHeader('Cache-Control', `max-age=${86400}`);;
|
|
98
112
|
}
|
|
@@ -100,7 +114,10 @@
|
|
|
100
114
|
computed: {
|
|
101
115
|
...mapGetters('page', ['routeInfo']),
|
|
102
116
|
...mapGetters(['notificationBar', 'shop', 'country', 'currency']),
|
|
103
|
-
...mapGetters('products', ['category', 'categories', 'brands', 'types', 'tags', 'loadError', 'count', 'products', 'minPrice', 'maxPrice']),
|
|
117
|
+
...mapGetters('products', ['category', 'childrenCategories', 'categories', 'brands', 'types', 'tags', 'loadError', 'count', 'products', 'minPrice', 'maxPrice', 'page']),
|
|
118
|
+
visiblChildrenCategories() {
|
|
119
|
+
return this.page === 1 && this.products.length === 0 && this.childrenCategories.length > 0;
|
|
120
|
+
},
|
|
104
121
|
pageItemCanonical() {
|
|
105
122
|
const page = +this.$route.query.page;
|
|
106
123
|
return `${this.breadcrumbs[this.breadcrumbs.length - 1]?.to}${page > 1 ? `?page=${page}` : ''}`;
|
|
@@ -302,7 +319,8 @@
|
|
|
302
319
|
'loadState'
|
|
303
320
|
]),
|
|
304
321
|
...mapActions('products', [
|
|
305
|
-
'fetchProducts'
|
|
322
|
+
'fetchProducts',
|
|
323
|
+
'fetchChildrenCategories'
|
|
306
324
|
]),
|
|
307
325
|
...mapMutations('products', [
|
|
308
326
|
'setPlaceholder'
|
package/nuxt.config.js
CHANGED
|
@@ -38,6 +38,7 @@ module.exports = (config, axios, { raygunClient, publicPath, productUrlToEditor
|
|
|
38
38
|
'/checkout/**',
|
|
39
39
|
'/customer/**'
|
|
40
40
|
],
|
|
41
|
+
cacheTime: 10,
|
|
41
42
|
routes: async () => {
|
|
42
43
|
const { data } = await axios.get(`${config.LOCAL_API_URL}/feed/sitemap?host=${config.HOST_NAME}&toEditor=${!!productUrlToEditor}`);
|
|
43
44
|
return [
|
|
@@ -137,7 +138,7 @@ module.exports = (config, axios, { raygunClient, publicPath, productUrlToEditor
|
|
|
137
138
|
router: {
|
|
138
139
|
extendRoutes(routes, resolve) {
|
|
139
140
|
return [
|
|
140
|
-
...sharedRoutes(routes, resolve),
|
|
141
|
+
...sharedRoutes(routes, resolve, process.env),
|
|
141
142
|
...routes,
|
|
142
143
|
];
|
|
143
144
|
}
|
package/package.json
CHANGED
package/routes/index.js
CHANGED
|
@@ -1,52 +1,55 @@
|
|
|
1
|
-
module.exports = function(routes, resolve) {
|
|
1
|
+
module.exports = function(routes, resolve, config) {
|
|
2
|
+
const PRODUCT_LINK_EXTENSION = config?.PRODUCT_LINK_EXTENSION || '/info.html';
|
|
3
|
+
const PRODUCT_EDITOR_LINK_EXTENSION = config?.PRODUCT_EDITOR_LINK_EXTENSION || '.html';
|
|
4
|
+
|
|
2
5
|
return [{
|
|
3
6
|
name: 'product-taxonomy-view-level1',
|
|
4
|
-
path:
|
|
7
|
+
path: `/c/:category/:slug/:sku${PRODUCT_LINK_EXTENSION}`,
|
|
5
8
|
component: resolve('@/pages/_brand/_type/_slug/index.vue'),
|
|
6
9
|
chunkName: 'pages/product/view/level1'
|
|
7
10
|
}, {
|
|
8
11
|
name: 'product-taxonomy-view-level2',
|
|
9
|
-
path:
|
|
12
|
+
path: `/c/:category1/:category/:slug/:sku${PRODUCT_LINK_EXTENSION}`,
|
|
10
13
|
component: resolve('@/pages/_brand/_type/_slug/index.vue'),
|
|
11
14
|
chunkName: 'pages/product/view/level2'
|
|
12
15
|
}, {
|
|
13
16
|
name: 'product-taxonomy-view-level3',
|
|
14
|
-
path:
|
|
17
|
+
path: `/c/:category2/:category1/:category/:slug/:sku${PRODUCT_LINK_EXTENSION}`,
|
|
15
18
|
component: resolve('@/pages/_brand/_type/_slug/index.vue'),
|
|
16
19
|
chunkName: 'pages/product/view/level3'
|
|
17
20
|
}, {
|
|
18
21
|
name: 'product-taxonomy-view-level4',
|
|
19
|
-
path:
|
|
22
|
+
path: `/c/:category3/:category2/:category1/:category/:slug/:sku${PRODUCT_LINK_EXTENSION}`,
|
|
20
23
|
component: resolve('@/pages/_brand/_type/_slug/index.vue'),
|
|
21
24
|
chunkName: 'pages/product/view/level4'
|
|
22
25
|
}, {
|
|
23
26
|
name: 'product-taxonomy-view-level5',
|
|
24
|
-
path:
|
|
27
|
+
path: `/c/:category4/:category3/:category2/:category1/:category/:slug/:sku${PRODUCT_LINK_EXTENSION}`,
|
|
25
28
|
component: resolve('@/pages/_brand/_type/_slug/index.vue'),
|
|
26
29
|
chunkName: 'pages/product/view/level5'
|
|
27
30
|
}, {
|
|
28
31
|
name: 'product-taxonomy-editor-level1',
|
|
29
|
-
path:
|
|
32
|
+
path: `/c/:category/:slug/:sku${PRODUCT_EDITOR_LINK_EXTENSION}`,
|
|
30
33
|
component: resolve('@/pages/_brand/_type/_slug/editor/index.vue'),
|
|
31
34
|
chunkName: 'pages/product/editor/level1'
|
|
32
35
|
}, {
|
|
33
36
|
name: 'product-taxonomy-editor-level2',
|
|
34
|
-
path:
|
|
37
|
+
path: `/c/:category1/:category/:slug/:sku${PRODUCT_EDITOR_LINK_EXTENSION}`,
|
|
35
38
|
component: resolve('@/pages/_brand/_type/_slug/editor/index.vue'),
|
|
36
39
|
chunkName: 'pages/product/editor/level2'
|
|
37
40
|
}, {
|
|
38
41
|
name: 'product-taxonomy-editor-level3',
|
|
39
|
-
path:
|
|
42
|
+
path: `/c/:category2/:category1/:category/:slug/:sku${PRODUCT_EDITOR_LINK_EXTENSION}`,
|
|
40
43
|
component: resolve('@/pages/_brand/_type/_slug/editor/index.vue'),
|
|
41
44
|
chunkName: 'pages/product/editor/level3'
|
|
42
45
|
}, {
|
|
43
46
|
name: 'product-taxonomy-editor-level4',
|
|
44
|
-
path:
|
|
47
|
+
path: `/c/:category3/:category2/:category1/:category/:slug/:sku${PRODUCT_EDITOR_LINK_EXTENSION}`,
|
|
45
48
|
component: resolve('@/pages/_brand/_type/_slug/editor/index.vue'),
|
|
46
49
|
chunkName: 'pages/product/editor/level4'
|
|
47
50
|
}, {
|
|
48
51
|
name: 'product-taxonomy-editor-level5',
|
|
49
|
-
path:
|
|
52
|
+
path: `/c/:category4/:category3/:category2/:category1/:category/:slug/:sku${PRODUCT_EDITOR_LINK_EXTENSION}`,
|
|
50
53
|
component: resolve('@/pages/_brand/_type/_slug/editor/index.vue'),
|
|
51
54
|
chunkName: 'pages/product/editor/level5'
|
|
52
55
|
}, {
|
package/store/products.js
CHANGED
|
@@ -12,6 +12,7 @@ export const state = () => ({
|
|
|
12
12
|
brands: [],
|
|
13
13
|
category: null,
|
|
14
14
|
categories: [],
|
|
15
|
+
childrenCategories: [],
|
|
15
16
|
tags: [],
|
|
16
17
|
attributes: [],
|
|
17
18
|
loadError: null,
|
|
@@ -25,6 +26,7 @@ export const getters = {
|
|
|
25
26
|
brands: ({ brands }) => brands || [],
|
|
26
27
|
categories: ({ categories }) => categories || [],
|
|
27
28
|
category: ({ category }) => category,
|
|
29
|
+
childrenCategories: ({ childrenCategories }) => childrenCategories,
|
|
28
30
|
tags: ({ tags }) => tags || [],
|
|
29
31
|
attributes: ({ attributes }) => attributes || [],
|
|
30
32
|
page: ({ page }) => page,
|
|
@@ -79,10 +81,22 @@ export const actions = {
|
|
|
79
81
|
});
|
|
80
82
|
throw e;
|
|
81
83
|
}
|
|
84
|
+
},
|
|
85
|
+
async fetchChildrenCategories({ commit }, category) {
|
|
86
|
+
try {
|
|
87
|
+
const categories = await api.fetchChildrenCategories(category?._id);
|
|
88
|
+
commit('setChildrenCategories', categories);
|
|
89
|
+
return categories;
|
|
90
|
+
} catch (e) {
|
|
91
|
+
console.log(e);
|
|
92
|
+
}
|
|
82
93
|
}
|
|
83
94
|
};
|
|
84
95
|
|
|
85
96
|
export const mutations = {
|
|
97
|
+
setChildrenCategories(state, categories) {
|
|
98
|
+
state.childrenCategories = categories;
|
|
99
|
+
},
|
|
86
100
|
setPlaceholder(state, placeholder) {
|
|
87
101
|
state.placeholder = placeholder;
|
|
88
102
|
},
|