| 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 |
|