1const defineHost = function() { 2 const host = window.location.hostname; 3 const regex = /^dev\d{2}$/; 4 const splitted = host.split('.'); 5 if (splitted.length === 2) { 6 return host; 7 } 8 9 const index = splitted.indexOf('netpeak'); 10 if (index === -1) { 11 return host; 12 } 13 14 const requestHostPath = splitted.slice(index).join('.'); 15 16 if (regex.test(splitted[0])) { 17 return splitted[0] + '.' + requestHostPath; 18 } 19 20 return requestHostPath; 21} 22 23const host = defineHost(); 24const IS_DROPDOWN_INPUT_MASK = window.intlTelInput; 25let validationMessage = []; 26 27const NAME_BASE_REGEXP = /^(?=.{1,40}$)(?=.*\p{L})[\p{L}\s.'âʼ\-]+$/u; 28function validateName(name) { 29 if (!NAME_BASE_REGEXP.test(name)) return false; 30 31 const words = name.trim().split(/\s+/); 32 33 let longWords = 0; 34 let shortWords = 0; 35 36 for(const word of words) { 37 // пеÑевÑÑка на апоÑÑÑÐ¾Ñ Ñ Ð´ÐµÑÑÑ Ð²ÑеÑÐµÐ´Ð¸Ð½Ñ Ñлова 38 // пеÑевÑÑка на кÑÐ°Ð¿ÐºÑ Ð² кÑнÑÑ Ñлова 39 if (!/^[\p{L}]+([âʼ']\p{L}+)*(-\p{L}+)*\.?$/u.test(word)) { 40 return false; 41 } 42 43 if (word.endsWith('.')) { 44 const clean = word.slice(0, -1); 45 if (clean.length > 3) return false; 46 } 47 48 const cleanLength = word.replace(/[.âʼ']/g, '').length; 49 50 if (cleanLength > 3) longWords++; 51 else shortWords++; 52 } 53 54 if (longWords > 3) return false; 55 if (shortWords > 3) return false; 56 57 return true; 58} 59let validationMap = { 60 'email': /^[^@]+@[^@]+$/, 61 'name': validateName, 62 'phone': /[0-9]/, 63 'service': /^.+$/ 64}; 65 66if (IS_DROPDOWN_INPUT_MASK) { 67 validationMap.phone = /^[\d+\-\s]{7,14}$/ 68} 69 70const appendError = function (form, field, errorText) { 71 const isPhoneInput = field === 'phone'; 72 const fieldSelector = form.find(`[name=${field}]`); 73 const parentFieldSelector = fieldSelector.parent(); 74 const errorContainer = `<div class="ajax-error-container has-error-border-color has-error-color">${errorText}</div>`; 75 76 const errorExist = isPhoneInput && IS_DROPDOWN_INPUT_MASK 77 ? parentFieldSelector.next('.ajax-error-container').length 78 : fieldSelector.next('.ajax-error-container').length; 79 80 if (!errorExist) { 81 if (isPhoneInput && IS_DROPDOWN_INPUT_MASK) { 82 parentFieldSelector.after(errorContainer); 83 } else { 84 fieldSelector.after(errorContainer); 85 } 86 } 87 88 fieldSelector.addClass('has-error-border-color'); 89 (isPhoneInput && IS_DROPDOWN_INPUT_MASK ? parentFieldSelector.prev() : fieldSelector.prev()).addClass('has-error-color'); 90 91 window.formData = {currentForm: $(form)} 92 let eventData = { 93 event: 'Field_Sent_Error', 94 deviceID: getDeviceID(), 95 formType: getFormType(), 96 FormLocation: getFormLocation(), 97 errorText: errorText.replaceAll('<br>', '\n') 98 }; 99 100 window.dataLayer.push(eventData) 101} 102 103const removeError = function (form, field) { 104 let errorMessage; 105 const isPhoneInput = field === 'phone'; 106 if (isPhoneInput && IS_DROPDOWN_INPUT_MASK) { 107 errorMessage = form.find(`[name=${field}]`).parent().next('.ajax-error-container'); 108 form.find(`[name=${field}]`).removeClass('has-error-border-color'); 109 form.find(`[name=${field}]`).parent().prev().removeClass('has-error-color'); 110 } else { 111 errorMessage = form.find(`[name=${field}]`).next('.ajax-error-container'); 112 form.find(`[name=${field}]`).removeClass('has-error-border-color'); 113 form.find(`[name=${field}]`).prev().removeClass('has-error-color'); 114 } 115 116 if (errorMessage.length) { 117 errorMessage.remove(); 118 } 119} 120 121/** 122 * ÐолÑÑение кнопки ÑоÑÐ¼Ñ 123 */ 124function getSubmitButton(input) 125{ 126 let form = input.parent().parent(); 127 return form.find('input[type="submit"]'); 128 } 129 130const COUNTRY_CODES = { 131 us: 'US', 132 ua: 'UA', 133 kz: 'KZ', 134 uk: 'GB', 135 bg: 'BG', 136 gr: 'GR', 137 cy: 'CY' 138} 139function appendDropdownInputMask() { 140 const hostParts = window.location.host.split('.'); 141 const topLevelDomain = hostParts[hostParts.length - 1]; 142 const phoneSelector = 'div[data-submition-type="zapier"] form input[name=phone]' 143 .concat(', form[data-submition-type="zapier"] input[name=phone]')
144 .concat(', div[data-submition-type="ajax"] form input[name=phone]') 145 .concat(', form[data-submition-type="ajax"] input[name=phone]') 146 const inputs = Array.from(document.querySelectorAll(phoneSelector)); 147 148 inputs.forEach(input=>{ 149 $(input).after(`<input type="hidden" name="Full phone" class="full_phone">`); 150 const ipLookup = input.hasAttribute('data-ip-lookup'); 151 const initialCountry = $(input).attr('data-initial-country'); 152 const countryCode = initialCountry ? 'auto' : COUNTRY_CODES[topLevelDomain]; 153 154 const inputWithMask = window.intlTelInput(input, { 155 initialCountry: countryCode, 156 geoIpLookup: (success, failure) => { 157 if (!ipLookup && initialCountry) { 158 success(initialCountry); 159 return; 160 } 161 fetch("https://ipapi.co/json").then( (res) => res.json()).then( (data) => { 162 success(data.country_code) 163 }).catch( () => { 164 if (initialCountry) { 165 success(initialCountry); 166 return; 167 } 168 failure(); 169 }); 170 }, 171 separateDialCode: true, 172 strictMode: true, 173 countrySearch: true, 174 excludeCountries: ["RU"], 175 fixWidth: true, 176 useFullscreenPopup: false, 177 loadUtils: ()=>import("https://cdn.jsdelivr.net/npm/[email protected]/build/js/utils.js"), 178 }); 179 const form = $(input).parent().parent(); 180 if (form.attr('id') === 'form-TG-ads') { 181 form.find('.iti__tel-input').addClass('black-placeholder'); 182 } 183 input.addEventListener('change', function() { 184 const fullPhone = inputWithMask.getNumber() 185 form.find('.full_phone').val(fullPhone.replaceAll(" ", "")) 186 }) 187 const wrapper = input.closest('.iti'); 188 let removedList = null; 189 let originalParent = null; 190 191 const existingList = wrapper.querySelector('.iti__country-list'); 192 if (existingList) { 193 originalParent = existingList.parentNode; 194 removedList = originalParent.removeChild(existingList); 195 } 196 197 wrapper.querySelector('.iti__selected-country').addEventListener('click', () => { 198 if (removedList && originalParent && !originalParent.contains(removedList)) { 199 originalParent.appendChild(removedList); 200 } 201 }) 202 }); 203} 204 205function appendDefaultInputMask() { 206 let phoneInputs = $('input[name="phone"]'); 207 if (typeof ($.masksLoad) === 'function') { 208 var maskList = $.masksSort($.masksLoad('https://' + host + '/js/phone-codes.json'), ['#'], /[0-9]|#/, 'mask'); 209 var maskOpts = { 210 inputmask: { 211 oncomplete: function (event) { 212 $(event.currentTarget).removeClass('has-error-border-color'); 213 $(event.currentTarget).prev().removeClass('has-error-color'); 214 let errorMessage = $(event.currentTarget).next('.ajax-error-container'); 215 if (errorMessage.length) { 216 errorMessage.remove(); 217 } 218 let submitButton = getSubmitButton($(event.currentTarget)); 219 submitButton.attr("disabled", false); 220 }, 221 onincomplete: function (event) { 222 let inputValue = $(event.currentTarget).val() 223 if (inputValue === '' || inputValue === $(event.currentTarget).placeholder) { 224 return; 225 } 226 $(event.currentTarget).addClass('has-error-border-color'); 227 $(event.currentTarget).prev().addClass('has-error-color'); 228 if (!$(event.currentTarget).next('.ajax-error-container').length) { 229 $(event.currentTarget).after( 230 `<div class="ajax-error-container has-error-border-color has-error-color">${validationMessage['phone'].join('<br>')}</div>` 231 ); 232 } 233 let submitButton = getSubmitButton($(event.currentTarget)); 234 window.formData = {currentForm: $(submitButton[0].form)}; 235 let eventData = { 236 event: 'Field_Sent_Error', 237 deviceID: getDeviceID(), 238 formType: getFormType(), 239 FormLocation: getFormLocation(), 240 errorText: validationMessage[$(event.target).attr('name')].join('\n') 241 }; 242 window.dataLayer.push(eventData); 243 }, 244 definitions: { 245 '#': { 246 validator: "[0-9]", 247 cardinality: 1 248 } 249 }, 250 showMaskOnHover: false, 251 autoUnmask: true 252 }, 253 match: /[0-9]/, 254 replace: '#', 255 list: maskList, 256 listKey: 'mask', 257 onMaskChange: function (maskObj, completed) { 258 $(this).attr("placeholder", $(this).inputmask("getemptymask")); 259 } 260 }; 261 262 phoneInputs.each((phoneInputIndex, phoneInput) => { 263 $(phoneInput).inputmasks(maskOpts); 264 }) 265 } 266} 267 268$(document).ready(function () { 269 if (IS_DROPDOWN_INPUT_MASK) { 270 appendDropdownInputMask(); 271 } else { 272 appendDefaultInputMask(); 273 } 274 275 $.ajax({ 276 method: 'GET',
277 url: 'https://' + host + '/api_webflow/load_validation_map/', 278 xhrFields: { 279 withCredentials: true 280 }, 281 crossDomain: true, 282 dataType: 'json', 283 success: function (response) { 284 validationMessage = response; 285 if (!validationMessage.service) { 286 validationMessage.service = ['Това поле е задÑлжиÑелно'] 287 } 288 }, 289 error: function (e) {} 290 }); 291}); 292 293const RECAPTCHA2_PUBLIC_KEY = '6LdrxSgoAAAAADCFHy72H5WdYrdxAI3bWJOoBxXp'; 294const RECAPTCHA3_PUBLIC_KEY = '6LcUhvMmAAAAAAWq0LekHjyB82VRKFlV80OHeCGn'; 295 296const makeRecaptchaBlock = function () { 297 let form = window.formData.currentForm; 298 let widget = document.createElement('div'); 299 widget.id = 'recaptchaV2-widget'; 300 widget.classList.add('g-recaptcha'); 301 widget.style.margin = '15px 0'; 302 widget.setAttribute('data-sitekey', RECAPTCHA2_PUBLIC_KEY); 303 form.append(widget); 304 widget.scrollIntoView({ 305 behavior: 'smooth', 306 block: 'center' 307 }); 308} 309 310const getFormData = function (form, token, firstKey) { 311 form.find('input[name="g-recaptcha-token"]').remove(); 312 form.find('input[name="g-recaptcha-key"]').remove(); 313 form.append("<input type='hidden' name='g-recaptcha-token' value='" + token + "'/>"); 314 form.append("<input type='hidden' name='g-recaptcha-key' value='" + firstKey + "'/>"); 315 const serializedForm = form.serialize(); 316 const params = new URLSearchParams(serializedForm); 317 const fullPhone = params.get('Full phone'); 318 if (fullPhone) { 319 params.set('phone', fullPhone); 320 } 321 params.delete("Full phone"); 322 return params.toString(); 323} 324 325const handleRecaptchaV2Response = function (response) { 326 if (!response) { 327 handleErrorResponse('recaptcha not valid'); 328 return 329 } 330 331 $('#recaptchaV2-widget').remove(); 332 makeRequest(response, RECAPTCHA2_PUBLIC_KEY); 333} 334 335const handleErrorResponse = function (error) { 336 let form = window.formData.currentForm; 337 let submit = form.find('[type=submit]'); 338 let currentValue = window.formData.currentValue; 339 let container = form.parent(); 340 let doneBlock = $(".w-form-done", container); 341 let failBlock = $(".w-form-fail", container); 342 343 // show error (fail) block 344 form.show(); 345 doneBlock.hide(); 346 failBlock.show(); 347 submit.attr('disabled', false).val(currentValue); 348 349 for (let field in error) { 350 appendError(form, field, validationMessage[field].join('<br>')); 351 } 352} 353 354const makeRequest = function ( 355 token, 356 firstKey, 357 successCallback, 358 errorCallback 359) { 360 const cookies = document.cookie; 361 362 let form = window.formData.currentForm; 363 let actionUrl = form.attr("action"); 364 let submit = form.find('[type=submit]'); 365 let currentValue = window.formData.currentValue; 366 let container = form.parent(); 367 let doneBlock = $(".w-form-done", container); 368 let failBlock = $(".w-form-fail", container); 369 failBlock.hide(); 370 371 $('.ajax-error-container').remove(); 372 form.find('.has-error-color').removeClass('has-error-color'); 373 form.find('.has-error-border-color').removeClass('has-error-border-color'); 374 375 if (actionUrl.includes('order.netpeak.net')) { 376 const apiBase = getApiBaseUrl(); 377 const urlObj = new URL(actionUrl); 378 379 actionUrl = apiBase + urlObj.pathname; 380 } 381 382 $.ajax({ 383 type: form.attr("method"), 384 url: actionUrl, 385 data: getFormData(form, token, firstKey), 386 headers: { 387 'X-Custom-Cookies': cookies 388 }, 389 xhrFields: { 390 withCredentials: true 391 }, 392 crossDomain: true, 393 dataType: 'json', 394 success: function (resultData) { 395 if (typeof successCallback === 'function') { 396 // call custom callback 397 let result = successCallback(resultData); 398 if (!result) { 399 // show error (fail) block 400 form.show(); 401 doneBlock.hide(); 402 failBlock.show(); 403 submit.attr('disabled', false).val(currentValue); 404 return; 405 } 406 } 407 // show success (done) block 408 form.hide(); 409 doneBlock.show(); 410 failBlock.hide(); 411 submit.attr('disabled', false).val(currentValue); 412 setFormEventDataLayer(true); 413 }, 414 error: function (xhr, status, error) { 415 let responseJSON = xhr.responseJSON; 416 let errorMessage = (responseJSON && responseJSON.message && responseJSON.message.message) 417 || (responseJSON && responseJSON.data && responseJSON.data.message); 418 419 if (!errorMessage) { 420 errorMessage = responseJSON && responseJSON.detail; 421 } 422 423 if (errorMessage === 'recaptcha v3 not valid' || errorMessage === 'An authentication exception occurred.') { 424 makeRecaptchaBlock(); 425 window.grecaptchaWidget = grecaptcha.render('recaptchaV2-widget', { 426 'sitekey': RECAPTCHA2_PUBLIC_KEY, 427 'callback': handleRecaptchaV2Response 428 }); 429 return; 430 } 431 432 // call custom callback 433 if (typeof errorCallback === 'function') { 434 errorCallback(xhr); 435 submit.attr('disabled', false).val(currentValue); 436 } 437 handleErrorResponse(responseJSON ? responseJSON.message : undefined); 438 setFormEventDataLayer(false, errorMessage); 439 } 440 }); 441} 442const makeWebflowFormAjax = function (forms, successCallback, errorCallback) { 443 let firstKey = RECAPTCHA3_PUBLIC_KEY; 444 forms.each(function () { 445 let form = $(this), 446 validationFunction = (e) => { 447 const target = e.target ? e.target : e; 448 let validationItem = validationMap[$(target).attr('name')]; 449 if (!validationItem) { 450 return; 451 } 452 453 let inputValue = $(target).val().replaceAll(" ", ""); 454 const clearInputValue = $(target).val(); 455 456 const isPhoneInput = $(target).attr('name') === 'phone'; 457 if (isPhoneInput){ 458 inputValue = form.find('input[class="full_phone"]').val().replace('+', '') 459 } 460 461 let messageText = validationMessage[$(target).attr('name')].join('<br>'); 462 463 let isValid = false;
464 if (validationItem instanceof RegExp) { 465 let regular = new RegExp(validationItem); 466 isValid = regular.test(inputValue); 467 } else { 468 isValid = validationItem(clearInputValue) 469 } 470 471 if (!isValid) { 472 appendError(form, $(target).attr('name'), messageText) 473 } else { 474 removeError(form, $(target).attr('name')) 475 } 476 }, 477 focusRemoveValidation = (e) => { 478 let validationItem = validationMap[$(e.target).attr('name')]; 479 if (!validationItem) { 480 return; 481 } 482 483 removeError(form, $(e.target).attr('name')); 484 }, 485 checkboxValidationFunction = () => { 486 const checkboxInput = form.find('input[type=checkbox]'); 487 const checkboxError = checkboxInput.parent().find('.ajax-error-container'); 488 489 if (checkboxInput.is(':checked')) { 490 checkboxError.remove(); 491 return; 492 } 493 494 if (checkboxError.length) { 495 return; 496 } 497 498 checkboxInput.parent().find('span').append( 499 `<div class="ajax-error-container has-error-border-color has-error-color">${validationMessage['checkbox']}</div>` 500 ) 501 502 let eventData = { 503 event: 'Field_Sent_Error', 504 deviceID: getDeviceID(), 505 formType: getFormType(), 506 FormLocation: getFormLocation(), 507 errorText: validationMessage['checkbox'].join("; ") 508 }; 509 window.dataLayer.push(eventData) 510 }; 511 512 window.formData = { 513 currentForm: form, 514 } 515 form.find('input, select').on('focusout', validationFunction); 516 form.find('input, select').on('focus', focusRemoveValidation); 517 form.find('input[type=checkbox]').on('change', checkboxValidationFunction); 518 519 form.on("submit", function (event) { 520 event.preventDefault(); 521 522 let emailInput = form.find('[name=email]'); 523 emailInput.val(emailInput.val().trim()); 524 525 form.find('input, select').each((key, element) => { 526 const inputElement = $(element); 527 const isPhoneInput = inputElement.attr('name') === 'phone'; 528 const isCheckbox = inputElement.attr('type') === 'checkbox'; 529 const hasError = isPhoneInput && IS_DROPDOWN_INPUT_MASK 530 ? inputElement.parent().next('.ajax-error-container').length 531 : inputElement.next('.ajax-error-container').length; 532 if (hasError) { 533 return; 534 } 535 if (isCheckbox) { 536 checkboxValidationFunction() 537 } 538 validationFunction(element) 539 }) 540 541 window.formData = {currentForm: form} 542 if (form.find('.ajax-error-container').length) { 543 let errorText = ''; 544 form.find('.ajax-error-container').each(function(){ 545 errorText += $(this).text().trim() + '; '; 546 }); 547 548 window.dataLayer.push({ 549 'event': 'Form_Sent_button_Error', 550 'deviceID': getDeviceID(), 551 'formType': getFormType(), 552 'FormLocation': getFormLocation(), 553 'errorText': errorText 554 }) 555 556 return; 557 } 558 559 let submit = form.find('[type=submit]'), 560 currentValue = submit.val(), 561 waitValue = submit.data('wait'); 562 submit.attr('disabled', true).val(waitValue); 563 window.formData = { 564 currentForm: form, 565 currentValue: currentValue 566 } 567 grecaptcha.ready(function () { 568 grecaptcha.execute(firstKey, {action: 'homepage'}) 569 .then(function (token) {
570 makeRequest(token, firstKey, successCallback, errorCallback); 571 }); 572 }); 573 return false; 574 }); 575 }); 576} 577makeWebflowFormAjax($('div[data-submition-type="ajax"] form, form[data-submition-type="ajax"]')); 578 579 580// Logic for Zapier forms with captcha v2 and v3, will move it to separate file with more configuration in the task pryvyd#147641 581function makeRecaptchaBlockZapier(form) { 582 let widget = document.createElement('div'); 583 widget.id = 'recaptchaV2-widget'; 584 widget.classList.add('g-recaptcha'); 585 widget.style.margin = '15px 0'; 586 widget.setAttribute('data-sitekey', RECAPTCHA2_PUBLIC_KEY); 587 form.append(widget); 588 widget.scrollIntoView({ 589 behavior: 'smooth', 590 block: 'center' 591 }); 592} 593 594function handleRecaptchaV2ResponseZapier(response) { 595 if (!response) { 596 setFormEventDataLayer(false, 'recaptcha v2 not valid'); 597 return; 598 } 599 $('#recaptchaV2-widget').remove(); 600 makeRequestZapier(); 601} 602 603function makeRequestZapier() { 604 const form = window.formData.currentForm; 605 const action = form.attr('action'); 606 const redirect = form.attr('redirect'); 607 const formData = new FormData(form[0]); 608 const fullPhone = formData.get('Full phone'); 609 const submitButton = form.find('[type=submit]'); 610 611 if (fullPhone) { 612 formData.set('phone', fullPhone); 613 } 614 formData.delete("Full phone"); 615 616 const paylaod = { 617 method: "POST", 618 body: formData 619 } 620 621 submitButton.attr('disabled', true); 622 fetch(action, paylaod).then(() => { 623 setFormEventDataLayer(true); 624 if (redirect) { 625 window.location.href = redirect 626 } 627 }) 628} 629 630const getApiBaseUrl = () => { 631 const host = window.location.hostname; 632 633 const match = host.match(/^dev\d+/); 634 635 if (match) { 636 return `https://${match[0]}.order.netpeak.net`; 637 } 638 639 return 'https://order.netpeak.net'; 640}; 641 642const API_BASE_URL = getApiBaseUrl(); 643 644const zapierForms = $('div[data-submition-type="zapier"] form, form[data-submition-type="zapier"]') 645zapierForms.each(function() { 646 const form = $(this); 647 form.on('submit', (event) => { 648 event.preventDefault(); 649 650 const submitButton = form.find('[type=submit]'); 651 const waitValue = submitButton.data('wait'); 652 submitButton.val(waitValue); 653 submitButton.attr('disabled', true); 654 655 grecaptcha.ready(function () { 656 grecaptcha.execute(RECAPTCHA3_PUBLIC_KEY, { action: 'form_submit' }).then(async function (token) { 657 const params = new URLSearchParams(); 658 params.append('g-recaptcha-key', RECAPTCHA3_PUBLIC_KEY); 659 params.append('g-recaptcha-token', token); 660 661 const result = await fetch(`${API_BASE_URL}/api/captcha/verify`, { 662 method: "POST", 663 body: params 664 }); 665 666 const data = await result.json() 667 668 window.formData = { currentForm: form } 669 if (data.success) { 670 makeRequestZapier() 671 } else { 672 setFormEventDataLayer(false, 'recaptcha v3 not valid'); 673 makeRecaptchaBlockZapier(form); 674 window.grecaptchaWidget = grecaptcha.render('recaptchaV2-widget', { 675 'sitekey': RECAPTCHA2_PUBLIC_KEY, 676 'callback': handleRecaptchaV2ResponseZapier 677 }); 678 } 679 }); 680 }); 681 }) 682})
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.