@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 CHANGED
@@ -15,17 +15,31 @@ export function reachGoal(eventAction, t = {}) {
15
15
  });
16
16
  }
17
17
  if(window.calltouch_params) {
18
- t.siteId = window.calltouch_params.site_id;
19
- // console.log("sendToCallTouch:", t);
20
- sendToCallTouch(t)
21
- .then(result => {
22
- console.log('Данные успешно отправлены в колтач:', eventAction, result);
23
- })
24
- .catch(error => {
25
- console.error('Ошибка при отправке данных в колтач:', eventAction, error);
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
- window.ct(window.calltouch_params.mod_id, 'goal', eventAction);
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, eventCategory, eventProperties, sessionId = window.call_value } = options;
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(getCookie('fta')) {
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
- props.verbose && console.log('Calltouch submit debug:', {
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, props.verbose, dealerModId);
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 params = new URLSearchParams([...formData]);
355
-
356
- await fetch(url, {
357
- method: "POST",
358
- mode: "cors",
359
- cache: "no-cache",
360
- credentials: "same-origin",
361
- headers: {
362
- "Content-Type": "application/x-www-form-urlencoded",
363
- },
364
- body: params,
365
- })
366
- .then((res) => res.json())
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
- props.verbose && console.log(data);
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
- if(data.attention != true) {
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
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@alexsab-ru/scripts",
3
- "version": "0.16.0",
3
+ "version": "0.16.2",
4
4
  "description": "common libs for websites",
5
5
  "main": "index.js",
6
6
  "scripts": {