PluginProbe
Property Hive / 2.4.0
Property Hive v2.4.0
2.4.0 2.3.1 2.3.0 2.2.6 2.2.5 2.2.4 2.2.3 2.2.2 1.4.46 1.4.47 1.4.48 1.4.49 1.4.5 1.4.50 1.4.51 1.4.52 1.4.53 1.4.54 1.4.55 1.4.56 1.4.57 1.4.58 1.4.59 1.4.6 1.4.60 All 262 releases
propertyhive / assets / js / admin / onboarding.js

onboarding.js in Property Hive 2.4.0, at assets/js/admin/onboarding.js

1,016 lines 30.3 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 /* global propertyhive_onboarding */
2
3 /**
4 * Property Hive onboarding wizard.
5 */
6 jQuery( function( $ ) {
7 'use strict';
8
9 var config = window.propertyhive_onboarding || {};
10 var fallbackSteps = [ 'intro', 'departments', 'country', 'office', 'usage', 'license', 'demo-data', 'complete' ];
11 var steps = $.isArray( config.steps ) && config.steps.length ? config.steps : fallbackSteps;
12 var $wizard = $( '.ph-onboarding' );
13 var currentStep = $wizard.data( 'current-step' ) || 'intro';
14 var demoImported = $wizard.data( 'demo-imported' ) === 'yes';
15 var demoImporting = false;
16 var licenseActivated = config.license_activated === 'yes';
17 var licenseActivating = false;
18 var addressLookupTimer = null;
19 var addressLookupXhr = null;
20 var addressGetXhr = null;
21 var fieldControls = {
22 departments: 'input[name="departments[]"]',
23 country: '[data-country-select]',
24 office_name: '[data-office-field="office_name"]',
25 office_address_1: '[data-office-field="office_address_1"]',
26 office_address_2: '[data-office-field="office_address_2"]',
27 office_address_3: '[data-office-field="office_address_3"]',
28 office_address_4: '[data-office-field="office_address_4"]',
29 office_postcode: '[data-office-field="office_postcode"]',
30 office_telephone_number: '[data-office-field="office_telephone_number"]',
31 office_email_address: '[data-office-field="office_email_address"]',
32 usage: 'input[name="usage[]"]',
33 license_key: 'input[name="license_key"]',
34 demo_data_choice: 'input[name="demo_data_choice"]'
35 };
36 var officeRules = [
37 { field: 'office_name', maxLength: 120, maxMessage: 'officeNameTooLong' },
38 { field: 'office_address_1', maxLength: 120, maxMessage: 'officeAddressTooLong' },
39 { field: 'office_address_2', required: false, maxLength: 120, maxMessage: 'officeAddress2TooLong' },
40 { field: 'office_address_3', required: false, maxLength: 120, maxMessage: 'officeAddress3TooLong' },
41 { field: 'office_address_4', required: false, maxLength: 120, maxMessage: 'officeAddress4TooLong' },
42 { field: 'office_postcode', maxLength: 20, maxMessage: 'officePostcodeTooLong' },
43 { field: 'office_telephone_number', maxLength: 30, maxMessage: 'officePhoneTooLong' },
44 { field: 'office_email_address', maxLength: 100, maxMessage: 'officeEmailTooLong' }
45 ];
46
47 function getStepIndex( step ) {
48 var index = $.inArray( step, steps );
49 return index < 0 ? 0 : index;
50 }
51
52 function selectedValues( name ) {
53 return $( 'input[name="' + name + '[]"]:checked' ).map( function() {
54 return $( this ).val();
55 } ).get();
56 }
57
58 function text( key, fallback ) {
59 return config.i18n && config.i18n[ key ] ? config.i18n[ key ] : fallback;
60 }
61
62 function fieldSelector( field ) {
63 return '[data-validation-field="' + field + '"]';
64 }
65
66 function errorSelector( field ) {
67 return '[data-field-error="' + field + '"]';
68 }
69
70 function clearFieldError( field ) {
71 $( fieldSelector( field ) ).removeClass( 'has-error' );
72 $( errorSelector( field ) ).text( '' ).hide();
73
74 if ( fieldControls[ field ] ) {
75 $( fieldControls[ field ] ).removeAttr( 'aria-invalid' );
76 }
77 }
78
79 function setFieldError( field, message ) {
80 var $field = $( fieldSelector( field ) );
81 var $error = $( errorSelector( field ) );
82
83 if ( ! $field.length || ! $error.length ) {
84 return;
85 }
86
87 $field.addClass( 'has-error' );
88 $error.text( message ).css( 'display', 'block' );
89
90 if ( fieldControls[ field ] ) {
91 $( fieldControls[ field ] ).attr( 'aria-invalid', 'true' );
92 }
93 }
94
95 function clearValidation( step ) {
96 var $scope = step ? $( '.ph-onboarding__panel[data-step="' + step + '"]' ) : $wizard;
97
98 $scope.find( '[data-validation-field]' ).removeClass( 'has-error' );
99 $scope.find( '[data-field-error]' ).text( '' ).hide();
100 $scope.find( '[aria-invalid="true"]' ).removeAttr( 'aria-invalid' );
101 }
102
103 function applyFieldErrors( errors ) {
104 var firstMessage = '';
105 var firstField = '';
106
107 if ( ! errors ) {
108 return '';
109 }
110
111 $.each( errors, function( field, message ) {
112 if ( ! firstMessage ) {
113 firstMessage = message;
114 firstField = field;
115 }
116 setFieldError( field, message );
117 } );
118
119 if ( firstField && fieldControls[ firstField ] ) {
120 $( fieldControls[ firstField ] ).first().trigger( 'focus' );
121 }
122
123 return firstMessage;
124 }
125
126 function fieldValue( field ) {
127 return $.trim( $( fieldControls[ field ] ).val() || '' );
128 }
129
130 function isValidEmail( value ) {
131 return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test( value );
132 }
133
134 function isValidPhone( value ) {
135 var digits = value.replace( /\D+/g, '' );
136 return digits.length >= 7 && /^[0-9+\-\s().]+$/.test( value );
137 }
138
139 function setMessage( message, type ) {
140 var $message = $( '[data-message]' );
141 $message.removeClass( 'is-error is-success' );
142
143 if ( ! message ) {
144 $message.text( '' ).hide();
145 return;
146 }
147
148 $message.addClass( type === 'error' ? 'is-error' : 'is-success' ).text( message ).show();
149 }
150
151 function setAddressStatus( message, type ) {
152 var $status = $( '[data-address-lookup-status]' );
153 $status.removeClass( 'is-error is-success' );
154
155 if ( ! message ) {
156 $status.text( '' ).hide();
157 return;
158 }
159
160 $status.addClass( type === 'error' ? 'is-error' : 'is-success' ).text( message ).show();
161 }
162
163 function clearAddressResults() {
164 $( '[data-address-lookup-results]' ).empty().prop( 'hidden', true );
165 }
166
167 function renderAddressSuggestions( suggestions ) {
168 var $results = $( '[data-address-lookup-results]' );
169 $results.empty();
170
171 if ( ! suggestions || ! suggestions.length ) {
172 clearAddressResults();
173 setAddressStatus( text( 'addressLookupNoResults', 'No matching addresses found.' ), 'error' );
174 return;
175 }
176
177 $.each( suggestions, function( index, suggestion ) {
178 if ( ! suggestion.id || ! suggestion.address ) {
179 return;
180 }
181
182 $( '<button />', {
183 type: 'button',
184 class: 'ph-onboarding__address-result',
185 text: suggestion.address
186 } ).attr( 'data-address-id', suggestion.id ).appendTo( $results );
187 } );
188
189 $results.prop( 'hidden', false );
190 setAddressStatus( '' );
191 }
192
193 function setOfficeField( field, value ) {
194 $( '[data-office-field="' + field + '"]' ).val( value || '' ).trigger( 'input' ).trigger( 'change' );
195 }
196
197 function fillOfficeAddress( address ) {
198 address = address || {};
199
200 setOfficeField( 'office_address_1', address.line_1 );
201 setOfficeField( 'office_address_2', address.line_2 );
202 setOfficeField( 'office_address_3', address.town_or_city );
203 setOfficeField( 'office_address_4', address.county );
204 setOfficeField( 'office_postcode', address.postcode );
205
206 clearAddressResults();
207 $( '[data-address-lookup-input]' ).val( '' );
208 setAddressStatus( text( 'addressLookupSelected', 'Address selected.' ), 'success' );
209 }
210
211 function fetchAddress( id ) {
212 if ( ! id || config.address_lookup_enabled !== 'yes' ) {
213 return;
214 }
215
216 if ( addressGetXhr ) {
217 addressGetXhr.abort();
218 }
219
220 setAddressStatus( text( 'addressLookupSearching', 'Searching addresses...' ) );
221
222 addressGetXhr = $.post( config.ajax_url, {
223 action: 'propertyhive_getaddress_get',
224 security: config.address_lookup_nonce,
225 id: id
226 } ).done( function( response ) {
227 if ( response && response.success && response.data && response.data.address ) {
228 fillOfficeAddress( response.data.address );
229 return;
230 }
231
232 setAddressStatus( text( 'addressLookupFailed', 'Address lookup could not be completed. Please enter the address manually.' ), 'error' );
233 } ).fail( function( xhr ) {
234 if ( xhr.statusText === 'abort' ) {
235 return;
236 }
237
238 setAddressStatus( text( 'addressLookupFailed', 'Address lookup could not be completed. Please enter the address manually.' ), 'error' );
239 } );
240 }
241
242 function searchAddresses( term ) {
243 if ( config.address_lookup_enabled !== 'yes' ) {
244 return;
245 }
246
247 term = $.trim( term || '' );
248
249 if ( term.length < 3 ) {
250 if ( addressLookupXhr ) {
251 addressLookupXhr.abort();
252 }
253 clearAddressResults();
254 setAddressStatus( '' );
255 return;
256 }
257
258 if ( addressLookupXhr ) {
259 addressLookupXhr.abort();
260 }
261
262 setAddressStatus( text( 'addressLookupSearching', 'Searching addresses...' ) );
263
264 addressLookupXhr = $.post( config.ajax_url, {
265 action: 'propertyhive_getaddress_autocomplete',
266 security: config.address_lookup_nonce,
267 term: term
268 } ).done( function( response ) {
269 if ( response && response.success && response.data ) {
270 renderAddressSuggestions( response.data.suggestions || [] );
271 return;
272 }
273
274 clearAddressResults();
275 setAddressStatus( text( 'addressLookupFailed', 'Address lookup could not be completed. Please enter the address manually.' ), 'error' );
276 } ).fail( function( xhr ) {
277 if ( xhr.statusText === 'abort' ) {
278 return;
279 }
280
281 clearAddressResults();
282 setAddressStatus( text( 'addressLookupFailed', 'Address lookup could not be completed. Please enter the address manually.' ), 'error' );
283 } );
284 }
285
286 function track( eventName, step ) {
287 if ( ! config.ajax_url || ! config.nonce ) {
288 return;
289 }
290
291 $.post( config.ajax_url, {
292 action: 'propertyhive_onboarding_track',
293 security: config.nonce,
294 event: eventName,
295 step: step || currentStep
296 } );
297 }
298
299 function showStep( step ) {
300 currentStep = step;
301 var index = getStepIndex( step );
302 var percent = ( index / ( steps.length - 1 ) ) * 100;
303
304 $wizard.attr( 'data-current-step', step ).data( 'current-step', step );
305 $wizard.toggleClass( 'is-intro-step', step === 'intro' );
306 $( '.ph-onboarding__panel' ).removeClass( 'is-active' );
307 $( '.ph-onboarding__panel[data-step="' + step + '"]' ).addClass( 'is-active' );
308
309 $( '[data-progress-bar]' ).css( 'width', percent + '%' );
310 $( '[data-back]' ).toggle( index > 0 && step !== 'complete' );
311 $( '[data-next]' ).toggle( step !== 'complete' ).text( config.i18n.continue );
312 $( '[data-ph-onboarding-skip]' ).toggle( step !== 'complete' );
313 setMessage( '' );
314 clearValidation( step );
315 if ( step === 'complete' ) {
316 updateExitRecommendations();
317 }
318 track( 'step_viewed', step );
319 }
320
321 function setSaving( saving ) {
322 $( '[data-next]' ).prop( 'disabled', saving ).toggleClass( 'is-busy', saving );
323 if ( saving ) {
324 $( '[data-next]' ).text( config.i18n.saving );
325 } else {
326 $( '[data-next]' ).text( config.i18n.continue );
327 }
328 }
329
330 function stepPayload( step ) {
331 var payload = {
332 action: 'propertyhive_onboarding_save_step',
333 security: config.nonce,
334 step: step
335 };
336
337 if ( step === 'departments' ) {
338 payload.departments = selectedValues( 'departments' );
339 }
340
341 if ( step === 'intro' ) {
342 payload.usage_tracking = $( '[data-usage-tracking]' ).is( ':checked' ) ? 'yes' : 'no';
343 }
344
345 if ( step === 'country' ) {
346 payload.country = $( '[data-country-select]' ).val();
347 }
348
349 if ( step === 'office' ) {
350 $( '[data-office-field]' ).each( function() {
351 var key = $( this ).attr( 'data-office-field' );
352 if ( key ) {
353 payload[ key ] = $( this ).val();
354 }
355 } );
356 }
357
358 if ( step === 'usage' ) {
359 payload.usage = selectedValues( 'usage' );
360 }
361
362 if ( step === 'license' ) {
363 payload.has_license_key = $( 'input[name="has_license_key"]:checked' ).val() || $( 'input[name="has_license_key"][type="hidden"]' ).val() || 'no';
364 payload.license_key_type = $( 'input[name="license_key_type"]:checked' ).val() || $( 'input[name="license_key_type"][type="hidden"]' ).val() || 'pro';
365 }
366
367 if ( step === 'demo-data' ) {
368 payload.demo_data_choice = $( 'input[name="demo_data_choice"]:checked' ).val() || '';
369 payload.demo_data_imported = demoImported ? 'yes' : 'no';
370 }
371
372 return payload;
373 }
374
375 function validateStep( step ) {
376 var errors = {};
377
378 clearValidation( step );
379
380 if ( step === 'departments' && ! selectedValues( 'departments' ).length ) {
381 errors.departments = text( 'chooseDepartment', 'Please choose at least one property sector.' );
382 }
383
384 if ( step === 'country' && ! $( '[data-country-select]' ).val() ) {
385 errors.country = text( 'chooseCountry', 'Please choose a valid country.' );
386 }
387
388 if ( step === 'office' ) {
389 $.each( officeRules, function( index, rule ) {
390 var value = fieldValue( rule.field );
391
392 if ( rule.required && ! value ) {
393 errors[ rule.field ] = text( rule.requiredMessage, 'This field is required.' );
394 return;
395 }
396
397 if ( value && rule.maxLength && value.length > rule.maxLength ) {
398 errors[ rule.field ] = text( rule.maxMessage, 'This field is too long.' );
399 return;
400 }
401
402 if ( value && rule.field === 'office_telephone_number' && ! isValidPhone( value ) ) {
403 //errors.office_telephone_number = text( 'officePhoneInvalid', 'Please enter a valid phone number.' );
404 }
405
406 if ( value && rule.field === 'office_email_address' && ! isValidEmail( value ) ) {
407 errors.office_email_address = text( 'officeEmailInvalid', 'Please enter a valid email address.' );
408 }
409 } );
410 }
411
412 if ( step === 'usage' && ! selectedValues( 'usage' ).length ) {
413 errors.usage = text( 'chooseUsage', 'Please choose how you will use Property Hive.' );
414 }
415
416 if ( step === 'demo-data' ) {
417 if ( ! $( 'input[name="demo_data_choice"]:checked' ).length ) {
418 errors.demo_data_choice = text( 'chooseDemoData', 'Please choose whether to import demo data.' );
419 }
420 }
421
422 if ( ! $.isEmptyObject( errors ) ) {
423 setMessage( applyFieldErrors( errors ), 'error' );
424 return false;
425 }
426
427 return true;
428 }
429
430 function saveStep( step, skipClientValidation ) {
431 if ( ! skipClientValidation && ! validateStep( step ) ) {
432 return $.Deferred().reject().promise();
433 }
434
435 setSaving( true );
436
437 return $.post( config.ajax_url, stepPayload( step ) )
438 .done( function( response ) {
439 if ( ! response || ! response.success ) {
440 var message = response && response.data && response.data.message ? response.data.message : 'Setup could not be saved.';
441 clearValidation( step );
442 if ( response && response.data && response.data.errors ) {
443 message = applyFieldErrors( response.data.errors ) || message;
444 }
445 setMessage( message, 'error' );
446 }
447 } )
448 .fail( function( xhr ) {
449 var message = xhr.responseJSON && xhr.responseJSON.data && xhr.responseJSON.data.message ? xhr.responseJSON.data.message : 'Setup could not be saved.';
450 clearValidation( step );
451 if ( xhr.responseJSON && xhr.responseJSON.data && xhr.responseJSON.data.errors ) {
452 message = applyFieldErrors( xhr.responseJSON.data.errors ) || message;
453 }
454 setMessage( message, 'error' );
455 } )
456 .always( function() {
457 setSaving( false );
458 } );
459 }
460
461 function demoSections() {
462 var usage = selectedValues( 'usage' );
463 var usesCrm = $.inArray( 'crm', usage ) >= 0;
464
465 if ( ! usesCrm ) {
466 return {
467 base: [ 'property' ],
468 sub: []
469 };
470 }
471
472 return {
473 base: config.demo_base_sections || [ 'property', 'contact' ],
474 sub: config.demo_crm_sub_sections || []
475 };
476 }
477
478 function updateDemoProgress( done, total, text ) {
479 var percent = total > 0 ? ( done / total ) * 100 : 0;
480 $( '[data-demo-progress-bar]' ).css( 'width', percent + '%' );
481 $( '[data-demo-status]' ).text( text );
482 }
483
484 function refreshDemoState() {
485 if ( demoImported ) {
486 $( 'input[name="demo_data_choice"][value="yes"]' ).prop( 'checked', true );
487 updateDemoProgress( 1, 1, config.i18n.importComplete );
488 }
489
490 updateDemoChoice();
491 }
492
493 function appendDemoResult( text ) {
494 $( '[data-demo-results]' ).append( $( '<div />' ).text( text ) );
495 }
496
497 function demoResponseKey( section ) {
498 return section === 'applicant' || section === 'property_owner' ? 'contact' : section;
499 }
500
501 function getSectionDemoData( section ) {
502 return $.ajax( {
503 url: config.ajax_url,
504 method: 'POST',
505 dataType: 'json',
506 data: {
507 action: 'propertyhive_get_section_demo_data',
508 nonce: config.demo_data_nonce,
509 section: section
510 }
511 } );
512 }
513
514 function createDemoRecords( dataItems ) {
515 return $.ajax( {
516 url: config.ajax_url,
517 method: 'POST',
518 dataType: 'json',
519 data: {
520 action: 'propertyhive_create_demo_data_records',
521 nonce: config.demo_data_nonce,
522 data_items: dataItems
523 }
524 } );
525 }
526
527 function importDemoData() {
528 if ( demoImporting ) {
529 return $.Deferred().reject().promise();
530 }
531
532 if ( config.demo_data_active !== 'yes' ) {
533 return $.Deferred().reject().promise();
534 }
535
536 var sections = demoSections();
537 var total = sections.base.length + sections.sub.length;
538 var done = 0;
539
540 demoImporting = true;
541 demoImported = false;
542 $( '[data-next]' ).prop( 'disabled', true ).addClass( 'is-busy' ).text( config.i18n.importing );
543 $( '[data-demo-results]' ).empty();
544 updateDemoProgress( done, total, config.i18n.importing );
545 track( 'demo_data_started', 'demo-data' );
546
547 return getSectionDemoData( sections.base )
548 .then( function( dataItems ) {
549 return createDemoRecords( dataItems );
550 } )
551 .then( function( response ) {
552 $.each( sections.base, function( index, section ) {
553 done++;
554 appendDemoResult( section + ': ' + ( response[ demoResponseKey( section ) ] || 0 ) + ' records created' );
555 } );
556 updateDemoProgress( done, total, config.i18n.importing );
557
558 var chain = $.Deferred().resolve().promise();
559 $.each( sections.sub, function( index, section ) {
560 chain = chain.then( function() {
561 return getSectionDemoData( section )
562 .then( function( dataItems ) {
563 return createDemoRecords( dataItems );
564 } )
565 .then( function( subResponse ) {
566 done++;
567 appendDemoResult( section + ': ' + ( subResponse[ section ] || 0 ) + ' records created' );
568 updateDemoProgress( done, total, config.i18n.importing );
569 } );
570 } );
571 } );
572
573 return chain;
574 } )
575 .done( function() {
576 demoImported = true;
577 demoImporting = false;
578 revealSiteExit();
579 updateDemoProgress( total, total, config.i18n.importComplete );
580 } )
581 .fail( function() {
582 demoImporting = false;
583 updateDemoProgress( done, total, config.i18n.importFailed );
584 setMessage( config.i18n.importFailed, 'error' );
585 track( 'demo_data_failed', 'demo-data' );
586 } )
587 .always( function() {
588 setSaving( false );
589 } );
590 }
591
592 function updateDemoChoice() {
593 var wantsDemo = $( 'input[name="demo_data_choice"]:checked' ).val() === 'yes';
594 $( '[data-demo-progress-box]' ).toggle( wantsDemo );
595 }
596
597 function updateLicenseChoice() {
598 var choice = $( 'input[name="has_license_key"]:checked' ).val() || 'no';
599 $( '[data-license-panel]' ).removeClass( 'is-open' );
600 $( '[data-license-panel="' + choice + '"]' ).addClass( 'is-open' );
601 }
602
603 function updateLicenseType() {
604 var type = $( 'input[name="license_key_type"]:checked' ).val() || 'pro';
605 $( '[data-license-old-note]' ).toggle( type === 'old' );
606 }
607
608 function setLicenseState( type, content ) {
609 var $state = $( '[data-license-state]' );
610
611 $state.removeClass( 'is-busy is-ok is-error' );
612 if ( ! content ) {
613 $state.empty().hide();
614 return;
615 }
616
617 $state.addClass( type ? 'is-' + type : '' ).empty().append( content ).show();
618 }
619
620 function renderLicenseBusy() {
621 setLicenseState( 'busy', $( '<span />' ).text( text( 'licenseChecking', 'Checking your key... Contacting wp-property-hive.com - a couple of seconds.' ) ) );
622 }
623
624 function renderLicenseSuccess( licenseType, summary ) {
625 var $content = $( '<div />' );
626 var message = licenseType === 'old' ? text( 'licenseOldSuccess', 'License valid - updates are enabled for your purchased add-ons.' ) : text( 'licenseProSuccess', 'Pro activated. The features in your plan are ready to use.' );
627
628 if ( licenseType === 'old' && summary ) {
629 message += ' Expires ' + summary + '.';
630 }
631
632 $( '<strong />' ).text( '\u2713 ' + message ).appendTo( $content );
633
634 if ( licenseType === 'old' ) {
635 $( '<a />', {
636 href: config.license_trial_url || config.import_features_url || '#',
637 target: '_blank',
638 rel: 'noopener noreferrer',
639 text: text( 'licenseOldTrial', 'Want the Pro features too? Start a free 7-day trial.' )
640 } ).appendTo( $( '<p />', { class: 'ph-onboarding__license-trial-link' } ).appendTo( $content ) );
641 }
642
643 setLicenseState( 'ok', $content.contents() );
644 }
645
646 function renderLicenseError( message, renewUrl ) {
647 var $content = $( '<div />' );
648
649 $( '<strong />' ).text( message || text( 'licenseError', "That key wasn't recognised. Check for typos, or recover your key. You can keep going and add it later - nothing is lost." ) ).appendTo( $content );
650
651 if ( renewUrl ) {
652 $content.append( ' ' );
653 $( '<a />', {
654 href: renewUrl,
655 target: '_blank',
656 rel: 'noopener noreferrer',
657 text: text( 'renewLicense', 'Renew License' )
658 } ).appendTo( $content );
659 }
660
661 setLicenseState( 'error', $content.contents() );
662 }
663
664 function lockLicenseSuccess() {
665 licenseActivated = true;
666 $( 'input[name="has_license_key"][value="yes"]' ).prop( 'checked', true ).trigger( 'change' );
667 $( 'input[name="has_license_key"], input[name="license_key_type"]' ).prop( 'disabled', true ).closest( '.ph-onboarding__choice' ).addClass( 'is-disabled' );
668 $( '[data-license-entry]' ).hide();
669 $( '[data-license-old-note]' ).hide();
670 updateExitRecommendations();
671 }
672
673 function revealImportExit() {
674 $( '[data-import-setup]' )
675 .attr( 'href', config.import_setup_url || 'admin.php?page=propertyhive_import_properties' )
676 .removeAttr( 'hidden' );
677 }
678
679 function updateExitRecommendations() {
680 var usage = selectedValues( 'usage' );
681 var canRecommend = ! licenseActivated;
682
683 $( '[data-recommendation="import"]' ).attr( 'hidden', 'hidden' );
684 $( '[data-recommendation="portal"]' ).attr( 'hidden', 'hidden' );
685
686 if ( canRecommend && $.inArray( 'import_properties', usage ) >= 0 ) {
687 $( '[data-recommendation="import"]' ).removeAttr( 'hidden' );
688 }
689
690 if ( canRecommend && $.inArray( 'portal_uploads', usage ) >= 0 ) {
691 $( '[data-recommendation="portal"]' ).removeAttr( 'hidden' );
692 }
693 }
694
695 function updateUsageProNote() {
696 var usage = selectedValues( 'usage' );
697 var needsPro = $.inArray( 'import_properties', usage ) >= 0 || $.inArray( 'portal_uploads', usage ) >= 0;
698
699 $( '[data-usage-pro-note]' ).prop( 'hidden', ! needsPro );
700 }
701
702 function revealSiteExit() {
703 $( '[data-exit="site"]' ).removeAttr( 'hidden' );
704 }
705
706 function appendImportEnabledLine() {
707 var $state = $( '[data-license-state]' );
708
709 if ( $state.find( '[data-license-import-enabled]' ).length ) {
710 return;
711 }
712
713 $( '<p />', {
714 'data-license-import-enabled': 'yes',
715 text: '\u2713 ' + text( 'importFeatureEnabled', 'Property Import feature enabled' )
716 } ).appendTo( $state );
717 }
718
719 function importFeatureSucceeded() {
720 appendImportEnabledLine();
721 revealImportExit();
722 }
723
724 function maybeEnableImportFeature() {
725 if ( $.inArray( 'import_properties', selectedValues( 'usage' ) ) < 0 ) {
726 return;
727 }
728
729 if ( config.import_feature_active === 'yes' ) {
730 importFeatureSucceeded();
731 return;
732 }
733
734 if ( config.can_install_plugins !== 'yes' || ! config.import_feature_slug || ! config.updates_nonce ) {
735 return;
736 }
737
738 $.ajax( {
739 url: config.ajax_url,
740 method: 'POST',
741 dataType: 'json',
742 data: {
743 action: 'propertyhive_activate_pro_feature',
744 slug: config.import_feature_slug,
745 _ajax_nonce: config.updates_nonce
746 }
747 } ).done( function( response ) {
748 if ( response && response.success === true ) {
749 if ( response.data && response.data.activateUrl ) {
750 $.get( response.data.activateUrl ).done( importFeatureSucceeded );
751 return;
752 }
753
754 importFeatureSucceeded();
755 return;
756 }
757
758 if ( response && response.data && response.data.errorMessage === 'Plugin already active' ) {
759 importFeatureSucceeded();
760 }
761 } ).fail( function() {} );
762 }
763
764 function activateLicense() {
765 if ( licenseActivating ) {
766 return;
767 }
768
769 var licenseKey = $.trim( $( 'input[name="license_key"]' ).val() || '' );
770 var licenseType = $( 'input[name="license_key_type"]:checked' ).val() || 'pro';
771
772 clearFieldError( 'license_key' );
773 setMessage( '' );
774
775 if ( ! licenseKey ) {
776 setFieldError( 'license_key', text( 'licenseKeyRequired', 'Please enter your license key.' ) );
777 return;
778 }
779
780 licenseActivating = true;
781 $( '[data-license-activate]' ).prop( 'disabled', true ).addClass( 'is-busy' );
782 renderLicenseBusy();
783
784 $.post( config.ajax_url, {
785 action: 'propertyhive_onboarding_activate_license',
786 security: config.nonce,
787 license_key_type: licenseType,
788 license_key: licenseKey
789 } ).done( function( response ) {
790 var data = response && response.data ? response.data : {};
791
792 if ( response && response.success && data.activated ) {
793 renderLicenseSuccess( data.license_type, data.summary );
794 lockLicenseSuccess();
795
796 if ( data.license_type === 'pro' ) {
797 maybeEnableImportFeature();
798 }
799
800 return;
801 }
802
803 renderLicenseError( data.message, data.renew_url );
804 $( '[data-license-activate]' ).prop( 'disabled', false ).removeClass( 'is-busy' );
805 } ).fail( function( xhr ) {
806 var data = xhr.responseJSON && xhr.responseJSON.data ? xhr.responseJSON.data : {};
807 renderLicenseError( data.message, data.renew_url );
808 $( '[data-license-activate]' ).prop( 'disabled', false ).removeClass( 'is-busy' );
809 } ).always( function() {
810 licenseActivating = false;
811 } );
812 }
813
814 function maybeImportDemoData() {
815 if ( currentStep !== 'demo-data' || $( 'input[name="demo_data_choice"]:checked' ).val() !== 'yes' || demoImported ) {
816 return $.Deferred().resolve().promise();
817 }
818
819 if ( config.demo_data_active === 'yes' ) {
820 return importDemoData();
821 }
822
823 $( '[data-next]' ).prop( 'disabled', true ).addClass( 'is-busy' ).text( text( 'activatingDemoData', 'Activating Demo Data...' ) );
824 updateDemoProgress( 0, 1, text( 'activatingDemoData', 'Activating Demo Data...' ) );
825
826 function activationSucceeded() {
827 config.demo_data_active = 'yes';
828 return importDemoData();
829 }
830
831 function activationFailed( message ) {
832 setSaving( false );
833 setMessage( message || text( 'demoDataInactive', 'The Demo Data feature could not be activated on this site.' ), 'error' );
834 return $.Deferred().reject().promise();
835 }
836
837 if ( config.can_install_plugins !== 'yes' || ! config.demo_data_feature_slug || ! config.updates_nonce ) {
838 return activationFailed();
839 }
840
841 return $.ajax( {
842 url: config.ajax_url,
843 method: 'POST',
844 dataType: 'json',
845 data: {
846 action: 'propertyhive_activate_pro_feature',
847 slug: config.demo_data_feature_slug,
848 _ajax_nonce: config.updates_nonce
849 }
850 } ).then( function( response ) {
851 if ( response && response.success === true ) {
852 if ( response.data && response.data.activateUrl ) {
853 return $.get( response.data.activateUrl ).then( activationSucceeded, function() {
854 return activationFailed();
855 } );
856 }
857
858 return activationSucceeded();
859 }
860
861 if ( response && response.data && response.data.errorMessage === 'Plugin already active' ) {
862 return activationSucceeded();
863 }
864
865 return activationFailed( response && response.data ? response.data.errorMessage : '' );
866 }, function( xhr ) {
867 var message = xhr.responseJSON && xhr.responseJSON.data ? xhr.responseJSON.data.errorMessage : '';
868 return activationFailed( message );
869 } );
870 }
871
872 $( document ).on( 'change', 'input[name="demo_data_choice"]', updateDemoChoice );
873 $( document ).on( 'change', 'input[name="has_license_key"]', updateLicenseChoice );
874 $( document ).on( 'change', 'input[name="license_key_type"]', updateLicenseType );
875 $( document ).on( 'click', '[data-license-activate]', activateLicense );
876 $( document ).on( 'click', '[data-onboarding-exit]', function( event ) {
877 var $exit = $( this );
878 var destination = $exit.attr( 'href' );
879 var completedVia = $exit.attr( 'data-exit' ) || '';
880 var opensInNewTab = $exit.attr( 'target' ) === '_blank';
881
882 if ( $exit.attr( 'aria-disabled' ) === 'true' || ! destination ) {
883 event.preventDefault();
884 return;
885 }
886
887 if ( opensInNewTab ) {
888 return;
889 }
890
891 event.preventDefault();
892
893 $( '[data-onboarding-exit]' ).attr( 'aria-disabled', 'true' ).addClass( 'is-disabled' );
894 $exit.addClass( 'is-busy' );
895
896 $.post( config.ajax_url, {
897 action: 'propertyhive_onboarding_save_step',
898 security: config.nonce,
899 step: 'complete',
900 completed_via: completedVia
901 } ).always( function() {
902 window.location.href = destination;
903 } );
904 } );
905 $( document ).on( 'input', '[data-address-lookup-input]', function() {
906 var value = $( this ).val();
907
908 window.clearTimeout( addressLookupTimer );
909 addressLookupTimer = window.setTimeout( function() {
910 searchAddresses( value );
911 }, 300 );
912 } );
913 $( document ).on( 'click', '[data-address-id]', function() {
914 fetchAddress( $( this ).attr( 'data-address-id' ) );
915 } );
916
917 $( document ).on( 'input change', '[data-office-field], [data-country-select]', function() {
918 var field = $( this ).attr( 'data-office-field' ) || 'country';
919 clearFieldError( field );
920 setMessage( '' );
921 } );
922
923 $( document ).on( 'input change', 'input[name="license_key"]', function() {
924 clearFieldError( 'license_key' );
925 } );
926
927 $( '[data-next]' ).on( 'click', function() {
928 var index = getStepIndex( currentStep );
929
930 if ( demoImporting ) {
931 return;
932 }
933
934 if ( ! validateStep( currentStep ) ) {
935 return;
936 }
937
938 maybeImportDemoData().done( function() {
939 saveStep( currentStep, true ).done( function( response ) {
940 if ( response && response.success === false ) {
941 return;
942 }
943
944 showStep( steps[ index + 1 ] );
945 } );
946 } );
947 } );
948
949 $( '[data-back]' ).on( 'click', function() {
950 var index = getStepIndex( currentStep );
951 if ( index > 0 ) {
952 showStep( steps[ index - 1 ] );
953 }
954 } );
955
956 $( '[data-ph-onboarding-skip]' ).on( 'click', function() {
957 if ( ! window.confirm( config.i18n.skipConfirm ) ) {
958 return;
959 }
960
961 $.post( config.ajax_url, {
962 action: 'propertyhive_onboarding_skip',
963 security: config.nonce,
964 step: currentStep,
965 usage_tracking: $( '[data-usage-tracking]' ).is( ':checked' ) ? 'yes' : 'no'
966 } ).done( function( response ) {
967 window.location.href = response && response.data && response.data.redirect_url ? response.data.redirect_url : config.settings_url;
968 } );
969 } );
970
971 $( document ).on( 'change', '.ph-onboarding__choice input', function() {
972 var name = $( this ).attr( 'name' );
973
974 if ( name === 'departments[]' ) {
975 clearFieldError( 'departments' );
976 setMessage( '' );
977 }
978
979 if ( name === 'usage[]' ) {
980 clearFieldError( 'usage' );
981 setMessage( '' );
982 updateExitRecommendations();
983 updateUsageProNote();
984 }
985
986 if ( name === 'demo_data_choice' ) {
987 clearFieldError( 'demo_data_choice' );
988 setMessage( '' );
989 }
990
991 if ( name === 'has_license_key' ) {
992 setMessage( '' );
993 }
994
995 if ( $( this ).attr( 'type' ) === 'radio' ) {
996 if ( $( this ).is( ':checked' ) ) {
997 $( 'input[name="' + $( this ).attr( 'name' ) + '"]' ).closest( '.ph-onboarding__choice' ).removeClass( 'is-selected' );
998 $( this ).closest( '.ph-onboarding__choice' ).addClass( 'is-selected' );
999 } else {
1000 $( this ).closest( '.ph-onboarding__choice' ).removeClass( 'is-selected' );
1001 }
1002 return;
1003 }
1004
1005 $( this ).closest( '.ph-onboarding__choice' ).toggleClass( 'is-selected', $( this ).is( ':checked' ) );
1006 } );
1007
1008 $( '.ph-onboarding__choice input' ).trigger( 'change' );
1009 showStep( currentStep );
1010 refreshDemoState();
1011 updateLicenseChoice();
1012 updateLicenseType();
1013 updateExitRecommendations();
1014 updateUsageProNote();
1015 } );
1016