PageSourceSearch

https://netpeak.ua/assets/js/webflow-form-ajax.js?v=20261002145459

js netpeak.ua collected 2026-10-02 12:23:30 UTC 24,203 bytes, 682 lines download raw bytes

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.