@alexsab-ru/scripts 0.16.0 → 0.16.2
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/lib/analytics.js +24 -10
- package/lib/calltouch/calltouch-module.js +4 -9
- package/lib/form.js +99 -19
- package/package.json +1 -1
package/lib/analytics.js
CHANGED
|
@@ -15,17 +15,31 @@ export function reachGoal(eventAction, t = {}) {
|
|
|
15
15
|
});
|
|
16
16
|
}
|
|
17
17
|
if(window.calltouch_params) {
|
|
18
|
-
|
|
19
|
-
//
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
18
|
+
// sendToCallTouch внутри проверяет eventCategory === 'Lead',
|
|
19
|
+
// поэтому вызываем только для лидов, чтобы не делать лишнюю работу.
|
|
20
|
+
if(t.eventCategory === 'Lead') {
|
|
21
|
+
// Если siteId уже задан конкретным дилером (из data-атрибута),
|
|
22
|
+
// не перезатираем его глобальным значением из calltouch_params.
|
|
23
|
+
if(!t.siteId) {
|
|
24
|
+
var rawSiteId = window.calltouch_params.site_id;
|
|
25
|
+
t.siteId = Array.isArray(rawSiteId) ? rawSiteId[0] : rawSiteId;
|
|
26
|
+
}
|
|
27
|
+
// console.log("sendToCallTouch:", t);
|
|
28
|
+
sendToCallTouch(t)
|
|
29
|
+
.then(result => {
|
|
30
|
+
console.log('Данные успешно отправлены в колтач:', eventAction, result);
|
|
31
|
+
})
|
|
32
|
+
.catch(error => {
|
|
33
|
+
console.error('Ошибка при отправке данных в колтач:', eventAction, error);
|
|
34
|
+
});
|
|
35
|
+
}
|
|
27
36
|
if(window.ct) {
|
|
28
|
-
|
|
37
|
+
// mod_id может быть массивом (multi-project) или строкой (legacy).
|
|
38
|
+
var modIds = window.calltouch_params.mod_id;
|
|
39
|
+
var ids = Array.isArray(modIds) ? modIds : [modIds];
|
|
40
|
+
ids.forEach(function(modId) {
|
|
41
|
+
window.ct(modId, 'goal', eventAction);
|
|
42
|
+
});
|
|
29
43
|
}
|
|
30
44
|
}
|
|
31
45
|
ymGoal(eventAction, {
|
|
@@ -98,16 +98,11 @@ function processEventData(eventData, sessionId) {
|
|
|
98
98
|
function sendToCallTouch(options) {
|
|
99
99
|
return new Promise((resolve, reject) => {
|
|
100
100
|
try {
|
|
101
|
-
const { siteId,
|
|
102
|
-
|
|
103
|
-
// Проверяем, что категория события - Lead
|
|
104
|
-
if (eventCategory !== 'Lead') {
|
|
105
|
-
return resolve({ status: 'skipped', message: 'Not a Lead event' });
|
|
106
|
-
}
|
|
107
|
-
|
|
101
|
+
const { siteId, eventProperties, sessionId = window.call_value } = options;
|
|
102
|
+
|
|
108
103
|
// Проверяем обязательные параметры
|
|
109
|
-
if (!siteId) {
|
|
110
|
-
return reject(new Error('siteId is required'));
|
|
104
|
+
if (!siteId || typeof siteId !== 'string') {
|
|
105
|
+
return reject(new Error('siteId is required and must be a string'));
|
|
111
106
|
}
|
|
112
107
|
|
|
113
108
|
// Обрабатываем данные с помощью оптимизированной функции
|
package/lib/form.js
CHANGED
|
@@ -79,6 +79,47 @@ const stateBtn = (btn, value, disable = false) => {
|
|
|
79
79
|
}
|
|
80
80
|
};
|
|
81
81
|
|
|
82
|
+
const hasFileUploadField = (form, selector) => {
|
|
83
|
+
if (!selector) {
|
|
84
|
+
return false;
|
|
85
|
+
}
|
|
86
|
+
return !!form.querySelector(selector);
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
const appendDropzoneFiles = (formData, { filesToUploadKey, fileFieldName }) => {
|
|
90
|
+
if (!filesToUploadKey || !fileFieldName) {
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
const files = window[filesToUploadKey];
|
|
95
|
+
if (!Array.isArray(files)) {
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
files.forEach((file) => {
|
|
100
|
+
formData.append(fileFieldName, file);
|
|
101
|
+
});
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
const resetDropzones = ({ dropzonesKey, filesToUploadKey }) => {
|
|
105
|
+
if (!dropzonesKey || !filesToUploadKey) {
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
const dropzones = window[dropzonesKey];
|
|
110
|
+
if (Array.isArray(dropzones)) {
|
|
111
|
+
dropzones.forEach((dropzone) => {
|
|
112
|
+
if (dropzone && typeof dropzone.removeAllFiles === 'function') {
|
|
113
|
+
dropzone.removeAllFiles();
|
|
114
|
+
}
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
if (Array.isArray(window[filesToUploadKey])) {
|
|
119
|
+
window[filesToUploadKey] = [];
|
|
120
|
+
}
|
|
121
|
+
};
|
|
122
|
+
|
|
82
123
|
// Показ сообщения об ошибке
|
|
83
124
|
export const showErrorMes = (form, el, text) => {
|
|
84
125
|
let field = form.querySelector(el);
|
|
@@ -111,6 +152,11 @@ const propsParams = {
|
|
|
111
152
|
verbose: false,
|
|
112
153
|
agreeSelector: "agree",
|
|
113
154
|
sendMailCookie: "SEND_MAIL",
|
|
155
|
+
fileUploadSelector: ".file-upload",
|
|
156
|
+
fileFieldName: "file[]",
|
|
157
|
+
filesToUploadKey: "filesToUpload",
|
|
158
|
+
dropzonesKey: "dropzones",
|
|
159
|
+
successOnAttentionPhone: "79000000000",
|
|
114
160
|
}
|
|
115
161
|
|
|
116
162
|
export const errorIcon = '<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 52 52"><path fill="#ed1c24" d="M26,0A26,26,0,1,0,52,26,26,26,0,0,0,26,0Zm9.6,17.5a1.94,1.94,0,0,1,2,2,2,2,0,1,1-2-2Zm-19.2,0a1.94,1.94,0,0,1,2,2,2,2,0,1,1-2-2ZM39.65,40.69a.93.93,0,0,1-.45.11,1,1,0,0,1-.89-.55,13.81,13.81,0,0,0-24.62,0,1,1,0,1,1-1.78-.9,15.8,15.8,0,0,1,28.18,0A1,1,0,0,1,39.65,40.69Z"></path></svg>';
|
|
@@ -171,6 +217,9 @@ const submitForm = async (form) => {
|
|
|
171
217
|
const phone = form.querySelector('[name="phone"]');
|
|
172
218
|
const name = form.querySelector('[name="name"]');
|
|
173
219
|
const dealer = form.querySelector('[name="dealer"]');
|
|
220
|
+
const hasFileUpload = hasFileUploadField(form, props.fileUploadSelector);
|
|
221
|
+
const ftaCookie = Boolean(getCookie('fta'));
|
|
222
|
+
const verbose = Boolean(props.verbose || ftaCookie);
|
|
174
223
|
|
|
175
224
|
let validate;
|
|
176
225
|
|
|
@@ -256,11 +305,14 @@ const submitForm = async (form) => {
|
|
|
256
305
|
reachGoal("form_submit");
|
|
257
306
|
|
|
258
307
|
let formData = new FormData(form);
|
|
308
|
+
if (hasFileUpload) {
|
|
309
|
+
appendDropzoneFiles(formData, props);
|
|
310
|
+
}
|
|
259
311
|
let sendMailCookie = props.sendMailCookie;
|
|
260
312
|
if(formData.get('dealer')) {
|
|
261
313
|
sendMailCookie += "_" + formData.get('dealer');
|
|
262
314
|
}
|
|
263
|
-
if(
|
|
315
|
+
if(ftaCookie) {
|
|
264
316
|
formData.append("fta", true);
|
|
265
317
|
}
|
|
266
318
|
if(getCookie('__gtm_campaign_url')) {
|
|
@@ -284,6 +336,9 @@ const submitForm = async (form) => {
|
|
|
284
336
|
.split("&")
|
|
285
337
|
.forEach(function (pair) {
|
|
286
338
|
let param = pair.split("=");
|
|
339
|
+
if(!param[0]){
|
|
340
|
+
return;
|
|
341
|
+
}
|
|
287
342
|
if(formData.get(param[0])){
|
|
288
343
|
formData.set(param[0], decodeURIComponent(param[1]));
|
|
289
344
|
} else {
|
|
@@ -295,6 +350,7 @@ const submitForm = async (form) => {
|
|
|
295
350
|
// Считаем значения один раз, чтобы одинаково использовать их в try/catch и логах.
|
|
296
351
|
const dealerRouteKey = dealer && dealer.dataset ? (dealer.dataset.ctRouteKey || '').trim() : '';
|
|
297
352
|
const dealerModId = dealer && dealer.dataset ? (dealer.dataset.ctModId || '').trim() : '';
|
|
353
|
+
const dealerSiteId = dealer && dealer.dataset ? (dealer.dataset.ctSiteId || '').trim() : '';
|
|
298
354
|
const calltouchRouteKey = dealerRouteKey || props.ct_routeKey;
|
|
299
355
|
const scopedCtwName = dealerModId ? `ctw_${dealerModId}` : '';
|
|
300
356
|
const hasScopedCtw = scopedCtwName ? typeof window[scopedCtwName] !== 'undefined' : false;
|
|
@@ -303,13 +359,14 @@ const submitForm = async (form) => {
|
|
|
303
359
|
try {
|
|
304
360
|
// Для multi-project берём routeKey/mod_id у выбранного дилера (data-атрибуты select).
|
|
305
361
|
// Если дилерские значения отсутствуют, используем старый глобальный props.ct_routeKey.
|
|
306
|
-
|
|
362
|
+
verbose && console.log('Calltouch submit debug:', {
|
|
307
363
|
formId: form.id || '',
|
|
308
364
|
phone: phone && phone.value ? phone.value : '',
|
|
309
365
|
name: name && name.value ? name.value : '',
|
|
310
366
|
dealerValue: dealer && dealer.value ? dealer.value : '',
|
|
311
367
|
dealerRouteKey,
|
|
312
368
|
dealerModId,
|
|
369
|
+
dealerSiteId,
|
|
313
370
|
fallbackRouteKey: props.ct_routeKey,
|
|
314
371
|
calltouchRouteKey,
|
|
315
372
|
scopedCtwName,
|
|
@@ -319,7 +376,7 @@ const submitForm = async (form) => {
|
|
|
319
376
|
|
|
320
377
|
// Отправка заявки на обратный вызов в CallTouch.
|
|
321
378
|
if(calltouchRouteKey != '') {
|
|
322
|
-
const requestData = await createRequest(calltouchRouteKey, phone.value, name.value,
|
|
379
|
+
const requestData = await createRequest(calltouchRouteKey, phone.value, name.value, verbose, dealerModId);
|
|
323
380
|
formData.append("ct_callback", true);
|
|
324
381
|
formData.append("ctw_createRequest", JSON.stringify(requestData));
|
|
325
382
|
if (dealerModId !== '') {
|
|
@@ -342,6 +399,7 @@ const submitForm = async (form) => {
|
|
|
342
399
|
formId: form.id || '',
|
|
343
400
|
dealerRouteKey,
|
|
344
401
|
dealerModId,
|
|
402
|
+
dealerSiteId,
|
|
345
403
|
fallbackRouteKey: props.ct_routeKey,
|
|
346
404
|
calltouchRouteKey,
|
|
347
405
|
scopedCtwName,
|
|
@@ -349,23 +407,40 @@ const submitForm = async (form) => {
|
|
|
349
407
|
hasGlobalCtw
|
|
350
408
|
});
|
|
351
409
|
formDataObj = getFormDataObject(formData, form.id);
|
|
410
|
+
// Если известен конкретный siteId дилера, прокидываем его,
|
|
411
|
+
// чтобы reachGoal → sendToCallTouch отправил лид в нужный проект.
|
|
412
|
+
if (dealerSiteId) {
|
|
413
|
+
formDataObj.siteId = dealerSiteId;
|
|
414
|
+
}
|
|
352
415
|
}
|
|
353
416
|
|
|
354
|
-
const
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
417
|
+
const requestOptions = hasFileUpload
|
|
418
|
+
? {
|
|
419
|
+
method: "POST",
|
|
420
|
+
body: formData,
|
|
421
|
+
}
|
|
422
|
+
: {
|
|
423
|
+
method: "POST",
|
|
424
|
+
mode: "cors",
|
|
425
|
+
cache: "no-cache",
|
|
426
|
+
credentials: "same-origin",
|
|
427
|
+
headers: {
|
|
428
|
+
"Content-Type": "application/x-www-form-urlencoded",
|
|
429
|
+
},
|
|
430
|
+
body: new URLSearchParams([...formData]),
|
|
431
|
+
};
|
|
432
|
+
|
|
433
|
+
await fetch(url, requestOptions)
|
|
434
|
+
.then(async (res) => {
|
|
435
|
+
const text = await res.text();
|
|
436
|
+
try {
|
|
437
|
+
return JSON.parse(text);
|
|
438
|
+
} catch (error) {
|
|
439
|
+
throw new Error("Ошибка обработки данных");
|
|
440
|
+
}
|
|
441
|
+
})
|
|
367
442
|
.then((data) => {
|
|
368
|
-
|
|
443
|
+
verbose && console.log(data);
|
|
369
444
|
stateBtn(btn, btnText);
|
|
370
445
|
if (data.answer == "required") {
|
|
371
446
|
reachGoal("form_required");
|
|
@@ -382,7 +457,9 @@ const submitForm = async (form) => {
|
|
|
382
457
|
}
|
|
383
458
|
return;
|
|
384
459
|
} else {
|
|
385
|
-
|
|
460
|
+
const normalizedPhone = String(formData.get("phone") || (phone && phone.value ? phone.value : "")).replace(/\D/g, "");
|
|
461
|
+
const attentionPhone = String(props.successOnAttentionPhone || "").replace(/\D/g, "");
|
|
462
|
+
if(data.attention != true || (attentionPhone && normalizedPhone === attentionPhone)) {
|
|
386
463
|
reachGoal("form_success", formDataObj);
|
|
387
464
|
}
|
|
388
465
|
setCookie(sendMailCookie, true, {'domain': window.location.hostname,'path':'/','expires':600});
|
|
@@ -394,6 +471,9 @@ const submitForm = async (form) => {
|
|
|
394
471
|
}
|
|
395
472
|
}
|
|
396
473
|
form.reset();
|
|
474
|
+
if (hasFileUpload) {
|
|
475
|
+
resetDropzones(props);
|
|
476
|
+
}
|
|
397
477
|
})
|
|
398
478
|
.catch((error) => {
|
|
399
479
|
reachGoal("form_error");
|
|
@@ -469,4 +549,4 @@ document.querySelectorAll("form:not(.vue-form)").forEach((form) => {
|
|
|
469
549
|
await sendForm(form);
|
|
470
550
|
})
|
|
471
551
|
});
|
|
472
|
-
}
|
|
552
|
+
}
|