PageSourceSearch

https://www.ati.tn/wp-content/plugins/latepoint/public/javascripts/front.js?ver=5.1.2

js ati.tn collected 2026-09-24 18:23:28 UTC 152,603 bytes, 3,421 lines download raw bytes

1function latepoint_is_timeframe_in_periods(timeframe_start, timeframe_end, periods_arr) {
2  var is_inside = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : false;
3
4  for (var i = 0; i < periods_arr.length; i++) {
5
6    var period_start = 0;
7    var period_end = 0;
8    var buffer_before = 0;
9    var buffer_after = 0;
10
11    var period_info = periods_arr[i].split(':');
12    if (period_info.length == 2) {
13      period_start = period_info[0];
14      period_end = period_info[1];
15    } else {
16      buffer_before = period_info[2];
17      buffer_after = period_info[3];
18      period_start = parseFloat(period_info[0]) - parseFloat(buffer_before);
19      period_end = parseFloat(period_info[1]) + parseFloat(buffer_after);
20    }
21    if (is_inside) {
22      if (latepoint_is_period_inside_another(timeframe_start, timeframe_end, period_start, period_end)) {
23        return true;
24      }
25    } else {
26      if (latepoint_is_period_overlapping(timeframe_start, timeframe_end, period_start, period_end)) {
27        return true;
28      }
29    }
30  }
31  return false;
32}
33
34function latepoint_is_period_overlapping(period_one_start, period_one_end, period_two_start, period_two_end) {
35  // https://stackoverflow.com/questions/325933/determine-whether-two-date-ranges-overlap/
36  return period_one_start < period_two_end && period_two_start < period_one_end;
37}
38
39function latepoint_is_period_inside_another(period_one_start, period_one_end, period_two_start, period_two_end) {
40  return period_one_start >= period_two_start && period_one_end <= period_two_end;
41}
42
43
44// Converts time in minutes to hours if possible, if minutes also exists - shows minutes too
45function latepoint_minutes_to_hours_preferably(time) {
46  var army_clock = latepoint_is_army_clock();
47
48  var hours = Math.floor(time / 60);
49  if (!army_clock && hours > 12) hours = hours - 12;
50
51  var minutes = time % 60;
52  if (minutes > 0) hours = hours + ':' + minutes;
53  return hours;
54}
55
56
57function latepoint_minutes_to_hours(time) {
58  var army_clock = latepoint_is_army_clock();
59
60  var hours = Math.floor(time / 60);
61  if (!army_clock && hours > 12) hours = hours - 12;
62  return hours;
63}
64
65
66function latepoint_am_or_pm(minutes) {
67  if (latepoint_is_army_clock()) return '';
68  return (minutes < 720 || minutes == 1440) ? 'am' : 'pm';
69}
70
71function latepoint_hours_and_minutes_to_minutes(hours_and_minutes, ampm) {
72  var hours_and_minutes_arr = hours_and_minutes.split(':');
73  var hours = hours_and_minutes_arr[0];
74  var minutes = hours_and_minutes_arr[1];
75  if (ampm == "pm" && hours < 12) hours = parseInt(hours) + 12;
76  if (ampm == "am" && hours == 12) hours = 0;
77  minutes = parseInt(minutes) + (hours * 60);
78  return minutes;
79}
80
81function latepoint_get_time_system() {
82  return latepoint_helper.time_system;
83}
84
85function latepoint_is_army_clock() {
86  return (latepoint_get_time_system() == '24');
87}
88
89function latepoint_minutes_to_hours_and_minutes(minutes) {
90  var army_clock = latepoint_is_army_clock();
91  var format = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : '%02d:%02d';
92
93  var hours = Math.floor(minutes / 60);
94  if (!army_clock && (hours > 12)) hours = hours - 12;
95  if (!army_clock && hours == 0) hours = 12;
96  minutes = minutes % 60;
97  return sprintf(format, hours, minutes);
98}
99
100
101function latepoint_timestamped_ajaxurl(){
102    let url = latepoint_helper.ajaxurl;
103    let timestamp = Date.now();
104
105    // Check if the URL already has GET parameters
106    if (url.includes('?')) {
107        return `${url}&t=${timestamp}`;
108    } else {
109        return `${url}?t=${timestamp}`;
110    }
111}
112
113function latepoint_random_generator() {
114  var S4 = function () {
115    return (((1 + Math.random()) * 0x10000) | 0).toString(16).substring(1);
116  };
117  return (S4() + S4() + "-" + S4() + "-" + S4() + "-" + S4() + "-" + S4() + S4() + S4());
118}
119
120function latepoint_validate_form($form) {
121  let errors = [];
122  $form.find('select[data-os-validate], input[data-os-validate], textarea[data-os-validate]').each(function () {
123    let validations = jQuery(this).data('os-validate').split(' ');
124    let $input = jQuery(this);
125    let label = $input.closest('.os-form-group').find('label').text();
126    let field_has_errors = false;
127    if (validations) {
128      for (let i = 0; i < validations.length; i++) {
129        switch (validations[i]) {
130          case 'presence':
131            if($input.is(':checkbox')){
132              if (!$input.is(':checked')) {
133                errors.push({message: label + ' ' + latepoint_helper.msg_validation_presence_checkbox});
134                field_has_errors = true;
135              }
136            }else{
137              if (!$input.val()) {
138                errors.push({message: label + ' ' + latepoint_helper.msg_validation_presence});
139                field_has_errors = true;
140              }
141            }
142            break;
143          case 'phone':
144            if (!window.intlTelInputGlobals.getInstance($input[0]).isValidNumber()) {
145              errors.push({message: label + ' ' + latepoint_helper.msg_validation_invalid});
146              field_has_errors = true;
147            }
148            break;
149        }
150      }
151    }
152    if (field_has_errors) {
153      $input.closest('.os-form-group').addClass('os-invalid');
154    } else {
155      $input.closest('.os-form-group').removeClass('os-invalid');
156    }
157  });
158  return errors;
159}
160
161function latepoint_create_form_data_from_non_form_element($elem) {
162  let formData = new FormData();
163  // create objecte from all input fields that are inside of the element
164  let fields = $elem.find('select, input, textarea').serializeArray();
165  if (fields) {
166    fields.forEach(field => formData.append(field.name, field.value));
167  }
168  return formData;
169}
170
171function latepoint_create_form_data($form, route_name = false, extra_params = false) {
172  let form_data = new FormData();
173  let params = new FormData($form[0]);
174
175  if (extra_params) {
176    Object.keys(extra_params).forEach(key => {
177      params.set(key, extra_params[key]);
178    });
179  }
180
181  // get values from phone number fields
182  if (('intlTelInputGlobals' in window) && ('intlTelInputUtils' in window)) {
183    $form.find('input.os-mask-phone').each(function () {
184      const phoneInputName = this.getAttribute('name');
185      const phoneInputValue = window.intlTelInputGlobals.getInstance(this).getNumber(window.intlTelInputUtils.numberFormat.E164);
186      // override value generated automatically by formdata with a formatted value of a phone field with country code
187      params.set(phoneInputName, phoneInputValue);
188    });
189  }
190
191  form_data.append('params', latepoint_formdata_to_url_encoded_string(params));
192  form_data.append('action', latepoint_helper.route_action);
193  form_data.append('route_name', route_name ? route_name : $form.data('route-name'));
194  form_data.append('layout', 'none');
195  form_data.append('return_format', 'json');
196
197  let file_data;
198  // put file data into main form_data object, since we can't send them in "params" string
199  $form.find('input[type="file"]').each(function () {
200    file_data = this.files; // get multiple files from input file
201    let file_name = this.getAttribute("name");
202    for (let i = 0; i < file_data.length; i++) {
203      form_data.append(file_name + '[]', file_data[i]);
204    }
205  });
206  return form_data;
207}
208
209function latepoint_mask_timefield($elem) {
210  if (jQuery().inputmask) {
211    $elem.inputmask({
212      'mask': '99:99',
213      'placeholder': 'HH:MM'
214    });
215  }
216}
217
218function latepoint_formdata_to_url_encoded_string(form_data) {
219  let filtered_form_data = new FormData();
220  // remove file fields from params, so we can serialize it into string,
221  // !important, this will not include file fields into the form_data, so you have to include them manually, see latepoint_create_form_data() that does it
222  // note: we don't use form_data.remove(key) on original object because we might want to preserve it
223  for (const [key, value] of form_data) {
224    if (value instanceof File) continue;
225    if (key.slice(-2) === '[]') {
226      // expecting array, append
227      filtered_form_data.append(key, value);
228    } else {
229      filtered_form_data.set(key, value);
230    }
231  }
232  return new URLSearchParams(filtered_form_data).toString();
233}
234
235function latepoint_mask_percent($elem) {
236  if (jQuery().inputmask) {
237    $elem.inputmask({
238      'alias': 'decimal',
239      'radixPoint': latepoint_helper.decimal_separator,
240      'digits': 4,
241      'digitsOptional': false,
242      'suffix': '%',
243      'placeholder': '0',
244      'rightAlign': false
245    });
246  }
247}
248
249function latepoint_mask_minutes($elem) {
250  if (jQuery().inputmask) {
251    $elem.inputmask({
252      'removeMaskOnSubmit': true,
253      'alias': 'numeric',
254      'digits': 0,
255      'suffix': latepoint_helper.msg_minutes_suffix,
256      'placeholder': '0',
257      'rightAlign': false
258    });
259  }
260}
261
262
263function latepoint_mask_money($elem) {
264  if (jQuery().inputmask) {
265    $elem.inputmask({
266      'alias': 'currency',
267      'groupSeparator': latepoint_helper.thousand_separator,
268      'radixPoint': latepoint_helper.decimal_separator,
269      'digits': latepoint_helper.number_of_decimals,
270      'digitsOptional': false,
271      'prefix': latepoint_helper.currency_symbol_before ? latepoint_helper.currency_symbol_before + ' ' : '',
272      'suffix': latepoint_helper.currency_symbol_after ? ' ' + latepoint_helper.currency_symbol_after : '',
273      'placeholder': '0',
274      'rightAlign': false
275    });
276  }
277}
278
279function latepoint_mask_date($elem) {
280  if (jQuery().inputmask) {
281    $elem.inputmask({
282      'alias': 'datetime',
283      'inputFormat': latepoint_helper.date_format_for_js
284    });
285  }
286}
287
288function latepoint_init_phone_masking_from_placeholder($input) {
289  if (!latepoint_helper.mask_phone_number_fields) return;
290  let format = $input.attr('placeholder');
291  if (format && jQuery().inputmask) {
292    $input.inputmask(format.replace(/[0-9]/g, 9));
293  }
294}
295
296function latepoint_mask_phone($elem) {
297  let jsElem = $elem[0];
298
299  // First priority is to prevent duplicates (common in non-document.body contexts)
300  if (jsElem && !window.intlTelInputGlobals.getInstance(jsElem)) {
301    let dropdownContainer = document.body;
302
303    let onlyCountries = JSON.parse(latepoint_helper.included_phone_countries);
304    // Remedy a quirk with json_encode(EMPTY_ARRAY)
305    if (onlyCountries.length === 1 && onlyCountries[0] === "") {
306      onlyCountries = [];
307    }
308    const preferredCountries = onlyCountries.length ? [] : window.intlTelInputGlobals.defaults.preferredCountries;
309
310    // remove country name in english and only use names in country language
311    var countryData = window.intlTelInputGlobals.getCountryData();
312
313    for (var i = 0; i < countryData.length; i++) {
314      var country = countryData[i];
315      country.name = country.name.replace(/ *\([^)]*\) */g, "");
316    }
317
318    let defaultCountryCode = latepoint_helper.default_phone_country;
319    if (onlyCountries.length && !onlyCountries.includes(defaultCountryCode)) {
320      defaultCountryCode = onlyCountries[0];
321    }
322
323
324    let iti = window.intlTelInput(jsElem, {
325      dropdownContainer: dropdownContainer,
326      formatOnDisplay: true,
327      nationalMode: true,
328      autoPlaceholder: 'aggressive',
329      initialCountry: defaultCountryCode,
330      geoIpLookup: function (callback) {
331        const cookieName = 'latepoint_phone_country';
332
333        if (latepoint_has_cookie(cookieName)) {
334          callback(latepoint_get_cookie(cookieName));
335        } else {
336          jQuery.get('https://ipinfo.io', function () {
337          }, 'jsonp').always(function (response) {
338            // Sensible default
339            let countryCode = defaultCountryCode;
340
341            if (response && response.country) {
342              countryCode = response.country.toLowerCase();
343              latepoint_set_cookie(cookieName, countryCode);
344            }
345            callback(countryCode);
346          })
347        }
348      },
349      allowDropdown: onlyCountries.length != 1,
350      onlyCountries: onlyCountries,
351      preferredCountries: preferredCountries,
352      separateDialCode: latepoint_helper.is_enabled_show_dial_code_with_flag
353    });
354
355    iti.promise.then(function () {
356      latepoint_init_phone_masking_from_placeholder($elem);
357    });
358
359
360    $elem.on("countrychange", function (event) {
361      latepoint_init_phone_masking_from_placeholder(jQuery(this));
362    });
363  }
364}
365
366function latepoint_show_booking_end_time() {
367  return (latepoint_helper.show_booking_end_time == 'yes');
368}
369
370function latepoint_set_cookie(name, value, days) {
371  let date = new Date;
372  date.setTime(date.getTime() + 24 * 60 * 60 * 1000 * days);
373  document.cookie = name + "=" + value + ";path=/;expires=" + date.toGMTString();
374}
375
376function latepoint_get_cookie(name) {
377  let cookie = document.cookie.match('(^|;) ?' + name + '=([^;]*)(;|$)');
378  return cookie ? cookie[2] : null;
379}
380
381function latepoint_has_cookie(name) {
382  return latepoint_get_cookie(name) !== null;
383}
384
385function latepoint_delete_cookie(name) {
386  latepoint_set_cookie(name, '', -1);
387}
388
389function latepoint_add_notification(message, message_type = 'success'){
390	var wrapper = jQuery('body').find('.os-notifications');
391	if(!wrapper.length){
392		jQuery('body').append('<div class="os-notifications"></div>');
393		wrapper = jQuery('body').find('.os-notifications');
394	}
395	if(wrapper.find('.item').length > 0) wrapper.find('.item:first-child').remove();
396	wrapper.append('<div class="item item-type-'+ message_type +'">' + message + '<span class="os-notification-close"><i class="latepoint-icon latepoint-icon-x"></i></span></div>');
397}
398
399function latepoint_add_lightbox_notification(message, message_type = 'success'){
400	var wrapper = jQuery('.latepoint-lightbox-content').find('.os-notifications');
401	if(!wrapper.length){
402		jQuery('.latepoint-lightbox-content').prepend('<div class="os-notifications"></div>');
403		wrapper = jQuery('.latepoint-lightbox-content').find('.os-notifications');
404	}
405	if(wrapper.find('.item').length > 0) wrapper.find('.item:first-child').remove();
406	wrapper.append('<div class="item item-type-'+ message_type +'">' + message + '<span class="os-notification-close"><i class="latepoint-icon latepoint-icon-x"></i></span></div>');
407}
408
409function latepoint_generate_form_message_html(messages, status){
410  var message_html = '<div class="os-form-message-w status-' + status + '"><ul>';
411  if(Array.isArray(messages)){
412    messages.forEach(function(message){
413      message_html+= '<li>' + message + '</li>';
414    });
415  }else{
416    message_html+= '<li>' + messages + '</li>';
417  }
418  message_html+= '</ul></div>';
419  return message_html;
420}
421
422function latepoint_display_in_side_sub_panel(html){
423  if(!jQuery('.latepoint-side-panel-w').length) latepoint_show_data_in_side_panel('');
424  jQuery('.latepoint-side-panel-w .latepoint-side-panels .side-sub-panel-wrapper').remove();
425  jQuery('.latepoint-side-panel-w .latepoint-side-panels').append(html);
426}
427
428function latepoint_clear_form_messages($form){
429  $form.find('.os-form-message-w').remove();
430}
431
432function latepoint_show_data_in_side_panel(message, extra_classes = '', close_btn = true){
433  jQuery('.latepoint-side-panel-w').remove();
434  jQuery('body').append('<div class="latepoint-side-panel-w ' + extra_classes + ' os-loading"><div class="latepoint-side-panel-shadow"></div><div class="latepoint-side-panels"><div class="latepoint-side-panel-i"></div></div></div>');
435  jQuery('.latepoint-side-panel-i').html(message);
436  if(close_btn){
437    jQuery('.latepoint-side-panel-i').find('.os-form-header .latepoint-side-panel-close').remove();
438    jQuery('.latepoint-side-panel-i').find('.os-form-header').append('<a href="#" class="latepoint-side-panel-close latepoint-side-panel-close-trigger"><i class="latepoint-icon latepoint-icon-x"></i></a>');
439  }
440  setTimeout(function(){
441    jQuery('.latepoint-side-panel-w').removeClass('os-loading');
442  }, 100);
443}
444
445function latepoint_show_data_in_lightbox(message, extra_classes = '', close_btn = true, tag = 'div', inner_extra_classes = '', inner_tag = 'div'){
446  jQuery('.latepoint-lightbox-w').remove();
447  let lightbox_css_classes = 'latepoint-lightbox-w latepoint-w latepoint-border-radius-' + latepoint_helper.style_border_radius+ ' ';
448  if(extra_classes) lightbox_css_classes+= extra_classes;
449  let lightbox_css_inner_classes = 'latepoint-lightbox-i ';
450  if(inner_extra_classes) lightbox_css_inner_classes += inner_extra_classes;
451
452  let close_btn_html = close_btn ? '<a href="#" class="latepoint-lightbox-close" tabindex="0"><i class="latepoint-icon latepoint-icon-x"></i></a>' : '';
453  jQuery('body').append('<'+tag+' class="'+ lightbox_css_classes +'"><'+inner_tag+' class="'+ lightbox_css_inner_classes +'">' + message + close_btn_html + '</'+inner_tag+'><div class="latepoint-lightbox-shadow"></div></'+tag+'>');
454
455  jQuery('body').addClass('latepoint-lightbox-active');
456}
457
458
459
460// DOCUMENT READY
461jQuery(function( $ ) {
462
463  if($('.latepoint').find('[data-os-action-onload]').length){
464    $('.latepoint').find('[data-os-action-onload]').each(function(){
465      var $this = jQuery(this);
466      $this.addClass('os-loading');
467      var params = $this.data('os-params');
468      var return_format = $this.data('os-return-format') ? $this.data('os-return-format') : 'json'
469      var data = { action: 'latepoint_route_call', route_name: $this.data('os-action-onload'), params: params, return_format: return_format }
470      jQuery.ajax({
471        type : "post",
472        dataType : "json",
473        url : latepoint_timestamped_ajaxurl(),
474        data : data,
475        success: function(response) {
476          $this.removeClass('os-loading');
477          if (response.status === "success") {
478            if($this.data('os-output-target') == 'self'){
479              $this.html(response.message);
480            }
481          }
482        }
483      });
484    });
485  }
486
487  /*
488    Ajax buttons action
489  */
490  $('.latepoint').on('click', 'button[data-os-action], a[data-os-action], div[data-os-action], span[data-os-action], tr[data-os-action]', function(e){
491    var $this = jQuery(this);
492    if($this.data('os-prompt') && !confirm($this.data('os-prompt'))) return false;
493    var params = $this.data('os-params');
494    if($this.data('os-source-of-params')){
495      var form_data = latepoint_create_form_data_from_non_form_element($($this.data('os-source-of-params')));
496      params = latepoint_formdata_to_url_encoded_string(form_data);
497    }
498    var return_format = $this.data('os-return-format') ? $this.data('os-return-format') : 'json'
499    var data = { action: 'latepoint_route_call', route_name: $this.data('os-action'), params: params, return_format: return_format }
500    $this.addClass('os-loading');
501    if($this.data('os-output-target') == 'side-panel'){
502      $('.latepoint-side-panel-w').remove();
503      let css_classes = $this.data('os-lightbox-classes') ? $this.data('os-lightbox-classes') : '';
504      $('body').append('<div class="latepoint-side-panel-w ' + css_classes + ' os-loading"><div class="latepoint-side-panel-shadow"></div><div class="latepoint-side-panels"><div class="latepoint-side-panel-i"></div></div></div>');
505    }
506    $.ajax({
507      type : "post",
508      dataType : "json",
509      url : latepoint_timestamped_ajaxurl(),
510      data : data,
511      success: function(response){
512        if(response.status === "success"){
513          if($this.data('os-output-target') == 'lightbox'){
514            latepoint_show_data_in_lightbox(response.message, $this.data('os-lightbox-classes'), ($this.data('os-lightbox-no-close-button') !== 'yes'), $this.data('os-lightbox-tag'), $this.data('os-lightbox-inner-classes'), $this.data('os-lightbox-inner-tag'));
515          }else if($this.data('os-output-target') == 'side-panel'){
516            $('.latepoint-side-panel-i').html(response.message);
517            jQuery('.latepoint-side-panel-i').find('.os-form-header .latepoint-side-panel-close').remove();
518            jQuery('.latepoint-side-panel-i').find('.os-form-header').append('<a href="#" class="latepoint-side-panel-close latepoint-side-panel-close-trigger"><i class="latepoint-icon latepoint-icon-x"></i></a>');
519            setTimeout(function(){
520              $('.latepoint-side-panel-w').removeClass('os-loading');
521            }, 100);
522          }else if($this.data('os-success-action') == 'reload'){
523            latepoint_add_notification(response.message);
524            location.reload();
525            return;
526          }else if($this.data('os-success-action') == 'redirect'){
527            if($this.data('os-redirect-to')){
528              latepoint_add_notification(response.message);
529              window.location.replace($this.data('os-redirect-to'));
530            }else{
531              window.location.replace(response.message); 
532            }
533            return;
534          }else if($this.data('os-output-target') && $($this.data('os-output-target')).length){
535            if($this.data('os-output-target-do') == 'append') {
536              $($this.data('os-output-target')).append(response.message);
537            }else if($this.data('os-output-target-do') == 'prepend'){
538              $($this.data('os-output-target')).prepend(response.message);
539            }else{
540              $($this.data('os-output-target')).html(response.message);
541            }
542          }else{
543            switch($this.data('os-before-after')){
544              case 'before':
545                $this.before(response.message);
546                break;
547              case 'after':
548                $this.after(response.message);
549                break;
550              case 'replace':
551                $this.replaceWith(response.message);
552                break;
553              case 'none':
554                break;
555              default:
556                latepoint_add_notification(response.message);
557            }
558          }
559          if($this.data('os-after-call')){
560            var func_name = $this.data('os-after-call');
561            var callback = false;
562            if(func_name.includes('.')){
563              var func_arr = func_name.split('.');
564              if(typeof window[func_arr[0]][func_arr[1]] !== 'function'){
565                console.log(func_name + ' is undefined');
566              }
567              if($this.data('os-pass-this') && $this.data('os-pass-response')){
568                window[func_arr[0]][func_arr[1]]($this, response);
569              }else if($this.data('os-pass-this')){
570                window[func_arr[0]][func_arr[1]]($this);
571              }else if($this.data('os-pass-response')){
572                window[func_arr[0]][func_arr[1]](response);
573              }else{
574                window[func_arr[0]][func_arr[1]]();
575              }
576            }else{
577              if(typeof window[func_name] !== 'function'){
578                console.log(func_name + ' is undefined');
579              }
580              if($this.data('os-pass-this') && $this.data('os-pass-response')){
581                window[func_name]($this, response);
582              }else if($this.data('os-pass-this')){
583                window[func_name]($this);
584              }else if($this.data('os-pass-response')){
585                window[func_name](response);
586              }else{
587                window[func_name]();
588              }
589            }
590          }
591          $this.removeClass('os-loading');
592        }else{
593          $this.removeClass('os-loading');
594          if($this.data('os-output-target') && $($this.data('os-output-target')).length){
595            $($this.data('os-output-target')).prepend(latepoint_generate_form_message_html(response.message, 'error'));
596          }else{
597            alert(response.message);
598          }
599          if($this.data('os-after-call-error')){
600            var func_name = $this.data('os-after-call-error');
601            var callback = false;
602            if(func_name.includes('.')){
603              var func_arr = func_name.split('.');
604              if(typeof window[func_arr[0]][func_arr[1]] !== 'function'){
605                console.log(func_name + ' is undefined');
606              }
607              if($this.data('os-pass-this') && $this.data('os-pass-response')){
608                window[func_arr[0]][func_arr[1]]($this, response);
609              }else if($this.data('os-pass-this')){
610                window[func_arr[0]][func_arr[1]]($this);
611              }else if($this.data('os-pass-response')){
612                window[func_arr[0]][func_arr[1]](response);
613              }else{
614                window[func_arr[0]][func_arr[1]]();
615              }
616            }else{
617              if(typeof window[func_name] !== 'function'){
618                console.log(func_name + ' is undefined');
619              }
620              if($this.data('os-pass-this') && $this.data('os-pass-response')){
621                window[func_name]($this, response);
622              }else if($this.data('os-pass-this')){
623                window[func_name]($this);
624              }else if($this.data('os-pass-response')){
625                window[func_name](response);
626              }else{
627                window[func_name]();
628              }
629            }
630          }
631        }
632      }
633    });
634    return false;
635  });
636
637
638  $('.latepoint').on('click', 'form[data-os-action] button[type="submit"]', function(e){
639    $(this).addClass('os-loading');
640  });
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658  /* 
659    Form ajax submit action
660  */
661  $('.latepoint').on('submit', 'form[data-os-action]', function(e){
662    e.preventDefault(); // prevent native submit
663      var $form = $(this);
664      var form_data = new FormData($form[0]);
665
666    if (('intlTelInputGlobals' in window) && ('intlTelInputUtils' in window)) {
667      // Get e164 formatted number from phone fields when form is submitted
668      $form.find('input.os-mask-phone').each(function () {
669        let telInstance = window.intlTelInputGlobals.getInstance(this);
670        if(telInstance){
671          const phoneInputName = this.getAttribute('name');
672          const phoneInputValue = window.intlTelInputGlobals.getInstance(this).getNumber(window.intlTelInputUtils.numberFormat.E164);
673          form_data.set(phoneInputName, phoneInputValue);
674        }
675      });
676    }
677
678    var data = { action: 'latepoint_route_call', route_name: $(this).data('os-action'), params: latepoint_formdata_to_url_encoded_string(form_data), return_format: 'json' }
679    $form.find('button[type="submit"]').addClass('os-loading');
680    $.ajax({
681      type : "post",
682      dataType : "json",
683      url : latepoint_timestamped_ajaxurl(),
684      data : data,
685      success: function(response){
686        $form.find('button[type="submit"].os-loading').removeClass('os-loading');
687        latepoint_clear_form_messages($form);
688        if(response.status === "success"){
689          if($form.data('os-success-action') == 'reload'){
690            latepoint_add_notification(response.message);
691            location.reload();
692            return;
693          }else if($form.data('os-success-action') == 'redirect'){
694            if($form.data('os-redirect-to')){
695              latepoint_add_notification(response.message);
696              window.location.replace($form.data('os-redirect-to'));
697            }else{
698              window.location.replace(response.message);
699            }
700            return;
701          }else if($form.data('os-output-target') && $($form.data('os-output-target')).length){
702            $($form.data('os-output-target')).html(response.message);
703          }else{
704            if(response.message == 'redirect'){
705              window.location.replace(response.url);
706            }else{
707              latepoint_add_notification(response.message);
708              $form.prepend(latepoint_generate_form_message_html(response.message, 'success'));
709            }
710          }
711          if($form.data('os-record-id-holder') && response.record_id){
712            $form.find('[name="' + $form.data('os-record-id-holder') + '"]').val(response.record_id)
713          }
714          if($form.data('os-after-call')){
715
716            var func_name = $form.data('os-after-call');
717            var callback = false;
718            if(func_name.includes('.')){
719              var func_arr = func_name.split('.');
720              if(typeof window[func_arr[0]][func_arr[1]] !== 'function'){
721                console.log(func_name + ' is undefined');
722              }
723              if($form.data('os-pass-this') && $form.data('os-pass-response')){
724                window[func_arr[0]][func_arr[1]]($form, response);
725              }else if($form.data('os-pass-this')){
726                window[func_arr[0]][func_arr[1]]($form);
727              }else if($form.data('os-pass-response')){
728                window[func_arr[0]][func_arr[1]](response);
729              }else{
730                window[func_arr[0]][func_arr[1]]();
731              }
732            }else{
733              if(typeof window[func_name] !== 'function'){
734                console.log(func_name + ' is undefined');
735              }
736              if($form.data('os-pass-this') && $form.data('os-pass-response')){
737                window[func_name]($form, response);
738              }else if($form.data('os-pass-this')){
739                window[func_name]($form);
740              }else if($form.data('os-pass-response')){
741                window[func_name](response);
742              }else{
743                window[func_name]();
744              }
745            }
746          }
747          $('button.os-loading').removeClass('os-loading');
748        }else{
749          $('button.os-loading').removeClass('os-loading');
750          if($form.data('os-show-errors-as-notification')){
751            latepoint_add_notification(response.message, 'error');
752          }else{
753            $form.prepend(latepoint_generate_form_message_html(response.message, 'error'));
754            $([document.documentElement, document.body]).animate({
755                scrollTop: ($form.find(".os-form-message-w").offset().top - 30)
756            }, 200);
757          }
758        }
759        if(response.form_values_to_update){
760          $.each(response.form_values_to_update, function(name, value){
761            $form.find('[name="'+ name +'"]').val(value);
762          });
763        }
764      }
765    });
766    return false;
767  });
768});
769
770/*
771 * Copyright (c) 2022 LatePoint LLC. All rights reserved.
772 */
773
774
775function latepoint_init_order_summary_lightbox() {
776
777}
778
779function latepoint_init_bundle_scheduling_summary() {
780
781}
782
783
784function latepoint_manage_by_key_reload_booking() {
785    let $wrapper = jQuery('.manage-booking-wrapper');
786    $wrapper.addClass('os-loading')
787    let params = {
788        key: $wrapper.data('key')
789    }
790    let data = {
791        action: latepoint_helper.route_action,
792        route_name: $wrapper.data('route-name'),
793        params: params,
794        layout: 'none',
795        return_format: 'json'
796    };
797
798    jQuery.ajax({
799        type: "post",
800        dataType: "json",
801        url: latepoint_timestamped_ajaxurl(),
802        data: data,
803        success: function (data) {
804            $wrapper.removeClass('os-loading')
805            if (data.status === "success") {
806                $wrapper.replaceWith(data.message);
807            } else {
808                latepoint_show_message_inside_element(data.message, $wrapper, 'error');
809            }
810        }
811    });
812}
813
814function latepoint_init_manage_booking_by_key() {
815    let $wrapper = jQuery('.manage-booking-wrapper');
816    if (!$wrapper.length) return;
817    jQuery('.latepoint-w').on('change', '.change-booking-status-trigger', function () {
818        $wrapper.addClass('os-loading')
819        let params = {
820            key: $wrapper.data('key'),
821            status: jQuery(this).val()
822        }
823        let data = {
824            action: latepoint_helper.route_action,
825            route_name: jQuery(this).closest('.change-booking-status-trigger-wrapper').data('route-name'),
826            params: params,
827            layout: 'none',
828            return_format: 'json'
829        };
830
831        jQuery.ajax({
832            type: "post",
833            dataType: "json",
834            url: latepoint_timestamped_ajaxurl(),
835            data: data,
836            success: function (data) {
837                $wrapper.removeClass('os-loading')
838                if (data.status === "success") {
839                    latepoint_manage_by_key_reload_booking();
840                } else {
841                    latepoint_show_message_inside_element(data.message, $wrapper, 'error');
842                }
843            }
844        });
845        return false;
846    });
847
848
849    $wrapper.on('click', '.qr-show-trigger', function () {
850        jQuery(this).closest('.manage-booking-wrapper').find('.qr-code-on-full-summary').addClass('show-vevent-qr-code');
851        return false;
852    });
853    $wrapper.on('click', '.os-item-details-popup-close', function () {
854        var $wrapper = jQuery(this).closest('.manage-booking-wrapper');
855        $wrapper.find('.os-item-details-popup.open').remove();
856        $wrapper.find('.manage-booking-inner, .manage-booking-controls').show();
857        return false;
858    });
859
860    $wrapper.on('click', '.os-trigger-item-details-popup', function () {
861        var $wrapper = jQuery(this).closest('.manage-booking-wrapper');
862        $wrapper.find('.manage-booking-inner, .manage-booking-controls').hide();
863        $wrapper.find('.os-item-details-popup.open').remove();
864        var $popup = $wrapper.find('#' + jQuery(this).data('item-details-popup-id')).clone();
865        $popup.addClass('open').appendTo($wrapper);
866        return false;
867    });
868}
869
870function latepoint_init_form_masks() {
871    if (('intlTelInput' in window) && ('intlTelInputGlobals' in window)) {
872        jQuery('.os-mask-phone').each(function () {
873            latepoint_mask_phone(jQuery(this));
874        });
875    }
876}
877
878function latepoint_scroll_to_top_of_booking_form($booking_form_element) {
879    // if it's a form shortcode (not lightbox), scroll to top of the form
880    if ($booking_form_element.parent().hasClass('latepoint-inline-form')) {
881        $booking_form_element[0].scrollIntoView({block: "nearest", behavior: 'smooth'}); // SHOULD NOT BE FIRST!! Also need to FIX, scroll only if TOP of the booking form is above the viewport
882    }
883    // if lightbox - scroll body of lightbox to top
884    if ($booking_form_element.parent().hasClass('latepoint-lightbox-i')) {
885        $booking_form_element.find('.latepoint-body').scrollTop(0);
886    }
887}
888
889async function latepoint_init_payment_method_actions($booking_form_element, payment_method) {
890    let callbacks_list = [];
891    let is_last_step = $booking_form_element.data('next-submit-is-last') == 'yes';
892    $booking_form_element.trigger('latepoint:initPaymentMethod', [{
893        payment_method: payment_method,
894        callbacks_list: callbacks_list,
895        is_last_step: is_last_step
896    }]);
897    $booking_form_element.removeClass('step-content-loaded').addClass('step-content-loading');
898
899
900    try {
901        for (const callback of callbacks_list) {
902            await callback.action();
903        }
904        $booking_form_element.removeClass('step-content-loading').addClass('step-content-loaded').find('.lp-payment-method-content[data-payment-method="' + payment_method + '"]').show();
905    } catch (error) {
906        latepoint_show_error_and_stop_loading_booking_form(error, $booking_form_element);
907    }
908}
909
910function latepoint_lightbox_close() {
911    jQuery('body').removeClass('latepoint-lightbox-active');
912    jQuery('.latepoint-lightbox-w').remove();
913}
914
915function latepoint_show_next_btn($booking_form_element) {
916    $booking_form_element.find('.latepoint-next-btn').removeClass('disabled');
917    $booking_form_element.removeClass('hidden-buttons');
918}
919
920function clear_step_services($booking_form_element) {
921}
922
923function clear_step_service_extras($booking_form_element) {
924}
925
926function clear_step_locations($booking_form_element) {
927}
928
929function clear_step_agents($booking_form_element) {
930}
931
932function clear_step_datepicker($booking_form_element) {
933}
934
935function latepoint_hide_next_btn($booking_form_element) {
936    $booking_form_element.find('.latepoint-next-btn').addClass('disabled');
937    if ($booking_form_element.find('.latepoint-prev-btn.disabled').length) $booking_form_element.addClass('hidden-buttons');
938}
939
940
941function latepoint_show_prev_btn($booking_form_element) {
942    $booking_form_element.find('.latepoint-prev-btn').removeClass('disabled');
943    $booking_form_element.removeClass('hidden-buttons');
944}
945
946function latepoint_hide_prev_btn($booking_form_element) {
947    $booking_form_element.find('.latepoint-prev-btn').addClass('disabled');
948    if ($booking_form_element.find('.latepoint-next-btn.disabled').length) $booking_form_element.addClass('hidden-buttons');
949}
950
951
952function latepoint_remove_cart_item($trigger) {
953    let $booking_form_element = $trigger.closest('.latepoint-booking-form-element');
954    let cart_item_id = $trigger.data('cart-item-id');
955
956
957    $trigger.addClass('os-loading');
958    let data = {
959        action: latepoint_helper.route_action,
960        route_name: $trigger.data('route'),
961        params: jQuery.param({cart_item_id: cart_item_id}),
962        layout: 'none',
963        return_format: 'json'
964    }
965    jQuery.ajax({
966        type: "post",
967        dataType: "json",
968        url: latepoint_timestamped_ajaxurl(),
969        data: data,
970        success: function (data) {
971            if (data.status === "success") {
972                if(cart_item_id != $booking_form_element.find('input[name="active_cart_item[id]"]').val()){
973                    // cart has other items - just reload the summary/step
974                    if ($trigger.closest('.latepoint-summary-w').length) {
975                        // removed by clicking on summary side panel
976                        latepoint_reload_summary($booking_form_element);
977                    } else {
978                        // remove by clicking on cart item on verify step
979                        latepoint_reload_step($booking_form_element);
980                    }
981                }else{
982                    // this was a last item, need to go back to the start of a booking process
983                    latepoint_restart_booking_process($booking_form_element);
984                }
985            } else {
986                $trigger.removeClass('os-loading');
987                latepoint_show_message_inside_element(data.message, $booking_form_element.find('.latepoint-body'), 'error');
988            }
989        }
990    });
991}
992
993function latepoint_apply_coupon($elem) {
994    var $booking_form_element = $elem.closest('.latepoint-booking-form-element');
995
996    var $coupon_input = $elem;
997    $coupon_input.closest('.coupon-code-input-w').addClass('os-loading');
998    var form_data = new FormData($booking_form_element.find('.latepoint-form')[0]);
999    var data = {
1000        action: latepoint_helper.route_action,
1001        route_name: $elem.data('route'),
1002        params: latepoint_formdata_to_url_encoded_string(form_data),
1003        layout: 'none',
1004        return_format: 'json'
1005    }
1006    jQuery.ajax({
1007        type: "post",
1008        dataType: "json",
1009        url: latepoint_timestamped_ajaxurl(),
1010        data: data,
1011        success: function (data) {
1012            $coupon_input.closest('.coupon-code-input-w').removeClass('os-loading');
1013            if (data.status === "success") {
1014                latepoint_show_message_inside_element(data.message, $booking_form_element.find('.latepoint-body'), 'success');
1015                $booking_form_element.find('.step-payment-w input[name="cart[payment_method]"]').val('');
1016                $booking_form_element.find('input[name="cart[payment_token]"]').val('');
1017                $booking_form_element.find('input[name="cart[payment_portion]"]').val('');
1018                latepoint_reload_step($booking_form_element);
1019            } else {
1020                latepoint_show_message_inside_element(data.message, $booking_form_element.find('.latepoint-body'), 'error');
1021            }
1022        }
1023    });
1024}
1025
1026function latepoint_remove_coupon($elem) {
1027    $elem.closest('.applied-coupon-code-wrapper').fadeOut();
1028    var $booking_form_element = $elem.closest('.latepoint-booking-form-element');
1029    let $coupon_input = $booking_form_element.find('input[name="coupon_code"]');
1030    $coupon_input.val('');
1031    latepoint_apply_coupon($coupon_input);
1032}
1033
1034function latepoint_restart_booking_process($booking_form_element) {
1035    // first first step
1036    let first_step_code = $booking_form_element.find('.latepoint-step-content').first().data('step-code');
1037    latepoint_reload_step($booking_form_element, first_step_code);
1038    return false;
1039}
1040
1041function latepoint_reload_step($booking_form_element, step_code = false) {
1042
1043    if (step_code) {
1044        $booking_form_element.find('.latepoint_current_step_code').val(step_code);
1045        $booking_form_element.removeClass(function (index, className) {
1046            return (className.match(/(^|\s)current-step-\S+/g) || []).join(' ');
1047        }).addClass('current-step-' + step_code);
1048        if ($booking_form_element.find('.latepoint-step-content[data-step-code="' + step_code + '"]')) {
1049            $booking_form_element.find('.latepoint-step-content[data-step-code="' + step_code + '"]').nextAll('.latepoint-step-content').remove();
1050            $booking_form_element.find('.latepoint-step-content[data-step-code="' + step_code + '"]').remove();
1051        }
1052    }
1053
1054    $booking_form_element.find('.latepoint_step_direction').val('specific');
1055    latepoint_submit_booking_form($booking_form_element.find('.latepoint-form'));
1056
1057    return false;
1058}
1059
1060
1061function latepoint_reset_password_from_booking_init() {
1062    jQuery('.os-step-existing-customer-login-w').hide();
1063    jQuery('.os-password-reset-form-holder').on('click', '.password-reset-back-to-login', function () {
1064        jQuery('.os-password-reset-form-holder').html('');
1065        jQuery('.os-step-existing-customer-login-w').show();
1066        return false;
1067    });
1068}
1069
1070function latepoint_bundle_selected($item) {
1071    let $booking_form_element = $item.closest('.latepoint-booking-form-element');
1072    $booking_form_element.find('input[name="active_cart_item[variant]"]').val('bundle');
1073    $booking_form_element.find('input[name="booking[service_id]"]').val('');
1074}
1075
1076function latepoint_service_selected($item) {
1077    let $booking_form_element = $item.closest('.latepoint-booking-form-element');
1078    $booking_form_element.find('input[name="active_cart_item[variant]"]').val('booking');
1079}
1080
1081async function latepoint_reload_summary($booking_form_element) {
1082    let $summary_panel = $booking_form_element.closest('.latepoint-with-summary');
1083    if (!$summary_panel.length) return;
1084
1085    let current_step = $booking_form_element.find('.latepoint_current_step_code').val();
1086
1087    $booking_form_element.find('.latepoint-summary-w').addClass('os-loading');
1088    let $booking_form = $booking_form_element.find('.latepoint-form');
1089    let form_data = new FormData($booking_form[0]);
1090    let data = {
1091        action: latepoint_helper.route_action,
1092        route_name: latepoint_helper.reload_booking_form_summary_route,
1093        params: latepoint_formdata_to_url_encoded_string(form_data),
1094        layout: 'none',
1095        return_format: 'json'
1096    }
1097
1098    let response = await jQuery.ajax({
1099        type: "post",
1100        dataType: "json",
1101        url: latepoint_timestamped_ajaxurl(),
1102        data: data
1103    });
1104    if(response.status === 'success'){
1105        $booking_form_element.find('.os-summary-contents').html(response.message);
1106        $booking_form_element.find('.latepoint-summary-w').removeClass('os-loading');
1107        // hide on verify and confirmation steps
1108        if (current_step && !['verify', 'confirmation'].includes(current_step) && response.message) {
1109            $summary_panel.addClass('latepoint-summary-is-open');
1110        } else {
1111            $summary_panel.removeClass('latepoint-summary-is-open');
1112        }
1113        latepoint_init_booking_summary_panel($booking_form_element);
1114    }else{
1115        throw new Error(response.message ? response.message : 'Error reloading summary');
1116    }
1117}
1118
1119function latepoint_init_booking_summary_panel($booking_form_element) {
1120    let $summary_panel = $booking_form_element.find('.latepoint-summary-w');
1121    if (!$summary_panel.length) return;
1122
1123    $summary_panel.find('.price-breakdown-unfold').on('click', function () {
1124        jQuery(this).closest('.summary-price-breakdown-wrapper').removeClass('compact-summary');
1125        return false;
1126    });
1127
1128    $summary_panel.find('.os-remove-item-from-cart').on('click keydown', function (event) {
1129        if(event.type === 'keydown' && event.key !== ' ' &&  event.key !== 'Enter') return;
1130        latepoint_remove_cart_item(jQuery(this));
1131        return false;
1132    });
1133
1134    $summary_panel.find('.latepoint-lightbox-summary-trigger').on('click', function () {
1135        var $wrapper = jQuery(this).closest('.latepoint-w');
1136        $wrapper.toggleClass('show-summary-on-mobile');
1137        return false;
1138    });
1139}
1140
1141function latepoint_password_changed_show_login(response) {
1142    jQuery('.os-step-existing-customer-login-w').show();
1143    jQuery('.os-password-reset-form-holder').html('');
1144    latepoint_show_message_inside_element(response.message, jQuery('.os-step-existing-customer-login-w'), 'success');
1145}
1146
1147function latepoint_hide_message_inside_element($elem = jQuery('.latepoint-body')) {
1148    if ($elem.length && $elem.find('.latepoint-message').length) {
1149        $elem.find('.latepoint-message').remove();
1150    }
1151}
1152
1153function latepoint_show_message_inside_element(message, $elem = jQuery('.latepoint-body'), message_type = 'error') {
1154    message = message || 'Error. Please try again.';
1155    if ($elem.length) {
1156        if ($elem.find('.latepoint-message').length) {
1157            $elem.find('.latepoint-message').removeClass('latepoint-message-success').removeClass('latepoint-message-error').addClass('latepoint-message-' + message_type + '').html(message).show();
1158        } else {
1159            $elem.prepend('<div class="latepoint-message latepoint-message-' + message_type + '">' + message + '</div>');
1160        }
1161        // scroll errors into view
1162        if (message_type == 'error') $elem.find('.latepoint-message')[0].scrollIntoView();
1163    }
1164}
1165
1166function latepoint_add_action(callbacks_list, action, priority = 10) {
1167    callbacks_list.push({priority: priority, action: action});
1168    callbacks_list.sort((a, b) => a.priority - b.priority);
1169    return callbacks_list;
1170}
1171
1172function latepoint_update_next_btn_label($booking_form_element) {
1173    let btn_label = $booking_form_element.find('.latepoint-step-content').last().data('next-btn-label')
1174    if (btn_label) {
1175        $booking_form_element.find('.latepoint-next-btn span').text(btn_label);
1176    }
1177}
1178
1179function latepoint_init_step(step_code, $booking_form_element) {
1180    latepoint_init_step_selectable_items($booking_form_element);
1181    latepoint_init_step_category_items(step_code);
1182    switch (step_code) {
1183        case 'customer':
1184            latepoint_init_step_contact();
1185            break;
1186        case 'booking__datepicker':
1187            latepoint_init_step_datepicker($booking_form_element);
1188            break;
1189        case 'booking__agents':
1190            latepoint_init_step_agents();
1191            break;
1192        case 'booking__locations':
1193            latepoint_init_step_locations();
1194            break;
1195        case 'booking__services':
1196            latepoint_init_step_services();
1197            break;
1198        case 'payment__methods':
1199            latepoint_init_step_payment__methods($booking_form_element);
1200            break;
1201        case 'payment__times':
1202            latepoint_init_step_payment__times($booking_form_element);
1203            break;
1204        case 'payment__portions':
1205            latepoint_init_step_payment__portions($booking_form_element);
1206            break;
1207        case 'payment__pay':
1208            latepoint_init_step_payment__pay($booking_form_element);
1209            break;
1210        case 'verify':
1211            latepoint_init_step_verify($booking_form_element);
1212            break;
1213        case 'confirmation':
1214            latepoint_init_step_confirmation($booking_form_element);
1215            break;
1216    }
1217
1218    $booking_form_element.trigger("latepoint:initStep", [{step_code: step_code}]);
1219    $booking_form_element.trigger("latepoint:initStep:" + step_code);
1220}
1221
1222
1223function day_timeslots($day, $wrapper_element = false, $scrollable_wrapper = false) {
1224    if (!$wrapper_element) $wrapper_element = $day.closest('.latepoint-booking-form-element');
1225    $day.addClass('selected');
1226
1227    var service_duration = $day.data('service-duration');
1228    var interval = $day.data('interval');
1229    var work_start_minutes = $day.data('work-start-time');
1230    var work_end_minutes = $day.data('work-end-time');
1231    var total_work_minutes = $day.data('total-work-minutes');
1232    var bookable_minutes = [];
1233    var available_capacities_of_bookable_minute = [];
1234    if ($day.attr('data-bookable-minutes')) {
1235        if ($day.data('bookable-minutes').toString().indexOf(':') > -1) {
1236            // has capacity information embedded into bookable minutes string
1237            let bookable_minutes_with_capacity = $day.data('bookable-minutes').toString().split(',');
1238            for (let i = 0; i < bookable_minutes_with_capacity.length; i++) {
1239                bookable_minutes.push(parseInt(bookable_minutes_with_capacity[i].split(':')[0]));
1240                available_capacities_of_bookable_minute.push(parseInt(bookable_minutes_with_capacity[i].split(':')[1]));
1241            }
1242        } else {
1243            bookable_minutes = $day.data('bookable-minutes').toString().split(',').map(Number);
1244        }
1245    }
1246    var work_minutes = $day.data('work-minutes').toString().split(',').map(Number);
1247
1248    var $timeslots = $wrapper_element.find('.timeslots');
1249    $timeslots.html('');
1250
1251    if (total_work_minutes > 0 && bookable_minutes.length && work_minutes.length) {
1252        var prev_minutes = false;
1253        work_minutes.forEach(function (current_minutes) {
1254            var ampm = latepoint_am_or_pm(current_minutes);
1255
1256            var timeslot_class = 'dp-timepicker-trigger';
1257            var timeslot_available_capacity = 0;
1258            if (latepoint_helper.time_pick_style == 'timeline') {
1259                timeslot_class += ' dp-timeslot';
1260            } else {
1261                timeslot_class += ' dp-timebox';
1262            }
1263
1264            if (prev_minutes !== false && ((current_minutes - prev_minutes) > service_duration)) {
1265                // show interval that is off between two work periods
1266                var off_label = latepoint_minutes_to_hours_and_minutes(prev_minutes + service_duration) + ' ' + latepoint_am_or_pm(prev_minutes + service_duration) + ' - ' + latepoint_minutes_to_hours_and_minutes(current_minutes) + ' ' + latepoint_am_or_pm(current_minutes);
1267                var off_width = (((current_minutes - prev_minutes - service_duration) / total_work_minutes) * 100);
1268                $timeslots.append('<div class="' + timeslot_class + ' is-off" style="max-width:' + off_width + '%; width:' + off_width + '%"><span class="dp-label">' + off_label + '</span></div>');
1269            }
1270
1271            if (!bookable_minutes.includes(current_minutes)) {
1272                timeslot_class += ' is-booked';
1273            } else {
1274                if (available_capacities_of_bookable_minute.length) timeslot_available_capacity = available_capacities_of_bookable_minute[bookable_minutes.indexOf(current_minutes)];
1275            }
1276            var tick_html = '';
1277            var capacity_label = '';
1278            var capacity_label_html = '';
1279            var capacity_internal_label_html = '';
1280
1281            if (((current_minutes % 60) == 0) || (interval >= 60)) {
1282                timeslot_class += ' with-tick';
1283                tick_html = '<span class="dp-tick"><strong>' + latepoint_minutes_to_hours_preferably(current_minutes) + '</strong>' + ' ' + ampm + '</span>';
1284            }
1285            var timeslot_label = latepoint_minutes_to_hours_and_minutes(current_minutes) + ' ' + ampm;
1286            if (latepoint_show_booking_end_time()) {
1287                var end_minutes = current_minutes + service_duration;
1288                if (end_minutes > 1440) end_minutes = end_minutes - 1440;
1289                var end_minutes_ampm = latepoint_am_or_pm(end_minutes);
1290                timeslot_label += ' - <span class="dp-label-end-time">' + latepoint_minutes_to_hours_and_minutes(end_minutes) + ' ' + end_minutes_ampm + '</span>';
1291            }
1292            if (timeslot_available_capacity) {
1293                var spaces_message = timeslot_available_capacity > 1 ? latepoint_helper.many_spaces_message : latepoint_helper.single_space_message;
1294                capacity_label = timeslot_available_capacity + ' ' + spaces_message;
1295                capacity_label_html = '<span class="dp-capacity">' + capacity_label + '</span>';
1296                capacity_internal_label_html = '<span class="dp-label-capacity">' + capacity_label + '</span>';
1297            }
1298            timeslot_label = timeslot_label.trim();
1299            $timeslots.removeClass('slots-not-available').append('<div tabindex="0" class="' + timeslot_class + '" data-minutes="' + current_minutes + '"><span class="dp-label">' + capacity_internal_label_html + '<span class="dp-label-time">' + timeslot_label + '</span>' + '</span>' + tick_html + capacity_label_html + '</div>');
1300            prev_minutes = current_minutes;
1301        });
1302    } else {
1303        // No working hours this day
1304        $timeslots.addClass('slots-not-available').append('<div class="not-working-message">' + latepoint_helper.msg_not_available + "</div>");
1305    }
1306    jQuery('.times-header-label span').text($day.data('nice-date'));
1307    $wrapper_element.find('.time-selector-w').slideDown(200, function () {
1308        if (!$scrollable_wrapper) $scrollable_wrapper = $wrapper_element.find('.latepoint-body');
1309        $scrollable_wrapper.stop();
1310        $wrapper_element.find('.time-selector-w')[0].scrollIntoView({block: "nearest", behavior: 'smooth'});
1311    });
1312}
1313
1314
1315function latepoint_timeslot_clicked(event) {
1316    if(event.type === 'keydown' && event.key !== ' ' &&  event.key !== 'Enter') return;
1317    let $booking_form_element = jQuery(this).closest('.latepoint-booking-form-element');
1318    if (jQuery(this).hasClass('is-booked') || jQuery(this).hasClass('is-off')) {
1319        // Show error message that you cant select a booked period
1320    } else {
1321        if (jQuery(this).hasClass('selected')) {
1322            jQuery(this).removeClass('selected');
1323            jQuery(this).find('.dp-success-label').remove();
1324            $booking_form_element.find('.latepoint_start_time').val('');
1325            latepoint_hide_next_btn($booking_form_element);
1326            latepoint_reload_summary($booking_form_element);
1327        } else {
1328            $booking_form_element.find('.dp-timepicker-trigger.selected').removeClass('selected').find('.dp-success-label').remove();
1329            var selected_timeslot_time = jQuery(this).find('.dp-label-time').html();
1330            jQuery(this).addClass('selected').find('.dp-label').prepend('<span class="dp-success-label">' + latepoint_helper.datepicker_timeslot_selected_label + '</span>');
1331
1332            var minutes = parseInt(jQuery(this).data('minutes'));
1333            var timeshift_minutes = parseInt($booking_form_element.find('.latepoint_timeshift_minutes').val());
1334            // we substract timeshift minutes because its timeshift minutes that the business is running in, in opposite of what we do when we generate a calendar for a client
1335            if (timeshift_minutes) minutes = minutes - timeshift_minutes;
1336            var start_date = new Date($booking_form_element.find('.os-day.selected').data('date'));
1337            if (minutes < 0) {
1338                // business minutes are in previous day
1339                minutes = 24 * 60 + minutes;
1340                // move start date back 1 day
1341                start_date.setDate(start_date.getDate() - 1);
1342            } else if (minutes >= 24 * 60) {
1343                // business minutes are in next day
1344                minutes = minutes - 24 * 60;
1345                start_date.setDate(start_date.getDate() + 1);
1346            }
1347            $booking_form_element.find('.latepoint_start_date').val(start_date.toISOString().split('T')[0])
1348            $booking_form_element.find('.latepoint_start_time').val(minutes);
1349            latepoint_trigger_next_btn($booking_form_element);
1350        }
1351    }
1352    return false;
1353}
1354
1355function latepoint_init_timeslots($booking_form_element = false) {
1356    if (!$booking_form_element) return;
1357    $booking_form_element.off('click', '.dp-timepicker-trigger', latepoint_timeslot_clicked);
1358    $booking_form_element.on('click', '.dp-timepicker-trigger', latepoint_timeslot_clicked);
1359    $booking_form_element.off('keydown', '.dp-timepicker-trigger', latepoint_timeslot_clicked);
1360    $booking_form_element.on('keydown', '.dp-timepicker-trigger', latepoint_timeslot_clicked);
1361}
1362
1363async function latepoint_monthly_calendar_load_next_month($booking_form_element){
1364    try {
1365
1366        if ($booking_form_element.find('.os-monthly-calendar-days-w.active + .os-monthly-calendar-days-w').length) {
1367            $booking_form_element.find('.os-monthly-calendar-days-w.active').removeClass('active').next('.os-monthly-calendar-days-w').addClass('active');
1368            latepoint_calendar_set_month_label($booking_form_element);
1369            return true;
1370        } else {
1371            let $btn = $booking_form_element.find('.os-month-next-btn');
1372            let next_month_route_name = $btn.data('route');
1373                $btn.addClass('os-loading');
1374                let $calendar_element = $booking_form_element.find('.os-monthly-calendar-days-w').last();
1375                let calendar_year = $calendar_element.data('calendar-year');
1376                let calendar_month = $calendar_element.data('calendar-month');
1377                if (calendar_month == 12) {
1378                    calendar_year = calendar_year + 1;
1379                    calendar_month = 1;
1380                } else {
1381                    calendar_month = calendar_month + 1;
1382                }
1383                let form_data = new FormData($booking_form_element.find('.latepoint-form')[0]);
1384                form_data.set('target_date_string', `${calendar_year}-${calendar_month}-1`);
1385                let params = latepoint_formdata_to_url_encoded_string(form_data);
1386                let data = {
1387                    action: latepoint_helper.route_action,
1388                    route_name: next_month_route_name,
1389                    params: params,
1390                    layout: 'none',
1391                    return_format: 'json'
1392                }
1393                let response = await jQuery.ajax({
1394                    type: "post",
1395                    dataType: "json",
1396                    url: latepoint_timestamped_ajaxurl(),
1397                    data: data,
1398                    success: function (data) {
1399                    }
1400                });
1401                $btn.removeClass('os-loading');
1402                if (response.status === "success") {
1403                    $booking_form_element.find('.os-months').append(response.message);
1404                    $booking_form_element.find('.os-monthly-calendar-days-w.active').removeClass('active').next('.os-monthly-calendar-days-w').addClass('active');
1405                    latepoint_calendar_set_month_label($booking_form_element);
1406                    latepoint_calendar_show_or_hide_prev_next_buttons($booking_form_element);
1407                    return true;
1408                } else {
1409                    console.log(response.message);
1410                    return false;
1411                }
1412
1413        }
1414    } catch (e) {
1415        console.log(e);
1416        alert('Error:' + e);
1417        return false;
1418    }
1419}
1420
1421function latepoint_init_monthly_calendar_navigation($booking_form_element = false) {
1422    if (!$booking_form_element) return;
1423    $booking_form_element.find('.os-month-next-btn').on('click', async function () {
1424        let $booking_form_element = jQuery(this).closest('.latepoint-booking-form-element');
1425        return latepoint_monthly_calendar_load_next_month($booking_form_element);
1426    });
1427    $booking_form_element.find('.os-month-prev-btn').on('click', function () {
1428        var $booking_form_element = jQuery(this).closest('.latepoint-booking-form-element');
1429        if ($booking_form_element.find('.os-monthly-calendar-days-w.active').prev('.os-monthly-calendar-days-w').length) {
1430            $booking_form_element.find('.os-monthly-calendar-days-w.active').removeClass('active').prev('.os-monthly-calendar-days-w').addClass('active');
1431            latepoint_calendar_set_month_label($booking_form_element);
1432        }
1433        latepoint_calendar_show_or_hide_prev_next_buttons($booking_form_element);
1434        return false;
1435    });
1436}
1437
1438function latepoint_calendar_set_month_label($booking_form_element) {
1439    $booking_form_element.find('.os-current-month-label .current-year').text($booking_form_element.find('.os-monthly-calendar-days-w.active').data('calendar-year'));
1440    $booking_form_element.find('.os-current-month-label .current-month').text($booking_form_element.find('.os-monthly-calendar-days-w.active').data('calendar-month-label'));
1441}
1442
1443
1444function latepoint_calendar_show_or_hide_prev_next_buttons($booking_form_element) {
1445    $booking_form_element.find('.os-current-month-label .current-year').text($booking_form_element.find('.os-monthly-calendar-days-w.active .os-monthly-calendar-days').data('calendar-year'));
1446    $booking_form_element.find('.os-current-month-label .current-month').text($booking_form_element.find('.os-monthly-calendar-days-w.active .os-monthly-calendar-days').data('calendar-month-label'));
1447
1448    if ($booking_form_element.find('.os-monthly-calendar-days-w.active').prev('.os-monthly-calendar-days-w').length) {
1449        $booking_form_element.find('.os-month-prev-btn').removeClass('disabled');
1450    } else {
1451        $booking_form_element.find('.os-month-prev-btn').addClass('disabled');
1452    }
1453}
1454
1455function latepoint_format_minutes_to_time(minutes, service_duration) {
1456    var ampm = latepoint_am_or_pm(minutes);
1457    var formatted_time = latepoint_minutes_to_hours_and_minutes(minutes) + ' ' + ampm;
1458    if (latepoint_show_booking_end_time()) {
1459        var end_minutes = minutes + service_duration;
1460        var end_minutes_ampm = latepoint_am_or_pm(end_minutes);
1461        formatted_time += ' - ' + latepoint_minutes_to_hours_and_minutes(end_minutes) + ' ' + end_minutes_ampm;
1462    }
1463    formatted_time = formatted_time.trim();
1464    return formatted_time;
1465}
1466
1467function latepoint_monthly_calendar_day_clicked(event) {
1468    if(event.type === 'keydown' && event.key !== ' ' &&  event.key !== 'Enter') return;
1469    if (jQuery(this).hasClass('os-day-passed')) return false;
1470    if (jQuery(this).hasClass('os-not-in-allowed-period')) return false;
1471    var $booking_form_element = jQuery(this).closest('.latepoint-booking-form-element');
1472    if (jQuery(this).closest('.os-monthly-calendar-days-w').hasClass('hide-if-single-slot')) {
1473        // HIDE TIMESLOT IF ONLY ONE TIMEPOINT
1474        if (jQuery(this).hasClass('os-not-available')) {
1475            // clicked on a day that has no available timeslots
1476            // do nothing
1477        } else {
1478            $booking_form_element.find('.os-day.selected').removeClass('selected');
1479            jQuery(this).addClass('selected');
1480            // set date
1481            $booking_form_element.find('.latepoint_start_date').val(jQuery(this).data('date'));
1482            if (jQuery(this).hasClass('os-one-slot-only')) {
1483                // clicked on a day that has only one slot available
1484                var bookable_minutes = jQuery(this).data('bookable-minutes').toString().split(':')[0];
1485                var selected_timeslot_time = latepoint_format_minutes_to_time(Number(bookable_minutes), Number(jQuery(this).data('service-duration')));
1486                $booking_form_element.find('.latepoint_start_time').val(jQuery(this).data('bookable-minutes'));
1487                latepoint_show_next_btn($booking_form_element);
1488                $booking_form_element.find('.time-selector-w').slide
1488Up(200);
1489            } else {
1490                // regular day with more than 1 timeslots available
1491                // build timeslots
1492                day_timeslots(jQuery(this));
1493                // clear time and hide next btn
1494                $booking_form_element.find('.latepoint_start_time').val('');
1495                latepoint_hide_next_btn($booking_form_element);
1496            }
1497            latepoint_reload_summary($booking_form_element);
1498        }
1499    } else {
1500
1501        // SHOW TIMESLOTS EVEN IF ONLY ONE TIMEPOINT
1502        $booking_form_element.find('.latepoint_start_date').val(jQuery(this).data('date'));
1503        $booking_form_element.find('.os-day.selected').removeClass('selected');
1504        jQuery(this).addClass('selected');
1505
1506        // build timeslots
1507        day_timeslots(jQuery(this));
1508        // clear time and hide next btn
1509        latepoint_reload_summary($booking_form_element);
1510        $booking_form_element.find('.latepoint_start_time').val('');
1511        latepoint_hide_next_btn($booking_form_element);
1512    }
1513
1514
1515    return false;
1516}
1517
1518async function latepoint_init_step_datepicker($booking_form_element = false) {
1519    if (!$booking_form_element) return true;
1520    latepoint_init_timeslots($booking_form_element);
1521    latepoint_init_monthly_calendar_navigation($booking_form_element);
1522    $booking_form_element.off('click', '.os-months .os-day', latepoint_monthly_calendar_day_clicked);
1523    $booking_form_element.on('click', '.os-months .os-day', latepoint_monthly_calendar_day_clicked);
1524    $booking_form_element.off('keydown', '.os-months .os-day', latepoint_monthly_calendar_day_clicked);
1525    $booking_form_element.on('keydown', '.os-months .os-day', latepoint_monthly_calendar_day_clicked);
1526    if ($booking_form_element.find('input[name="booking[start_date]"]').val()){
1527        $booking_form_element.find('.os-day[data-date="' + $booking_form_element.find('input[name="booking[start_date]"]').val() + '"]').trigger('click');
1528    }else{
1529        let max_number_of_months_to_check = 24;
1530        let current_year = new Date().getFullYear();
1531        for (let i = 0; i < max_number_of_months_to_check; i++){
1532            let $active_month = $booking_form_element.find('.os-monthly-calendar-days-w.active');
1533            let searching_month_label = $active_month.data('calendar-month-label');
1534            if($active_month.data('calendar-year') != current_year) searching_month_label+= ' '+$active_month.data('calendar-year');
1535            $booking_form_element.find('.os-calendar-searching-info span').text(searching_month_label);
1536            // check if active month has any days available for booking
1537            let $first_available = $active_month.find('.os-day').not('.os-not-available').first();
1538            if($first_available.length){
1539                break;
1540            }else{
1541                await latepoint_monthly_calendar_load_next_month($booking_form_element);
1542            }
1543        }
1544        $booking_form_element.find('.os-dates-w').removeClass('is-searching');
1545    }
1546    return true;
1547}
1548
1549
1550function latepoint_init_step_verify($booking_form_element = false) {
1551    if (!$booking_form_element) return;
1552    $booking_form_element.closest('.latepoint-summary-is-open').removeClass('latepoint-summary-is-open');
1553
1554    $booking_form_element.find('.coupon-code-wrapper-on-verify .coupon-code-trigger-on-verify-w a').on('click', function (e) {
1555        jQuery(this).closest('.coupon-code-wrapper-on-verify').addClass('entering-coupon').find('.coupon-code-input').trigger('focus');
1556        return false;
1557    });
1558    $booking_form_element.find('.coupon-code-wrapper-on-verify .coupon-code-input-cancel').on('click', function (e) {
1559        jQuery(this).closest('.coupon-code-wrapper-on-verify').removeClass('entering-coupon');
1560        return false;
1561    });
1562
1563    $booking_form_element.find('.coupon-code-wrapper-on-verify .coupon-code-input-submit').on('click', function (e) {
1564        latepoint_apply_coupon(jQuery(this).closest('.coupon-code-input-w').find('.coupon-code-input'));
1565        return false;
1566    });
1567
1568    $booking_form_element.find('.os-remove-item-from-cart').on('click keydown', function (event) {
1569        if(event.type === 'keydown' && event.key !== ' ' &&  event.key !== 'Enter') return;
1570        //make sure to clear active cart item so it doesn't add it again on reload!
1571        if (confirm(jQuery(this).data('confirm-text'))) {
1572            latepoint_remove_cart_item(jQuery(this));
1573        }
1574        return false;
1575    });
1576
1577    $booking_form_element.find('.coupon-code-wrapper-on-verify .coupon-code-clear').on('click', function (e) {
1578        latepoint_remove_coupon(jQuery(this));
1579        return false;
1580    });
1581
1582    $booking_form_element.find('.coupon-code-wrapper-on-verify input.coupon-code-input').on('keyup', function (e) {
1583        if (e.which === 13) {
1584            latepoint_apply_coupon(jQuery(this));
1585            return false;
1586        }
1587    });
1588}
1589
1590
1591function latepoint_init_step_payment__pay($booking_form_element = false) {
1592    var selected_payment_method = $booking_form_element.find('input[name="cart[payment_method]"]').val();
1593    latepoint_init_payment_method_actions($booking_form_element, selected_payment_method);
1594}
1595
1596function latepoint_init_step_payment__portions($booking_form_element = false) {
1597    // Selecting Payment Time
1598    $booking_form_element.find('.lp-payment-trigger-payment-portion-selector').on('click keydown', function (event) {
1599        if(event.type === 'keydown' && event.key !== ' ' &&  event.key !== 'Enter') return;
1600        var $booking_form_element = jQuery(this).closest('.latepoint-booking-form-element');
1601        $booking_form_element.find('input[name="' + jQuery(this).data('holder') + '"]').val(jQuery(this).data('value'));
1602        latepoint_show_prev_btn($booking_form_element);
1603        latepoint_trigger_next_btn($booking_form_element);
1604        return false;
1605    });
1606}
1607
1608function latepoint_init_step_payment__times($booking_form_element = false) {
1609    // Selecting Payment Time
1610    $booking_form_element.find('.lp-payment-trigger-payment-time-selector').on('click keydown', function (event) {
1611        if(event.type === 'keydown' && event.key !== ' ' &&  event.key !== 'Enter') return;
1612        var $booking_form_element = jQuery(this).closest('.latepoint-booking-form-element');
1613        $booking_form_element.find('input[name="' + jQuery(this).data('holder') + '"]').val(jQuery(this).data('value'));
1614        latepoint_show_prev_btn($booking_form_element);
1615        latepoint_trigger_next_btn($booking_form_element);
1616        return false;
1617    });
1618}
1619
1620
1621function latepoint_init_step_payment__methods($booking_form_element = false) {
1622    // Selecting Payment Time
1623    $booking_form_element.find('.lp-payment-trigger-payment-method-selector').on('click', function (e) {
1624        var $booking_form_element = jQuery(this).closest('.latepoint-booking-form-element');
1625        $booking_form_element.find('input[name="' + jQuery(this).data('holder') + '"]').val(jQuery(this).data('value'));
1626        latepoint_show_prev_btn($booking_form_element);
1627        latepoint_trigger_next_btn($booking_form_element);
1628        return false;
1629    });
1630}
1631
1632function latepoint_category_item_clicked(event){
1633    if(event.type === 'keydown' && event.key !== ' ' &&  event.key !== 'Enter') return;
1634    let $item = jQuery(event.target);
1635
1636    let $booking_form_element = $item.closest('.latepoint-booking-form-element');
1637    latepoint_show_prev_btn($booking_form_element);
1638    $item.closest('.latepoint-step-content').addClass('selecting-item-category');
1639    let $category_wrapper = $item.closest('.os-item-category-w');
1640    let $main_parent = $item.closest('.os-item-categories-main-parent');
1641    if ($category_wrapper.hasClass('selected')) {
1642        $category_wrapper.removeClass('selected');
1643        if ($category_wrapper.parent().closest('.os-item-category-w').length) {
1644            $category_wrapper.parent().closest('.os-item-category-w').addClass('selected');
1645        } else {
1646            $main_parent.removeClass('show-selected-only');
1647        }
1648    } else {
1649        $main_parent.find('.os-item-category-w.selected').removeClass('selected');
1650        $main_parent.addClass('show-selected-only');
1651        $category_wrapper.addClass('selected');
1652    }
1653    return false;
1654}
1655
1656function latepoint_init_step_category_items(step_code) {
1657    let $category_items = jQuery('.latepoint-step-content[data-step-code="' + step_code + '"] .os-item-category-info');
1658    $category_items.on('click', latepoint_category_item_clicked);
1659    $category_items.on('keydown', latepoint_category_item_clicked);
1660}
1661
1662
1663function latepoint_init_step_selectable_items($booking_form_element) {
1664    $booking_form_element.off('click', '.os-selectable-items .os-selectable-item', latepoint_selectable_item_clicked);
1665    $booking_form_element.on('click', '.os-selectable-items .os-selectable-item', latepoint_selectable_item_clicked);
1666
1667    $booking_form_element.off('click', '.os-selectable-items .os-selectable-item .item-quantity-selector-input', latepoint_selectable_item_quantity_keyup);
1668    $booking_form_element.on('click', '.os-selectable-items .os-selectable-item .item-quantity-selector-input', latepoint_selectable_item_quantity_keyup);
1669
1670
1671    $booking_form_element.off('keydown', '.os-selectable-items .os-selectable-item', latepoint_selectable_item_clicked);
1672    $booking_form_element.on('keydown', '.os-selectable-items .os-selectable-item', latepoint_selectable_item_clicked);
1673}
1674
1675
1676function latepoint_update_quantity_for_selectable_items($item) {
1677    var ids = $item.closest('.os-selectable-items')
1678        .find('.os-selectable-item.selected')
1679        .map(function () {
1680            if (jQuery(this).hasClass('has-quantity')) {
1681                return jQuery(this).data('item-id') + ':' + jQuery(this).find('input.item-quantity-selector-input').val();
1682            } else {
1683                return jQuery(this).data('item-id');
1684            }
1685        }).get();
1686    $item.closest('.latepoint-booking-form-element').find($item.data('id-holder')).val(ids);
1687}
1688
1689function latepoint_selectable_item_quantity_keyup(event) {
1690    var $booking_form_element = jQuery(this).closest('.latepoint-booking-form-element');
1691    var $item = jQuery(this).closest('.os-selectable-item');
1692    var new_value = jQuery(this).val();
1693    if (new_value && new_value.match(/^\d+$/)) {
1694        var max_quantity = $item.data('max-quantity');
1695        if (max_quantity && (new_value > max_quantity)) new_value = max_quantity;
1696    } else {
1697        new_value = 0;
1698    }
1699    jQuery(this).val(new_value);
1700
1701    if (($item.hasClass('selected') && (new_value > 0)) || (!$item.hasClass('selected') && (new_value == 0))) {
1702        latepoint_update_quantity_for_selectable_items($item);
1703        latepoint_reload_summary($booking_form_element);
1704        return false;
1705    } else {
1706        $item.trigger('click');
1707    }
1708}
1709
1710function latepoint_selectable_item_clicked(event) {
1711    if(event.type === 'keydown' && event.key !== ' ' &&  event.key !== 'Enter') return;
1712    event.stopPropagation();
1713    event.stopImmediatePropagation();
1714    var $booking_form_element = jQuery(this).closest('.latepoint-booking-form-element');
1715    if (jQuery(this).hasClass('has-quantity')) {
1716        if (jQuery(event.target).hasClass('item-quantity-selector')) {
1717            var current_value = parseInt(jQuery(this).find('input.item-quantity-selector-input').val());
1718            var new_value = (jQuery(event.target).data('sign') == 'minus') ? current_value - 1 : current_value + 1;
1719            var max_quantity = jQuery(this).data('max-quantity');
1720            if (new_value < 0) new_value = 0;
1721            if (max_quantity && (new_value > max_quantity)) new_value = max_quantity;
1722            jQuery(this).find('input.item-quantity-selector-input').val(new_value);
1723            if ((jQuery(this).hasClass('selected') && (new_value > 0)) || (!jQuery(this).hasClass('selected') && (new_value == 0))) {
1724                latepoint_update_quantity_for_selectable_items(jQuery(this));
1725                latepoint_reload_summary($booking_form_element);
1726                return false;
1727            }
1728        }
1729        if (jQuery(event.target).hasClass('item-quantity-selector-input')) {
1730            latepoint_update_quantity_for_selectable_items(jQuery(this));
1731            latepoint_reload_summary($booking_form_element);
1732            return false;
1733        }
1734    }
1735    var summary_value = '';
1736    if (jQuery(this).hasClass('os-allow-multiselect')) {
1737        if (jQuery(this).hasClass('selected')) {
1738            jQuery(this).removeClass('selected');
1739            if (jQuery(this).hasClass('has-quantity')) jQuery(this).find('input.item-quantity-selector-input').val(0);
1740        } else {
1741            jQuery(this).addClass('selected');
1742            if (jQuery(this).hasClass('has-quantity') && !(jQuery(this).find('input.item-quantity-selector-input').val() > 0)) {
1743                jQuery(this).find('input.item-quantity-selector-input').val(1);
1744            }
1745        }
1746        latepoint_update_quantity_for_selectable_items(jQuery(this));
1747        summary_value = String(jQuery(this).closest('.os-selectable-items').find('.os-selectable-item.selected').map(function () {
1748            return (' ' + jQuery(this).data('summary-value'));
1749        }).get()).trim();
1750        latepoint_show_next_btn($booking_form_element);
1751    } else {
1752        if (!jQuery(this).hasClass('os-duration-item')) jQuery(this).closest('.os-item-categories-main-parent').find('.os-selectable-item.selected').removeClass('selected');
1753        jQuery(this).closest('.os-selectable-items').find('.os-selectable-item.selected').removeClass('selected');
1754        jQuery(this).addClass('selected');
1755        $booking_form_element.find(jQuery(this).data('id-holder')).val(jQuery(this).data('item-id'));
1756        summary_value = jQuery(this).data('summary-value');
1757        if (jQuery(this).data('cart-item-item-data-key')) {
1758            latepoint_update_active_cart_item_item_data($booking_form_element, jQuery(this).data('cart-item-item-data-key'), jQuery(this).data('item-id'));
1759        }
1760        if (jQuery(this).data('os-call-func')) {
1761            window[jQuery(this).data('os-call-func')](jQuery(this));
1762        }
1763        if (jQuery(this).data('activate-sub-step')) {
1764            window[jQuery(this).data('activate-sub-step')](jQuery(this));
1765        } else {
1766            latepoint_trigger_next_btn($booking_form_element);
1767        }
1768    }
1769    return false;
1770}
1771
1772function latepoint_update_active_cart_item_item_data($booking_form_element, key, value) {
1773    let item_data_json = $booking_form_element.find('input[name="active_cart_item[item_data]"]').val();
1774    let item_data = item_data_json ? JSON.parse($booking_form_element.find('input[name="active_cart_item[item_data]"]').val()) : {};
1775    item_data[key] = value;
1776    $booking_form_element.find('input[name="active_cart_item[item_data]"]').val(JSON.stringify(item_data));
1777}
1778
1779function latepoint_format_price(price) {
1780    // replace default decimal separator dot with comma if it's in settings
1781    if (latepoint_helper.decimal_separator == ',') price = String(price).replace('.', ',');
1782    return latepoint_helper.currency_symbol_before + String(price) + latepoint_helper.currency_symbol_after;
1783}
1784
1785
1786function latepoint_init_step_services() {
1787}
1788
1789
1790function latepoint_trigger_next_btn($booking_form_element) {
1791    $booking_form_element.find('.latepoint_step_direction').val('next');
1792    latepoint_submit_booking_form($booking_form_element.find('.latepoint-form'));
1793}
1794
1795function latepoint_init_step_locations() {
1796}
1797
1798function latepoint_init_agent_details_link($booking_form_element) {
1799    $booking_form_element.on('click', '.os-trigger-item-details-popup', function () {
1800        $booking_form_element.find('.os-item-details-popup.open').remove();
1801        var $popup = $booking_form_element.find('#' + jQuery(this).data('item-details-popup-id')).first().clone().attr('id', '');
1802        $booking_form_element.find('.latepoint-form-w').addClass('showing-item-details-popup');
1803        $popup.addClass('open').appendTo($booking_form_element.find('.latepoint-body'));
1804        return false;
1805    });
1806    $booking_form_element.on('click', '.os-item-details-popup.open .os-item-details-popup-close', function () {
1807        $booking_form_element.find('.latepoint-form-w').removeClass('showing-item-details-popup');
1808        jQuery(this).closest('.os-item-details-popup.open').remove();
1809        return false;
1810    });
1811}
1812
1813function latepoint_init_step_agents() {
1814}
1815
1816
1817function latepoint_init_booking_summary_lightbox() {
1818    jQuery('.customer-dashboard-booking-summary-lightbox').on('click', '.qr-show-trigger', function () {
1819        jQuery(this).closest('.latepoint-lightbox-i').find('.qr-code-on-full-summary').addClass('show-vevent-qr-code');
1820        return false;
1821    });
1822    jQuery('.customer-dashboard-booking-summary-lightbox').on('click', '.os-item-details-popup-close', function () {
1823        var $ligthbox = jQuery(this).closest('.latepoint-lightbox-content');
1824        $ligthbox.find('.os-item-details-popup.open').remove();
1825        $ligthbox.find('.full-summary-wrapper').show();
1826        return false;
1827    });
1828
1829    jQuery('.customer-dashboard-booking-summary-lightbox').on('click', '.os-trigger-item-details-popup', function () {
1830        var $ligthbox = jQuery(this).closest('.latepoint-lightbox-content');
1831        $ligthbox.find('.full-summary-wrapper').hide();
1832        $ligthbox.find('.os-item-details-popup.open').remove();
1833        var $popup = $ligthbox.find('#' + jQuery(this).data('item-details-popup-id')).clone();
1834        $popup.addClass('open').appendTo($ligthbox);
1835        return false;
1836    });
1837}
1838
1839function latepoint_init_step_confirmation($booking_form_element = false) {
1840    if (!$booking_form_element) return;
1841    $booking_form_element.on('click', '.set-customer-password-btn', function () {
1842        var $btn = jQuery(this);
1843        var $booking_form_element = jQuery(this).closest('.latepoint-booking-form-element');
1844
1845        $btn.addClass('os-loading');
1846        var params = {
1847            account_nonse: jQuery('input[name="account_nonse"]').val(),
1848            password: jQuery('input[name="customer[password]"]').val(),
1849            password_confirmation: jQuery('input[name="customer[password_confirmation]"]').val()
1850        }
1851        var data = {
1852            action: latepoint_helper.route_action,
1853            route_name: jQuery(this).data('btn-action'),
1854            params: jQuery.param(params),
1855            layout: 'none',
1856            return_format: 'json'
1857        }
1858        jQuery.ajax({
1859            type: "post",
1860            dataType: "json",
1861            url: latepoint_timestamped_ajaxurl(),
1862            data: data,
1863            success: function (data) {
1864                $btn.removeClass('os-loading');
1865                if (data.status === "success") {
1866                    $booking_form_element.find('.step-confirmation-set-password').html('').hide();
1867                    $booking_form_element.find('.confirmation-cabinet-info').show();
1868                } else {
1869                    latepoint_show_message_inside_element(data.message, $booking_form_element.find('.step-confirmation-set-password'), 'error');
1870                }
1871            }
1872        });
1873        return false;
1874    });
1875
1876    $booking_form_element.on('click', '.qr-show-trigger', function () {
1877        var $booking_form_element = jQuery(this).closest('.latepoint-booking-form-element');
1878        $booking_form_element.find('.qr-code-on-full-summary').addClass('show-vevent-qr-code');
1879        return false;
1880    });
1881
1882    $booking_form_element.on('click', '.show-set-password-fields', function () {
1883        var $booking_form_element = jQuery(this).closest('.latepoint-booking-form-element');
1884
1885        $booking_form_element.find('.step-confirmation-set-password').show();
1886        $booking_form_element.find('#customer_password').trigger('focus');
1887        jQuery(this).closest('.info-box').hide();
1888        return false;
1889    });
1890}
1891
1892function latepoint_init_customer_dashboard() {
1893    latepoint_init_form_masks();
1894    jQuery('.latepoint-customer-timezone-selector-w select').on('change', function (e) {
1895        var $select_box = jQuery(this);
1896        $select_box.closest('.latepoint-customer-timezone-selector-w').addClass('os-loading');
1897        var data = {
1898            action: latepoint_helper.route_action,
1899            route_name: jQuery(this).closest('.latepoint-customer-timezone-selector-w').data('route-name'),
1900            params: {timezone_name: jQuery(this).val()},
1901            layout: 'none',
1902            return_format: 'json'
1903        }
1904        jQuery.ajax({
1905            type: "post",
1906            dataType: "json",
1907            url: latepoint_timestamped_ajaxurl(),
1908            data: data,
1909            success: function (data) {
1910                $select_box.closest('.latepoint-customer-timezone-selector-w').removeClass('os-loading');
1911                if (data.status === "success") {
1912                    location.reload();
1913                } else {
1914
1915                }
1916            }
1917        });
1918    });
1919
1920
1921    jQuery('.latepoint-request-booking-cancellation').on('click', function () {
1922        if (!confirm(latepoint_helper.cancel_booking_prompt)) return false;
1923        var $this = jQuery(this);
1924        var $booking_box = $this.closest('.customer-booking');
1925
1926        var route = jQuery(this).data('route');
1927        var params = {id: $booking_box.data('id')};
1928
1929        var data = {
1930            action: latepoint_helper.route_action,
1931            route_name: route,
1932            params: params,
1933            layout: 'none',
1934            return_format: 'json'
1935        }
1936        $this.addClass('os-loading');
1937        jQuery.ajax({
1938            type: "post",
1939            dataType: "json",
1940            url: latepoint_timestamped_ajaxurl(),
1941            data: data,
1942            success: function (data) {
1943                if (data.status === "success") {
1944                    $this.remove();
1945                    location.reload();
1946                } else {
1947                    $this.removeClass('os-loading');
1948                }
1949            }
1950        });
1951        return false;
1952    });
1953
1954}
1955
1956
1957function get_customer_name($wrapper) {
1958    var customer_name = '';
1959    var first_name = $wrapper.find('input[name="customer[first_name]"]').val();
1960    var last_name = $wrapper.find('input[name="customer[last_name]"]').val();
1961    if (first_name) customer_name += first_name;
1962    if (last_name) customer_name += ' ' + last_name;
1963    return customer_name.trim();
1964}
1965
1966function latepoint_init_step_contact() {
1967    latepoint_init_form_masks();
1968
1969    // Init Logout button
1970    jQuery('.step-customer-logout-btn').on('click', function () {
1971        var $booking_form_element = jQuery(this).closest('.latepoint-booking-form-element');
1972        var data = {
1973            action: latepoint_helper.route_action,
1974            route_name: jQuery(this).data('btn-action'),
1975            layout: 'none',
1976            return_format: 'json'
1977        }
1978        latepoint_step_content_change_start($booking_form_element);
1979        jQuery.ajax({
1980            type: "post",
1981            dataType: "json",
1982            url: latepoint_timestamped_ajaxurl(),
1983            data: data,
1984            success: function (data) {
1985                latepoint_reload_step($booking_form_element);
1986            }
1987        });
1988        return false;
1989    });
1990
1991    // Init Login Existing Customer Button
1992    jQuery('.step-login-existing-customer-btn').on('click', function () {
1993        var $booking_form_element = jQuery(this).closest('.latepoint-booking-form-element');
1994        var params = {
1995            email: $booking_form_element.find('.os-step-existing-customer-login-w input[name="customer_login[email]"]').val(),
1996            password: $booking_form_element.find('.os-step-existing-customer-login-w input[name="customer_login[password]"]').val()
1997        }
1998        var data = {
1999            action: latepoint_helper.route_action,
2000            route_name: jQuery(this).data('btn-action'),
2001            params: jQuery.param(params),
2002            layout: 'none',
2003            return_format: 'json'
2004        }
2005        latepoint_step_content_change_start($booking_form_element);
2006        jQuery.ajax({
2007            type: "post",
2008            dataType: "json",
2009            url: latepoint_timestamped_ajaxurl(),
2010            data: data,
2011            success: function (data) {
2012                if (data.status === "success") {
2013                    latepoint_reload_step($booking_form_element);
2014                } else {
2015                    latepoint_show_message_inside_element(data.message, $booking_form_element.find('.os-step-existing-customer-login-w'));
2016                    latepoint_step_content_change_end(false, $booking_form_element);
2017                }
2018            }
2019        });
2020        return false;
2021    });
2022}
2023
2024function latepoint_step_content_change_start($booking_form_element) {
2025    $booking_form_element.removeClass('step-content-loaded').addClass('step-content-loading');
2026}
2027
2028// TODO
2029function latepoint_step_content_change_end(new_content, $booking_form_element) {
2030    if (new_content) $booking_form_element.find('.latepoint-body .latepoint-step-content').replaceWith(new_content);
2031    $booking_form_element.removeClass('step-content-loading').addClass('step-content-mid-loading');
2032    setTimeout(function () {
2033        $booking_form_element.removeClass('step-content-mid-loading').addClass('step-content-loaded');
2034    }, 50);
2035}
2036
2037
2038function latepoint_change_step_desc($booking_form_element, step_code) {
2039    $booking_form_element.removeClass('step-changed').addClass('step-changing');
2040    setTimeout(function () {
2041        // Progress bar
2042        var $step_progress = $booking_form_element.find('.latepoint-progress li[data-step-code="' + step_code + '"]');
2043        $step_progress.addClass('active').addClass('complete').prevAll().addClass('complete').removeClass('active');
2044        $step_progress.nextAll().removeClass('complete').removeClass('active');
2045        // Side panel
2046        var side_panel_desc = $booking_form_element.find('.latepoint-step-desc-library[data-step-code="' + step_code + '"]').html();
2047        $booking_form_element.find('.latepoint-step-desc').html(side_panel_desc);
2048
2049        // Top header
2050        var top_header_desc = $booking_form_element.find('.os-heading-text-library[data-step-code="' + step_code + '"]').html();
2051        $booking_form_element.find('.os-heading-text').html(top_header_desc);
2052        setTimeout(function () {
2053            $booking_form_element.removeClass('step-changing').addClass('step-changed');
2054        }, 50);
2055    }, 500);
2056}
2057
2058
2059function latepoint_progress_prev($booking_form_element, step_code) {
2060    var $step_progress = $booking_form_element.find('.latepoint-progress li[data-step-code="' + step_code + '"]');
2061    $step_progress.addClass('active').addClass('complete').prevAll().addClass('complete').removeClass('active');
2062    $step_progress.nextAll().removeClass('complete').removeClass('active');
2063}
2064
2065
2066function latepoint_progress_next($booking_form_element, step_code) {
2067    var $step_progress = $booking_form_element.find('.latepoint-progress li[data-step-code="' + step_code + '"]');
2068    $step_progress.addClass('active').addClass('complete').prevAll().addClass('complete').removeClass('active');
2069    $step_progress.nextAll().removeClass('complete').removeClass('active');
2070}
2071
2072
2073function latepoint_next_step_description($booking_form_element, step_code) {
2074    $booking_form_element.removeClass('step-changed').addClass('step-changing');
2075    setTimeout(function () {
2076        $booking_form_element.find('.latepoint-step-desc').html($booking_form_element.find('.latepoint-step-desc-library.active').removeClass('active').next('.latepoint-step-desc-library').addClass('active').html());
2077        $booking_form_element.find('.os-heading-text').html($booking_form_element.find('.os-heading-text-library.active').removeClass('active').next('.os-heading-text-library').addClass('active').html());
2078        setTimeout(function () {
2079            $booking_form_element.removeClass('step-changing').addClass('step-changed');
2080        }, 50);
2081    }, 500);
2082}
2083
2084function latepoint_prev_step_description($booking_form_element, step_code) {
2085    $booking_form_element.removeClass('step-changed').addClass('step-changing');
2086    setTimeout(function () {
2087        $booking_form_element.find('.latepoint-step-desc').html($booking_form_element.find('.latepoint-step-desc-library.active').removeClass('active').prev('.latepoint-step-desc-library').addClass('active').html());
2088        $booking_form_element.find('.os-heading-text').html($booking_form_element.find('.os-heading-text-library.active').removeClass('active').prev('.os-heading-text-library').addClass('active').html());
2089        setTimeout(function () {
2090            $booking_form_element.removeClass('step-changing').addClass('step-changed');
2091        }, 50);
2092    }, 500);
2093}
2094
2095
2096function latepoint_validate_fields($fields) {
2097    var is_valid = true;
2098    $fields.each(function (index) {
2099        if (jQuery(this).val() == '') {
2100            is_valid = false;
2101            return false;
2102        }
2103    });
2104    return is_valid;
2105}
2106
2107
2108async function latepoint_submit_booking_form($booking_form) {
2109    let $booking_form_element = $booking_form.closest('.latepoint-booking-form-element');
2110
2111    let current_step = $booking_form_element.find('.latepoint_current_step_code').val();
2112    let callbacks_list = [];
2113    if (latepoint_check_if_booking_form_is_final_submit($booking_form_element)) {
2114        // check if order intent is still bookable
2115        latepoint_add_action(callbacks_list, async () => {
2116            return await latepoint_check_if_order_intent_still_bookable($booking_form_element);
2117        }, 1);
2118    }
2119    $booking_form_element.trigger('latepoint:submitBookingForm', [{
2120        current_step: current_step,
2121        callbacks_list: callbacks_list,
2122        is_final_submit: latepoint_check_if_booking_form_is_final_submit($booking_form_element),
2123        direction: $booking_form_element.find('.latepoint_step_direction').val()
2124    }]);
2125    try {
2126        latepoint_hide_prev_btn($booking_form_element);
2127        await latepoint_process_list_of_callbacks(callbacks_list, $booking_form_element, $booking_form);
2128    } catch (error) {
2129        latepoint_show_prev_btn($booking_form_element);
2130        latepoint_show_error_and_stop_loading_booking_form(error, $booking_form_element);
2131        return false;
2132    }
2133
2134
2135
2136    $booking_form_element.removeClass('step-content-loaded').addClass('step-content-loading');
2137    latepoint_hide_prev_btn($booking_form_element);
2138    try{
2139        latepoint_hide_message_inside_element($booking_form_element.find('.latepoint-body'));
2140        let response = await jQuery.ajax({
2141            type: "post",
2142            dataType: "json",
2143            processData: false,
2144            contentType: false,
2145            url: latepoint_timestamped_ajaxurl(),
2146            data: latepoint_create_form_data($booking_form)
2147        });
2148
2149        $booking_form.find('.latepoint_step_direction').val('next');
2150        if(response.status === 'success'){
2151            if (response.fields_to_update) {
2152                for (const [key, value] of Object.entries(response.fields_to_update)) {
2153                    $booking_form_element.find('input[name="' + key + '"]').val(value)
2154                }
2155            }
2156            if ($booking_form_element.data('flash-error')) {
2157                latepoint_show_message_inside_element($booking_form_element.data('flash-error'), $booking_form_element.find('.latepoint-body'));
2158                $booking_form_element.data('flash-error', '');
2159            }
2160            $booking_form_element.find('.latepoint_current_step_code').val(response.step_code);
2161            $booking_form_element.removeClass(function (index, className) {
2162                return (className.match(/(^|\s)current-step-\S+/g) || []).join(' ');
2163            }).addClass('current-step-' + response.step_code);
2164            setTimeout(function () {
2165                $booking_form_element.removeClass('step-content-loading').addClass('step-content-mid-loading');
2166                $booking_form_element.find('.latepoint-body').find('.latepoint-step-content').addClass('is-hidden');
2167                if ($booking_form_element.find('.latepoint-step-content[data-step-code="' + response.step_code + '"]')) {
2168                    $booking_form_element.find('.latepoint-step-content[data-step-code="' + response.step_code + '"]').remove();
2169                }
2170                $booking_form_element.find('.latepoint-body').append(response.message);
2171
2172
2173                latepoint_update_next_btn_label($booking_form_element);
2174                latepoint_init_step(response.step_code, $booking_form_element);
2175                setTimeout(function () {
2176                    $booking_form_element.removeClass('step-content-mid-loading').addClass('step-content-loaded');
2177                    $booking_form_element.find('.latepoint-next-btn, .latepoint-prev-btn').removeClass('os-loading');
2178                    latepoint_scroll_to_top_of_booking_form($booking_form_element);
2179                }, 50);
2180            }, 500);
2181
2182            if (response.is_pre_last_step) {
2183                $booking_form_element.data('next-submit-is-last', 'yes');
2184            } else {
2185                $booking_form_element.data('next-submit-is-last', 'no');
2186            }
2187            if (response.is_last_step) {
2188                $booking_form_element.addClass('hidden-buttons').find('.latepoint-footer').remove();
2189                $booking_form_element.find('.latepoint-progress').css('opacity', 0);
2190                $booking_form_element.closest('.latepoint-summary-is-open').removeClass('latepoint-summary-is-open');
2191                $booking_form_element.closest('.latepoint-show-side-panel').removeClass('latepoint-show-side-panel').addClass('latepoint-hide-side-panel');
2192                $booking_form_element.addClass('is-final-step');
2193            } else {
2194                if (response.show_next_btn === true) {
2195                    latepoint_show_next_btn($booking_form_element);
2196                } else {
2197                    latepoint_hide_next_btn($booking_form_element);
2198                }
2199                if (response.show_prev_btn === true) {
2200                    latepoint_show_prev_btn($booking_form_element);
2201                } else {
2202                    latepoint_hide_prev_btn($booking_form_element);
2203                }
2204            }
2205            latepoint_change_step_desc($booking_form_element, response.step_code);
2206            latepoint_reload_summary($booking_form_element);
2207        }else{
2208            if(response.send_to_step && response.send_to_step === 'resubmit'){
2209                let current_resubmit_count = parseInt($booking_form.data('resubmit-count')) ? parseInt($booking_form.data('resubmit-count')) : 1;
2210                $booking_form.data('resubmit-count', current_resubmit_count + 1);
2211                if(current_resubmit_count > 6){
2212                    latepoint_show_message_inside_element(response.message, $booking_form_element.find('.latepoint-body'));
2213                }else{
2214                    // resubmission probably caused by order intent still being processed, since
2215                    // order intent is still processing, give it a little more time and try again
2216                    await latepoint_sleep(2000);
2217                    return latepoint_submit_booking_form($booking_form);
2218                }
2219            }else{
2220                $booking_form_element.removeClass('step-content-loading').addClass('step-content-loaded');
2221                $booking_form_element.find('.latepoint-next-btn, .latepoint-prev-btn').removeClass('os-loading');
2222                if (response.send_to_step && $booking_form_element.find('.latepoint-step-content[data-step-code="' + response.send_to_step + '"]').length) {
2223                    $booking_form_element.data('flash-error', response.message);
2224                    latepoint_reload_step($booking_form_element, response.send_to_step);
2225                } else {
2226                    latepoint_show_message_inside_element(response.message, $booking_form_element.find('.latepoint-body'));
2227                    latepoint_show_prev_btn($booking_form_element);
2228                }
2229            }
2230        }
2231    }catch(e){
2232        console.log(e);
2233        alert('Error:' + e);
2234    }
2235}
2236
2237function latepoint_sleep(ms) {
2238    return new Promise(resolve => setTimeout(resolve, ms));
2239}
2240
2241function latepoint_show_error_and_stop_loading_booking_form(error, $booking_form_element) {
2242    if (error.send_to_step && $booking_form_element.find('.latepoint-step-content[data-step-code="' + error.send_to_step + '"]').length) {
2243        latepoint_reload_step($booking_form_element, error.send_to_step);
2244        $booking_form_element.data('flash-error', error.message);
2245    } else {
2246        latepoint_show_message_inside_element(error.message, $booking_form_element.find('.latepoint-body'), 'error');
2247
2248        if ($booking_form_element.hasClass('step-content-loading')) $booking_form_element.removeClass('step-content-loading').addClass('step-content-loaded');
2249        $booking_form_element.find('.latepoint-next-btn').removeClass('os-loading');
2250
2251        // if previous step exists - show prev button
2252        if ($booking_form_element.find('.latepoint-step-content:last-child').prev('.latepoint-step-content').length) latepoint_show_prev_btn($booking_form_element);
2253        latepoint_scroll_to_top_of_booking_form($booking_form_element);
2254    }
2255}
2256
2257function latepoint_reset_active_cart_item($booking_form_element) {
2258    $booking_form_element.find('input[name="active_cart_item[id]"]').val('');
2259    $booking_form_element.find('input[name="active_cart_item[variant]"]').val('');
2260    $booking_form_element.find('input[name="active_cart_item[item_data]"]').val('');
2261}
2262
2263function latepoint_check_if_booking_form_is_final_submit($booking_form_element) {
2264    return ($booking_form_element.data('next-submit-is-last') == 'yes');
2265}
2266
2267
2268async function latepoint_check_if_order_intent_still_bookable($booking_form_element) {
2269    let response = await jQuery.ajax({
2270        type: "post",
2271        dataType: "json",
2272        processData: false,
2273        contentType: false,
2274        url: latepoint_timestamped_ajaxurl(),
2275        data: latepoint_create_form_data($booking_form_element.find('.latepoint-form'), latepoint_helper.check_order_intent_bookable_route)
2276    });
2277    if(response.status === 'success'){
2278        return true;
2279    }else{
2280        throw new Error(response.message);
2281    }
2282}
2283
2284async function latepoint_process_list_of_callbacks(callbacks, $booking_form_element, $booking_form) {
2285    for (const callback of callbacks) {
2286        await callback.action();
2287    }
2288}
2289
2290function latepoint_clear_presets($booking_form_element){
2291    $booking_form_element.find('.clear_for_new_item').val('');
2292}
2293
2294function latepoint_init_booking_form($booking_form_element) {
2295    $booking_form_element.on('click keydown', '.checkout-from-summary-panel-btn', function (event) {
2296        if(event.type === 'keydown' && event.key !== ' ' &&  event.key !== 'Enter') return;
2297        latepoint_reload_step($booking_form_element, jQuery(this).data('step'));
2298        jQuery(this).closest('.latepoint-w').removeClass('show-summary-on-mobile');
2299        return false;
2300    });
2301
2302    $booking_form_element.on('click keydown', '.latepoint-add-another-item-trigger', function (event) {
2303        if(event.type === 'keydown' && event.key !== ' ' &&  event.key !== 'Enter') return;
2304        latepoint_clear_presets($booking_form_element);
2305        latepoint_reset_active_cart_item($booking_form_element);
2306        latepoint_reload_step($booking_form_element, jQuery(this).data('step'));
2307        return false;
2308    });
2309    $booking_form_element.find('.latepoint-form').on('submit', function (e) {
2310        e.preventDefault();
2311        let $booking_form = jQuery(this);
2312        latepoint_submit_booking_form($booking_form);
2313    });
2314
2315    latepoint_init_booking_summary_panel($booking_form_element);
2316
2317    $booking_form_element.find('.latepoint-heading-w .latepoint-lightbox-summary-trigger').on('click', function () {
2318        var $wrapper = jQuery(this).closest('.latepoint-w');
2319        $wrapper.toggleClass('show-summary-on-mobile');
2320        return false;
2321    });
2322
2323    $booking_form_element.find('.latepoint-lightbox-close').on('click', function () {
2324
2325        let params = new URLSearchParams(location.search);
2326        if (params.has('latepoint_order_intent_key')) {
2327            params.delete('latepoint_order_intent_key');
2328            history.replaceState(null, '', '?' + params + location.hash);
2329        }
2330
2331        jQuery('body').removeClass('latepoint-lightbox-active');
2332        jQuery('.latepoint-lightbox-w').remove();
2333        return false;
2334    });
2335
2336
2337    $booking_form_element.find('.latepoint-timezone-selector-w select').on('change', function (e) {
2338        var $select_box = jQuery(this);
2339        $select_box.closest('.latepoint-timezone-selector-w').addClass('os-loading');
2340        var data = {
2341            action: latepoint_helper.route_action,
2342            route_name: jQuery(this).closest('.latepoint-timezone-selector-w').data('route-name'),
2343            params: {timezone_name: jQuery(this).val()},
2344            layout: 'none',
2345            return_format: 'json'
2346        }
2347        $booking_form_element.removeClass('step-content-loaded').addClass('step-content-loading');
2348        jQuery.ajax({
2349            type: "post",
2350            dataType: "json",
2351            url: latepoint_timestamped_ajaxurl(),
2352            data: data,
2353            success: function (data) {
2354                $select_box.closest('.latepoint-timezone-selector-w').removeClass('os-loading');
2355                $booking_form_element.removeClass('step-content-loading');
2356                if (data.status === "success") {
2357                    // reload datepicker if its the step
2358                    if ($select_box.closest('.latepoint-booking-form-element').hasClass('current-step-booking__datepicker')) {
2359                        latepoint_reload_step($select_box.closest('.latepoint-booking-form-element'));
2360                    }
2361                } else {
2362
2363                }
2364            }
2365        });
2366    });
2367
2368    if (!latepoint_helper.is_timezone_selected) {
2369        const tzid = Intl.DateTimeFormat().resolvedOptions().timeZone;
2370        if (tzid) {
2371            if (tzid != $booking_form_element.find('.latepoint-timezone-selector-w select').val()) $booking_form_element.find('.latepoint-timezone-selector-w select').val(tzid).trigger('change');
2372        }
2373    }
2374
2375
2376    $booking_form_element.on('click keydown', '.lp-option', function (event) {
2377        if(event.type === 'keydown' && event.key !== ' ' &&  event.key !== 'Enter') return;
2378        jQuery(this).closest('.lp-options').find('.lp-option.selected').removeClass('selected');
2379        jQuery(this).addClass('selected');
2380    });
2381
2382
2383    // Next Step button Click
2384    $booking_form_element.find('.latepoint-next-btn').on('click', async function (e) {
2385        e.preventDefault();
2386        if (jQuery(this).hasClass('disabled') || jQuery(this).hasClass('os-loading')) return false;
2387        var $next_btn = jQuery(this);
2388        $next_btn.addClass('os-loading');
2389        var $booking_form = jQuery(this).closest('.latepoint-form');
2390
2391        var current_step = $booking_form_element.find('.latepoint_current_step_code').val();
2392
2393        $booking_form.find('.latepoint_step_direction').val('next');
2394        var callbacks_list = [];
2395
2396        $booking_form_element.trigger('latepoint:nextStepClicked', [{
2397            current_step: current_step,
2398            callbacks_list: callbacks_list
2399        }]);
2400        latepoint_hide_prev_btn($booking_form_element);
2401
2402        try{
2403            await latepoint_process_list_of_callbacks(callbacks_list, $booking_form_element, $booking_form );
2404            await latepoint_submit_booking_form($booking_form);
2405        }catch(error){
2406            latepoint_show_error_and_stop_loading_booking_form(error, $booking_form_element);
2407        }
2408        return false;
2409    });
2410
2411
2412    // Previous Step button Click
2413    $booking_form_element.find('.latepoint-prev-btn').on('click', function (e) {
2414        if (jQuery(this).hasClass('disabled') || jQuery(this).hasClass('os-loading')) return false;
2415
2416        let $current_step = $booking_form_element.find('.latepoint-step-content:last-child');
2417
2418
2419        // handle categories
2420        if ($current_step.hasClass('selecting-item-category')) {
2421            if ($current_step.find('.os-item-category-w .os-item-category-w.selected').length) {
2422                $current_step.find('.os-item-category-w .os-item-category-w.selected').parents('.os-item-category-w').addClass('selected').find('.os-item-category-w.selected').removeClass('selected');
2423            } else {
2424                $current_step.removeClass('selecting-item-category').find('.os-item-category-w.selected').removeClass('selected');
2425                $current_step.removeClass('selecting-item-category').find('.os-item-categories-holder.show-selected-only').removeClass('show-selected-only');
2426            }
2427            if (($booking_form_element.find('.latepoint-step-content').length <= 1) && !$current_step.hasClass('selecting-item-category')) {
2428                latepoint_hide_prev_btn($booking_form_element);
2429            }
2430            latepoint_reload_summary($booking_form_element);
2431            return false;
2432        }
2433
2434        if ($current_step.data('clear-action')) {
2435            window[$current_step.data('clear-action')]($booking_form_element);
2436        }
2437
2438        let $back_btn = jQuery(this);
2439        $back_btn.addClass('os-loading');
2440        $booking_form_element.removeClass('step-content-loaded').addClass('step-content-loading');
2441        let $new_current_step = $booking_form_element.find('.latepoint-step-content.is-hidden').last();
2442        let new_current_step_code = $new_current_step.data('step-code');
2443        let current_step_code = $current_step.data('step-code');
2444
2445
2446        let current_parent_code_name = current_step_code.split('__')[0];
2447        let new_parent_code_name = new_current_step_code.split('__')[0];
2448
2449        let active_cart_item_id = $booking_form_element.find('input[name="active_cart_item[id]"]').val();
2450
2451        latepoint_change_step_desc($booking_form_element, new_current_step_code);
2452        setTimeout(function () {
2453            $new_current_step.removeClass('is-hidden');
2454            $current_step.remove();
2455            $booking_form_element.find('.latepoint_current_step_code').val(new_current_step_code);
2456            $booking_form_element.removeClass(function (index, className) {
2457                return (className.match(/(^|\s)current-step-\S+/g) || []).join(' ');
2458            }).addClass('current-step-' + new_current_step_code);
2459            $booking_form_element.find('.latepoint-next-btn span').text($booking_form_element.find('.latepoint-next-btn').data('label'));
2460            $booking_form_element.data('next-submit-is-last', 'no');
2461
2462            latepoint_update_next_btn_label($booking_form_element);
2463            latepoint_show_next_btn($booking_form_element);
2464            $back_btn.removeClass('os-loading');
2465            if ($booking_form_element.find('.latepoint-step-content').length <= 1) {
2466                if ($new_current_step.hasClass('selecting-item-category')) {
2467
2468                }
2469                if (new_current_step_code == 'booking__services') {
2470                    var $services_step = $booking_form_element.find('.step-services-w');
2471                    if ($services_step.hasClass('selecting-item-category')) {
2472                        if ($services_step.find('.os-services > .os-item.selected').hasClass('is-preselected')) {
2473                            // if service is preselected check if there are both multiple durations and quantity selector and only then show prev button
2474                        } else {
2475                            latepoint_show_prev_btn($booking_form_element);
2476                        }
2477                    } else {
2478                        latepoint_hide_prev_btn($booking_form_element);
2479                    }
2480                } else {
2481                    if (!$new_current_step.hasClass('selecting-item-category')) {
2482                        latepoint_hide_prev_btn($booking_form_element);
2483                    }
2484                }
2485            }
2486            $booking_form_element.removeClass('step-content-loading').addClass('step-content-mid-loading');
2487
2488
2489            if(new_parent_code_name == 'booking' && current_parent_code_name != 'booking' && active_cart_item_id){
2490
2491                // we are going back to one of the steps of a booking process, we need to remove the item that was just added to the cart and start over
2492                $booking_form_element.find('.latepoint-summary-w').addClass('os-loading');
2493                let data = {
2494                    action: latepoint_helper.route_action,
2495                    route_name: latepoint_helper.remove_cart_item_route,
2496                    params: jQuery.param({cart_item_id: active_cart_item_id}),
2497                    layout: 'none',
2498                    return_format: 'json'
2499                }
2500                jQuery.ajax({
2501                    type: "post",
2502                    dataType: "json",
2503                    url: latepoint_timestamped_ajaxurl(),
2504                    data: data,
2505                    success: function (data) {
2506                        if (data.status === "success") {
2507                            $booking_form_element.find('input[name="active_cart_item[id]"]').val('');
2508                            if($booking_form_element.find('input[name="active_cart_item[variant]"]').val() == 'bundle'){
2509                                latepoint_update_active_cart_item_item_data($booking_form_element, 'bundle_id', '');
2510                                $booking_form_element.find('input[name="active_cart_item[variant]"]').val('');
2511                            }
2512                            latepoint_reload_summary($booking_form_element);
2513                        } else {
2514                            $booking_form_element.find('.latepoint-summary-w').removeClass('os-loading');
2515                            latepoint_show_message_inside_element
2515(data.message, $booking_form_element.find('.latepoint-body'), 'error');
2516                        }
2517                    }
2518                });
2519            }else{
2520                latepoint_reload_summary($booking_form_element);
2521            }
2522            setTimeout(function () {
2523                $booking_form_element.removeClass('step-content-mid-loading').addClass('step-content-loaded');
2524                latepoint_hide_message_inside_element($booking_form_element.find('.latepoint-body'));
2525                latepoint_scroll_to_top_of_booking_form($booking_form_element);
2526            }, 150);
2527        }, 700);
2528        return false;
2529    });
2530
2531    latepoint_init_agent_details_link($booking_form_element);
2532}
2533
2534
2535function latepoint_init_booking_form_by_trigger($trigger) {
2536    let route = latepoint_helper.booking_button_route;
2537    let params = {};
2538    let restrictions = {};
2539    let presets = {};
2540    let booking_element_styles = {};
2541    if ($trigger.data('show-service-categories')) restrictions.show_service_categories = $trigger.data('show-service-categories');
2542    if ($trigger.data('show-locations')) restrictions.show_locations = $trigger.data('show-locations');
2543    if ($trigger.data('show-services')) restrictions.show_services = $trigger.data('show-services');
2544    if ($trigger.data('show-agents')) restrictions.show_agents = $trigger.data('show-agents');
2545    if ($trigger.data('calendar-start-date')) restrictions.calendar_start_date = $trigger.data('calendar-start-date');
2546
2547    if ($trigger.data('selected-location')) presets.selected_location = $trigger.data('selected-location');
2548    if ($trigger.data('selected-agent')) presets.selected_agent = $trigger.data('selected-agent');
2549    if ($trigger.data('selected-service')) presets.selected_service = $trigger.data('selected-service');
2550    if ($trigger.data('selected-bundle')) presets.selected_bundle = $trigger.data('selected-bundle');
2551    if ($trigger.data('selected-duration')) presets.selected_duration = $trigger.data('selected-duration');
2552    if ($trigger.data('selected-total-attendees')) presets.selected_total_attendees = $trigger.data('selected-total-attendees');
2553    if ($trigger.data('selected-service-category')) presets.selected_service_category = $trigger.data('selected-service-category');
2554    if ($trigger.data('selected-start-date')) presets.selected_start_date = $trigger.data('selected-start-date');
2555    if ($trigger.data('selected-start-time')) presets.selected_start_time = $trigger.data('selected-start-time');
2556    if ($trigger.data('order-item-id')) presets.order_item_id = $trigger.data('order-item-id');
2557    if ($trigger.data('source-id')) presets.source_id = $trigger.data('source-id');
2558
2559    if ($trigger.data('hide-summary') == 'yes') booking_element_styles.hide_summary = true;
2560    if ($trigger.data('hide-side-panel') == 'yes') booking_element_styles.hide_side_panel = true;
2561
2562
2563    if (jQuery.isEmptyObject(restrictions) == false) params.restrictions = restrictions;
2564    if (jQuery.isEmptyObject(presets) == false) params.presets = presets;
2565    if (jQuery.isEmptyObject(booking_element_styles) == false) params.booking_element_styles = booking_element_styles;
2566
2567    let data = {
2568        action: latepoint_helper.route_action,
2569        route_name: route,
2570        params: params,
2571        layout: 'none',
2572        return_format: 'json'
2573    }
2574
2575    let is_inline_form = $trigger.hasClass('latepoint-book-form-wrapper');
2576    if(is_inline_form){
2577        data.params.booking_element_type = 'inline_form';
2578    }
2579
2580    $trigger.addClass('os-loading');
2581    jQuery.ajax({
2582        type: "post",
2583        dataType: "json",
2584        url: latepoint_timestamped_ajaxurl(),
2585        data: data,
2586        success: (data) => {
2587            if (data.status === "success") {
2588                let $booking_form_element = false;
2589                if(is_inline_form){
2590                    $trigger.html(data.message);
2591                    $booking_form_element = $trigger.find('.latepoint-booking-form-element');
2592                }else{
2593                    let lightbox_class = 'booking-form-in-lightbox';
2594                    latepoint_show_data_in_lightbox(data.message, lightbox_class, false);
2595                    $booking_form_element = jQuery('.latepoint-lightbox-w .latepoint-booking-form-element');
2596                    jQuery('body').addClass('latepoint-lightbox-active');
2597                }
2598                latepoint_init_booking_form($booking_form_element);
2599                latepoint_init_step(data.step, $booking_form_element);
2600                $trigger.removeClass('os-loading');
2601            } else {
2602                $trigger.removeClass('os-loading');
2603                // console.log(data.message);
2604            }
2605        }
2606    });
2607}
2608
2609
2610/*
2611 * Copyright (c) 2023 LatePoint LLC. All rights reserved.
2612 */
2613
2614async function latepoint_init_transaction_payment_form() {
2615    let callbacks_list = [];
2616    let $transaction_payment_form = jQuery('.latepoint-transaction-payment-form');
2617    let current_step = $transaction_payment_form.find('input[name="current_step"]').val();
2618
2619    $transaction_payment_form.on('click keydown', '.lp-option', (e) => {
2620        let $option = jQuery(e.currentTarget);
2621        if(e.type === 'keydown' && e.key !== ' ' &&  e.key !== 'Enter') return;
2622        $option.closest('.lp-options').find('.lp-option.selected').removeClass('selected');
2623        $option.addClass('selected');
2624        $transaction_payment_form.find('input[name="' + $option.data('holder') + '"]').val($option.data('value'));
2625        $transaction_payment_form.trigger('submit');
2626        return false;
2627    });
2628
2629
2630    switch (current_step) {
2631        case 'methods':
2632            break;
2633        case 'processors':
2634            break;
2635        case 'pay':
2636            $transaction_payment_form.trigger('latepoint:initOrderPaymentMethod', [{
2637                callbacks_list: callbacks_list,
2638                payment_method: $transaction_payment_form.find('input[name="payment_method"]').val(),
2639                payment_processor: $transaction_payment_form.find('input[name="payment_processor"]').val(),
2640            }]);
2641            $transaction_payment_form.addClass('os-loading');
2642
2643            try {
2644                for (const callback of callbacks_list) {
2645                    await callback.action();
2646                }
2647                $transaction_payment_form.removeClass('os-loading');
2648            } catch (error) {
2649                latepoint_show_error_and_stop_loading_booking_form(error, $transaction_payment_form);
2650            }
2651            break;
2652        case 'confirmation':
2653            break;
2654    }
2655
2656    $transaction_payment_form.on('submit', async function (e) {
2657        e.preventDefault();
2658        await latepoint_submit_transaction_payment_form(jQuery(e.target));
2659    });
2660}
2661
2662async function latepoint_submit_transaction_payment_form($transaction_payment_form) {
2663
2664    if($transaction_payment_form.hasClass('os-loading')) return false;
2665    let callbacks_list = [];
2666
2667    $transaction_payment_form.find('.latepoint-message').remove();
2668    $transaction_payment_form.addClass('os-loading');
2669    $transaction_payment_form.find('.latepoint-btn').addClass('os-loading');
2670
2671    $transaction_payment_form.trigger('latepoint:submitTransactionPaymentForm', [{
2672        callbacks_list: callbacks_list,
2673        payment_method: $transaction_payment_form.find('input[name="payment_method"]').val(),
2674        payment_processor: $transaction_payment_form.find('input[name="payment_processor"]').val(),
2675        current_step: $transaction_payment_form.find('input[name="current_step"]').val(),
2676    }]);
2677
2678    try {
2679        for (const callback of callbacks_list) {
2680            await callback.action();
2681        }
2682    } catch (error) {
2683        $transaction_payment_form.removeClass('os-loading').find('.os-loading').removeClass('os-loading');
2684        latepoint_show_message_inside_element(error.message, $transaction_payment_form.find('.lp-payment-method-content'), 'error');
2685        return false;
2686    }
2687
2688
2689    try {
2690        let response = await jQuery.ajax({
2691            type: "post",
2692            dataType: "json",
2693            processData: false,
2694            contentType: false,
2695            url: latepoint_timestamped_ajaxurl(),
2696            data: latepoint_create_form_data($transaction_payment_form, latepoint_helper.invoices_payment_form_route)
2697        });
2698
2699        $transaction_payment_form.removeClass('os-loading').find('.os-loading').removeClass('os-loading');
2700
2701        if (response.status === 'success') {
2702            $transaction_payment_form.html(response.message);
2703            return await latepoint_init_transaction_payment_form();
2704
2705        } else {
2706            latepoint_show_message_inside_element(response.message, $transaction_payment_form.find('.lp-payment-method-content'), 'error');
2707            return false;
2708        }
2709    } catch (e) {
2710
2711        $transaction_payment_form.removeClass('os-loading').find('.os-loading').removeClass('os-loading');
2712        console.log(e);
2713        alert('Error:' + e);
2714    }
2715
2716}
2717
2718function latepoint_hide_reschedule_button() {
2719    jQuery('.reschedule-confirmation-button-wrapper').hide();
2720}
2721
2722function latepoint_show_reschedule_button() {
2723    jQuery('.reschedule-confirmation-button-wrapper').show();
2724}
2725
2726function latepoint_customer_cabinet_reload_booking_tile($booking_tile) {
2727    $booking_tile.addClass('os-loading');
2728    let params = {
2729        booking_id: $booking_tile.data('id'),
2730    }
2731    let data = {
2732        action: latepoint_helper.route_action,
2733        route_name: $booking_tile.data('route-name'),
2734        params: params,
2735        layout: 'none',
2736        return_format: 'json'
2737    };
2738    jQuery.ajax({
2739        type: "post",
2740        dataType: "json",
2741        url: latepoint_timestamped_ajaxurl(),
2742        data: data,
2743        success: function (data) {
2744            $booking_tile.removeClass('os-loading')
2745            if (data.status === "success") {
2746                $booking_tile.replaceWith(data.message);
2747            } else {
2748                alert(data.message);
2749            }
2750        }
2751    });
2752}
2753
2754
2755function latepoint_init_reschedule() {
2756
2757    jQuery('.reschedule-calendar-wrapper').on('click', '.latepoint-request-reschedule-trigger', function () {
2758        let $trigger = jQuery(this)
2759        let $wrapper = $trigger.closest('.reschedule-calendar-wrapper')
2760        let booking_id = $wrapper.find('input[type="hidden"].latepoint_booking_id').val()
2761
2762        $trigger.addClass('os-loading')
2763        let params = {
2764            booking_id: booking_id,
2765            key: $wrapper.find('input[type="hidden"].latepoint_manage_booking_key').val(),
2766            start_date: $wrapper.find('input[type="hidden"].latepoint_start_date').val(),
2767            start_time: $wrapper.find('input[type="hidden"].latepoint_start_time').val(),
2768        }
2769        let data = {
2770            action: latepoint_helper.route_action,
2771            route_name: $trigger.data('route-name'),
2772            params: params,
2773            layout: 'none',
2774            return_format: 'json'
2775        };
2776        jQuery.ajax({
2777            type: "post",
2778            dataType: "json",
2779            url: latepoint_timestamped_ajaxurl(),
2780            data: data,
2781            success: function (data) {
2782                $trigger.removeClass('os-loading')
2783                if (data.status === "success") {
2784                    jQuery('.latepoint-lightbox-content').html(data.message);
2785                    jQuery('.latepoint-lightbox-footer, .latepoint-lightbox-heading').remove();
2786                    if (jQuery('.customer-bookings-tiles').length) {
2787                        // called from customer cabinet
2788                        latepoint_customer_cabinet_reload_booking_tile(jQuery('.customer-bookings-tiles .customer-booking[data-id="' + booking_id + '"]'));
2789                    } else {
2790                        // called from manage by key
2791                        latepoint_manage_by_key_reload_booking();
2792                    }
2793                } else {
2794                    latepoint_show_message_inside_element(data.message, jQuery('.latepoint-lightbox-content'), 'error');
2795                    jQuery('.latepoint-lightbox-content').animate({scrollTop: 0}, 300);
2796                }
2797            }
2798        });
2799        return false;
2800    });
2801
2802    jQuery('.reschedule-calendar-wrapper').on('click keydown', '.dp-timepicker-trigger', function (event) {
2803        if (event.type === 'keydown' && event.key !== ' ' && event.key !== 'Enter') return;
2804        var $reschedule_form_element = jQuery(this).closest('.reschedule-calendar-wrapper');
2805        if (jQuery(this).hasClass('is-booked') || jQuery(this).hasClass('is-off')) {
2806            // Show error message that you cant select a booked period
2807        } else {
2808            if (jQuery(this).hasClass('selected')) {
2809                jQuery(this).removeClass('selected');
2810                jQuery(this).find('.dp-success-label').remove();
2811                $reschedule_form_element.find('.latepoint_start_time').val('');
2812                latepoint_hide_reschedule_button();
2813            } else {
2814                $reschedule_form_element.find('.dp-timepicker-trigger.selected').removeClass('selected').find('.dp-success-label').remove();
2815                var selected_timeslot_time = jQuery(this).find('.dp-label-time').html();
2816                jQuery(this).addClass('selected').find('.dp-label').prepend('<span class="dp-success-label">' + latepoint_helper.datepicker_timeslot_selected_label + '</span>');
2817
2818                var minutes = parseInt(jQuery(this).data('minutes'));
2819                var timeshift_minutes = parseInt($reschedule_form_element.find('.latepoint_timeshift_minutes').val());
2820                // we substract timeshift minutes because its timeshift minutes that the business is running in, in opposite of what we do when we generate a calendar for a client
2821                if (timeshift_minutes) minutes = minutes - timeshift_minutes;
2822                var start_date = new Date($reschedule_form_element.find('.os-day.selected').data('date'));
2823                if (minutes < 0) {
2824                    // business minutes are in previous day
2825                    minutes = 24 * 60 + minutes;
2826                    // move start date back 1 day
2827                    start_date.setDate(start_date.getDate() - 1);
2828                } else if (minutes >= 24 * 60) {
2829                    // business minutes are in next day
2830                    minutes = minutes - 24 * 60;
2831                    start_date.setDate(start_date.getDate() + 1);
2832                }
2833                $reschedule_form_element.find('.latepoint_start_date').val(start_date.toISOString().split('T')[0])
2834                $reschedule_form_element.find('.latepoint_start_time').val(minutes);
2835                latepoint_show_reschedule_button();
2836            }
2837        }
2838        return false;
2839    });
2840
2841
2842    jQuery('.reschedule-calendar-wrapper').on('click', '.os-month-next-btn', function () {
2843        var $reschedule_form_element = jQuery(this).closest('.reschedule-calendar-wrapper');
2844        var next_month_route_name = jQuery(this).data('route');
2845        if ($reschedule_form_element.find('.os-monthly-calendar-days-w.active + .os-monthly-calendar-days-w').length) {
2846            $reschedule_form_element.find('.os-monthly-calendar-days-w.active').removeClass('active').next('.os-monthly-calendar-days-w').addClass('active');
2847            latepoint_calendar_set_month_label($reschedule_form_element);
2848        } else {
2849            // TODO add condition to check maximum number months to call into the future
2850            if (true) {
2851                var $btn = jQuery(this);
2852                $btn.addClass('os-loading');
2853                var $calendar_element = $reschedule_form_element.find('.os-monthly-calendar-days-w').last();
2854                var calendar_year = $calendar_element.data('calendar-year');
2855                var calendar_month = $calendar_element.data('calendar-month');
2856                if (calendar_month == 12) {
2857                    calendar_year = calendar_year + 1;
2858                    calendar_month = 1;
2859                } else {
2860                    calendar_month = calendar_month + 1;
2861                }
2862                var data = {
2863                    action: latepoint_helper.route_action,
2864                    route_name: next_month_route_name,
2865                    params: {
2866                        timezone_name: $reschedule_form_element.find('input[type="hidden"].latepoint_timezone_name').val(),
2867                        key: $reschedule_form_element.find('input[type="hidden"].latepoint_manage_booking_key').val(),
2868                        target_date_string: `${calendar_year}-${calendar_month}-1`,
2869                        booking: {
2870                            id: $reschedule_form_element.find('input[type="hidden"].latepoint_booking_id').val()
2871                        }
2872                    },
2873                    layout: 'none',
2874                    return_format: 'json'
2875                }
2876                jQuery.ajax({
2877                    type: "post",
2878                    dataType: "json",
2879                    url: latepoint_timestamped_ajaxurl(),
2880                    data: data,
2881                    success: function (data) {
2882                        $btn.removeClass('os-loading');
2883                        if (data.status === "success") {
2884                            $reschedule_form_element.find('.os-months').append(data.message);
2885                            $reschedule_form_element.find('.os-monthly-calendar-days-w.active').removeClass('active').next('.os-monthly-calendar-days-w').addClass('active');
2886                            latepoint_calendar_set_month_label($reschedule_form_element);
2887                        } else {
2888                            // console.log(data.message);
2889                        }
2890                    }
2891                });
2892            }
2893        }
2894        latepoint_calendar_show_or_hide_prev_next_buttons($reschedule_form_element);
2895        return false;
2896    });
2897
2898    jQuery('.reschedule-calendar-wrapper').on('click', '.os-month-prev-btn', function () {
2899        var $reschedule_form_element = jQuery(this).closest('.reschedule-calendar-wrapper');
2900        if ($reschedule_form_element.find('.os-monthly-calendar-days-w.active').prev('.os-monthly-calendar-days-w').length) {
2901            $reschedule_form_element.find('.os-monthly-calendar-days-w.active').removeClass('active').prev('.os-monthly-calendar-days-w').addClass('active');
2902            latepoint_calendar_set_month_label($reschedule_form_element);
2903        }
2904        latepoint_calendar_show_or_hide_prev_next_buttons($reschedule_form_element);
2905        return false;
2906    });
2907
2908    jQuery('.reschedule-calendar-wrapper .os-months').on('click', '.os-day', function () {
2909        if (jQuery(this).hasClass('os-day-passed')) return false;
2910        if (jQuery(this).hasClass('os-not-in-allowed-period')) return false;
2911        var $reschedule_form_element = jQuery(this).closest('.reschedule-calendar-wrapper');
2912        if (jQuery(this).closest('.os-monthly-calendar-days-w').hasClass('hide-if-single-slot')) {
2913
2914            // HIDE TIMESLOT IF ONLY ONE TIMEPOINT
2915            if (jQuery(this).hasClass('os-not-available')) {
2916                // clicked on a day that has no available timeslots
2917                // do nothing
2918            } else {
2919                $reschedule_form_element.find('.os-day.selected').removeClass('selected');
2920                jQuery(this).addClass('selected');
2921                // set date
2922                $reschedule_form_element.find('.latepoint_start_date').val(jQuery(this).data('date'));
2923                if (jQuery(this).hasClass('os-one-slot-only')) {
2924                    // clicked on a day that has only one slot available
2925                    var bookable_minutes = jQuery(this).data('bookable-minutes').toString().split(':')[0];
2926                    var selected_timeslot_time = latepoint_format_minutes_to_time(Number(bookable_minutes), Number(jQuery(this).data('service-duration')));
2927                    $reschedule_form_element.find('.latepoint_start_time').val(jQuery(this).data('bookable-minutes'));
2928                    $reschedule_form_element.find('.time-selector-w').slide
2928Up(200);
2929                    latepoint_show_reschedule_button()
2930                } else {
2931                    // regular day with more than 1 timeslots available
2932                    // build timeslots
2933                    day_timeslots(jQuery(this), $reschedule_form_element, $reschedule_form_element.find('.latepoint-lightbox-content'));
2934                    // initialize timeslots events
2935                    // clear time and hide next btn
2936                    $reschedule_form_element.find('.latepoint_start_time').val('');
2937                }
2938            }
2939        } else {
2940
2941            // SHOW TIMESLOTS EVEN IF ONLY ONE TIMEPOINT
2942            $reschedule_form_element.find('.latepoint_start_date').val(jQuery(this).data('date'));
2943            $reschedule_form_element.find('.os-day.selected').removeClass('selected');
2944            jQuery(this).addClass('selected');
2945
2946            // build timeslots
2947            day_timeslots(jQuery(this), $reschedule_form_element, $reschedule_form_element.find('.latepoint-lightbox-content'));
2948            // initialize timeslots events
2949            // clear time and hide next btn
2950            let $booking_form_element = jQuery(this).closest('.latepoint-booking-form-element');
2951            if ($booking_form_element.length) latepoint_reload_summary($booking_form_element);
2952            $reschedule_form_element.find('.latepoint_start_time').val('');
2953            latepoint_hide_next_btn($reschedule_form_element);
2954        }
2955
2956        return false;
2957    });
2958}
2959
2960
2961/*
2962 * Copyright (c) 2024 LatePoint LLC. All rights reserved.
2963 */
2964
2965function latepoint_reload_day_schedule($day_view){
2966  $day_view.addClass('os-loading');
2967  let data = {
2968    action: latepoint_helper.route_action,
2969    route_name: $day_view.data('route-name'),
2970    params: $day_view.find("select, textarea, input").serialize(),
2971    layout: 'none',
2972    return_format: 'json'
2973  }
2974
2975  jQuery.ajax({
2976    type: "post",
2977    dataType: "json",
2978    url: latepoint_timestamped_ajaxurl(),
2979    data: data,
2980    success: function (data) {
2981      if (data.status === "success") {
2982        $day_view.replaceWith(data.message);
2983      }
2984    }
2985  });
2986}
2987
2988function latepoint_reload_events_calendar($events_calendar){
2989  $events_calendar.addClass('os-loading');
2990  let data = {
2991    action: latepoint_helper.route_action,
2992    route_name: $events_calendar.data('route-name'),
2993    params: $events_calendar.find("select, textarea, input").serialize(),
2994    layout: 'none',
2995    return_format: 'json'
2996  }
2997
2998  jQuery.ajax({
2999    type: "post",
3000    dataType: "json",
3001    url: latepoint_timestamped_ajaxurl(),
3002    data: data,
3003    success: function (data) {
3004      if (data.status === "success") {
3005        $events_calendar.replaceWith(data.message);
3006      }
3007    }
3008  });
3009
3010}
3011
3012/*
3013 * Copyright (c) 2024 LatePoint LLC. All rights reserved.
3014 */
3015
3016class LatepointStripeConnectFront {
3017
3018    // Init
3019    constructor(stripeKey) {
3020        this.stripeKey = stripeKey;
3021        this.stripeElements = null;
3022        this.stripeCore = null;
3023        this.stripePaymentMethod = null;
3024        this.stripeContinueOrderIntentURL = null;
3025        this.stripeContinueTransactionIntentURL = null;
3026        this.stripePaymentIntentSecret = null;
3027        this.stripePaymentElement = null;
3028        this.ready();
3029    }
3030
3031    ready() {
3032        jQuery(document).ready(() => {
3033            jQuery('body').on('latepoint:submitBookingForm', '.latepoint-booking-form-element', (e, data) => {
3034                if (!latepoint_helper.demo_mode && data.is_final_submit && data.direction == 'next') {
3035                    let payment_method = jQuery(e.currentTarget).find('input[name="cart[payment_method]"]').val();
3036                    switch (payment_method) {
3037                        case 'payment_element':
3038                            latepoint_add_action(data.callbacks_list, async () => {
3039                                if (this.stripePaymentIntentSecret && this.stripeCore) {
3040                                    return await this.confirmPaymentElementPayment(jQuery(e.currentTarget));
3041                                }
3042                            });
3043                            break;
3044                    }
3045                }
3046            });
3047            jQuery('body').on('latepoint:submitTransactionPaymentForm', '.latepoint-transaction-payment-form', (e, data) => {
3048                if (data.current_step === 'pay' && data.payment_processor === 'stripe_connect' && data.payment_method === 'payment_element') {
3049                    latepoint_add_action(data.callbacks_list, async () => {
3050                        if (this.stripePaymentIntentSecret && this.stripeCore) {
3051                            return await this.confirmPaymentElementPaymentForTransaction(jQuery(e.currentTarget));
3052                        }
3053                    });
3054                }
3055            });
3056
3057            // INITIALIZE PAYMENT METHOD
3058            jQuery('body').on('latepoint:initPaymentMethod', '.latepoint-booking-form-element', (e, data) => {
3059                if (!latepoint_helper.demo_mode) {
3060                    switch (data.payment_method) {
3061                        case 'payment_element':
3062                            latepoint_add_action(data.callbacks_list, async () => {
3063                                return await this.createPaymentIntent(jQuery(e.currentTarget), data.payment_method);
3064                            });
3065                            break;
3066                    }
3067                } else {
3068                    latepoint_show_next_btn(jQuery(e.currentTarget));
3069                }
3070            });
3071            // INITIALIZE PAYMENT METHOD on order payment form
3072            jQuery('body').on('latepoint:initOrderPaymentMethod', '.latepoint-transaction-payment-form', (e, data) => {
3073                if (data.payment_processor === 'stripe_connect') {
3074                    switch (data.payment_method) {
3075                        case 'payment_element':
3076                            latepoint_add_action(data.callbacks_list, async () => {
3077                                return await this.createPaymentIntentForTransaction(jQuery(e.currentTarget));
3078                            });
3079                            break;
3080                    }
3081                }
3082            });
3083        });
3084    }
3085
3086
3087    async createPaymentIntentForTransaction($transaction_intent_form) {
3088
3089        try {
3090            this.stripeCore = Stripe(this.stripeKey, {stripeAccount: latepoint_helper.stripe_connected_account_id});
3091            this.stripeElements = this.stripeCore.elements();
3092        }catch(e){
3093            console.log(e);
3094            alert(e);
3095        }
3096
3097        let data = latepoint_create_form_data($transaction_intent_form, latepoint_helper.stripe_connect_route_create_payment_intent_for_transaction_intent);
3098
3099        let response = await jQuery.ajax({
3100            type: "post",
3101            dataType: "json",
3102            processData: false,
3103            contentType: false,
3104            url: latepoint_timestamped_ajaxurl(),
3105            data: data
3106        });
3107
3108        if (response.status === "success") {
3109            $transaction_intent_form.find('input[name="payment_token"]').val(response.payment_intent_id);
3110            this.stripePaymentIntentSecret = response.payment_intent_secret;
3111            this.stripeContinueTransactionIntentURL = response.continue_transaction_intent_url;
3112            latepoint_show_next_btn($transaction_intent_form);
3113
3114            if ($transaction_intent_form.find('.stripe-payment-element').length) {
3115                return this.initPaymentElement($transaction_intent_form);
3116            }
3117        } else {
3118            alert(response.message);
3119            throw new Error(response.message);
3120        }
3121    }
3122
3123    async createPaymentIntent($booking_form_element, payment_method) {
3124        this.stripeCore = Stripe(this.stripeKey, {stripeAccount: latepoint_helper.stripe_connected_account_id});
3125        this.stripeElements = this.stripeCore.elements();
3126
3127        let data = latepoint_create_form_data($booking_form_element.find('.latepoint-form'), latepoint_helper.stripe_connect_route_create_payment_intent, {booking_form_page_url: window.location.href});
3128
3129        let response = await jQuery.ajax({
3130            type: "post",
3131            dataType: "json",
3132            processData: false,
3133            contentType: false,
3134            url: latepoint_timestamped_ajaxurl(),
3135            data: data
3136        });
3137
3138        if (response.status === "success") {
3139            $booking_form_element.find('input[name="cart[payment_token]"]').val(response.payment_intent_id);
3140            this.stripePaymentIntentSecret = response.payment_intent_secret;
3141            this.stripeContinueOrderIntentURL = response.continue_order_intent_url;
3142            latepoint_show_next_btn($booking_form_element);
3143
3144            if ($booking_form_element.find('.stripe-payment-element').length) {
3145                return this.initPaymentElement($booking_form_element);
3146            }
3147        } else {
3148            alert(response.message);
3149            throw new Error(response.message);
3150        }
3151    }
3152
3153    async confirmPaymentElementPaymentForTransaction($transaction_intent_form) {
3154        let elements = this.stripeElements;
3155        let continue_transaction_intent_url = this.stripeContinueTransactionIntentURL;
3156        let result = await this.stripeCore.confirmPayment({
3157            elements,
3158            confirmParams: {
3159                // Return URL where the customer should be redirected after the PaymentIntent is confirmed.
3160                return_url: continue_transaction_intent_url,
3161            },
3162            redirect: "if_required",
3163        });
3164        if (result.error) {
3165            throw new Error(result.error.message);
3166        } else {
3167            $transaction_intent_form.find('input[name="payment_token"]').val(result.paymentIntent.i
3167d);
3168            return result.paymentIntent.id;
3169        }
3170    }
3171
3172    async confirmPaymentElementPayment($booking_form_element) {
3173        let elements = this.stripeElements;
3174        let continue_order_intent_url = this.stripeContinueOrderIntentURL;
3175        let result = await this.stripeCore.confirmPayment({
3176            elements,
3177            confirmParams: {
3178                // Return URL where the customer should be redirected after the PaymentIntent is confirmed.
3179                return_url: continue_order_intent_url,
3180            },
3181            redirect: "if_required",
3182        });
3183        if (result.error) {
3184            throw new Error(result.error.message);
3185        } else {
3186            $booking_form_element.find('input[name="cart[payment_token]"]').val(result.paymentIntent.id);
3187            return result.paymentIntent.id;
3188        }
3189    }
3190
3191    initPaymentElement($booking_form_element) {
3192        let appearance = {
3193            theme: 'stripe',
3194            variables: {
3195                fontFamily: 'Overpass',
3196                colorPrimary: '#1d7bff'
3197            },
3198            rules: {
3199                '.Tab': {
3200                    border: '1px solid #E0E6EB',
3201                    boxShadow: 'none',
3202                    borderRadius: '0',
3203                    marginBottom: '10px'
3204                },
3205                '.Input': {
3206                    boxShadow: 'none',
3207                    borderRadius: '0'
3208                },
3209
3210                '.Tab:hover': {
3211                    color: 'var(--colorText)',
3212                },
3213
3214                '.Tab--selected': {
3215                    borderColor: 'var(--colorPrimary)',
3216                    boxShadow: `0 0 0 1px var(--colorPrimary)`,
3217                },
3218
3219                '.Input--invalid': {
3220                    boxShadow: '0 1px 1px 0 rgba(0, 0, 0, 0.07), 0 0 0 2px var(--colorPrimary)',
3221                },
3222
3223                // See all supported class names and selector syntax below
3224            }
3225        };
3226
3227
3228        // Create an instance of the Payment Element
3229        this.stripeElements = this.stripeCore.elements({
3230            clientSecret: this.stripePaymentIntentSecret,
3231            appearance,
3232            fonts: [{cssSrc: 'https://fonts.googleapis.com/css2?family=Overpass&display=swap'}],
3233        });
3234
3235        let options = {
3236            layout: {
3237                type: 'tabs',
3238                defaultCollapsed: false,
3239            },
3240        };
3241        this.stripePaymentElement = this.stripeElements.create('payment', options);
3242
3243        return this.stripePaymentElement.mount($booking_form_element.find('.stripe-payment-element')[0]);
3244    }
3245
3246
3247}
3248
3249
3250if (latepoint_helper.is_stripe_connect_enabled) window.latepointStripeConnectFront = new LatepointStripeConnectFront(latepoint_helper.stripe_connect_key);
3251
3252/*
3253 * Copyright (c) 2022 LatePoint LLC. All rights reserved.
3254 */
3255// @codekit-prepend "bin/time.js"
3256// @codekit-prepend "bin/shared.js"
3257// @codekit-prepend "bin/notifications.js";
3258// @codekit-prepend "bin/actions.js"
3259// @codekit-prepend "bin/front/main.js"
3260// @codekit-prepend "bin/front/_customer.js"
3261// @codekit-prepend "bin/front/_events.js"
3262// @codekit-prepend "bin/front/_stripe_connect.js"
3263
3264
3265// DOCUMENT READY
3266jQuery(document).ready(function ($) {
3267
3268    latepoint_init_customer_dashboard();
3269    latepoint_init_manage_booking_by_key();
3270
3271
3272    jQuery('body').on('click', '.le-filter-trigger', function () {
3273        let $events_calendar = jQuery(this).closest('.latepoint-calendar-wrapper');
3274        if ($events_calendar.hasClass('show-filters')) {
3275            $events_calendar.removeClass('show-filters');
3276            $events_calendar.find('.latepoint-calendar-filters select').val('');
3277            latepoint_reload_events_calendar($events_calendar);
3278        } else {
3279            $events_calendar.addClass('show-filters');
3280        }
3281        return false;
3282    });
3283
3284    jQuery('body').on('click', '.le-navigation-trigger', function () {
3285        let $trigger = jQuery(this);
3286        let $events_calendar = $trigger.closest('.latepoint-calendar-wrapper');
3287        $events_calendar.find('input[name="target_date_string"]').val($trigger.data('target-date'));
3288        $trigger.addClass('os-loading');
3289        latepoint_reload_events_calendar($events_calendar);
3290        return false;
3291    });
3292
3293    jQuery('body').on('change', '.le-day-filters select', function () {
3294        let $trigger = jQuery(this);
3295        let $day_view = $trigger.closest('.le-day-view-wrapper');
3296        latepoint_reload_day_schedule($day_view);
3297        return false;
3298    });
3299
3300    jQuery('body').on('change', '.latepoint-calendar-filters select, .le-range-selector select', function () {
3301        let $trigger = jQuery(this);
3302        let $events_calendar = $trigger.closest('.latepoint-calendar-wrapper');
3303        $events_calendar.find('.le-filter').addClass('os-loading');
3304        latepoint_reload_events_calendar($events_calendar);
3305        return false;
3306    });
3307
3308    jQuery('body').on('click', '.close-calendar-types', function () {
3309        jQuery(this).closest('.add-to-calendar-wrapper').removeClass('show-types');
3310        return false;
3311    });
3312    jQuery('body').on('click', '.open-calendar-types', function () {
3313        jQuery(this).closest('.add-to-calendar-wrapper').addClass('show-types');
3314        return false;
3315    });
3316
3317    jQuery('body').on('latepoint:nextStepClicked', '.latepoint-booking-form-element', (e, data) => {
3318
3319        latepoint_add_action(data.callbacks_list, async () => {
3320            let $booking_form = jQuery(e.currentTarget).find('.latepoint-form');
3321            let errors = latepoint_validate_form($booking_form);
3322            if (errors.length) {
3323                let error_messages = errors.map(error => error.message).join(', ');
3324                throw new Error(error_messages);
3325            } else {
3326                return true;
3327            }
3328        }, 1);
3329
3330    });
3331
3332    if (latepoint_helper.start_from_order_intent_key) {
3333        $('body').append('<div class="latepoint-continue-intent-loading"></div>');
3334
3335        var data = {
3336            action: latepoint_helper.route_action,
3337            route_name: latepoint_helper.start_from_order_intent_route,
3338            params: {order_intent_key: latepoint_helper.start_from_order_intent_key},
3339            layout: 'none',
3340            return_format: 'json'
3341        }
3342
3343        $.ajax({
3344            type: "post",
3345            dataType: "json",
3346            url: latepoint_timestamped_ajaxurl(),
3347            data: data,
3348            success: function (data) {
3349                $('.latepoint-continue-intent-loading').remove();
3350                if (data.status === "success") {
3351                    var lightbox_class = '';
3352                    latepoint_show_data_in_lightbox(data.message, data.lightbox_class);
3353                    var $booking_form_element = jQuery('.latepoint-lightbox-w .latepoint-booking-form-element');
3354                    latepoint_init_booking_form($booking_form_element);
3355                    $booking_form_element.find('.latepoint-step-content').addClass('is-hidden').last().removeClass('is-hidden');
3356                    if ($booking_form_element.find('.latepoint-step-content').length > 1) latepoint_show_prev_btn($booking_form_element);
3357                    var $booking_form_element = jQuery('.latepoint-lightbox-w .latepoint-booking-form-element');
3358                    $booking_form_element.find('.latepoint-step-content').each(function () {
3359                        latepoint_init_step($(this).data('step-code'), $booking_form_element);
3360                    });
3361                    $('body').addClass('latepoint-lightbox-active');
3362                } else {
3363                    // console.log(data.message);
3364                }
3365            }
3366        });
3367    }
3368
3369    if (jQuery('.latepoint-booking-form-element').length) {
3370        jQuery('.latepoint-booking-form-element').each(function () {
3371            latepoint_init_booking_form(jQuery(this));
3372            latepoint_init_step(jQuery(this).find('.latepoint_current_step_code').val(), jQuery(this));
3373        });
3374    }
3375
3376
3377    jQuery('body').on('click', '.latepoint-lightbox-close', function () {
3378        latepoint_lightbox_close();
3379        return false;
3380    });
3381
3382
3383    jQuery('body').on('click', '.os-step-tabs .os-step-tab', function () {
3384        jQuery(this).closest('.os-step-tabs').find('.os-step-tab').removeClass('active');
3385        jQuery(this).addClass('active');
3386        var target = jQuery(this).data('target');
3387        jQuery(this).closest('.os-step-tabs-w').find('.os-step-tab-content').hide();
3388        jQuery(target).show();
3389    });
3390
3391    jQuery('body').on('keyup', '.os-form-group .os-form-control', function () {
3392        if (jQuery(this).val()) {
3393            jQuery(this).closest('.os-form-group').addClass('has-value');
3394        } else {
3395            jQuery(this).closest('.os-form-group').removeClass('has-value');
3396        }
3397    });
3398
3399    jQuery('.latepoint-tab-triggers').on('click', '.latepoint-tab-trigger', function () {
3400        var $tabs_wrapper = jQuery(this).closest('.latepoint-tabs-w')
3401        $tabs_wrapper.find('.latepoint-tab-trigger.active').removeClass('active');
3402        $tabs_wrapper.find('.latepoint-tab-content').removeClass('active');
3403        jQuery(this).addClass('active');
3404        $tabs_wrapper.find('.latepoint-tab-content' + jQuery(this).data('tab-target')).addClass('active');
3405        return false;
3406    });
3407
3408
3409    // Main Button to trigger lightbox opening
3410    if(jQuery('.latepoint-book-form-wrapper').length){
3411        jQuery('.latepoint-book-form-wrapper').each(function(){
3412            latepoint_init_booking_form_by_trigger(jQuery(this));
3413        });
3414    }
3415
3416    jQuery('body').on('click', '.latepoint-book-button, .os_trigger_booking', function () {
3417        latepoint_init_booking_form_by_trigger(jQuery(this));
3418        return false;
3419    });
3420
3421});

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.