PageSourceSearch

https://kitech.ai/wp-content/plugins/kitech-meetflow/assets/js/frontend.js?ver=2.2.56

js kitech.ai collected 2026-10-05 15:22:49 UTC 19,336 bytes, 435 lines download raw bytes

1/**
2 * MeetFlow — modern booking wizard (month calendar, slots, provider choice).
3 */
4(function($) {
5    'use strict';
6
7    var strings = (typeof meetflowBooking !== 'undefined' && meetflowBooking.strings) ? meetflowBooking.strings : {};
8    var locale = (meetflowBooking && meetflowBooking.locale) ? meetflowBooking.locale : 'en-US';
9
10    function escHtml(value) {
11        return $('<div>').text(value == null ? '' : String(value)).html();
12    }
13
14    function formatDate(date) {
15        var y = date.getFullYear();
16        var m = String(date.getMonth() + 1).padStart(2, '0');
17        var d = String(date.getDate()).padStart(2, '0');
18        return y + '-' + m + '-' + d;
19    }
20
21    function parseDate(str) {
22        var p = String(str).split('-');
23        return new Date(parseInt(p[0], 10), parseInt(p[1], 10) - 1, parseInt(p[2], 10));
24    }
25
26    function formatDisplayDate(str) {
27        try {
28            return new Intl.DateTimeFormat(locale, { weekday: 'long', year: 'numeric', month: 'long', day: 'numeric' }).format(parseDate(str));
29        } catch (e) {
30            return str;
31        }
32    }
33
34    var MeetFlowBooking = {
35        selectedDate: null,
36        selectedTime: null,
37        selectedProvider: null,
38        viewMonth: null,
39        minDate: null,
40        maxDate: null,
41
42        init: function() {
43            var today = new Date();
44            today.setHours(0, 0, 0, 0);
45            this.minDate = new Date(today);
46            this.maxDate = new Date(today);
47            this.maxDate.setDate(this.maxDate.getDate() + 60);
48            this.viewMonth = new Date(today.getFullYear(), today.getMonth(), 1);
49
50            var tomorrow = new Date(today);
51            tomorrow.setDate(tomorrow.getDate() + 1);
52            this.selectedDate = formatDate(tomorrow);
53            $('#kitech-datepicker').val(this.selectedDate);
54
55            this.renderMonthCalendar();
56            this.initProviders();
57            this.bindEvents();
58            this.goToStep('date');
59            this.updateResetButton();
60        },
61
62        initProviders: function() {
63            var cfg = (meetflowBooking && meetflowBooking.meetingProviders) ? meetflowBooking.meetingProviders : null;
64            if (!cfg || !cfg.allow_choice || !cfg.providers || cfg.providers.length < 2) {
65                $('#meetflow-provider-picker').prop('hidden', true);
66                if (cfg && cfg.providers && cfg.providers.length === 1) {
67                    this.selectedProvider = cfg.providers[0].id;
68                }
69                return;
70            }
71
72            var html = '';
73            cfg.providers.forEach(function(p, idx) {
74                var checked = (cfg.default === p.id || idx === 0) ? ' checked' : '';
75                if (checked) {
76                    MeetFlowBooking.selectedProvider = p.id;
77                }
78                html += '<label class="meetflow-provider-option" style="--mf-provider-accent:' + escHtml(p.accent) + '">' +
79                    '<input type="radio" name="meetflow_conference_provider" value="' + escHtml(p.id) + '"' + checked + '>' +
80                    '<span class="meetflow-provider-option__label">' + escHtml(p.label) + '</span>' +
81                    '</label>';
82            });
83
84            $('.meetflow-provider-options').html(html);
85            $('#meetflow-provider-picker').prop('hidden', false);
86        },
87
88        bindEvents: function() {
89            var self = this;
90
91            $(document).on('click', '.mf-cal-prev', function(e) {
92                e.preventDefault();
93                self.viewMonth.setMonth(self.viewMonth.getMonth() - 1);
94                self.renderMonthCalendar();
95            });
96
97            $(document).on('click', '.mf-cal-next', function(e) {
98                e.preventDefault();
99                self.viewMonth.setMonth(self.viewMonth.getMonth() + 1);
100                self.renderMonthCalendar();
101            });
102
103            $(document).on('click', '.mf-cal-day:not(.is-disabled):not(.is-empty)', function() {
104                var date = $(this).data('date');
105                if (!date) return;
106                self.selectedDate = date;
107                $('#kitech-datepicker').val(date);
108                $('.mf-cal-day').removeClass('is-selected');
109                $(this).addClass('is-selected');
110                self.loadTimeslots(date);
111                self.goToStep('time');
112            });
113
114            $(document).on('click', '.kitech-timeslot', function() {
115                if ($(this).hasClass('booked') || $(this).hasClass('blocked')) {
116                    return false;
117                }
118                $('.kitech-timeslot').removeClass('selected');
119                $(this).addClass('selected');
120                self.selectedTime = $(this).data('time');
121                self.goToStep('details');
122            });
123
124            $(document).on('change', 'input[name="meetflow_conference_provider"]', function() {
125                self.selectedProvider = $(this).val();
126            });
127
128            $('#meetflow-form').on('submit', function(e) {
129                e.preventDefault();
130                self.submitBooking();
131            });
132
133            $('#meetflow-reset-btn').on('click', function(e) {
134                e.preventDefault();
135                self.resetBooking();
136            });
137        },
138
139        updateResetButton: function() {
140            var step = $('.meetflow-panel.is-active').data('panel');
141            $('#meetflow-reset-btn').prop('hidden', step === 'date');
142        },
143
144        resetBooking: function() {
145            this.selectedTime = null;
146            this.selectedProvider = null;
147
148            var today = new Date();
149            today.setHours(0, 0, 0, 0);
150            var tomorrow = new Date(today);
151            tomorrow.setDate(tomorrow.getDate() + 1);
152            this.selectedDate = formatDate(tomorrow);
153            this.viewMonth = new Date(tomorrow.getFullYear(), tomorrow.getMonth(), 1);
154            $('#kitech-datepicker').val(this.selectedDate);
155
156            $('#meetflow-form')[0].reset();
157            if (meetflowBooking && meetflowBooking.defaultPhonePrefix) {
158                $('#kitech-phone-prefix').val(meetflowBooking.defaultPhonePrefix);
159            }
160            $('.kitech-message').remove();
161            $('.kitech-timeslot').removeClass('selected');
162            $('.kitech-timeslots').html('<p class="kitech-loading">' + escHtml(strings.loading || 'Loading available times...') + '</p>');
163
164            this.initProviders();
165            this.renderMonthCalendar();
166            this.goToStep('date');
167            this.updateResetButton();
168        },
169
170        getFullPhone: function() {
171            var prefix = ($('#kitech-phone-prefix').val() || '').trim();
172            var local = ($('#kitech-phone').val() || '').trim();
173            if (!prefix && local.indexOf('+') === 0) {
174                return local.replace(/\s+/g, '');
175            }
176            var digits = local.replace(/[^\d]/g, '').replace(/^0+/, '');
177            return prefix + digits;
178        },
179
180        renderMonthCalendar: function() {
181            var self = this;
182            var month = this.viewMonth.getMonth();
183            var year = this.viewMonth.getFullYear();
184            var first = new Date(year, month, 1);
185            var startDay = (first.getDay() + 6) % 7;
186            var daysInMonth = new Date(year, month + 1, 0).getDate();
187
188            var monthLabel;
189            try {
190                monthLabel = new Intl.DateTimeFormat(locale, { month: 'long', year: 'numeric' }).format(first);
191            } catch (e) {
192                monthLabel = (month + 1) + '/' + year;
193            }
194            $('#mf-cal-month-label').text(monthLabel);
195
196            var weekdays = [];
197            var base = new Date(2024, 0, 1);
198            for (var w = 0; w < 7; w++) {
199                try {
200                    weekdays.push(new Intl.DateTimeFormat(locale, { weekday: 'short' }).format(new Date(2024, 0, 1 + w)));
201                } catch (e2) {
202                    weekdays.push(['Mo', 'Tu', 'We', 'Th', 'Fr', 'Sa', 'Su'][w]);
203                }
204            }
205            $('.mf-cal-weekdays').html(weekdays.map(function(d) {
206                return '<span class="mf-cal-weekday">' + escHtml(d) + '</span>';
207            }).join(''));
208
209            var html = '';
210            var cell = 0;
211            for (var i = 0; i < startDay; i++) {
212                html += '<span class="mf-cal-day is-empty" role="gridcell"></span>';
213                cell++;
214            }
215
216            for (var day = 1; day <= daysInMonth; day++) {
217                var dateObj = new Date(year, month, day);
218                var iso = formatDate(dateObj);
219                var classes = 'mf-cal-day';
220                var disabled = dateObj < self.minDate || dateObj > self.maxDate;
221                if (disabled) classes += ' is-disabled';
222                if (iso === self.selectedDate) classes += ' is-selected';
223                if (iso === formatDate(new Date())) classes += ' is-today';
224                html += '<button type="button" class="' + classes + '" data-date="' + escHtml(iso) + '" role="gridcell"' + (disabled ? ' disabled' : '') + '>' + day + '</button>';
225                cell++;
226            }
227
228            while (cell % 7 !== 0) {
229                html += '<span class="mf-cal-day is-empty" role="gridcell"></span>';
230                cell++;
231            }
232
233            $('#mf-cal-grid').html(html);
234        },
235
236        goToStep: function(step) {
237            $('.meetflow-step').removeClass('is-active is-done');
238            $('.meetflow-panel').prop('hidden', true).removeClass('is-active');
239
240            var order = ['date', 'time', 'details'];
241            order.forEach(function(s, idx) {
242                var $step = $('.meetflow-step[data-step="' + s + '"]');
243                if (s === step) {
244                    $step.addClass('is-active');
245                } else if (order.indexOf(step) > idx) {
246                    $step.addClass('is-done');
247                }
248            });
249
250            $('.meetflow-panel[data-panel="' + step + '"]').prop('hidden', false).addClass('is-active');
251
252            if (step === 'time' && this.selectedDate) {
253                $('#meetflow-selected-date').text(formatDisplayDate(this.selectedDate));
254            }
255
256            this.updateResetButton();
257        },
258
259        loadTimeslots: function(date) {
260            var self = this;
261            this.selectedDate = date;
262            $('.kitech-timeslots').html('<div class="kitech-loading"><div class="kitech-spinner"></div><p>' + escHtml(strings.loading || 'Loading available times...') + '</p></div>');
263
264            $.ajax({
265                url: meetflowBooking.restUrl + 'timeslots',
266                method: 'GET',
267                data: { date: date },
268                contentType: 'application/json; charset=UTF-8',
269                dataType: 'json',
270                beforeSend: function(xhr) {
271                    xhr.setRequestHeader('X-WP-Nonce', meetflowBooking.restNonce);
272                },
273                success: function(response) {
274                    self.renderTimeslots(response);
275                },
276                error: function() {
277                    $('.kitech-timeslots').html('<div class="kitech-message error">' + escHtml(strings.errorLoading || 'Error loading time slots.') + '</div>');
278                }
279            });
280        },
281
282        renderTimeslots: function(slots) {
283            var html = '';
284            if (!slots || slots.length === 0) {
285                html = '<div class="kitech-message info">' + escHtml(strings.noSlots || 'No available time slots') + '</div>';
286            } else {
287                var availableCount = 0;
288                slots.forEach(function(slot) {
289                    var classes = 'kitech-timeslot';
290                    var title = '';
291                    if (!slot.available) {
292                        classes += ' booked blocked';
293                        title = ' title="' + escHtml(strings.alreadyBooked || 'Already booked') + '"';
294                    } else {
295                        availableCount++;
296                    }
297                    var selected = (slot.time === MeetFlowBooking.selectedTime) ? ' selected' : '';
298                    html += '<div class="' + classes + selected + '" data-time="' + escHtml(slot.time) + '"' + title + '>' + escHtml(slot.display) + '</div>';
299                });
300                if (availableCount === 0) {
301                    html = '<div class="kitech-message warning">' + escHtml(strings.allBooked || 'All time slots booked. Please choose another date.') + '</div>' + html;
302                }
303            }
304            $('.kitech-timeslots').html(html);
305        },
306
307        submitBooking: function() {
308            var self = this;
309            if (!this.validateForm()) return;
310
311            var formData = {
312                date: this.selectedDate,
313                time: this.selectedTime,
314                name: $('#kitech-name').val(),
315                email: $('#kitech-email').val(),
316                phone: this.getFullPhone(),
317                notes: $('#kitech-notes').val()
318            };
319
320            if (this.selectedProvider) {
321                formData.conference_provider = this.selectedProvider;
322            }
323
324            var $submitBtn = $('#kitech-submit-btn');
325            $submitBtn.prop('disabled', true).addClass('loading').text(strings.booking || 'Booking...');
326            $('.kitech-message').remove();
327
328            $.ajax({
329                url: meetflowBooking.restUrl + 'booking',
330                method: 'POST',
331                contentType: 'application/json; charset=UTF-8',
332                dataType: 'json',
333                headers: {
334                    'Content-Type': 'application/json; charset=UTF-8',
335                    'X-WP-Nonce': meetflowBooking.restNonce
336                },
337                data: JSON.stringify(formData),
338                success: function(response) {
339                    self.showConfirmation(response);
340                },
341                error: function(xhr) {
342                    var message = strings.errorCreating || 'Error creating booking';
343                    if (xhr.responseJSON && xhr.responseJSON.message) {
344                        message = xhr.responseJSON.message;
345                    } else if (xhr.status === 409) {
346                        message = strings.slotTaken || 'Slot just booked. Please choose another.';
347                        self.goToStep('time');
348                        self.loadTimeslots(self.selectedDate);
349                    }
350                    $('.meetflow-form').prepend('<div class="kitech-message error">' + escHtml(message) + '</div>');
351                    $submitBtn.prop('disabled', false).removeClass('loading').text(strings.bookAppointment || 'Book appointment');
352                }
353            });
354        },
355
356        validateForm: function() {
357            var errors = [];
358            if (!this.selectedDate) errors.push(strings.selectDate || 'Please select a date');
359            if (!this.selectedTime) errors.push(strings.selectTime || 'Please select a time');
360
361            var cfg = meetflowBooking.meetingProviders;
362            if (cfg && cfg.allow_choice && cfg.providers && cfg.providers.length > 1 && !this.selectedProvider) {
363                errors.push(strings.platformRequired || 'Please choose a meeting platform');
364            }
365
366            var name = $('#kitech-name').val();
367            if (!name || name.trim().length < 2) errors.push(strings.enterName || 'Please enter your name');
368
369            var email = $('#kitech-email').val();
370            if (!email) {
371                errors.push(strings.emailRequired || 'Email required');
372            } else if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email)) {
373                errors.push(strings.validEmail || 'Enter a valid email');
374            }
375
376            if (!$('#kitech-phone').val().trim()) {
377                errors.push(strings.phoneRequired || 'Phone required');
378            } else {
379                var fullPhone = this.getFullPhone();
380                if (!/^\+[1-9]\d{7,14}$/.test(fullPhone)) {
381                    errors.push(strings.phoneInvalid || 'Please enter a valid phone number');
382                }
383            }
384            if (!$('#kitech-dsgvo').is(':checked')) errors.push(strings.acceptPrivacy || 'Accept privacy policy');
385
386            if (errors.length) {
387                $('.kitech-message').remove();
388                $('.meetflow-form').prepend('<div class="kitech-message error">' + errors.map(escHtml).join('<br>') + '</div>');
389                return false;
390            }
391            return true;
392        },
393
394        showConfirmation: function(data) {
395            var html = '<div class="kitech-confirmation">' +
396                '<div class="kitech-confirmation-icon"></div>' +
397                '<h3>' + escHtml(strings.bookingSuccess || 'Booking successful!') + '</h3>' +
398                '<p>' + escHtml(strings.confirmationEmail || 'You will receive a confirmation email.') + '</p>' +
399                '<p><strong>' + escHtml(strings.bookingIdLabel || 'Booking ID:') + '</strong> ' + escHtml(data.booking_id) + '</p>';
400
401            if (data.meeting_url) {
402                html += '<p><a href="' + escHtml(data.meeting_url) + '" target="_blank" rel="noopener noreferrer" class="button meetflow-meeting-link kitech-meeting-link">' + escHtml(strings.joinMeeting || 'Join meeting') + '</a></p>';
403            }
404
405            if (data.calendar && (data.calendar.google || data.calendar.outlook || data.calendar.ics_url)) {
406                html += '<div class="meetflow-calendar-actions"><p class="meetflow-calendar-actions__title">' + escHtml(strings.addToCalendar || 'Add to calendar') + '</p><div class="meetflow-calendar-actions__buttons">';
407                if (data.calendar.google) {
408                    html += '<a href="' + escHtml(data.calendar.google) + '" target="_blank" rel="noopener noreferrer" class="button meetflow-cal-btn meetflow-cal-btn--google">' + escHtml(strings.googleCalendar || 'Google Calendar') + '</a>';
409                }
410                if (data.calendar.outlook) {
411                    html += '<a href="' + escHtml(data.calendar.outlook) + '" target="_blank" rel="noopener noreferrer" class="button meetflow-cal-btn meetflow-cal-btn--outlook">' + escHtml(strings.outlookCalendar || 'Outlook') + '</a>';
412                }
413                if (data.calendar.ics_url) {
414                    html += '<a href="' + escHtml(data.calendar.ics_url) + '" class="button meetflow-cal-btn meetflow-cal-btn--ics">' + escHtml(strings.downloadIcs || 'Download .ics') + '</a>';
415                }
416                html += '</div><p class="meetflow-calendar-actions__hint">' + escHtml(strings.icsHint || '') + '</p></div>';
417            }
418
419            html += '<p class="kitech-confirmation-actions"><button type="button" class="button meetflow-submit-btn kitech-submit-btn kitech-book-again">' + escHtml(strings.anotherBooking || 'Another booking') + '</button></p></div>';
420
421            $('.meetflow-calendar').html(html);
422        }
423    };
424
425    $(document).ready(function() {
426        if ($('.meetflow-calendar[data-meetflow-wizard]').length) {
427            MeetFlowBooking.init();
428        }
429        $(document).on('click', '.kitech-book-again', function() {
430            window.location.reload();
431        });
432    });
433
434    window.MeetFlowBooking = MeetFlowBooking;
435})(jQuery);

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.