@gudhub/ssg-web-components-library 1.0.23 → 1.0.25
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/package.json +1 -1
- package/src/components/breadcrumbs/breadcrumbs-component.js +42 -44
- package/src/components/breadcrumbs/breadcrumbs-component.readme.md +31 -0
- package/src/components/breadcrumbs/breadcrumbs.html +10 -9
- package/src/components/breadcrumbs/breadcrumbs.scss +12 -12
- package/src/components/contact-us-block/contact-us-block.html +4 -4
- package/src/components/contact-us-block/contact-us-block.js +10 -1
- package/src/components/contact-us-block/contact-us-block.scss +3 -0
- package/src/components/contact-us-block/default-icons.js +9 -0
- package/src/components/get-in-touch-block/get-in-touch-block.html +4 -2
- package/src/components/get-in-touch-block/get-in-touch-block.js +32 -3
- package/src/components/get-in-touch-form/get-in-touch-form.html +15 -11
- package/src/components/get-in-touch-form/get-in-touch-form.js +87 -71
- package/src/components/get-in-touch-form/get-in-touch-form.readme.md +58 -3
- package/src/components/get-in-touch-form/validationCallbacks.js +36 -0
- package/src/components/grid-component/grid-items/grid-item-expandable-vertical/grid-item-expandable-vertical.html +3 -1
- package/src/components/grid-component/grid-items/grid-item-expandable-vertical/grid-item-expandable-vertical.js +2 -1
- package/src/components/grid-component/grid-items/grid-item-expandable-vertical/grid-item-expandable-vertical.scss +8 -3
- package/src/components/meta/meta-tag.js +0 -2
- package/src/components/get-in-touch-form/sendEmail.js +0 -152
package/package.json
CHANGED
|
@@ -2,64 +2,62 @@ import html from './breadcrumbs.html';
|
|
|
2
2
|
import './breadcrumbs.scss';
|
|
3
3
|
|
|
4
4
|
class BreadcrumbsComponent extends GHComponent {
|
|
5
|
-
/**
|
|
6
|
-
* data-items - in this attribute need set stringified object with items of list of breadcrumbs
|
|
7
|
-
*/
|
|
8
5
|
constructor() {
|
|
9
6
|
super();
|
|
10
7
|
}
|
|
11
8
|
|
|
12
9
|
async onServerRender() {
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
url = url.searchParams.get('path');
|
|
16
|
-
if (url !== '/') {
|
|
17
|
-
this.items = this.getAttribute('data-items');
|
|
18
|
-
this.items = JSON.parse(this.items);
|
|
10
|
+
let currentUrl = new URL(window.location.href);
|
|
11
|
+
currentUrl = currentUrl.searchParams.get('path');
|
|
19
12
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
"@type": "BreadcrumbList",
|
|
23
|
-
"itemListElement": [
|
|
24
|
-
{
|
|
25
|
-
"@type": "ListItem",
|
|
26
|
-
"position": 1,
|
|
27
|
-
"name": "Home",
|
|
28
|
-
"item": `${window.MODE === 'production' ? 'https' : 'http'}://${window.getConfig().website}`
|
|
29
|
-
}
|
|
30
|
-
]
|
|
31
|
-
}
|
|
13
|
+
this.breadcrumbsConfig = window.getConfig().componentsConfigs.breadcrumbsConfig;
|
|
14
|
+
this.initialRoute = this.breadcrumbsConfig[0].routesTree;
|
|
32
15
|
|
|
16
|
+
this.items = this.generateBreadcrumbs(this.initialRoute, currentUrl);
|
|
33
17
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
"name": bc.title,
|
|
41
|
-
"item": `${window.MODE === 'production' ? 'https' : 'http'}://${window.getConfig().website}` + bc.slug
|
|
42
|
-
})
|
|
43
|
-
} else {
|
|
44
|
-
schema.itemListElement.push({
|
|
45
|
-
"@type": "ListItem",
|
|
46
|
-
"position": (realIndex == 0 ? 1 : realIndex) + 1,
|
|
47
|
-
"name": bc.title,
|
|
48
|
-
})
|
|
49
|
-
}
|
|
50
|
-
});
|
|
51
|
-
if(!document.head.querySelector('#breadcrumbsSchema')) {
|
|
18
|
+
this.items === null ? console.error(`Didn't find current route in config, current URL: ${currentUrl}`) : null;
|
|
19
|
+
|
|
20
|
+
if (this.items) {
|
|
21
|
+
super.render(html);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
52
24
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
25
|
+
onClientReady() {
|
|
26
|
+
if (!!this.innerHTML) {
|
|
27
|
+
console.error(`Didn't find current route in config`);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
56
30
|
|
|
31
|
+
generateBreadcrumbs(route, currentUrl, breadcrumbs = []) {
|
|
32
|
+
if (!!route.image) {
|
|
33
|
+
breadcrumbs.push({
|
|
34
|
+
title: route.title,
|
|
35
|
+
link: route.link,
|
|
36
|
+
image: route.image
|
|
37
|
+
});
|
|
38
|
+
} else {
|
|
39
|
+
breadcrumbs.push({ title: route.title, link: route.link });
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
if (route.link === currentUrl) {
|
|
43
|
+
if (breadcrumbs.length > 0) {
|
|
44
|
+
const { link, ...lastBreadcrumb } = breadcrumbs.pop();
|
|
45
|
+
breadcrumbs.push(lastBreadcrumb);
|
|
57
46
|
}
|
|
47
|
+
return breadcrumbs;
|
|
48
|
+
}
|
|
58
49
|
|
|
59
|
-
|
|
50
|
+
if (route.childs) {
|
|
51
|
+
for (const childRoute of (route.childs)) {
|
|
52
|
+
const result = this.generateBreadcrumbs(childRoute, currentUrl, [...breadcrumbs]);
|
|
53
|
+
if (result) {
|
|
54
|
+
return result;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
60
57
|
}
|
|
58
|
+
|
|
59
|
+
return null;
|
|
61
60
|
}
|
|
62
|
-
|
|
63
61
|
}
|
|
64
62
|
|
|
65
63
|
window.customElements.define('breadcrumbs-component', BreadcrumbsComponent);
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# Attributes:
|
|
2
|
+
|
|
3
|
+
None
|
|
4
|
+
|
|
5
|
+
# Component data-object:
|
|
6
|
+
|
|
7
|
+
("?" means "unnecessary")
|
|
8
|
+
|
|
9
|
+
```config file
|
|
10
|
+
[
|
|
11
|
+
{
|
|
12
|
+
langCode: 'string',
|
|
13
|
+
defaultLang: boolean,
|
|
14
|
+
routesTree: {
|
|
15
|
+
title: "string",
|
|
16
|
+
link?: "string",
|
|
17
|
+
childs?: [
|
|
18
|
+
{
|
|
19
|
+
title: "string",
|
|
20
|
+
link?: "string",
|
|
21
|
+
image?: {
|
|
22
|
+
src: "string",
|
|
23
|
+
alt: "string",
|
|
24
|
+
title: "string"
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
]
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
]
|
|
31
|
+
```
|
|
@@ -1,15 +1,16 @@
|
|
|
1
1
|
<ul class="breadcrumbs_list">
|
|
2
|
-
|
|
3
|
-
<a href="/">
|
|
4
|
-
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 20 20" fill="none"><g clip-path="url(#clip0_747_1141)"><path d="M16.4787 8.27295V17.5906H12.9606V13.5608C12.9606 13.2075 12.6742 12.9212 12.3209 12.9212H7.82199C7.46867 12.9212 7.18234 13.2075 7.18234 13.5608V17.5906H3.68555V8.27295H2.40625V18.2303C2.40625 18.5836 2.69262 18.8699 3.0459 18.8699H7.82203C8.17535 18.8699 8.46168 18.5836 8.46168 18.2303V14.2005H11.6813V18.2303C11.6813 18.5837 11.9677 18.87 12.3209 18.87H17.1184C17.4717 18.87 17.758 18.5836 17.758 18.2303V8.27295H16.4787Z" fill="#4D555B"/><path d="M19.7975 9.9808L10.5224 1.30279C10.2782 1.074 9.89871 1.07252 9.65246 1.29873L0.206833 9.97677C-0.0532847 10.2158 -0.070355 10.6203 0.168668 10.8804C0.294684 11.0179 0.46695 11.0874 0.639879 11.0874C0.79445 11.0874 0.949684 11.0316 1.0725 10.9188L10.0814 2.64201L18.9234 10.9149C19.1819 11.1565 19.5861 11.1427 19.8275 10.8849C20.0687 10.6269 20.0554 10.2222 19.7975 9.9808Z" fill="#4D555B"/></g><defs><clipPath id="clip0_747_1141"><rect width="20" height="20" fill="white"/></clipPath></defs></svg>
|
|
5
|
-
</a>
|
|
6
|
-
</li>
|
|
7
|
-
${
|
|
8
|
-
items.reduce((acc, item, index) => {
|
|
2
|
+
${items.reduce((acc, item) => {
|
|
9
3
|
return acc + `
|
|
10
|
-
${item.
|
|
4
|
+
${item.link ? `
|
|
11
5
|
<li>
|
|
12
|
-
|
|
6
|
+
${item.image
|
|
7
|
+
? ` <a href="${item.link}" aria-label="${item.title}">
|
|
8
|
+
<image-component lazyload src="${item.image.src}" alt="${item.image.alt}" title="${item.image.title}"></image-component>
|
|
9
|
+
</a>
|
|
10
|
+
` : `
|
|
11
|
+
<a href="${item.link}">${item.title}</a>
|
|
12
|
+
`
|
|
13
|
+
}
|
|
13
14
|
</li>
|
|
14
15
|
` : `
|
|
15
16
|
<li>${item.title}</li>
|
|
@@ -18,22 +18,23 @@ breadcrumbs-component {
|
|
|
18
18
|
color: var(--breadcrumbsColor);
|
|
19
19
|
position: relative;
|
|
20
20
|
&::after {
|
|
21
|
-
content:
|
|
21
|
+
content: "";
|
|
22
22
|
position: absolute;
|
|
23
|
-
bottom: -2px;
|
|
24
|
-
left: 0;
|
|
25
|
-
transition: all .2s ease;
|
|
26
23
|
height: 1px;
|
|
27
24
|
width: 0;
|
|
28
|
-
|
|
25
|
+
left: 0;
|
|
26
|
+
bottom: 0;
|
|
27
|
+
background-color: var(--breadcrumbsColor);
|
|
28
|
+
transition: all .2s ease;
|
|
29
29
|
}
|
|
30
|
-
&:hover
|
|
31
|
-
|
|
30
|
+
&:hover {
|
|
31
|
+
color: var(--breadcrumbsAccentColor);
|
|
32
|
+
&::after {
|
|
33
|
+
width: 100%;
|
|
34
|
+
background-color: var(--breadcrumbsAccentColor);
|
|
35
|
+
}
|
|
32
36
|
}
|
|
33
37
|
}
|
|
34
|
-
a[href="/"] {
|
|
35
|
-
transform: translateY(-2px);
|
|
36
|
-
}
|
|
37
38
|
&:last-child {
|
|
38
39
|
color: var(--breadcrumbsLastColor);
|
|
39
40
|
margin-right: 0;
|
|
@@ -48,7 +49,7 @@ breadcrumbs-component {
|
|
|
48
49
|
&::after {
|
|
49
50
|
content: '';
|
|
50
51
|
position: absolute;
|
|
51
|
-
top: calc(50% -
|
|
52
|
+
top: calc(50% - 4px);
|
|
52
53
|
right: -17px;
|
|
53
54
|
width: 6px;
|
|
54
55
|
height: 6px;
|
|
@@ -111,7 +112,6 @@ article breadcrumbs-component .breadcrumbs_list {
|
|
|
111
112
|
li {
|
|
112
113
|
font-size: 12px;
|
|
113
114
|
&::after {
|
|
114
|
-
top: calc(50% - 6px);
|
|
115
115
|
right: -13px;
|
|
116
116
|
}
|
|
117
117
|
}
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
${phones.reduce((acc, phone, index) => acc + `
|
|
11
11
|
<a href="tel:${phone.replace(/[ ()+-]/g, '')}">
|
|
12
12
|
<div class="icon-wrapper">
|
|
13
|
-
|
|
13
|
+
${phoneIcon}
|
|
14
14
|
</div>
|
|
15
15
|
<div class="flex-wrapper">
|
|
16
16
|
<h3>Phone:</h3>
|
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
` : `
|
|
23
23
|
<li class="tel manyPhones">
|
|
24
24
|
<div class="icon-wrapper">
|
|
25
|
-
|
|
25
|
+
${phoneIcon}
|
|
26
26
|
</div>
|
|
27
27
|
<div class="left_side">
|
|
28
28
|
<h3>Phone:</h3>
|
|
@@ -39,7 +39,7 @@
|
|
|
39
39
|
<li class="mail">
|
|
40
40
|
<a href="mailto:${info.email}">
|
|
41
41
|
<div class="icon-wrapper">
|
|
42
|
-
|
|
42
|
+
${mailIcon}
|
|
43
43
|
</div>
|
|
44
44
|
<div class="flex-wrapper">
|
|
45
45
|
<h3>Email:</h3>
|
|
@@ -50,7 +50,7 @@
|
|
|
50
50
|
<li class="address">
|
|
51
51
|
<a href="${info.googleMaps}" target="_blank" rel="noreferrer noopener nofollow">
|
|
52
52
|
<div class="icon-wrapper">
|
|
53
|
-
|
|
53
|
+
${addressIcon}
|
|
54
54
|
</div>
|
|
55
55
|
<div class="flex-wrapper">
|
|
56
56
|
<h3>Adress:</h3>
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import html from './contact-us-block.html';
|
|
2
2
|
import './contact-us-block.scss';
|
|
3
|
+
import { defaultIcons } from './default-icons.js';
|
|
3
4
|
|
|
4
5
|
import jsonTemplate from './contact-us-block-data.json';
|
|
5
6
|
|
|
@@ -20,7 +21,15 @@ class ContactUsBlock extends GHComponent {
|
|
|
20
21
|
this.manyPhones = Array.isArray(this.info.phone);
|
|
21
22
|
|
|
22
23
|
this.phones = this.manyPhones ? this.info.phone : new Array(this.info.phone);
|
|
23
|
-
|
|
24
|
+
|
|
25
|
+
this.customPhoneIcon = `<img src="${this.json.customPhoneIcon}" alt="phone icon" title="phone icon"></img>`
|
|
26
|
+
this.customMailIcon = `<img src="${this.json.customMailIcon}" alt="phone icon" title="mail icon"></img>`
|
|
27
|
+
this.customAddressIcon = `<img src="${this.json.customAddressIcon}" alt="phone icon" title="address icon"></img>`
|
|
28
|
+
|
|
29
|
+
this.phoneIcon = this.json.customPhoneIcon ? this.customPhoneIcon : defaultIcons.phone;
|
|
30
|
+
this.mailIcon = this.json.customMailIcon ? this.customMailIcon : defaultIcons.mail;
|
|
31
|
+
this.addressIcon = this.json.customAddressIcon ? this.customAddressIcon : defaultIcons.address;
|
|
32
|
+
|
|
24
33
|
super.render(html);
|
|
25
34
|
}
|
|
26
35
|
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
const defaultPhoneIcon = `<svg xmlns="http://www.w3.org/2000/svg" width="26" height="26" viewBox="0 0 26 26" fill="none"><g clip-path="url(#clip0_1428_29328)"><path d="M22.3204 17.1261C21.6107 16.4098 20.6649 16.0155 19.6566 16.0155C19.6556 16.0155 19.6545 16.0155 19.6535 16.0155C18.6442 16.0163 17.6978 16.4122 16.9885 17.1304L15.4698 18.668C11.8226 17.1394 8.90841 14.2224 7.38191 10.5773L8.93201 9.00779C10.3827 7.53899 10.3699 5.1619 8.90336 3.70885L5.36176 0.199707H5.05312C2.37882 0.199707 0.203125 2.3754 0.203125 5.0497C0.203125 7.85059 0.751874 10.5681 1.83402 13.1267C2.87917 15.5977 4.37522 17.8167 6.28061 19.7221C8.18601 21.6275 10.4051 23.1236 12.876 24.1687C15.4346 25.2509 18.1522 25.7997 20.9531 25.7997C23.6274 25.7997 25.8031 23.624 25.8031 20.9497V20.6411L22.3204 17.1261ZM20.9531 24.2997C10.3386 24.2997 1.70312 15.6642 1.70312 5.0497C1.70312 3.30205 3.04817 1.8627 4.75752 1.71265L7.84756 4.7743C8.72746 5.64615 8.73521 7.07239 7.86476 7.95369L5.63031 10.2161L5.80246 10.6671C7.46651 15.0268 10.9135 18.5018 15.2596 20.2011L15.8398 20.4279L18.0557 18.1844C18.4813 17.7535 19.0491 17.516 19.6547 17.5155C19.6554 17.5155 19.656 17.5155 19.6566 17.5155C20.2615 17.5155 20.829 17.7521 21.2548 18.1818L24.2902 21.2453C24.1401 22.9546 22.7007 24.2997 20.9531 24.2997Z" fill="white"/><path d="M22.0529 3.9488C19.6353 1.5312 16.4209 0.199707 13.002 0.199707V1.6997C19.2328 1.6997 24.3019 6.76884 24.3019 12.9997H25.8019C25.8019 9.58069 24.4705 6.36635 22.0529 3.9488Z" fill="white"/><path d="M13.002 4.69971V6.1997C16.7515 6.1997 19.8019 9.25015 19.8019 12.9997H21.3019C21.3019 8.42305 17.5786 4.69971 13.002 4.69971Z" fill="white"/></g><defs><clipPath id="clip0_1428_29328"><rect width="25.6" height="25.6" fill="white" transform="translate(0.199219 0.200195)"/></clipPath></defs></svg>`;
|
|
2
|
+
const defaultMailIcon = `<svg xmlns="http://www.w3.org/2000/svg" width="30" height="30" viewBox="0 0 30 30" fill="none"><g clip-path="url(#clip0_1428_29305)"><mask id="mask0_1428_29305" style="mask-type:luminance" maskUnits="userSpaceOnUse" x="0" y="0" width="30" height="30"><path d="M0.902344 0.919592H29.0623V29.0796H0.902344V0.919592Z" fill="white"/></mask><g mask="url(#mask0_1428_29305)"><path d="M1.72656 5.15522H28.2366V24.8452H1.72656V5.15522Z" stroke="white" stroke-width="1.5" stroke-miterlimit="10"/><path d="M1.7207 5.155L14.9757 17.75L28.2307 5.155" stroke="white" stroke-width="1.5" stroke-miterlimit="10"/><path d="M1.7207 24.8452L11.6517 14.5917" stroke="white" stroke-width="1.5" stroke-miterlimit="10"/><path d="M28.2278 24.8452L18.2969 14.5917" stroke="white" stroke-width="1.5" stroke-miterlimit="10"/></g></g><defs><clipPath id="clip0_1428_29305"><rect width="28.16" height="28.16" fill="white" transform="translate(0.919922 0.919922)"/></clipPath></defs></svg>`;
|
|
3
|
+
const defaultAddressIcon = `<svg xmlns="http://www.w3.org/2000/svg" width="24" height="29" viewBox="0 0 24 29" fill="none"><g clip-path="url(#clip0_1428_29403)"><path d="M12.0535 14.2955C10.984 14.2955 9.91434 13.8884 9.10029 13.0741C7.47178 11.4458 7.47178 8.79617 9.10029 7.16766C9.88898 6.37875 10.9379 5.94434 12.0535 5.94434C13.1692 5.94434 14.2179 6.37896 15.0068 7.16766C16.6353 8.79617 16.6353 11.4458 15.0068 13.0741C14.1925 13.8884 13.123 14.2955 12.0535 14.2955ZM12.0535 7.59756C11.4071 7.59756 10.7606 7.84355 10.2684 8.33576C9.28398 9.32018 9.28398 10.9218 10.2684 11.9062C10.7451 12.383 11.3791 12.6455 12.0535 12.6455C12.7279 12.6455 13.3619 12.383 13.8387 11.9062C14.8231 10.9218 14.8231 9.32018 13.8387 8.33576C13.3465 7.84355 12.7 7.59756 12.0535 7.59756Z" fill="white"/><path d="M12.0675 28.5799L11.4288 27.8297C11.0549 27.3903 2.26953 16.9919 2.26953 10.2082C2.26953 4.8109 6.66051 0.419922 12.0578 0.419922C17.4551 0.419922 21.8461 4.8109 21.8461 10.2082C21.8461 16.6582 13.0684 27.3673 12.6948 27.82L12.0675 28.5799ZM12.0578 2.07186C7.57145 2.07186 3.92146 5.72184 3.92146 10.2082C3.92146 12.7296 5.42494 16.3785 8.26947 20.7607C9.74566 23.0348 11.2322 24.9607 12.0488 25.9799C12.8667 24.9327 14.3674 22.9435 15.8528 20.6262C18.693 16.1959 20.1942 12.5932 20.1942 10.2082C20.1942 5.72184 16.5442 2.07186 12.0578 2.07186Z" fill="white"/></g><defs><clipPath id="clip0_1428_29403"><rect width="28.16" height="28.16" fill="white" transform="translate(-2.08008 0.419922)"/></clipPath></defs></svg>`;
|
|
4
|
+
|
|
5
|
+
export const defaultIcons = {
|
|
6
|
+
phone: defaultPhoneIcon,
|
|
7
|
+
mail: defaultMailIcon,
|
|
8
|
+
address: defaultAddressIcon,
|
|
9
|
+
}
|
|
@@ -1,13 +1,15 @@
|
|
|
1
1
|
<section class="get_in_touch_block">
|
|
2
2
|
<div class="container">
|
|
3
3
|
<div class="fet_in_touch_top">
|
|
4
|
-
|
|
4
|
+
${json.title ? `
|
|
5
|
+
<h2 gh-id="${ghId}.title">Lorem ipsum dolor sit amet consectetur?</h2>
|
|
6
|
+
` : ''}
|
|
5
7
|
${json.subtitle ? `
|
|
6
8
|
<p class="subtitle" gh-id="${ghId}.subtitle">Lorem ipsum dolor sit amet consectetur. Orci tempor bibendum sem.</p>
|
|
7
9
|
` : ''}
|
|
8
10
|
</div>
|
|
9
11
|
<div class="get_in_touch_bottom">
|
|
10
|
-
<get-in-touch-form
|
|
12
|
+
<get-in-touch-form ${getFormAttributes()}></get-in-touch-form>
|
|
11
13
|
</div>
|
|
12
14
|
</div>
|
|
13
15
|
</section>
|
|
@@ -7,15 +7,44 @@ class GetInTouchBlock extends GHComponent {
|
|
|
7
7
|
constructor() {
|
|
8
8
|
super();
|
|
9
9
|
super.setDefaultData(jsonTemplate);
|
|
10
|
+
|
|
11
|
+
this.getFormAttributes = this.getFormAttributes;
|
|
10
12
|
}
|
|
11
13
|
|
|
12
14
|
async onServerRender() {
|
|
13
|
-
|
|
14
15
|
this.ghId = this.getAttribute('data-gh-id') || null;
|
|
15
|
-
|
|
16
16
|
this.json = await super.getGhData(this.ghId);
|
|
17
17
|
|
|
18
|
-
|
|
18
|
+
this.getFormAttributes = this.getFormAttributes.bind(this);
|
|
19
|
+
|
|
20
|
+
super.render(html)
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
getFormAttributes() {
|
|
24
|
+
this.formId = this.hasAttribute('data-form-id') ? this.getAttribute('data-form-id') : 'main';
|
|
25
|
+
this.titleName = this.hasAttribute('data-form-title') ? this.getAttribute('data-form-title') : null;
|
|
26
|
+
this.subtitleName = this.hasAttribute('data-form-subtitle') ? this.getAttribute('data-form-subtitle') : null;
|
|
27
|
+
this.placement = this.hasAttribute('data-form-placement') ? this.getAttribute('data-form-placement') : "main";
|
|
28
|
+
this.buttonText = this.hasAttribute('data-form-button-text') ? this.getAttribute('data-form-button-text') : null;
|
|
29
|
+
|
|
30
|
+
const attributes = [{
|
|
31
|
+
'data-form-id': this.formId,
|
|
32
|
+
'data-form-title': this.titleName,
|
|
33
|
+
'data-form-subtitle': this.subtitleName,
|
|
34
|
+
'data-form-placement': this.placement,
|
|
35
|
+
'data-form-button-text': this.buttonText,
|
|
36
|
+
}];
|
|
37
|
+
|
|
38
|
+
const attributesString = attributes.reduce((acc, obj) => {
|
|
39
|
+
Object.entries(obj).forEach(([key, value]) => {
|
|
40
|
+
if (!!value) {
|
|
41
|
+
acc += `${key}="${value}"`;
|
|
42
|
+
}
|
|
43
|
+
});
|
|
44
|
+
return acc;
|
|
45
|
+
}, '');
|
|
46
|
+
|
|
47
|
+
return attributesString;
|
|
19
48
|
}
|
|
20
49
|
}
|
|
21
50
|
window.customElements.define('get-in-touch-block', GetInTouchBlock);
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
<div class="get-in-touch-form">
|
|
2
2
|
<form class="get-in-touch primary-block" action="#">
|
|
3
|
-
${
|
|
4
|
-
${
|
|
3
|
+
${titleName ? `<div class="popup_text like_title">${titleName}</div>` : ''}
|
|
4
|
+
${subtitleName ? `<div class="popup_text subtitle">${subtitleName}</div>` : ''}
|
|
5
5
|
|
|
6
6
|
<div class="form_wrap">
|
|
7
7
|
${generateInput(config)}
|
|
8
8
|
</div>
|
|
9
9
|
<div class="btn_wrapper">
|
|
10
|
-
<button type="submit" class="btn">${
|
|
10
|
+
<button type="submit" class="btn">${buttonText ? buttonText : 'Get in touch'}</button>
|
|
11
11
|
<div class="loader"></div>
|
|
12
12
|
</div>
|
|
13
13
|
</form>
|
|
@@ -21,14 +21,18 @@
|
|
|
21
21
|
<div class="bold">${config.titleSuccess ? config.titleSuccess : 'Successfull'}</div>
|
|
22
22
|
<div>${config.subtitleSuccess ? config.subtitleSuccess : 'Your form has been succesfully submitted! Please, check if info you provided is correct:'}</div>
|
|
23
23
|
<div class="check">
|
|
24
|
-
|
|
25
|
-
<
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
24
|
+
${config.inputs.find(input => input.name === "email") ? `
|
|
25
|
+
<div class="check_entity">
|
|
26
|
+
<span>Email:</span>
|
|
27
|
+
<span class="email"></span>
|
|
28
|
+
</div>
|
|
29
|
+
` : ''}
|
|
30
|
+
${config.inputs.find(input => input.name === "phone") ? `
|
|
31
|
+
<div class="check_entity phone_entity">
|
|
32
|
+
<span>Phone:</span>
|
|
33
|
+
<span class="phone"></span>
|
|
34
|
+
</div>
|
|
35
|
+
` : ''}
|
|
32
36
|
</div>
|
|
33
37
|
</div>
|
|
34
38
|
</div>
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import html from './get-in-touch-form.html';
|
|
2
2
|
import './get-in-touch-form.scss';
|
|
3
|
-
import { sendEmail } from './sendEmail.js';
|
|
4
3
|
import defaultConfigs from './get-in-touch-form-data.json';
|
|
4
|
+
import { validationCallbacks } from './validationCallbacks.js';
|
|
5
5
|
|
|
6
6
|
class GetInTouchForm extends GHComponent {
|
|
7
|
-
|
|
7
|
+
|
|
8
8
|
constructor() {
|
|
9
9
|
super();
|
|
10
10
|
this.formId = this.getAttribute("data-form-id");
|
|
@@ -12,8 +12,8 @@ class GetInTouchForm extends GHComponent {
|
|
|
12
12
|
|
|
13
13
|
this.generateInput = this.generateInput;
|
|
14
14
|
this.isFormSubmitted = false;
|
|
15
|
+
|
|
15
16
|
this.placement = 'main';
|
|
16
|
-
|
|
17
17
|
this.config = window.getConfig().componentsConfigs.formConfig;
|
|
18
18
|
}
|
|
19
19
|
|
|
@@ -21,7 +21,7 @@ class GetInTouchForm extends GHComponent {
|
|
|
21
21
|
if (this.hasAttribute('data-in-popup')) {
|
|
22
22
|
return;
|
|
23
23
|
}
|
|
24
|
-
|
|
24
|
+
|
|
25
25
|
this.initConfig(this.config);
|
|
26
26
|
super.render(html);
|
|
27
27
|
}
|
|
@@ -40,7 +40,7 @@ class GetInTouchForm extends GHComponent {
|
|
|
40
40
|
}
|
|
41
41
|
|
|
42
42
|
attachEventListeners() {
|
|
43
|
-
this.getElementsByTagName('form')[0].addEventListener('submit', (e) => this.handleSubmit(e
|
|
43
|
+
this.getElementsByTagName('form')[0].addEventListener('submit', (e) => this.handleSubmit(e));
|
|
44
44
|
this.getElementsByClassName('restart_button')[0].addEventListener('click', (e) => this.hideFail());
|
|
45
45
|
}
|
|
46
46
|
|
|
@@ -55,94 +55,106 @@ class GetInTouchForm extends GHComponent {
|
|
|
55
55
|
|
|
56
56
|
initConfig(formConfigs) {
|
|
57
57
|
try {
|
|
58
|
-
this.config = formConfigs.find(({id}) => id === this.formId);
|
|
58
|
+
this.config = formConfigs.find(({ id }) => id === this.formId);
|
|
59
59
|
if (!this.config) {
|
|
60
|
-
throw
|
|
60
|
+
throw new Error("Config not found");
|
|
61
61
|
}
|
|
62
62
|
} catch (error) {
|
|
63
63
|
const defaultId = this.isInPopup ? 'default popup' : 'default';
|
|
64
|
-
this.config = defaultConfigs.find(({id}) => id === defaultId);
|
|
64
|
+
this.config = defaultConfigs.find(({ id }) => id === defaultId);
|
|
65
65
|
}
|
|
66
|
-
}
|
|
67
66
|
|
|
68
|
-
|
|
69
|
-
|
|
67
|
+
this.titleName = this.hasAttribute('data-form-title') ? this.getAttribute('data-form-title') : this.config.title;
|
|
68
|
+
this.subtitleName = this.hasAttribute('data-form-subtitle') ? this.getAttribute('data-form-subtitle') : this.config.subtitle;
|
|
69
|
+
this.placement = this.hasAttribute('data-form-placement') ? this.getAttribute('data-form-placement') : "main";
|
|
70
|
+
this.buttonText = this.hasAttribute('data-form-button-text') ? this.getAttribute('data-form-button-text') : this.config.button_text;
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
checkInputsValidations = (inputs) => {
|
|
74
|
+
const result = inputs.map((input) => {
|
|
75
|
+
const validationCallback = validationCallbacks[input.name];
|
|
76
|
+
if (!validationCallback) return {
|
|
77
|
+
input,
|
|
78
|
+
isValid: true
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
return {
|
|
82
|
+
input,
|
|
83
|
+
isValid: validationCallback(input)
|
|
84
|
+
};
|
|
85
|
+
});
|
|
70
86
|
|
|
87
|
+
return result;
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
async handleSubmit(event) {
|
|
91
|
+
event.preventDefault();
|
|
92
|
+
const form = event.target;
|
|
93
|
+
|
|
94
|
+
const inputs = Array.from(form.querySelectorAll('input'));
|
|
71
95
|
const emailInput = this.querySelector('[name="email"]');
|
|
72
|
-
const email = emailInput ? emailInput.value : '';
|
|
73
|
-
|
|
74
96
|
const phoneInput = this.querySelector('[name="phone"]');
|
|
75
|
-
const phone = phoneInput ? phoneInput.value || '' : '';
|
|
76
|
-
|
|
77
|
-
const isValidFields = this.validation(email, phone);
|
|
78
97
|
|
|
79
|
-
|
|
80
|
-
this.addLoader();
|
|
81
|
-
|
|
82
|
-
const TIMEOUT_DURATION = 5000;
|
|
98
|
+
const validationResults = this.checkInputsValidations(inputs);
|
|
83
99
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
100
|
+
if (validationResults.every((res) => res.isValid)) {
|
|
101
|
+
this.addLoader();
|
|
102
|
+
try {
|
|
103
|
+
const res = await new Promise((resolve, reject) => {
|
|
104
|
+
const TIMEOUT_DURATION = 2000;
|
|
105
|
+
|
|
106
|
+
setTimeout(() => {
|
|
107
|
+
const isSuccess = true;
|
|
108
|
+
if (isSuccess) {
|
|
109
|
+
resolve(true);
|
|
110
|
+
} else {
|
|
111
|
+
reject(new Error('Failed to send email'));
|
|
112
|
+
}
|
|
88
113
|
}, TIMEOUT_DURATION);
|
|
89
|
-
|
|
90
|
-
sendEmail(element, config, placement)
|
|
91
|
-
.then((res) => {
|
|
92
|
-
clearTimeout(timer);
|
|
93
|
-
resolve(res);
|
|
94
|
-
})
|
|
95
|
-
.catch((error) => {
|
|
96
|
-
clearTimeout(timer);
|
|
97
|
-
reject(error);
|
|
98
|
-
});
|
|
99
114
|
});
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
const res = await sendEmailWithTimeout(element, this.config, this.placement);
|
|
104
|
-
this.removeLoader(element);
|
|
115
|
+
|
|
116
|
+
this.removeLoader(form);
|
|
117
|
+
|
|
105
118
|
if (res) {
|
|
106
|
-
this.showSuccess({ email, phone });
|
|
119
|
+
this.showSuccess({ email: emailInput ? emailInput.value : '', phone: phoneInput ? phoneInput.value : '' });
|
|
107
120
|
} else {
|
|
108
121
|
this.showFail();
|
|
109
122
|
}
|
|
110
123
|
} catch (error) {
|
|
111
|
-
this.removeLoader(
|
|
124
|
+
this.removeLoader(form);
|
|
112
125
|
this.showFail();
|
|
113
126
|
}
|
|
114
127
|
this.isFormSubmitted = true;
|
|
115
|
-
|
|
116
128
|
} else {
|
|
117
|
-
|
|
129
|
+
const validationResultsForErrors = validationResults.filter(item => typeof item === 'object')
|
|
130
|
+
validationResultsForErrors.forEach(({ input, isValid }) => this.toggleError(input, isValid));
|
|
118
131
|
}
|
|
119
|
-
}
|
|
120
132
|
|
|
121
|
-
|
|
122
|
-
isValidFields.emailValid ? emailInput.classList.remove('error') : emailInput.classList.add('error');
|
|
123
|
-
|
|
124
|
-
isValidFields.phoneValid ? phoneInput.classList.remove('error') : phoneInput.classList.add('error');
|
|
133
|
+
this.createDataObject(form, this.config, this.placement)
|
|
125
134
|
}
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
const
|
|
129
|
-
const
|
|
130
|
-
|
|
131
|
-
let emailValid;
|
|
132
|
-
if (email.length === 0 && isEmailRequired == 'false') {
|
|
133
|
-
emailValid = true;
|
|
134
|
-
} else {
|
|
135
|
-
emailValid = /\S+@\S+\.\S+/.test(email);
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
let phoneValid;
|
|
139
|
-
if (phone.length === 0 && isPhoneRequired == 'false') {
|
|
140
|
-
phoneValid = true;
|
|
141
|
-
} else {
|
|
142
|
-
phoneValid = /^\+?[\d()-\s]+$/.test(phone);
|
|
135
|
+
|
|
136
|
+
async createDataObject(form, formId, placement) {
|
|
137
|
+
const formData = {};
|
|
138
|
+
for (const [name, value] of (new FormData(form)).entries()) {
|
|
139
|
+
formData[name] = value;
|
|
143
140
|
}
|
|
144
141
|
|
|
145
|
-
|
|
142
|
+
const formDataObj = {
|
|
143
|
+
Website: window.location.hostname,
|
|
144
|
+
Url: window.location.pathname,
|
|
145
|
+
FormId: formId,
|
|
146
|
+
FormPlacement: placement,
|
|
147
|
+
FormData: {
|
|
148
|
+
...formData
|
|
149
|
+
},
|
|
150
|
+
Referrer: localStorage.getItem('referrer'),
|
|
151
|
+
};
|
|
152
|
+
|
|
153
|
+
window.dispatchEvent(new CustomEvent('submitForm', { detail: { formDataObj } }));
|
|
154
|
+
}
|
|
155
|
+
toggleError(input, isValid) {
|
|
156
|
+
input.classList[isValid ? 'remove' : 'add']('error');
|
|
157
|
+
input.parentElement.classList[isValid ? 'remove' : 'add']('error-input');
|
|
146
158
|
}
|
|
147
159
|
async addLoader() {
|
|
148
160
|
this.classList.add('loading');
|
|
@@ -156,7 +168,11 @@ class GetInTouchForm extends GHComponent {
|
|
|
156
168
|
}, 500);
|
|
157
169
|
}
|
|
158
170
|
async showSuccess({email, phone}) {
|
|
159
|
-
|
|
171
|
+
if (email) {
|
|
172
|
+
this.querySelector('.check_entity').classList.add('provided');
|
|
173
|
+
this.getElementsByClassName('email')[0].innerText = email;
|
|
174
|
+
}
|
|
175
|
+
this.classList.add('success');
|
|
160
176
|
if (phone) {
|
|
161
177
|
this.querySelector('.check_entity.phone_entity').classList.add('provided');
|
|
162
178
|
this.getElementsByClassName('phone')[0].innerText = phone;
|
|
@@ -180,7 +196,6 @@ class GetInTouchForm extends GHComponent {
|
|
|
180
196
|
overflowFail.style.opacity = '';
|
|
181
197
|
}, 500);
|
|
182
198
|
}
|
|
183
|
-
|
|
184
199
|
generateInput(config) {
|
|
185
200
|
return config.inputs.reduce((acc, input) => {
|
|
186
201
|
const maxSymbols = {
|
|
@@ -194,13 +209,14 @@ class GetInTouchForm extends GHComponent {
|
|
|
194
209
|
const maxLength = tag === 'textarea' ? maxSymbols.long : maxSymbols[input.type];
|
|
195
210
|
return acc + `
|
|
196
211
|
<div class="input-wrap col-${input.width}">
|
|
197
|
-
<${tag} type="text" name=${input.name} placeholder="${input.placeholder}" ${JSON.parse(input.required) ? 'required' : ''} ${
|
|
212
|
+
<${tag} type="text" name=${input.name} placeholder="${input.placeholder}" ${JSON.parse(input.required) ? 'required' : ''} ${maxLength ? `maxlength=${maxLength}` : ''}></${tag}>
|
|
198
213
|
${input.type === 'email' || input.type === 'phone' ? `<span class="${input.type}-error">${input.errorText}</span>` : ''}
|
|
199
214
|
</div>
|
|
200
|
-
|
|
215
|
+
`;
|
|
216
|
+
}, '');
|
|
201
217
|
}
|
|
202
218
|
}
|
|
203
219
|
|
|
204
|
-
if(!customElements.get('get-in-touch-form')) {
|
|
220
|
+
if (!customElements.get('get-in-touch-form')) {
|
|
205
221
|
customElements.define('get-in-touch-form', GetInTouchForm);
|
|
206
222
|
}
|
|
@@ -1,18 +1,24 @@
|
|
|
1
|
-
# Config:
|
|
1
|
+
# Config:
|
|
2
|
+
|
|
2
3
|
The form field settings are defined in the site's config.mjs, when rendered on the server it is stored in "window.getConfig().componentsConfigs.form_config", and this config is also defined in the client's "window.getConfig().componentsConfigs.formConfig" and is used when rendering the form on the client. If the config in "window" is not available, then the config is taken from "get-in-touch-form-data.json". There can be several settings for forms, the form selects it by id (it is defined in the "data-form-id" attribute of the form component), this allows you to customize the fields of several forms in different ways (for example, on the page the form has all the fields, and in the popup the form has other types of fields)
|
|
3
4
|
|
|
4
5
|
# Form in popup:
|
|
6
|
+
|
|
5
7
|
if form will be rendered in popup we need to define attribute "data-in-popup", that will change some styles of form to fit the popup styles.
|
|
6
8
|
|
|
7
9
|
# Placement:
|
|
10
|
+
|
|
8
11
|
The "placement" variable is needed to track conversions. If the form is in a popup, then "placement" determined by the button that opened the popup with the form. If the form is already on the page, this value is defined in the form constructor (currently "this.placement = 'main' ").
|
|
9
12
|
|
|
10
13
|
# Data-attributes:
|
|
14
|
+
|
|
11
15
|
data-in-popup: if form is on popup
|
|
12
16
|
data-form-id="form-id": determine id of config that will be applyed to form
|
|
13
17
|
|
|
14
|
-
# Config object:
|
|
18
|
+
# Config object:
|
|
19
|
+
|
|
15
20
|
("?" means "unnecessary")
|
|
21
|
+
|
|
16
22
|
```json
|
|
17
23
|
{
|
|
18
24
|
"id": "string",
|
|
@@ -26,6 +32,7 @@ data-form-id="form-id": determine id of config that will be applyed to form
|
|
|
26
32
|
"from": "string",
|
|
27
33
|
"subject": "string"
|
|
28
34
|
},
|
|
35
|
+
"endpointForEmails": "some fancy url for gudhub API",
|
|
29
36
|
"inputs": [
|
|
30
37
|
{
|
|
31
38
|
"name": "string",
|
|
@@ -37,7 +44,9 @@ data-form-id="form-id": determine id of config that will be applyed to form
|
|
|
37
44
|
]
|
|
38
45
|
},
|
|
39
46
|
```
|
|
47
|
+
|
|
40
48
|
## Types description:
|
|
49
|
+
|
|
41
50
|
email: will be checked by email rules;
|
|
42
51
|
phone: will be checked by phone number rules;
|
|
43
52
|
short: max length 64 symbols;
|
|
@@ -45,4 +54,50 @@ long: max length 128 symbols;
|
|
|
45
54
|
textarea: tag "textarea";
|
|
46
55
|
|
|
47
56
|
## Width:
|
|
48
|
-
|
|
57
|
+
|
|
58
|
+
defines the width of the field in the row. The total width of the row is 12. That is, if 2 fields have a width of 6, then they will take up half of the line each
|
|
59
|
+
|
|
60
|
+
## Correct way to handle submit in your project
|
|
61
|
+
|
|
62
|
+
```js
|
|
63
|
+
// by default this event handled in your main.js
|
|
64
|
+
window.addEventListener("submitForm", async (event) => {
|
|
65
|
+
const { formDataObj } = event.detail;
|
|
66
|
+
|
|
67
|
+
// add other code here
|
|
68
|
+
});
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
### What data contains in formDataObj
|
|
72
|
+
|
|
73
|
+
```js
|
|
74
|
+
{
|
|
75
|
+
FormData :
|
|
76
|
+
// all data from your form inputs on website
|
|
77
|
+
name : "name"
|
|
78
|
+
phone: "phhone"
|
|
79
|
+
email: 'email'
|
|
80
|
+
FormId:
|
|
81
|
+
// data from your form-config
|
|
82
|
+
button_text:"text"
|
|
83
|
+
defaultLang: "true or false"
|
|
84
|
+
id: "text"
|
|
85
|
+
inputs: Array()
|
|
86
|
+
0: {name: 'name', type: 'type', required: 'required', placeholder: "placeholder", width: 12}
|
|
87
|
+
1: {name: 'name', type: 'type', required: 'required', placeholder: 'placeholder *', errorText: 'errorText', …}
|
|
88
|
+
langCode: "text"
|
|
89
|
+
mailConfig:
|
|
90
|
+
from: "from"
|
|
91
|
+
subject: "subject"
|
|
92
|
+
to: "to"
|
|
93
|
+
subtitle: "subtitle"
|
|
94
|
+
subtitleSuccess: "subtitleSuccess"
|
|
95
|
+
title: "title"
|
|
96
|
+
titleFail:"titleFail"
|
|
97
|
+
titleSuccess: "titleSuccess"
|
|
98
|
+
FormPlacement: "FormPlacement"
|
|
99
|
+
Referrer: "Referrer"
|
|
100
|
+
Url: "/"
|
|
101
|
+
Website: "Website"
|
|
102
|
+
}
|
|
103
|
+
```
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
const emailValidation = (input) => {
|
|
2
|
+
const regex = /^[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+@[a-zA-Z0-9-]+(?:\.[a-zA-Z0-9-]+)*$/;
|
|
3
|
+
|
|
4
|
+
let isValid = regex.test(input.value);
|
|
5
|
+
|
|
6
|
+
if (!isValid) {
|
|
7
|
+
input.classList.add('error');
|
|
8
|
+
input.parentElement.classList.add('error-input');
|
|
9
|
+
} else {
|
|
10
|
+
input.classList.remove('error');
|
|
11
|
+
input.parentElement.classList.remove('error-input');
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
return isValid;
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
const phoneValidation = (input) => {
|
|
18
|
+
const regex = /^\d{10}$/;
|
|
19
|
+
|
|
20
|
+
let isValid = regex.test(input.value);
|
|
21
|
+
|
|
22
|
+
if (!isValid) {
|
|
23
|
+
input.classList.add('error');
|
|
24
|
+
input.parentElement.classList.add('error-input');
|
|
25
|
+
} else {
|
|
26
|
+
input.classList.remove('error');
|
|
27
|
+
input.parentElement.classList.remove('error-input');
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
return isValid;
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
export const validationCallbacks = {
|
|
34
|
+
email: emailValidation,
|
|
35
|
+
phone: phoneValidation
|
|
36
|
+
};
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
<div class="primary-block">
|
|
2
2
|
<div class="expandable-wrapper">
|
|
3
3
|
<div class="content">
|
|
4
|
-
|
|
4
|
+
<div class="title">
|
|
5
|
+
${json.title ? `<${titleTag} class="title-text" gh-id="${ghId}.title"></${titleTag}>` : ''}
|
|
6
|
+
</div>
|
|
5
7
|
${json.text ? `<div><p class="text" gh-id="${ghId}.text"></p></div>` : ''}
|
|
6
8
|
</div>
|
|
7
9
|
<div class="bottom">
|
|
@@ -11,7 +11,8 @@ class GridItemExpandableVertical extends GHComponent {
|
|
|
11
11
|
this.generalGhId = this.getAttribute('data-gh-id') || null;
|
|
12
12
|
this.ghId = `${this.generalGhId}.items.${this.itemIndex}`;
|
|
13
13
|
this.generalJson = await super.getGhData(this.generalGhId);
|
|
14
|
-
this.json = this.generalJson ? this.generalJson
|
|
14
|
+
this.json = this.generalJson ? this.generalJson?.items[this.itemIndex] : null;
|
|
15
|
+
this.titleTag = this.json?.titleLink ? `a href=${this.json.titleLink}`: `h3`;
|
|
15
16
|
|
|
16
17
|
if (this.ghId && this.generalJson) {
|
|
17
18
|
super.render(html);
|
|
@@ -29,8 +29,10 @@ grid-item-expandable-vertical {
|
|
|
29
29
|
bottom: auto;
|
|
30
30
|
z-index: 3;
|
|
31
31
|
background-color: var(--block-primary-bg-hover-color);
|
|
32
|
-
.title {
|
|
32
|
+
.title-text {
|
|
33
33
|
color: var(--block-primary-accent-color);
|
|
34
|
+
font-size: var(--h3-font-size);
|
|
35
|
+
font-weight: var(--h3-font-weight);
|
|
34
36
|
}
|
|
35
37
|
.text {
|
|
36
38
|
max-height: 500px;
|
|
@@ -60,9 +62,12 @@ grid-item-expandable-vertical {
|
|
|
60
62
|
.content {
|
|
61
63
|
display: flex;
|
|
62
64
|
flex-direction: column;
|
|
63
|
-
.title {
|
|
65
|
+
.title-text {
|
|
64
66
|
transition: color var(--transition-duration) ease;
|
|
65
67
|
margin-bottom: 15px;
|
|
68
|
+
font-size: var(--h3-font-size);
|
|
69
|
+
font-weight: var(--h3-font-weight);
|
|
70
|
+
color: var(--h-color);
|
|
66
71
|
}
|
|
67
72
|
.text {
|
|
68
73
|
height: 100%;
|
|
@@ -103,7 +108,7 @@ grid-item-expandable-vertical {
|
|
|
103
108
|
}
|
|
104
109
|
}
|
|
105
110
|
&:hover {
|
|
106
|
-
.title {
|
|
111
|
+
.title-text {
|
|
107
112
|
color: var(--block-primary-accent-color);
|
|
108
113
|
}
|
|
109
114
|
.icon {
|
|
@@ -53,7 +53,6 @@ class MetaTag extends GHComponent {
|
|
|
53
53
|
let ids = await super.findIds('blog');
|
|
54
54
|
await this.addTag(ids.appId, ids.itemId, `/blog/${category}/${article}/`, 'blog');
|
|
55
55
|
} else {
|
|
56
|
-
debugger;
|
|
57
56
|
const getCurrentChapter = await window?.getCurrentChapter();
|
|
58
57
|
const currentChapter = getCurrentChapter ? getCurrentChapter : 'pages';
|
|
59
58
|
|
|
@@ -65,7 +64,6 @@ class MetaTag extends GHComponent {
|
|
|
65
64
|
|
|
66
65
|
}
|
|
67
66
|
async addTag (appId, itemId, slug, chapter) {
|
|
68
|
-
debugger;
|
|
69
67
|
const app = await gudhub.getApp(appId);
|
|
70
68
|
const items = app.items_list;
|
|
71
69
|
let item;
|
|
@@ -1,152 +0,0 @@
|
|
|
1
|
-
export async function sendEmail(form, formConfig, placement) {
|
|
2
|
-
if (validate(form)) {
|
|
3
|
-
|
|
4
|
-
const response = await sendRequest(form, formConfig.mailConfig, formConfig.id, placement);
|
|
5
|
-
if (response.status == 'success') {
|
|
6
|
-
return true;
|
|
7
|
-
} else {
|
|
8
|
-
if (response.error) {
|
|
9
|
-
console.error(response.error)
|
|
10
|
-
}
|
|
11
|
-
return false;
|
|
12
|
-
}
|
|
13
|
-
}
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
async function sendRequest(form, mailConfig, formId, placement) {
|
|
17
|
-
const formData = {};
|
|
18
|
-
for (const [name, value] of (new FormData(form)).entries()) {
|
|
19
|
-
formData[name] = value;
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
const searchParams = getQueryParams();
|
|
23
|
-
|
|
24
|
-
const formDataObj = {
|
|
25
|
-
Name: formData.name,
|
|
26
|
-
Phone: formData.phone,
|
|
27
|
-
Email: formData.email,
|
|
28
|
-
Companty: formData.company,
|
|
29
|
-
Message: formData.message,
|
|
30
|
-
Website: window.location.hostname,
|
|
31
|
-
Url: window.location.pathname,
|
|
32
|
-
FormId: formId,
|
|
33
|
-
FormPlacement: placement,
|
|
34
|
-
SearchParams: {
|
|
35
|
-
...searchParams
|
|
36
|
-
},
|
|
37
|
-
Referrer: localStorage.getItem('referrer'),
|
|
38
|
-
};
|
|
39
|
-
|
|
40
|
-
window.dispatchEvent(new CustomEvent('submitForm', {detail: {formDataObj}}));
|
|
41
|
-
|
|
42
|
-
const template = {
|
|
43
|
-
...mailConfig,
|
|
44
|
-
replyTo: formData.email ? formData.email : 'none',
|
|
45
|
-
html: "<!DOCTYPE html>" +
|
|
46
|
-
"<html>" +
|
|
47
|
-
"<head>" +
|
|
48
|
-
`<title>${mailConfig.subject}</title>` +
|
|
49
|
-
"</head>" +
|
|
50
|
-
"<body>" +
|
|
51
|
-
"<h3>" +
|
|
52
|
-
`${mailConfig.subject}` +
|
|
53
|
-
"</h3>" +
|
|
54
|
-
"<ul>" +
|
|
55
|
-
"<li><strong>" +
|
|
56
|
-
"formId: </strong>" + `${formId}` +
|
|
57
|
-
"</li>" +
|
|
58
|
-
"<li><strong>" +
|
|
59
|
-
"placement: </strong>" + `${placement}` +
|
|
60
|
-
"</li>" +
|
|
61
|
-
`${Object.entries(formData).reduce((acc, [name, value]) => acc + `<li><strong>${name}: </strong>${value}</li>`, '')}` +
|
|
62
|
-
"<li> <strong>" +
|
|
63
|
-
"Url: </strong>" + window.location.pathname +
|
|
64
|
-
"</li>" +
|
|
65
|
-
"</ul>" +
|
|
66
|
-
"</body>" +
|
|
67
|
-
"</html>"
|
|
68
|
-
};
|
|
69
|
-
|
|
70
|
-
try {
|
|
71
|
-
const response = await fetch('https://gudhub.com/api/services/prod/send-email', {
|
|
72
|
-
method: 'POST',
|
|
73
|
-
body: JSON.stringify(template),
|
|
74
|
-
headers: {
|
|
75
|
-
'Content-Type': 'application/json'
|
|
76
|
-
},
|
|
77
|
-
})
|
|
78
|
-
if (response.status == 200) {
|
|
79
|
-
return {
|
|
80
|
-
status: 'success'
|
|
81
|
-
}
|
|
82
|
-
} else {
|
|
83
|
-
return {
|
|
84
|
-
status: 'error'
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
|
-
} catch (error) {
|
|
88
|
-
return {
|
|
89
|
-
status: 'error',
|
|
90
|
-
error
|
|
91
|
-
}
|
|
92
|
-
}
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
function getQueryParams() {
|
|
96
|
-
var url = window.location.search.substring(1);
|
|
97
|
-
var queryParamsArray = url.split("&");
|
|
98
|
-
if (queryParamsArray.length === 1 && queryParamsArray[0] === '') {
|
|
99
|
-
return {};
|
|
100
|
-
}
|
|
101
|
-
var queryParams = {};
|
|
102
|
-
for (var i = 0; i < queryParamsArray.length; i++) {
|
|
103
|
-
var param = queryParamsArray[i].split("=");
|
|
104
|
-
queryParams[param[0]] = decodeURIComponent(param[1]);
|
|
105
|
-
}
|
|
106
|
-
return queryParams;
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
function validate(form) {
|
|
110
|
-
const emailInput = form.querySelector('input[name="email"]');
|
|
111
|
-
const email = emailInput.value;
|
|
112
|
-
const phoneInput = form.querySelector('input[name="phone"]');
|
|
113
|
-
let errorEmail = false;
|
|
114
|
-
let errorPhone = false;
|
|
115
|
-
|
|
116
|
-
let emailRegex = /^[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+@[a-zA-Z0-9-]+(?:\.[a-zA-Z0-9-]+)*$/
|
|
117
|
-
if (!email.match(emailRegex)) {
|
|
118
|
-
emailInput.classList.add('error');
|
|
119
|
-
emailInput.parentElement.classList.add('error-input');
|
|
120
|
-
errorEmail = true;
|
|
121
|
-
} else {
|
|
122
|
-
emailInput.classList.remove('error');
|
|
123
|
-
emailInput.parentElement.classList.remove('error-input');
|
|
124
|
-
errorEmail = false;
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
if (phoneInput && phoneInput.value) {
|
|
128
|
-
const phone = phoneInput.value;
|
|
129
|
-
let phoneRegex = /^[0-9 ()+-]+$/;
|
|
130
|
-
if (!phone.match(phoneRegex)) {
|
|
131
|
-
phoneInput.classList.add('error');
|
|
132
|
-
phoneInput.parentElement.classList.add('error-input');
|
|
133
|
-
errorPhone = true;
|
|
134
|
-
} else {
|
|
135
|
-
phoneInput.classList.remove('error');
|
|
136
|
-
phoneInput.parentElement.classList.remove('error-input');
|
|
137
|
-
errorPhone = false;
|
|
138
|
-
}
|
|
139
|
-
}
|
|
140
|
-
if (errorPhone || errorEmail) {
|
|
141
|
-
return false;
|
|
142
|
-
} else {
|
|
143
|
-
let errors = document.querySelectorAll('.error-input');
|
|
144
|
-
if (errors.length) {
|
|
145
|
-
for (let error in errors) {
|
|
146
|
-
errors[error].classList.remove('error-input');
|
|
147
|
-
}
|
|
148
|
-
}
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
return true;
|
|
152
|
-
}
|