mod-build 3.6.75-beta.2 → 4.0.0-alpha.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (62) hide show
  1. package/.eslintignore +3 -0
  2. package/.eslintrc +18 -0
  3. package/CHANGELOG.md +2 -252
  4. package/README.md +16 -263
  5. package/gulp-tasks/grab-cdn.js +0 -10
  6. package/package.json +18 -68
  7. package/siteconfig.js +38 -0
  8. package/src/data/footer.js +117 -0
  9. package/src/data/seasons.js +5 -7
  10. package/src/index.html +18 -0
  11. package/src/main.js +45 -0
  12. package/src/scripts/has-qs-params.js +6 -5
  13. package/src/scripts/url-cleaner.js +3 -3
  14. package/src/scripts/utils.js +178 -0
  15. package/src/styles/home.scss +1 -0
  16. package/src/templates/_partials/scripts/deferred-styles.html +16 -16
  17. package/src/templates/_partials/scripts/vwo-redirect-callback.html +43 -45
  18. package/src/templates/components/head.html +70 -0
  19. package/tasks/clean.js +13 -0
  20. package/tasks/grab-cdn.js +107 -0
  21. package/tasks/grab-form-helpers.js +94 -0
  22. package/tasks/grab-shared-components.js +81 -0
  23. package/tasks/grab-shared-scripts.js +267 -0
  24. package/tasks/serve.js +15 -0
  25. package/tasks/templates.js +168 -0
  26. package/template.js +801 -0
  27. package/vite.config.js +56 -0
  28. package/.eslintrc.yml +0 -59
  29. package/src/data/common.js +0 -704
  30. package/src/data/components/qs-footer.js +0 -55
  31. package/src/data/components/quote-footer.js +0 -73
  32. package/src/scripts/apt-block.js +0 -919
  33. package/src/scripts/components/custom-selects.js +0 -48
  34. package/src/scripts/components/radio-panels.js +0 -45
  35. package/src/scripts/es6-1.js +0 -6
  36. package/src/scripts/es6-2.js +0 -2
  37. package/src/scripts/qs-form.js +0 -839
  38. package/src/scripts/vendor/maxmind-geoip2.js +0 -2
  39. package/src/scripts/vendor/swiper.min.js +0 -13
  40. package/src/styles/apt-block.scss +0 -888
  41. package/src/templates/_partials/apt-block.html +0 -30
  42. package/src/templates/_partials/scripts/analytics.html +0 -4
  43. package/src/templates/_partials/scripts/go-page-hiding-snippet.html +0 -8
  44. package/src/templates/_partials/scripts/google-maps.html +0 -1
  45. package/src/templates/_partials/scripts/google-optimize.html +0 -12
  46. package/src/templates/_partials/scripts/gtm-editorials/body/google-tag-manager-body.html +0 -5
  47. package/src/templates/_partials/scripts/gtm-editorials/head/google-tag-manager-head.html +0 -10
  48. package/src/templates/_partials/scripts/gtm-hil/body/google-tag-manager-body.html +0 -5
  49. package/src/templates/_partials/scripts/gtm-hil/head/google-tag-manager-head.html +0 -10
  50. package/src/templates/_partials/scripts/gtm-pro/body/google-tag-manager-body.html +0 -5
  51. package/src/templates/_partials/scripts/gtm-pro/head/google-tag-manager-head.html +0 -10
  52. package/src/templates/_partials/scripts/gtm-quote/body/google-tag-manager-body.html +0 -5
  53. package/src/templates/_partials/scripts/gtm-quote/head/google-tag-manager-head.html +0 -9
  54. package/src/templates/_partials/scripts/gtm-whitelabel/body/mod-google-tag-manager-body.html +0 -5
  55. package/src/templates/_partials/scripts/gtm-whitelabel/body/non-mod-google-tag-manager-body.html +0 -5
  56. package/src/templates/_partials/scripts/gtm-whitelabel/head/mod-google-tag-manager-head.html +0 -10
  57. package/src/templates/_partials/scripts/gtm-whitelabel/head/non-mod-google-tag-manager-head.html +0 -9
  58. package/src/templates/_partials/scripts/gtm-wordpress/body/google-tag-manager-body.html +0 -5
  59. package/src/templates/_partials/scripts/gtm-wordpress/head/google-tag-manager-head.html +0 -9
  60. package/src/templates/_partials/scripts/visual-website-optimizer.html +0 -5
  61. package/src/templates/index.html +0 -46
  62. /package/{src → public}/favicon.ico +0 -0
@@ -1,919 +0,0 @@
1
- /* global modUtils modForm heap aptBlockTCPA:true */
2
- /* eslint brace-style: 0 */
3
-
4
- (function($) {
5
- var apiUrl = modUtils.getApiDomain() + '/v1/',
6
- getParams = modUtils.getUrlParamsToObject(),
7
- projectId,
8
- $aptBlock, $aptBlockMatches, $aptBlockTitle, $aptBlockLoading,
9
- $instantAptBlock, $instantAptBlockMatches,
10
- servicesProcessingCount = 0,
11
- serviceAptSetIndex = 1,
12
- instantAptsTimeSlotLimit = 4,
13
- instantAptsData = {
14
- services: [],
15
- index: 0
16
- },
17
- gaTrackerNames = ['main', 'instantAppts'];
18
-
19
- /**
20
- * Request data from API
21
- * @param {String} type - request type
22
- * @param {String} url - url to request
23
- * @param {Object} data - data to send
24
- * @param {Function} successCallback - success callback
25
- * @param {Function} errorCallback - error callback
26
- */
27
- function apiRequestData(type, url, data, successCallback, errorCallback) {
28
- var ajaxSettings = {
29
- url: url,
30
- type: type,
31
- data: data,
32
- dataType: 'json',
33
- crossDomain: true,
34
- success: successCallback,
35
- error: errorCallback
36
- };
37
-
38
- // Modify it for POST request
39
- if ('post' === type) {
40
- ajaxSettings.data = JSON.stringify(data);
41
- ajaxSettings.contentType = 'application/json';
42
- }
43
-
44
- // And send
45
- $.ajax(ajaxSettings);
46
- }
47
-
48
- /**
49
- * Get available services from API
50
- * @param {Function} successCallback - success callback
51
- * @param {Function} errorCallback - error callback
52
- */
53
- function apiGetAvailableServices(successCallback, errorCallback) {
54
- apiRequestData('get', apiUrl + 'projects/' + projectId + '/available-services', {}, successCallback, errorCallback);
55
- }
56
-
57
- /**
58
- * Get available schedule for a service
59
- * @param {String} serviceId - service id
60
- * @param {Function} successCallback - success callback
61
- * @param {Function} errorCallback - error callback
62
- */
63
- function apiGetServiceAvailableSchedule(serviceId, successCallback, errorCallback) {
64
- apiRequestData('get', apiUrl + 'projects/' + projectId + '/available-services/' + serviceId + '/available-schedule', {}, successCallback, errorCallback);
65
- }
66
-
67
- /**
68
- * Post appointment data to API
69
- * @param {Object} data - appointment data
70
- * @param {Function} successCallback - success callback
71
- * @param {Function} errorCallback - error callback
72
- */
73
- function apiPostAppointmentData(data, successCallback, errorCallback) {
74
- apiRequestData('post', apiUrl + 'appointments', data, successCallback, errorCallback);
75
- }
76
-
77
- /**
78
- * Get saved scheduled appointment data
79
- * @param {String} pId - project id
80
- * @returns {Object} data - data
81
- */
82
- function getSavedScheduledAppsDataFromCookies(pId) {
83
- var scheduledApptsData = modUtils.getCookie('scheduledAppts_' + pId);
84
-
85
- if (scheduledApptsData) {
86
- try {
87
- return JSON.parse(scheduledApptsData);
88
- }
89
- catch (e) {
90
- return false;
91
- }
92
- }
93
-
94
- return false;
95
- }
96
-
97
- /**
98
- * Save scheduled appointment data
99
- * @param {String} pId - project id
100
- * @param {String} sId - service id
101
- * @param {Object} data - data to save
102
- */
103
- function saveScheduledAppsDataInCookies(pId, sId, data) {
104
- var scheduledApptsData = getSavedScheduledAppsDataFromCookies(pId);
105
-
106
- if (!scheduledApptsData) {
107
- scheduledApptsData = {};
108
- }
109
-
110
- if ('undefined' === typeof scheduledApptsData[sId]) {
111
- scheduledApptsData[sId] = [];
112
- }
113
-
114
- scheduledApptsData[sId].push(data);
115
- modUtils.setCookie('scheduledAppts_' + pId, JSON.stringify(scheduledApptsData), 60 * 24 * 7);
116
- }
117
-
118
- /**
119
- * Create acronym
120
- * @param {String} name - phrase to convert to acronym
121
- * @param {Integer} maxLength - max lengths
122
- * @returns {String} - acronym
123
- */
124
- function createAcronym(name, maxLength) {
125
- var abbr = '';
126
-
127
- // Default length
128
- if (!maxLength) {
129
- maxLength = 2;
130
- }
131
-
132
- // Split by spaces or -
133
- var words = String(name).split(/[\s|-]/);
134
-
135
- // Go through each word
136
- for (var i = 0; i < words.length; i ++) {
137
- var firstLetter = words[i].charAt(0);
138
-
139
- // Check if it is uppercase
140
- if (/[A-Z]|[\u0080-\u024F]/.test(firstLetter) && firstLetter === firstLetter.toUpperCase()) {
141
- abbr += firstLetter;
142
- }
143
- }
144
-
145
- return abbr.substring(0, maxLength);
146
- }
147
-
148
- /**
149
- * Format date to "Wednesday, 11/22"
150
- * @param {String} dateTimeStrUTC - date & time string in UTC
151
- * @param {String} dateStr - date string in yyyy-mm-dd format
152
- * @param {String} timeZone - time zone
153
- * @returns {String} - formatted date
154
- */
155
- function formatDate(dateTimeStrUTC, dateStr, timeZone) {
156
- var dateArr = dateTimeStrUTC.split(/[\-\+ :T]/),
157
- date = new Date(),
158
- formatter,
159
- formattedDate,
160
- months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
161
-
162
- try {
163
- formatter = new Intl.DateTimeFormat('en-US', {
164
- weekday: 'short',
165
- month: 'numeric',
166
- day: 'numeric',
167
- timeZone: timeZone
168
- });
169
-
170
- date.setUTCFullYear(dateArr[0]);
171
- date.setUTCMonth(dateArr[1] - 1);
172
- date.setUTCDate(dateArr[2]);
173
- date.setUTCHours(dateArr[3]);
174
- date.setUTCMinutes(dateArr[4]);
175
- date.setUTCSeconds(0);
176
-
177
- formattedDate = formatter.format(date);
178
- }
179
- catch (e) {
180
- dateArr = dateStr.split('-');
181
-
182
- formattedDate = months[dateArr[1] - 1] + ' ' + (dateArr[2] * 1);
183
- }
184
-
185
- return formattedDate;
186
- }
187
-
188
- /**
189
- * Update service's date display
190
- * @param {Object} $service - jQuery DOM object of a service
191
- * @param {String} date - date string
192
- */
193
- function updateServiceSelectedDate($service, date) {
194
- $service.find('[data-bind="apt-match-schedule-date"]').text(date);
195
- }
196
-
197
- /**
198
- * Update service's time display
199
- * @param {Object} $service - jQuery DOM object of a service
200
- * @param {String} time - time string
201
- */
202
- function updateServiceSelectedTime($service, time) {
203
- $service.find('[data-bind="apt-match-schedule-time"]').text(time);
204
- }
205
-
206
- /**
207
- * Update time selector with date's times
208
- * @param {Object} $service - jQuery DOM object of a service block
209
- * @param {Object} $timeSelector - jQuery DOM object of a time selector
210
- * @param {Array} appointmentTimes - array of available times
211
- * @param {String} timeZoneAbbr - timezone abbr
212
- */
213
- function updateTimeSelector($service, $timeSelector, appointmentTimes, timeZoneAbbr) {
214
- // Empty time selector
215
- $timeSelector
216
- .empty()
217
- .prop('title', false);
218
-
219
- // Check if times are not empty
220
- if (appointmentTimes) {
221
- $timeSelector.parents('[data-bind="apt-match-schedule-control"]').removeClass('apt-match__schedule-control--date-only');
222
- $timeSelector.prop('disabled', false);
223
-
224
- $.each(appointmentTimes, function() {
225
- var appointmentTime = this,
226
- $option;
227
-
228
- $option = $('<option value="' + appointmentTime.startDateTimeUTC + '">' + appointmentTime.startTime + ' ' + timeZoneAbbr + '</option>');
229
-
230
- $timeSelector.append($option);
231
- });
232
- }
233
- else {
234
- $timeSelector.parents('[data-bind="apt-match-schedule-control"]').addClass('apt-match__schedule-control--date-only');
235
- $timeSelector
236
- .append($('<option>Time</option>'))
237
- .prop('disabled', true)
238
- .prop('title', 'Please select date first');
239
- }
240
-
241
- $timeSelector.on('change', function() {
242
- updateServiceSelectedTime($service, $(this).find('option:selected').text().trim());
243
- });
244
- $timeSelector.trigger('change');
245
- }
246
-
247
- /**
248
- * Detect trade from URL
249
- * @returns {String} trade
250
- */
251
- function detectTrade() {
252
- var url = window.location.href.toLowerCase(),
253
- trade;
254
-
255
- if (url.indexOf('window') > -1) {
256
- trade = 'windows';
257
- }
258
- else if (url.indexOf('heating') > -1) {
259
- trade = 'hvac';
260
- }
261
- else if (url.indexOf('solar') > -1) {
262
- trade = 'solar';
263
- }
264
- else if (url.indexOf('roof') > -1) {
265
- trade = 'roof';
266
- }
267
- else {
268
- trade = 'other';
269
- }
270
-
271
- return trade;
272
- }
273
-
274
- /**
275
- * Track event in heap
276
- * @param {String} eventName - event name
277
- * @param {Object} props - props
278
- */
279
- function trackEventInHeap(eventName, props) {
280
- if (!props) {
281
- props = {};
282
- }
283
-
284
- props = $.extend({}, props, {
285
- trade: detectTrade()
286
- });
287
-
288
- if ('undefined' !== typeof heap) {
289
- heap.track(eventName, props);
290
- }
291
- }
292
-
293
- /**
294
- * Track apt view
295
- * @param {String} type - appt type
296
- */
297
- function trackAptView(type) {
298
- var eventNamePrefix = 'instant' === type ? 'Instant Apt' : 'Apt';
299
-
300
- // Track in GA
301
- for (var i = 0; i < gaTrackerNames.length; i ++) {
302
- modUtils.gaSend({
303
- hitType: 'event',
304
- eventCategory: eventNamePrefix,
305
- eventAction: eventNamePrefix + ' View',
306
- eventLabel: getParams.projectId
307
- }, gaTrackerNames[i]);
308
- }
309
-
310
- // Track in Heap
311
- trackEventInHeap(eventNamePrefix + ' View');
312
- }
313
-
314
- /**
315
- * Track apt set
316
- * @param {String} type - appt type
317
- * @param {Object} opts - options
318
- */
319
- function trackAptSet(type, opts) {
320
- var eventNamePrefix = 'instant' === type ? 'Instant Apt' : 'Apt',
321
- eventName,
322
- $services;
323
-
324
- if ('instant' === type) {
325
- eventName = eventNamePrefix + ' set #' + (instantAptsData.index + 1) + '/' + instantAptsTimeSlotLimit;
326
- }
327
- else {
328
- $services = $aptBlockMatches.find('[data-bind="apt-match"]'),
329
- eventName = eventNamePrefix + ' set #' + serviceAptSetIndex + ' ' + ($services.index(opts.$service) + 1) + '/' + $services.length;
330
- serviceAptSetIndex ++;
331
- }
332
-
333
- // Track in GA
334
- for (var i = 0; i < gaTrackerNames.length; i ++) {
335
- modUtils.gaSend({
336
- hitType: 'event',
337
- eventCategory: eventNamePrefix,
338
- eventAction: eventName,
339
- eventLabel: getParams.projectId
340
- }, gaTrackerNames[i]);
341
- }
342
-
343
- // Track in Heap
344
- trackEventInHeap(eventName);
345
- }
346
-
347
- /**
348
- * Switch service block state
349
- * @param {Object} $service - jQuery DOM object of a service
350
- * @param {String} state - state id
351
- */
352
- function switchServiceBlockState($service, state) {
353
- $service.find('[data-bind="apt-match-step-schedule"]').hide();
354
- $service.find('[data-bind="apt-match-step-confirm"]').hide();
355
- $service.find('[data-bind="apt-match-step-confirmed"]').hide();
356
- $service.find('[data-bind="apt-match-step-' + state + '"]').show();
357
- }
358
-
359
- /**
360
- * Switch instant apt block state
361
- * @param {Object} $intantAptForm - jQuery DOM object of a instant apt form
362
- * @param {String} state - state id
363
- */
364
- function switchInstantAptBlockState($intantAptForm, state) {
365
- $intantAptForm.removeClass('apt-instant-match__form--loading apt-instant-match__form--scheduled');
366
-
367
- if ('' !== state) {
368
- $intantAptForm.addClass('apt-instant-match__form--' + state);
369
- }
370
- }
371
-
372
- /**
373
- * Switch match block to/from loading state
374
- * @param {Object} $service - jQuery DOM object of a service
375
- * @param {Bool} loading - true form loading state, false for normal
376
- */
377
- function switchMatchLoadingState($service, loading) {
378
- var loadingCls = 'apt-match--loading';
379
-
380
- if (loading) {
381
- $service.addClass(loadingCls);
382
- }
383
- else {
384
- $service.removeClass(loadingCls);
385
- }
386
- }
387
-
388
- /**
389
- * Check if loading state is still needed
390
- */
391
- function checkLoadingState() {
392
- if (!servicesProcessingCount) {
393
- var servicesProcessedCount = $aptBlock.find('[data-bind="apt-match"]').length;
394
-
395
- $aptBlockTitle.text('You’ve got ' + servicesProcessedCount + ' instant match' + (servicesProcessedCount === 1 ? '' : 'es') + ':');
396
- $aptBlockLoading.remove();
397
-
398
- // Remove the block if no matching
399
- if (!servicesProcessedCount) {
400
- $aptBlock.remove();
401
- }
402
- }
403
- }
404
-
405
- /**
406
- * Init service block
407
- * @param {Object} service - service data object
408
- */
409
- function initServiceBlock(service) {
410
- var template, $service;
411
-
412
- template = [
413
- /* eslint-disable indent */
414
- '<div data-bind="apt-match" class="apt-match" id="service-' + service.serviceId + '">',
415
- '<div class="apt-match__logo' + (!service.contractorLogoUrl ? ' apt-match__logo--placeholder' : '') + '"' + (service.contractorLogoUrl ? ' style="background-image: url(' + service.contractorLogoUrl + ')"' : '') + '>',
416
- createAcronym(service.serviceName),
417
- '</div>',
418
- '<div class="apt-match__content">',
419
- '<div class="apt-match__label" data-bind="apt-match-label">',
420
- 'Match ' + ($aptBlock.find('[data-bind="apt-match"]').length + 1),
421
- '</div>',
422
- '<div class="apt-match__company">',
423
- service.serviceName,
424
- '</div>',
425
- '<form class="apt-match__form">',
426
- '<div data-bind="apt-match-step-schedule">',
427
- '<div class="apt-match__form-title">',
428
- 'Schedule an Appointment:',
429
- '</div>',
430
- '<div class="parent-error">',
431
- '<div data-bind="apt-match-schedule-control" class="apt-match__schedule-control apt-match__schedule-control--date-only">',
432
- '<span class="apt-match__schedule-control-controls">',
433
- '<div class="form-control-select">',
434
- '<select name="date" data-required="nonempty">',
435
- '<option value="">- Date -</option>',
436
- '</select>',
437
- '</div>',
438
- '<div class="form-control-select">',
439
- '<select name="time" data-required="nonempty" disabled="disabled">',
440
- '<option value="">- Time -</option>',
441
- '</select>',
442
- '</div>',
443
- '</span>',
444
- '<span class="apt-match__schedule-control-btn">',
445
- '<button type="submit" class="btn btn-primary">',
446
- 'Schedule',
447
- '</button>',
448
- '</span>',
449
- '</div>',
450
- '<div class="apt-match__schedule-error" data-bind="apt-match-schedule-error"></div>',
451
- '</div>',
452
- '</div>',
453
- '<div data-bind="apt-match-step-confirm" style="display: none">',
454
- '<div class="apt-match__form-title">',
455
- 'Appointment Confirmation:',
456
- '</div>',
457
- '<div class="apt-match__schedule-info">',
458
- '<div class="apt-match__schedule-info-row">',
459
- '<div class="apt-match__schedule-info-col-left">',
460
- '<div class="apt-match__schedule-info-date" data-bind="apt-match-schedule-date"> </div>',
461
- '<div class="apt-match__schedule-info-time" data-bind="apt-match-schedule-time"> </div>',
462
- '</div>',
463
- '<div class="apt-match__schedule-info-col-right">',
464
- '<button type="button" class="btn btn-link" data-bind="apt-match-schedule-edit-btn">',
465
- 'Edit',
466
- '</button>',
467
- '<button type="button" class="btn btn-primary" data-bind="apt-match-schedule-confirm-btn">',
468
- '<span>',
469
- 'Confirm',
470
- '</span>',
471
- '<span class="spinner"></span>',
472
- '</button>',
473
- '</div>',
474
- '</div>',
475
- '<div class="apt-match__tcpa">',
476
- aptBlockTCPA,
477
- '</div>',
478
- '</div>',
479
- '</div>',
480
- '<div data-bind="apt-match-step-confirmed" style="display: none">',
481
- '<div class="apt-match__form-title">',
482
- 'Appointment Confirmation:',
483
- '</div>',
484
- '<div class="apt-match__schedule-info">',
485
- '<div class="apt-match__schedule-info-row">',
486
- '<div class="apt-match__schedule-info-col-left">',
487
- '<div class="apt-match__schedule-info-date" data-bind="apt-match-schedule-date"> </div>',
488
- '<div class="apt-match__schedule-info-time" data-bind="apt-match-schedule-time"> </div>',
489
- '</div>',
490
- '<div class="apt-match__schedule-info-col-right">',
491
- '<div class="apt-match__success-msg">',
492
- '<div class="apt-match__success-msg-title">',
493
- 'Success',
494
- '</div>',
495
- '<div class="apt-match__success-msg-text">',
496
- 'You should be contacted shortly to confirm your&nbsp;reservation.',
497
- '</div>',
498
- '</div>',
499
- '</div>',
500
- '</div>',
501
- '</div>',
502
- '</div>',
503
- '</form>',
504
- '</div>',
505
- '</div>'
506
- /* eslint-enable indent */
507
- ].join('');
508
-
509
- // Convert template to jQuery object
510
- $service = $(template);
511
-
512
- // Init schedule form submit
513
- $service.find('form').on('submit', function(e) {
514
- e.preventDefault();
515
-
516
- if (modForm.isFormValid($(this))) {
517
- switchServiceBlockState($service, 'confirm');
518
- }
519
- });
520
-
521
- // Edit button
522
- $service.find('[data-bind="apt-match-schedule-edit-btn"]').on('click', function(e) {
523
- e.preventDefault();
524
-
525
- switchServiceBlockState($service, 'schedule');
526
- });
527
-
528
- // Confirm button
529
- $service.find('[data-bind="apt-match-schedule-confirm-btn"]').on('click', function(e) {
530
- e.preventDefault();
531
-
532
- var $timeSelector = $service.find('[name="time"]'),
533
- $dateSelector = $service.find('[name="date"]'),
534
- appointmentData = {
535
- projectId: projectId,
536
- serviceId: service.serviceId,
537
- startDateTimeUTC: $timeSelector.val()
538
- };
539
-
540
- if ($timeSelector.data('serviceCustomData')) {
541
- appointmentData.serviceCustomData = $timeSelector.data('serviceCustomData');
542
- }
543
-
544
- switchMatchLoadingState($service, true);
545
- apiPostAppointmentData(appointmentData, function() {
546
- switchMatchLoadingState($service, false);
547
- switchServiceBlockState($service, 'confirmed');
548
-
549
- saveScheduledAppsDataInCookies(projectId, service.serviceId, {
550
- service: service,
551
- date: $dateSelector.val(),
552
- time: $timeSelector.find('option:selected').eq(0).text()
553
- });
554
-
555
- trackAptSet('direct', {
556
- $service: $service
557
- });
558
- }, function(jqXHR) {
559
- switchMatchLoadingState($service, false);
560
-
561
- $service.find('[data-bind="apt-match-schedule-error"]').text(jqXHR.responseJSON.error.message);
562
- $service.find('[data-bind="apt-match-schedule-control"]').parent().addClass('has-error');
563
- switchServiceBlockState($service, 'schedule');
564
- });
565
- });
566
-
567
- // Check it was scheduled
568
- if (service.scheduledAppt) {
569
- $service.find('[data-bind="apt-match-schedule-date"]').text(service.scheduledAppt.date);
570
- $service.find('[data-bind="apt-match-schedule-time"]').text(service.scheduledAppt.time);
571
- switchServiceBlockState($service, 'confirmed');
572
- }
573
-
574
- // Add to the matches block
575
- $aptBlockMatches.append($service);
576
- }
577
-
578
- /**
579
- * Init instant apt block
580
- */
581
- function initInstantAptBlock() {
582
- var template = [
583
- /* eslint-disable indent */
584
- '<div data-bind="apt-instant-match" class="apt-instant-match">',
585
- '<div data-bind="apt-instant-match-placeholder" class="apt-instant-match__placeholder"> </div>',
586
- '<div class="apt-instant-match__placeholder apt-instant-match__placeholder--loading">',
587
- '<span class="apt-block__spinner"></span>',
588
- 'Loading...',
589
- '</div>',
590
- '</div>'
591
- /* eslint-enable indent */
592
- ].join('');
593
-
594
- for (var i = 0; i < instantAptsTimeSlotLimit; i ++) {
595
- var $template = $(template);
596
-
597
- $template.find('[data-bind="apt-instant-match-placeholder"]').text('Appointment ' + (i + 1));
598
- $instantAptBlockMatches.append($template);
599
- }
600
- }
601
-
602
- /**
603
- * Display service available dates & times
604
- * @param {String} serviceId - service id
605
- * @param {Object} schedule - time data
606
- */
607
- function displayServiceAvailableDateTime(serviceId, schedule) {
608
- // Stop if no dates
609
- if (!schedule.data.appointmentDates || !schedule.data.appointmentDates.length) {
610
- return;
611
- }
612
-
613
- var $service = $('#service-' + serviceId),
614
- $dateSelector = $service.find('[name="date"]'),
615
- $timeSelector = $service.find('[name="time"]');
616
-
617
- // Store data
618
- $dateSelector.data('timeZoneAbbr', schedule.timeZoneAbbr);
619
- if (schedule.data.serviceCustomData) {
620
- $timeSelector.data('serviceCustomData', schedule.data.serviceCustomData);
621
- }
622
-
623
- // Display date selector
624
- $.each(schedule.data.appointmentDates, function() {
625
- var appointmentDate = this,
626
- $option;
627
-
628
- // Display date only if it has times
629
- if (appointmentDate.appointmentTimes && appointmentDate.appointmentTimes.length) {
630
- var formattedDate = formatDate(appointmentDate.appointmentTimes[0].startDateTimeUTC, appointmentDate.date, schedule.timeZone);
631
-
632
- $option = $('<option value="' + formattedDate + '">' + formattedDate + '</option>');
633
- $option.data('appointmentTimes', appointmentDate.appointmentTimes);
634
- $dateSelector.append($option);
635
- }
636
- });
637
-
638
- $dateSelector.on('change', function() {
639
- updateServiceSelectedDate($service, $(this).val());
640
- updateTimeSelector($service, $timeSelector, $(this).find('option:selected').data('appointmentTimes'), $(this).data('timeZoneAbbr'));
641
- });
642
- $dateSelector.trigger('change');
643
- }
644
-
645
- /**
646
- * Display available services
647
- * @param {Array} services - array of services
648
- */
649
- function processAvailableServices(services) {
650
- $.each(services, function() {
651
- var service = this;
652
-
653
- // Request available schedule for this contractor
654
- apiGetServiceAvailableSchedule(service.serviceId, function(response) {
655
- servicesProcessingCount --;
656
-
657
- if ((response.data && response.data.appointmentDates) || service.scheduledAppt) {
658
- initServiceBlock(service);
659
-
660
- if (!service.scheduledAppt) {
661
- displayServiceAvailableDateTime(service.serviceId, response);
662
- }
663
- }
664
-
665
- checkLoadingState();
666
- }, function() {
667
- servicesProcessingCount --;
668
- checkLoadingState();
669
- });
670
-
671
- servicesProcessingCount ++;
672
- });
673
- }
674
-
675
- /**
676
- * Init instant apt time slot
677
- * @param {Object} service - service data object
678
- * @param {Object} schedule - time data
679
- * @param {Integer} index - time slot index
680
- */
681
- function initInstantAptTimeSlot(service, schedule, index) {
682
- var aptTimeSlotBlockId = service.serviceId + '-' + index, $form, template;
683
-
684
- template = [
685
- /* eslint-disable indent */
686
- '<form data-bind="apt-instant-match-form" class="apt-instant-match__form" id="service-' + aptTimeSlotBlockId + '">',
687
- '<div class="apt-instant-match__schedule">',
688
- '<div class="parent-error">',
689
- '<div data-bind="apt-match-schedule-control" class="apt-match__schedule-control apt-match__schedule-control--date-only">',
690
- '<span class="apt-match__schedule-control-controls">',
691
- '<div class="form-control-select">',
692
- '<select name="date" data-required="nonempty">',
693
- '<option value="">- Date -</option>',
694
- '</select>',
695
- '</div>',
696
- '<div class="form-control-select">',
697
- '<select name="time" data-required="nonempty" disabled="disabled">',
698
- '<option value="">- Time -</option>',
699
- '</select>',
700
- '</div>',
701
- '</span>',
702
- '<span class="apt-match__schedule-control-btn">',
703
- '<button type="submit" class="btn btn-primary">',
704
- 'Schedule',
705
- '</button>',
706
- '</span>',
707
- '</div>',
708
- '<div class="apt-match__schedule-error" data-bind="apt-match-schedule-error"></div>',
709
- '</div>',
710
- '</div>',
711
- '<div class="apt-instant-match__scheduled">',
712
- '<div class="apt-instant-match__scheduled-content">',
713
- '<span class="apt-block__spinner"></span>',
714
- '<span data-bind="apt-instant-match-scheduled-time" class="apt-instant-match__scheduled-text">',
715
- '<span data-bind="apt-match-schedule-date"></span>',
716
- ' | ',
717
- '<span data-bind="apt-match-schedule-time"></span>',
718
- '</span>',
719
- '</div>',
720
- '</div>',
721
- '</form>'
722
- /* eslint-enable indent */
723
- ].join('');
724
-
725
- $form = $(template);
726
-
727
- $form.on('submit', function(e) {
728
- e.preventDefault();
729
-
730
- if (modForm.isFormValid($form)) {
731
- var $timeSelector = $form.find('[name="time"]'),
732
- $dateSelector = $form.find('[name="date"]'),
733
- appointmentData = {
734
- projectId: projectId,
735
- serviceId: service.serviceId,
736
- startDateTimeUTC: $timeSelector.val()
737
- };
738
-
739
- if ($timeSelector.data('serviceCustomData')) {
740
- appointmentData.serviceCustomData = $timeSelector.data('serviceCustomData');
741
- }
742
-
743
- switchInstantAptBlockState($form, 'loading');
744
- apiPostAppointmentData(appointmentData, function() {
745
- switchInstantAptBlockState($form, 'scheduled');
746
-
747
- trackAptSet('instant');
748
-
749
- saveScheduledAppsDataInCookies(projectId, service.serviceId, {
750
- date: $dateSelector.val(),
751
- time: $timeSelector.find('option:selected').eq(0).text()
752
- });
753
-
754
- instantAptsData.index ++;
755
-
756
- if (instantAptsData.index < instantAptsTimeSlotLimit) {
757
- /* eslint-disable no-use-before-define */
758
- processInstantApts();
759
- /* eslint-enable no-use-before-define */
760
- }
761
- }, function(jqXHR) {
762
- if ('undefined' !== typeof jqXHR.responseJSON.error && 'undefined' !== typeof jqXHR.responseJSON.error.message) {
763
- $form.find('[data-bind="apt-match-schedule-error"]').text(jqXHR.responseJSON.error.message);
764
- }
765
- $form.find('[data-bind="apt-match-schedule-control"]').parent().addClass('has-error');
766
- switchInstantAptBlockState($form, '');
767
- });
768
- }
769
- });
770
-
771
- // Add form to instant apt block slots
772
- $instantAptBlockMatches.find('[data-bind="apt-instant-match"]').eq(index)
773
- .empty()
774
- .append($form);
775
-
776
- // Display available dates & times
777
- displayServiceAvailableDateTime(aptTimeSlotBlockId, schedule);
778
-
779
- // Check if there's any info about scheduled appts in cookies
780
- var scheduledApptsData = getSavedScheduledAppsDataFromCookies(projectId);
781
-
782
- if (scheduledApptsData && 'undefined' !== typeof scheduledApptsData[service.serviceId] && 'undefined' !== typeof scheduledApptsData[service.serviceId][index]) {
783
- var scheduledAppt = scheduledApptsData[service.serviceId][index];
784
-
785
- // Switch this time slot to "scheduled" UI
786
- $form.find('[data-bind="apt-match-schedule-date"]').text(scheduledAppt.date);
787
- $form.find('[data-bind="apt-match-schedule-time"]').text(scheduledAppt.time);
788
- switchInstantAptBlockState($form, 'scheduled');
789
-
790
- instantAptsData.index ++;
791
-
792
- if (instantAptsData.index < instantAptsTimeSlotLimit) {
793
- /* eslint-disable no-use-before-define */
794
- processInstantApts();
795
- /* eslint-enable no-use-before-define */
796
- }
797
- }
798
- }
799
-
800
- /**
801
- * Process instant apts
802
- */
803
- function processInstantApts() {
804
- if (!instantAptsData.services || !instantAptsData.services.length) {
805
- return;
806
- }
807
-
808
- var service = instantAptsData.services[0];
809
-
810
- // Request available schedule for instant apt
811
- $instantAptBlockMatches.find('[data-bind="apt-instant-match"]').eq(instantAptsData.index).addClass('apt-instant-match--loading');
812
- apiGetServiceAvailableSchedule(service.serviceId, function(response) {
813
- $instantAptBlockMatches.find('[data-bind="apt-instant-match"]').eq(instantAptsData.index).removeClass('apt-instant-match--loading');
814
-
815
- if (response.data && response.data.appointmentDates) {
816
- initInstantAptTimeSlot(service, response, instantAptsData.index);
817
- }
818
- else {
819
- if (0 === instantAptsData.index) {
820
- $instantAptBlock.hide();
821
- }
822
- }
823
- }, function() {
824
- if (0 === instantAptsData.index) {
825
- $instantAptBlock.hide();
826
- }
827
- });
828
- }
829
-
830
- /**
831
- * Init appointment control
832
- */
833
- function init() {
834
- $aptBlock = $('#apt-block');
835
- $aptBlockMatches = $('#apt-block-matches');
836
- $aptBlockTitle = $('#apt-block-title');
837
- $aptBlockLoading = $('#apt-block-loading');
838
- $instantAptBlock = $('#instant-apt-block');
839
- $instantAptBlockMatches = $('#apt-block-instant-matches');
840
-
841
- if (!$aptBlock.length || !getParams.projectId) {
842
- if ($aptBlock.length) {
843
- $aptBlock.remove();
844
- }
845
-
846
- return;
847
- }
848
-
849
- projectId = getParams.projectId;
850
-
851
- // Empty matches and load them from API
852
- $aptBlockMatches.empty();
853
- apiGetAvailableServices(function(response) {
854
- if (response.data && response.data.services) {
855
- // Filter apt services by type
856
- var normalApts = [],
857
- instantApts = [];
858
-
859
- $.each(response.data.services, function() {
860
- var service = this;
861
-
862
- if ('appointment' === service.serviceType) {
863
- normalApts.push(service);
864
- }
865
-
866
- if ('instant appointment' === service.serviceType) {
867
- instantApts.push(service);
868
- }
869
- });
870
-
871
- // Check if there are any scheduled normal appts in cookies
872
- var scheduledApptsInCookies = getSavedScheduledAppsDataFromCookies(projectId);
873
-
874
- if (scheduledApptsInCookies) {
875
- $.each(scheduledApptsInCookies, function(sId, sData) {
876
- if ('undefined' !== typeof sData[0] && 'undefined' !== typeof sData[0].service) {
877
- sData[0].service.scheduledAppt = {
878
- date: sData[0].date,
879
- time: sData[0].time
880
- };
881
- normalApts.push(sData[0].service);
882
- }
883
- });
884
- }
885
-
886
- if (instantApts.length) {
887
- instantAptsData.services = instantApts;
888
- instantAptsData.index = 0;
889
-
890
- $instantAptBlock.show();
891
- initInstantAptBlock();
892
- processInstantApts();
893
-
894
- trackAptView('instant');
895
- }
896
- else if (normalApts.length) {
897
- $aptBlock.show();
898
- processAvailableServices(normalApts);
899
-
900
- trackAptView('direct');
901
- }
902
- }
903
- else {
904
- $aptBlock.hide();
905
- }
906
- }, function() {});
907
-
908
- // Reset error class on date & time change
909
- $('body').on('change', 'select', function() {
910
- modForm.unmarkFieldAsInvalid($(this));
911
- $(this).parents('[data-bind="apt-match"]').find('[data-bind="apt-match-schedule-error"]').text('');
912
- });
913
- }
914
-
915
- $(document).ready(function() {
916
- init();
917
- });
918
- })(jQuery);
919
-