1// Version: 1.0.22 2 3var plansData; 4var recurly = window.recurly || null; 5 6// PROD SETTINGS 7var initSettings = { 8 apiKey: "AIzaSyBPYsY-89MlrB5w3-cQf92-ILGiHF46AZ0", 9 authDomain: "nuagedx-dev.firebaseapp.com", 10 databaseURL: "https://nuagedx-dev.firebaseio.com", 11 projectId: "nuagedx-dev", 12 storageBucket: "nuagedx-dev.appspot.com", 13 messagingSenderId: "409596160399", 14 appId: "1:409596160399:web:c94b8198a805007e", 15}; 16 17// TEST SETTINGS 18// initSettings = { 19// apiKey: "AIzaSyBAXagEIHyu8C08LTVv-nIcc9CHRYU5qh8", 20// authDomain: "nuagedx-test.firebaseapp.com", 21// projectId: "nuagedx-test", 22// storageBucket: "nuagedx-test.appspot.com", 23// messagingSenderId: "1037642805060", 24// appId: "1:1037642805060:web:32148008106162e8573111" 25// }; 26 27// const cloudfunctionsURI = `https://us-central1-nuagedx-test.cloudfunctions.net/`; 28const cloudfunctionsURI = `https://us-central1-nuagedx-dev.cloudfunctions.net/`; 29 30var invalid_fields = {}; 31var error_fields = { 32 first_name: "First name", 33 last_name: "Last name", 34 account_type: "Account Type", 35 email: "Email address", 36 verification_code: "Email code", 37 number: "Credit Card Number", 38 postal_code: "Postal Code", 39 month: "Expiration Month", 40 year: "Expiration Year", 41 cvv: "CVV", 42 address: "Street address", 43 city: "City", 44 country: "Country", 45 state: "State", 46 name_on_account: "Name on Account", 47 routing_number: "Routing Number", 48 account_number: "Account Number", 49 account_number_confirmation: "Account Number Confirmation", 50 account_type: "Bank Account Type", 51 username: "Username", 52 password: "Password", 53 phone: "Phone", 54 company: "Company", 55 phone: "Phone Number", 56}; 57 58var paypal_error_fields = { 59 first_name: "First name", 60 last_name: "Last name", 61}; 62 63if (!recurly) { 64 window.alert("no - recurly script"); 65} 66 67async function fetchRecurlyConfig() { 68 const cloudFunctionUrl = 'https://us-central1-nuagedx-dev.cloudfunctions.net/recurly-getRecurlyConfig'; 69 try { 70 const response = await fetch(cloudFunctionUrl, { 71 method: 'GET', 72 headers: { 73 'Content-Type': 'application/json' 74 } 75 }); 76 77 if (!response.ok) { 78 throw new Error('Network response was not ok'); 79 } 80 81 const data = await response.json(); 82 // Use the API key as needed 83 return data.apikey; 84 } catch (error) { 85 console.error('Error fetching Recurly API Key:', error); 86 } 87} 88 89if (!recurly.configured) { 90 fetchRecurlyConfig().then(v => { 91 recurly.configure(v); // NUAGEDX 92 }) 93} 94 95const fetchRequest = (url, params = {}, method = "GET") => { 96 let options = { 97 method, 98 }; 99 if ("GET" === method) { 100 url += "?" + new URLSearchParams(params).toString(); 101 } else { 102 options.body = JSON.stringify(params); 103 options.headers = { 104 "Content-Type": "application/json", 105 // 'Content-Type': 'application/x-www-form-urlencoded', 106 }; 107 } 108 109 return fetch(url, options).then((response) => response.json()); 110}; 111 112const post = (url, params) => fetchRequest(url, params, "POST"); 113 114function acceptTerms() { 115 closeTerms(); 116 document.querySelector("#agreeterms").checked = true; 117} 118 119/** 120 * Check is the given email is already registered in firestore. 121 * @param {*} email 122 * @returns boolean 123 */ 124async function checkOwnerExistence(email) { 125 const url = `${cloudfunctionsURI}admin-checkExistence`; 126 return post(url, { data: { email } }); 127} 128 129/** 130 * Collects data from the form fields and returns it as an object. 131 * 132 * @returns {Object} An object containing the collected form data. 133 */ 134function collectingData() { 135 const getFieldValue = (selector) => getElementValue(selector) || ""; 136 137 return { 138 "recurly-token": getFieldValue('input[name="recurly-token"]'), 139 "first-name": getFieldValue('input[name="first_name"]'), 140 "last-name": getFieldValue('input[name="last_name"]'), 141 plan: 142 getFieldValue('select[name="plan"]') === "free" 143 ? "fp" 144 : getFieldValue('select[name="plan"]'), 145 email: getFieldValue('input[name="email"]'), 146 email_code: getFieldValue('input[name="verification_code"]'), 147 address1: getFieldValue('input[name="address1"]'), 148 address2: getFieldValue('input[name="address2"]'), 149 city: getFieldValue('input[name="city"]'), 150 state: getFieldValue("#state"), 151 zip: getFieldValue('input[name="postal-code"]'), 152 number: getFieldValue('input[name="number"]'), 153 month: getFieldValue('input[name="month"]'), 154 year: getFieldValue('input[name="year"]'), 155 cvv: getFieldValue('input[name="cvv"]'), 156 username: getFieldValue('input[name="email"]'), 157 password: getFieldValue('input[name="password"]'),
158 company: getFieldValue('input[name="company"]'), 159 phone: getFieldValue('input[name="phone"]'), 160 }; 161} 162 163function toggleValidationCodeButton(show) { 164 const link = document.querySelector("#send-validation-code-link"); 165 if (link) { 166 link.disabled = !show; 167 link.style.color = show ? "" : "gray"; 168 link.style.cursor = show ? "" : "not-allowed"; 169 } 170} 171 172function addEmailValidationButton() { 173 const inputEmail = document.querySelector("#email"); 174 if (!inputEmail) return; 175 if ( 176 inputEmail.nextElementSibling && 177 inputEmail.nextElementSibling.classList.contains("btn-submit") 178 ) 179 return; 180 if (inputEmail.parentNode.querySelector(".email-validation-form")) return; 181 if (inputEmail.value.trim() === "") return; 182 if (inputEmail.value.trim().length < 5) return; 183 184 // Validate the email address. 185 if (inputEmail.validity && !inputEmail.validity.valid) { 186 return; 187 } 188 189 // NOTE: add an <a> after the inputEmail. 190 if (!document.querySelector("#send-validation-code-link")) { 191 const link = document.createElement("a"); 192 link.innerText = "Send validation code"; 193 link.style.color = "red"; 194 link.id = "send-validation-code-link"; 195 link.href = "#"; 196 link.addEventListener("click", async (event) => { 197 event.preventDefault(); 198 const email = inputEmail.value.trim(); 199 if (!email) { 200 showFormMessage("error", "Please enter a valid email address."); 201 handleOpenMessageCont(); 202 return; 203 } 204 205 showLoader(); 206 207 try { 208 const isEmailValid = await post( 209 `${cloudfunctionsURI}admin-validateEmailV3`, 210 { email } 211 ); 212 213 if (isEmailValid) { 214 toggleValidationCodeButton(false); 215 setTimeout(() => { 216 toggleValidationCodeButton(true); 217 }, 60000); 218 219 handleEmailValidationStart({ email }); 220 } else { 221 showFormMessage( 222 "error", 223 "The email address is invalid. Please, try again." 224 ); 225 handleOpenMessageCont(); 226 } 227 } catch (error) { 228 showFormMessage( 229 "error", 230 "An error occurred while validating the email. Please, try again." 231 ); 232 handleOpenMessageCont(); 233 } 234 }); 235 inputEmail.parentNode.appendChild(link); 236 } 237} 238 239document 240 .querySelector("#email") 241 .addEventListener("change", addEmailValidationButton); 242 243// Avoid PASTE on varification_code input. 244document 245 .querySelector("#verification_code") 246 .addEventListener("paste", (event) => { 247 event.preventDefault(); 248 }); 249 250// Avoid RIGHT CLICK on varification_code input. 251document 252 .querySelector("#verification_code") 253 .addEventListener("contextmenu", (event) => { 254 event.preventDefault(); 255 }); 256 257/** 258 * Handles the Recurly Subscription creation. 259 */ 260async function createSubscription() { 261 const recurring = true; 262 const data = collectingData(); 263 264 // Validate Email Code 265 const { email_code } = data; 266 const { answer } = await post(`${cloudfunctionsURI}admin-validateEmailCode`, { 267 email: data.email, 268 validationCode: email_code, 269 }); 270 271 if (!answer) { 272 showFormMessage("error", "The validation code is incorrect. Please, try again."); 273 return; 274 } 275 276 const themeApiPath = `${site_url}/wp-content/themes/Avada-Child-Theme/api/`; 277 278 try { 279 jQuery.ajax({ 280 type: "POST", 281 url: recurring 282 ? `${themeApiPath}subscriptions/new.php` 283 : `${themeApiPath}transactions/post.php`, 284 data: data, 285 success: (res) => handleSuccessSubscription(res), 286 error: (xhr, status, error) => { 287 showFormMessage("error", error); 288 data["error-msg"] = error; 289 data["reason"] = "Error on AJAX call"; 290 handleOpenMessageCont(); 291 createZenDeskTicket(data); 292 }, 293 dataType: "json" 294 }); 295 } catch (error) { 296 showFormMessage("error", error.message); 297 data["error-msg"] = error.message; 298 data["reason"] = "Error on AJAX call"; 299 handleOpenMessageCont(); 300 createZenDeskTicket(data); 301 } 302} 303 304function clearMessageContainer() { 305 const messageCont = document.querySelector(".messageCont ul"); 306 messageCont.innerHTML = ""; 307} 308 309function showLoaderAnimation() { 310 const loader = document.createElement("div"); 311 loader.classList.add("loader"); 312 document.querySelector(".messageCont").appendChild(loader); 313} 314 315function showLoader() { 316 cleanMessageCont(); 317 const message = 'Please wait...'; 318 showFormMessage("warn", message, false, "validation-code-message"); 319 handleOpenMessageCont(); 320} 321 322function hideLoader() { 323 cleanMessageCont(); 324 handleCloseMessageCont(); 325} 326 327const handleEmailValidationStart = (data) => { 328 clearMessageContainer(); 329 330 const message = `We have sent a validation code to your email address. Please, enter the code in the next text field.`; 331 // const message = `We have sent a validation code to your email address. Please, enter the code below to continue.`; 332 333 showFormMessage("warn", message, false, "validation-code-message"); 334 handleOpenMessageCont(); 335}; 336 337async function validateCodeAction(emailValidationForm, data) { 338 const validationCode = emailValidationForm 339 .querySelector('input[name="validationCode"]') 340 .value.trim(); 341 if (!validationCode) { 342 showFormMessage("error", "Please enter the validation code."); 343 handleOpenMessageCont(); 344 return; 345 } 346 347 try { 348 const isCodeValid = await post(
349 `${cloudfunctionsURI}admin-validateEmailCode`, 350 { email: data.email, validationCode } 351 ); 352 353 if (isCodeValid) { 354 create_subscription(null, true); 355 cleanMessageCont(); 356 handleCloseMessageCont(); 357 358 checkOwnerExistence(data.email) 359 .then((answer) => afterCheckOwnerExistence(answer, data)) 360 .catch((error) => handleErrorOnCheckOwnerExistence(error, data)); 361 362 // createZenDeskTicket({ ...collectingData(), reason: "Owner created" }); 363 // showSuccessUserCreationMessage(createOwnerAnswer) 364 // subscription_created(res, res.data); 365 // cleanUpForm('form#registerForm') 366 } else { 367 showFormMessage( 368 "error", 369 "The validation code is incorrect. Please, try again." 370 ); 371 handleOpenMessageCont(); 372 } 373 } catch (error) { 374 showFormMessage( 375 "error", 376 "An error occurred while validating the code. Please, try again." 377 ); 378 handleOpenMessageCont(); 379 } 380} 381 382function toggleEscKey(val) { 383 if (val === false) { 384 // Disable the ESC key action. 385 document.addEventListener( 386 "keydown", 387 function (event) { 388 if (event.key === "Escape") { 389 event.preventDefault(); 390 } 391 }, 392 { once: true } 393 ); 394 } else { 395 // Enable back the ESC key action. 396 document.addEventListener("keydown", function (event) { 397 if (event.key === "Escape") { 398 // Default behavior for ESC key 399 } 400 }); 401 } 402} 403 404async function create_subscription(recurring, direct = false) { 405 // Validate EMAIL value. 406 if (!direct) { 407 const emailValue = getElementValue('input[name="email"]'); 408 409 if (!emailValue) { 410 showFormMessage("error", "Please, enter a valid email address."); 411 handleOpenMessageCont(); 412 return; 413 } 414 } else { 415 if (!recurring) { 416 recurring = true; 417 } 418 419 } 420 var data = collectingData(); 421 422 // check if new user already exists in firestore. 423 checkOwnerExistence(data.email) 424 .then((answer) => afterCheckOwnerExistence(answer, data)) 425 .catch((error) => handleErrorOnCheckOwnerExistence(error, data)); 426} 427 428function handleErrorOnCheckOwnerExistence(error, data) { 429 console.error({ error }); 430 data["error-msg"] = "checkOwnerExistence error"; 431 data["reason"] = "checkOwnerExistence error"; 432 error(); 433 handleOpenMessageCont(); 434 createZenDeskTicket(data); 435} 436 437function clear_errors() { 438 invalid_fields = {}; 439 jQuery(".form-errors--invalid-field").removeClass( 440 "form-errors--invalid-field" 441 ); 442 jQuery(".form-input__error").removeClass("form-input__error"); 443 jQuery(".form-errors").addClass("form-errors__hidden"); 444} 445 446/** 447 * Handles the response after checking if the owner exists. 448 * 449 * @async 450 * @function afterCheckOwnerExistence 451 * @param {Object} answer - The response object containing the result of the existence check. 452 * @param {boolean} answer.result - Indicates whether the owner exists. 453 * @param {Object} data - Additional data to be used in case of an error. 454 * @returns {Promise<void>} - A promise that resolves when the function completes. 455 * 456 * @description 457 * If the owner does not exist, it creates a new subscription. If the owner exists, it shows an error message, 458 * updates the data object with an error message and reason, opens a message container, and creates a ZenDesk ticket. 459 */ 460async function afterCheckOwnerExistence(answer, data) { 461 if (!answer.result) { 462 createSubscription(); 463 } else { 464 const errorMessage = 465 "There is an account with this email already. Please try again or change it."; 466 showFormMessage("error", errorMessage); 467 Object.assign(data, { 468 "error-msg": errorMessage, 469 reason: "App user already registered.", 470 }); 471 handleOpenMessageCont(); 472 await createZenDeskTicket(data); 473 } 474} 475 476/** 477 * For some reason, cancel the form, probably when click CLOSE button. 478 */ 479function cancel() { 480 jQuery(".form-container").show(); 481 jQuery(".review-container").hide(); 482 jQuery(".quantity, .addon-item--quantity input, select.plan, select.curr") 483 .removeAttr("disabled") 484 .parents(".select-wrap") 485 .removeClass("disabled"); 486 var $advancedContinue = jQuery("#continue.advanced"); 487 if ($advancedContinue[0]) { 488 $advancedContinue.parent().show(); 489 jQuery("#subscribe").parent().hide(); 490 } 491} 492 493function closeTerms() { 494 const termsContainer = document.querySelector("#termsContainer"); 495 termsContainer.innerHTML = ""; 496 termsContainer.classList.remove("termsContainer"); 497 498 const modalBody = termsContainer.parentNode.parentNode.parentNode; 499 modalBody.style.overflow = "auto"; 500} 501 502/** 503 * Handling the error coming from Recurly. 504 */ 505function error(err) { 506 if (err.niceMessage) { 507 errors_markup = 508 '<li class="form-errors--invalid-field">' + err.niceMessage + "</li>"; 509 } else { 510 jQuery.each(err.fields, function (i, field) { 511 if (typeof invalid_fields[field] === "undefined") { 512 invalid_fields[field] = field; 513 } 514 }); 515 var errors_markup = 516 `Please check the following fields:` + 517 jQuery.map(invalid_fields, processFieldError).join(""); 518 } 519 jQuery(".form-errors").removeClass("form-errors__hidden"); 520 jQuery(".form-errors ul") 521 .empty() 522 .append(errors_markup || ""); 523 jQuery(".messageCont ul") 524 .empty() 525 .append(errors_markup || ""); 526 jQuery('input[type="submit"]').prop("disabled", false); 527} 528 529/** 530 * Get tokenID for billing info, using Recurly.js 531 */ 532async function getToken(form, next) { 533 // Recruly is defined in another file. 534 // const recurly = window.recurly; 535 536 const paymentType = "card";
537 const paymentTokens = { card: recurly, "bank-account": recurly.bankAccount }; 538 539 const runError = (err) => { 540 error(err); 541 console.error(err); 542 handleOpenMessageCont(); 543 }; 544 545 const populateToken = (token) => { 546 setElementValue("#recurly-token", token.id); 547 next(); 548 }; 549 550 const runPaypalToken = (token) => { 551 setElementValue("#recurly-token", token.id); 552 form.submit(); 553 }; 554 555 // NOTE: Probably not used at all. 556 if (paymentType === "paypal") { 557 recurly.paypal({ description: "test" }, (err, token) => { 558 err ? paypalError(err) : runPaypalToken(token); 559 }); 560 } else { 561 paymentTokens[paymentType].token(form, (err, token) => { 562 err ? runError(err) : populateToken(token); 563 }); 564 } 565} 566 567/** 568 * Retrieves the trimmed value of an HTML element specified by a query selector. 569 * 570 * @param {string} query - The query selector string to identify the HTML element. 571 * @returns {string} The trimmed value of the element. If the element is not found, returns an empty string. 572 */ 573function getElementValue(query) { 574 const element = document.querySelector(query); 575 if (!element) { 576 console.error(`Element not found for query: ${query}`); 577 return ""; 578 } 579 return element.value.trim(); 580} 581 582function handleSuccessSubscription(res) { 583 var data = collectingData(); 584 /** 585 * Empty the message container, remove error classes and hide it (add the hide class). 586 */ 587 const messageCont = document.querySelector(".messageCont"); 588 messageCont.classList.remove("error", "success", "warn"); 589 messageCont.classList.add("hide"); 590 const messageParagraph = messageCont.querySelector(".message p"); 591 592 if (messageParagraph) { 593 messageParagraph.textContent = ""; 594 } 595 596 if (res.success) { 597 createZenDeskTicket({ ...data, reason: "Subscription created" }); 598 599 handleCreateOwner(res) 600 .then((answer) => { }) 601 .catch((error) => { 602 data["error-msg"] = error.code; 603 data["reason"] = error.message; 604 createZenDeskTicket(data); 605 showFormMessage("error", error.message); 606 handleOpenMessageCont(); 607 }); 608 } else { 609 if (res.data.error === "Token is either invalid or expired") { 610 /* // Get the plan selected. 611 if (document.querySelector('select[name="plan"]').value === "free") { 612 create_subscription(); 613 } else { 614 getToken(document.querySelector("#registerForm"), create_subscription); 615 } 616 return; */ 617 } else { 618 showFormMessage("error", res.data.error); 619 } 620 621 data["error-msg"] = res.data.error; 622 data["reason"] = "Duplicated subscription"; 623 624 createZenDeskTicket(data); 625 res.data 626 ? () => { 627 showFormMessage("error", res.data.error); 628 } 629 : () => { 630 showFormMessage("error", res); 631 }; 632 633 cleanMessageCont(); 634 cleanUpForm("form#registerForm"); 635 handleOpenMessageCont(); 636 } 637 638 document.querySelector("html, body").scrollTo({ 639 top: document.querySelector(".messageCont").offsetTop, 640 behavior: "smooth", 641 }); 642} 643 644/** 645 * Cleans up the registration form by performing the following actions: 646 * - Resets the form fields. 647 * - Removes any error messages or classes from the form inputs. 648 * - Hides any form error messages. 649 * - Clears any custom messages in the message container. 650 * - Hides the message container. 651 * 652 * This function targets the form with the ID 'registerForm' and specific 653 * elements with classes related to form errors and messages. 654 */ 655function cleanUpForm(selector) { 656 const form = document.querySelector(selector); 657 if (form) { 658 // Reset the form fields 659 form.reset(); 660 661 // Remove any error messages or classes 662 document 663 .querySelectorAll(".form-input__error") 664 .forEach((el) => el.classList.remove("form-input__error")); 665 document 666 .querySelectorAll(".form-errors--invalid-field") 667 .forEach((el) => el.classList.remove("form-errors--invalid-field")); 668 document 669 .querySelectorAll(".form-errors") 670 .forEach((el) => el.classList.add("form-errors__hidden")); 671 672 // clearCustomMessages(); 673 } 674} 675 676/** 677 * Clears any custom messages from the message container and hides the container. 678 * 679 * This function selects the unordered list within the element with the class 680 * 'messageCont' and clears its inner HTML. It then hides the message container 681 * by adding the 'hide' class to the element with the class 'messageCont'. 682 */ 683function clearCustomMessages() { 684 // Clear any custom messages 685 const messageCont = document.querySelector(".messageCont ul");
686 if (messageCont) { 687 messageCont.innerHTML = ""; 688 } 689 690 // Hide the message container 691 const messageContainer = document.querySelector(".messageCont"); 692 if (messageContainer) { 693 messageContainer.classList.add("hide"); 694 } 695} 696 697/** 698 * Cleans up the message container by removing specific elements. 699 * 700 * This function performs the following actions: 701 * - Removes the email validation form inside the "registerForm". 702 * - Removes the validation code message from the message container. 703 */ 704function cleanMessageCont() { 705 // Remove the email validation form inside the "registerForm" 706 const formToBeRemoved = document.querySelector( 707 "#registerForm .email-validation-form" 708 ); 709 if (formToBeRemoved) { 710 formToBeRemoved.remove(); 711 } 712 713 // Remove the validation code message from the message container 714 const elementToBeRemoved = document.querySelector( 715 ".messageCont ul li.validation-code-message" 716 ); 717 if (elementToBeRemoved) { 718 elementToBeRemoved.remove(); 719 } 720} 721 722/** 723 * Creates a new owner account with the provided details. 724 * 725 * @param {string} recurlyAccountCode - The Recurly account code for the user. 726 * @param {string} planCode - The plan code associated with the user. 727 * @returns {Promise<Object>} The response from the server after creating the user. 728 * @throws Will throw an error if the user creation fails. 729 */ 730async function createOwner(recurlyAccountCode, planCode) { 731 try { 732 const password = getElementValue("#password"); 733 const email = getElementValue("#email").trim(); 734 const role = "Owner"; 735 const companyName = getElementValue("#company").trim(); 736 const phoneNumber = getElementValue("#phone"); 737 const createdBy = "nuagedx.com"; 738 const name = `${getElementValue("#first_name")} ${getElementValue( 739 "#last_name" 740 )}`; 741 // https://us-central1-nuagedx-dev.cloudfunctions.net/admin-createUserV2-1 742 const url = `${cloudfunctionsURI}admin-createUserV2-1`; 743 const user = { 744 name, 745 email, 746 role, 747 companyName, 748 phoneNumber, 749 recurlyAccountCode, 750 planCode, 751 createdBy, 752 }; 753 const data = { 754 email, 755 password, 756 displayName: name, 757 user, 758 }; 759 760 return await post(url, { data }); 761 } catch (error) { 762 console.error("Error creating owner:", error); 763 throw error; 764 } 765} 766 767/** 768 * Handles the response after checking the owner. 769 * 770 * @async 771 * @function handleCreateOwner 772 * @param {Object} res - The response object. 773 * @param {boolean} res.success - Indicates if the operation was successful. 774 * @param {string} res.accountcode - The account code. 775 * @param {string} res.plancode - The plan code. 776 * @param {Object} res.data - Additional data related to the response. 777 * @returns {Promise<Error|void>} Returns an error if there is an error in creating the owner, otherwise void. 778 * 779 * @description 780 * This function processes the response after checking the owner. If the response indicates success, 781 * it logs a success message and attempts to create an owner using the provided account code and plan code. 782 * If there is an error in creating the owner, it returns an error. Otherwise, it creates a ZenDesk ticket, 783 * logs the result, and updates the UI with a success message. Finally, it calls the `subscription_created` function. 784 */ 785async function handleCreateOwner(res) { 786 if (res.success) { 787 } 788 789 const createOwnerAnswer = await createOwner(res.accountcode, res.plancode); 790 791 if (createOwnerAnswer.errorInfo) { 792 return new Error(createOwnerAnswer.errorInfo); 793 } 794 795 createZenDeskTicket({ ...collectingData(), reason: "Owner created" }); 796 797 showSuccessUserCreationMessage(createOwnerAnswer); 798 subscription_created(res, res.data); 799} 800 801function showSuccessUserCreationMessage(createOwnerAnswer) { 802 const successMessage = createSuccessMessage(); 803 const failureMessage = `There was a problem with user creation, please check the logs.`; 804 805 // "Result", 806 // createOwnerAnswer.result ? successMessage : failureMessage 807 // ); 808 809 const li = document.createElement("li"); 810 li.innerHTML = successMessage; 811 812 const messageCont = document.querySelector(".messageCont"); 813 messageCont.classList.add("success"); 814 messageCont.classList.remove("hide"); 815 messageCont.querySelector("ul").appendChild(li); 816} 817 818function createSuccessMessage() { 819 const loginURL = "https://app.nuagedx.com/login"; 820 const successMessage = `The account has been created succesfully. Please click <a href="${loginURL}" target="_blank"><b>here</b></a> to login into the app.`; 821 cleanUpForm('form#registerForm') 822 return successMessage; 823} 824 825/** 826 * Handles the closing of the message container. 827 * 828 * This function performs the following actions: 829 * - Selects the message container using the query selector. 830 * - Clears the inner HTML of the message container. 831 * - Hides the message container by setting its display style to "none". 832 * - Removes the close button from the message container if it exists. 833 */ 834function handleCloseMessageCont() { 835 const messageCont = document.querySelector(".messageCont ul");
836 if (messageCont) { 837 messageCont.innerHTML = ""; 838 } 839 const messageContainer = document.querySelector(".messageCont"); 840 if (messageContainer) { 841 messageContainer.style.display = "none"; 842 const closeButton = messageContainer.querySelector("button"); 843 if (closeButton) { 844 closeButton.remove(); 845 } 846 } 847 // enableRegisterBtn(); 848} 849 850/** 851 * Disables the submit button in the registration form. 852 * 853 * This function selects the submit button within a form with the ID 'registerForm' 854 * and disables it, preventing the user from submitting the form. 855 */ 856function enableRegisterBtn() { 857 document.querySelector("form#registerForm #submit").disabled = true; 858} 859 860/** 861 * Enables the register button by removing the "disabled" attribute 862 * from the submit button within the form with the ID "registerForm". 863 */ 864function disableRegisterBtn() { 865 document 866 .querySelector("form#registerForm #submit") 867 .removeAttribute("disabled"); 868} 869 870/** 871 * Handles the opening of the message container. 872 * 873 * This function performs the following actions: 874 * - Selects the message container using jQuery. 875 * - Creates a close button element. 876 * - Sets the inner HTML of the close button to "x". 877 * - Adds a class "closeBtn" to the close button. 878 * - Adds an event listener to the close button to handle the close action. 879 * - Appends the close button to the message container. 880 * - Displays the message container. 881 */ 882function handleOpenMessageCont() { 883 toggleEscKey(false); 884 885 const messageCont = document.querySelector(".messageCont ul"); 886 const closeButton = document.createElement("button"); 887 888 closeButton.innerHTML = "x"; 889 closeButton.classList.add("closeBtn"); 890 closeButton.addEventListener("click", handleCloseMessageCont); 891 892 messageCont.appendChild(closeButton); 893 document.querySelector(".messageCont").style.display = "block"; 894 // disableRegisterBtn(); 895} 896 897/** 898 * Toggles the display of credit card fields based on the provided flag. 899 * 900 * @param {boolean} show - A flag indicating whether to show or hide the credit card fields. 901 * If true, the fields will be displayed; if false, they will be hidden. 902 */ 903function toggleCCFields(show) { 904 const displayStyle = show ? "block" : "none"; 905 document 906 .querySelectorAll(".cc-fields-title, .cc-section") 907 .forEach((element) => { 908 element.style.display = displayStyle; 909 }); 910} 911 912/** 913 * Do an ajax request to get the wordig for the selected plan. 914 */ 915function handlePlanSelection(event) { 916 document.querySelector(".plan-result").style.display = "none"; 917 918 const valueSelected = event.target.value; 919 if (valueSelected === "") return; 920 921 const getdataUrl = "./../../../../getplandata.php"; 922 const url = `${getdataUrl}?type=${valueSelected}`; 923 924 ajaxRequestV2("GET", url) 925 .then((answer) => handleAjaxRequestAnswer(answer, valueSelected)) 926} 927 928/** 929 * Handles the response from an AJAX request and updates the UI accordingly. 930 * 931 * @param {Object} answer - The response object containing the title and body to display. 932 * @param {string} answer.title - The title to display in the plan result element. 933 * @param {string} answer.body - The body content to display in the plan result element. 934 * @param {string} valueSelected - The selected plan value to determine UI changes. 935 * 936 * @returns {void} 937 */ 938function handleAjaxRequestAnswer(answer, valueSelected) { 939 const planResultElement = document.querySelector(".plan-result"); 940 planResultElement.innerHTML = `<h4>${answer.title}</h4>${answer.body}`; 941 planResultElement.style.display = "block"; 942 943 const isFreePlan = ["free", "lpmp", "lpmyearly"].includes(valueSelected); 944 toggleCCFields(!isFreePlan); 945 946 const isClioPlan = ["lpmp", "lpmyearly"].includes(valueSelected); 947 toggleSubmitBtn(!isClioPlan); 948 toggleFieldTerms(!isClioPlan); 949 toggleReCaptcha(!isClioPlan); 950 isClioPlan ? showGoToClioBtn() : removeGoToClioBtn(); 951} 952 953function createGoToClioBtn() { 954 const btn = document.createElement("a"); 955 btn.innerText = "Go to Clio Login"; 956 btn.classList.add("go-to-clio"); 957 btn.classList.add("btn-submit"); 958 btn.href = 959 "https://app.nuagedx.com/login?message=cliousersonly&fieldsdisabled=true"; 960 btn.target = "_blank"; 961 return btn; 962} 963 964function removeGoToClioBtn() { 965 const btn = document.querySelector(".go-to-clio"); 966 if (btn) btn.remove(); 967} 968 969function showGoToClioBtn() { 970 if (document.querySelector(".go-to-clio")) return; 971 const btn = createGoToClioBtn(); 972 973 // Add the Go to Clio button inside the last p tag inside the form. 974 document.querySelector("#submit").parentNode.appendChild(btn); 975} 976 977function toggleFieldTerms(show) { 978 document.querySelector(".field-terms").style.display = show 979 ? "block" 980 : "none"; 981} 982 983function toggleReCaptcha(show) { 984 document.querySelector(".g-recaptcha").style.display = show 985 ? "block" 986 : "none"; 987} 988 989function toggleSubmitBtn(show) { 990 // Get the input btn inside the last p tag inside the form. 991 document.querySelector("#submit").style.display = show ? "block" : "none"; 992} 993 994function handleCCNumberChange(event) { 995 const generis = () => { 996 document.querySelector(".icon-card").classList.add("icon-card__generic"); 997 const iconCard = document.querySelector(".icon-card"); 998 iconCard.classList.remove("icon-card__visa"); 999 iconCard.classList.remove("icon-card__mastercard"); 1000 iconCard.classList.remove("icon-card__amex"); 1001 iconCard.classList.remove("icon-card__visa"); 1002 iconCard.classList.remove("discover"); 1003 }; 1004 1005 const cardType = (card_type) => { 1006 document.querySelector(".icon-card") 1007 ? document 1008 .querySelector(".icon-card") 1009 .classList.remove("icon-card__generic") 1010 .add(`icon-card__${card_type}`) 1011 : false;
1012 }; 1013 1014 const card_number = getElementValue("#number"), 1015 card_type = recurly.validate.cardType(card_number), 1016 card_is_valid = recurly.validate.cardNumber(getElementValue("#number")), 1017 number_field = document.querySelector( 1018 ".customer-fields--card-number .form-input" 1019 ); 1020 1021 card_is_valid 1022 ? number_field.classList.remove("form-input__error") 1023 : number_field.classList.add("form-input__error"); 1024 1025 card_type == "default" || card_type == "unknown" 1026 ? generis() 1027 : cardType(card_type); 1028} 1029 1030/** 1031 * 1032 * Show terms of services content. 1033 */ 1034function handleTermsClick(event) { 1035 event.preventDefault(); 1036 1037 const termsContainer = document.querySelector("#termsContainer"); 1038 termsContainer.className = "termsContainer"; 1039 1040 const closeBtn = document.createElement("button"); 1041 closeBtn.innerText = "CLOSE"; 1042 closeBtn.className = "closeBtn"; 1043 closeBtn.addEventListener("click", closeTerms); 1044 1045 const acceptBtn = document.createElement("button"); 1046 acceptBtn.innerText = "ACCEPT"; 1047 acceptBtn.className = "acceptBtn"; 1048 acceptBtn.addEventListener("click", acceptTerms); 1049 1050 ajaxRequest( 1051 "GET", 1052 "terms.html", 1053 (data) => { 1054 termsContainer.innerHTML = data; 1055 termsContainer.appendChild(closeBtn); 1056 termsContainer.appendChild(acceptBtn); 1057 hideParentOverflow(); 1058 }, 1059 (error) => { 1060 }, 1061 (error) => { 1062 console.error(error); 1063 }, 1064 null, 1065 [] 1066 ); 1067} 1068 1069function hideParentOverflow() { 1070 const termsContainer = document.querySelector("#termsContainer"); 1071 const modalBody = termsContainer.parentNode.parentNode.parentNode; 1072 modalBody.style.overflow = "hidden"; 1073 modalBody.scrollTop = 0; 1074} 1075 1076function processFieldError(field) { 1077 jQuery(".form-input__" + field).addClass("form-input__error"); 1078 return ( 1079 `<li class="form-errors--invalid-field">${error_fields[field] || field}` + 1080 `</li>` 1081 ); 1082} 1083 1084/** 1085 * Resets the registration form by clearing all input fields and removing any error messages or classes. 1086 * 1087 * This function targets the form with the ID 'registerForm' and performs the following actions: 1088 * - Resets the form fields to their default values. 1089 * - Removes any error messages or classes from the form inputs. 1090 * - Hides any form error messages. 1091 * - Clears any custom messages in the message container. 1092 * - Hides the message container. 1093 */ 1094function resetForm(selector) { 1095 const form = document.querySelector(selector); 1096 if (form) { 1097 // Reset the form fields 1098 form.reset(); 1099 1100 // Remove any error messages or classes 1101 document 1102 .querySelectorAll(".form-input__error") 1103 .forEach((el) => el.classList.remove("form-input__error")); 1104 document 1105 .querySelectorAll(".form-errors--invalid-field") 1106 .forEach((el) => el.classList.remove("form-errors--invalid-field")); 1107 document 1108 .querySelectorAll(".form-errors") 1109 .forEach((el) => el.classList.add("form-errors__hidden")); 1110 1111 // Clear custom messages 1112 clearCustomMessages(); 1113 } 1114} 1115 1116/** 1117 * Executes the provided function when the DOM is fully loaded. 1118 * 1119 * @param {Function} fn - The function to execute when the DOM is ready. 1120 */ 1121function ready(fn) { 1122 if (document.readyState !== "loading") { 1123 fn(); 1124 } else { 1125 document.addEventListener("DOMContentLoaded", fn); 1126 } 1127} 1128 1129function setElementValue(query, value) { 1130 document.querySelector(query).value = value; 1131} 1132 1133async function startup() { 1134 // Handle Pricing Page Link Click 1135 document.querySelectorAll(".pricing-page-link").forEach((link) => { 1136 link.addEventListener("click", function (event) { 1137 event.preventDefault(); 1138 event.stopPropagation(); 1139 1140 // Get Registration Form data and convert it to URL encoded string. 1141 const form = document.querySelector("#registerForm"); 1142 const formData = new FormData(form); 1143 const params = new URLSearchParams(formData).toString(); 1144 const encodedData = encodeURIComponent(params); // Encode the data 1145 const url = `${event.currentTarget.href}?${encodedData}`; 1146 window.location.href = url; 1147 }); 1148 }); 1149 1150 // getActivePlans(); 1151 document.querySelector(".plan-result").style.display = "none"; 1152 handleCloseMessageCont(); 1153 1154 /** 1155 * Handles the sign-up process when an event is triggered. 1156 * 1157 * This function sets a timeout to delay the execution of the sign-up logic. 1158 * It checks if the signup element is present and has the class "in". 1159 * If so, it retrieves the data-plan attribute from the event's current target. 1160 * If the data-plan is not "free" and the global variable `window.annually` is true, 1161 * it modifies the data-plan to a yearly plan. 1162 * Finally, it sets the selected plan in a dropdown menu. 1163 * 1164 * @param {Event} event - The event object triggered by the sign-up action. 1165 */ 1166 const signUpHandler = function (event) { 1167 setTimeout(() => { 1168 const signupElement = document.querySelector(".signup"); 1169 const freePlan = "free"; 1170 1171 if (signupElement?.classList.contains("in")) { 1172 let dataPlan = event.currentTarget?.getAttribute("data-plan"); 1173 if (dataPlan && dataPlan !== freePlan && window.annually) { 1174 dataPlan = getYearlyPlan(dataPlan); 1175 } 1176 1177 // Set the plan selected. 1178 const planSelect = document.querySelector('select[name="plan"]'); 1179 if (planSelect) { 1180 planSelect.value = dataPlan; 1181 } 1182 } 1183 }, 1000); 1184 }; 1185 1186 const getYearlyPlan = (plan) => { 1187 const yearlyPlans = { 1188 silver: "silveryearly", 1189 gold: "goldyearly", 1190 platinum: "platinumyearly", 1191 lpmp: "lpmyearly", 1192 }; 1193 return yearlyPlans[plan] || plan; 1194 }; 1195 1196 document 1197 .querySelectorAll("#month, #year, #cvv, #postal_code") 1198 .forEach((element) => { 1199 element.addEventListener("keydown", (e) => { 1200 if (e.key.length === 1 && e.key.match(/[a-zA-Z]/)) { 1201 e.preventDefault(); 1202 } 1203 }); 1204 }); 1205 1206 document.querySelector("#state").addEventListener("keydown", (e) => { 1207 if (!e.key.match(/[a-zA-Z]/)) { 1208 e.preventDefault(); 1209 } 1210 e.target.value = e.target.value.toUpperCase(); 1211 }); 1212 1213 document.querySelector("#month").addEventListener("keyup", (e) => { 1214 if (parseInt(e.target.value) > 12) { 1215 e.target.value = 12; 1216 } 1217 }); 1218 1219 document 1220 .querySelector('[data-target=".signup"]') 1221 .addEventListener("click", signUpHandler); 1222 1223 // NOTE: Add a listener that will bring the wording for the selected plan. 1224 document 1225 .querySelector('select[name="plan"]') 1226 .addEventListener("change", handlePlanSelection, event); 1227 1228 // NOTE: Applying fields masks. 1229 jQuery("input#number").mask("0000 0000 0000 0000"); 1230 jQuery("input#phone").mask("(000) 000-0000"); 1231 1232 /** 1233 * Activate jQuery Validation for the form. 1234 */ 1235 jQuery("#registerForm").validate({ 1236 rules: { 1237 email: { email: true, required: true }, 1238 verification_code: { required: true }, 1239 }, 1240 errorPlacement: (error, element) => { 1241 if (element.attr("name") == "agreeterms") { 1242 error.appendTo(".checkbox-error"); 1243 } else { 1244 error.insertAfter(element); 1245 } 1246 }, 1247 submitHandler: function (form) { 1248 // do other things for a valid form 1249 grecaptcha.ready(function () { 1250 if (grecaptcha.getResponse() === "") { 1251 showFormMessage("error", "Please, check the reCAPTCHA box"); 1252 handleOpenMessageCont(); 1253 return; 1254 } else { 1255 showLoader(); 1256 // Check rate limit for form submissions. 1257 fetch(ajax_object.ajax_url, { 1258 method: "POST", 1259 headers: { "Content-Type": "application/x-www-form-urlencoded" }, 1260 body: new URLSearchParams({ action: "custom_form_submit" }), 1261 }) 1262 .then((response) => response.text()) 1263 .then((responseText) => { 1264 hideLoader() 1265 1266 const parsedResponse = JSON.parse(responseText); 1267 1268 if (parsedResponse && parsedResponse.success) { 1269 1270 // CREATE SUBSCRIPTION 1271 const registerForm = document.querySelector("#registerForm"); 1272 1273 if (!registerForm.classList.contains("form__success")) { 1274 clear_errors(); 1275 1276 const recurlyToken = 1277 document.querySelector("#recurly-token").value; 1278 const selectedPlan = document.querySelector( 1279 'select[name="plan"]' 1280 ).value; 1281 1282 if (selectedPlan === "free") { 1283 // Create FREE subscription 1284 create_subscription(); 1285 } else { 1286 // Get token and create subscription 1287 getToken(registerForm, create_subscription); 1288 } 1289 } 1290 } else { 1291 } 1292 }) 1293 .catch((error) => { 1294 }); 1295 } 1296 }); 1297 }, 1298 }); 1299 1300 document 1301 .querySelector("#number") 1302 .addEventListener("change", handleCCNumberChange); 1303 1304 document.querySelector("#submit").removeAttribute("disabled"); 1305 1306 await buildSelectPlanDropdown(); 1307} 1308 1309/** 1310 * Asynchronously builds and populates a dropdown menu with available plans. 1311 * 1312 * This function clears any existing options in the dropdown menu and adds a default 1313 * option prompting the user to choose a plan. It then fetches the available plans, 1314 * sorts them by index, and populates the dropdown with these plans. 1315 * 1316 * Each plan option displays the plan's title (or a capitalized version of its ID if no title is available) 1317 * and its price in USD. 1318 * 1319 * @async 1320 * @function buildSelectPlanDropdown 1321 * @returns {Promise<void>} A promise that resolves when the dropdown has been populated. 1322 * @throws Will log an error to the console if fetching plans data fails. 1323 */ 1324async function buildSelectPlanDropdown() { 1325 const selectPlan = document.querySelector('select[name="plan"]'); 1326 1327 // Clear existing options 1328 selectPlan.innerHTML = ""; 1329 1330 // Add a first element: "--Select a Plan--" 1331 const defaultOption = document.createElement("option"); 1332 defaultOption.value = ""; 1333 defaultOption.innerText = "Choose a Plan"; 1334 selectPlan.appendChild(defaultOption); 1335 1336 try { 1337 const plans = plansData ? plansData : await getPlansData(); 1338 1339 // Sort plans by index. 1340 plans.sort((a, b) => a.index - b.index); 1341 1342 plans.forEach((plan) => { 1343 const option = document.createElement("option"); 1344 option.value = plan.id; 1345 option.innerText = `${plan.content?.title || capitalCase(plan.id)} ($${plan.price 1346 } USD)`;
1347 selectPlan.appendChild(option); 1348 }); 1349 } catch (error) { 1350 console.error("Error fetching plans data:", error); 1351 } 1352} 1353 1354/** 1355 * Converts a string to capital case. 1356 * 1357 * @param {string} str - The string to convert. 1358 * @returns {string} The converted string with the first letter capitalized. 1359 */ 1360function capitalCase(str) { 1361 if (!str) return ""; 1362 return str.charAt(0).toUpperCase() + str.slice(1).toLowerCase(); 1363} 1364 1365/** 1366 * Fetches the plans data from a Firebase Cloud function. 1367 * 1368 * @async 1369 * @function getPlansData 1370 * @returns {Promise<Array>} A promise that resolves to an array of plans data. 1371 * @throws Will throw an error if the fetch request fails. 1372 */ 1373async function getPlansData() { 1374 try { 1375 const response = await fetch(`${cloudfunctionsURI}admin-getPlans`); 1376 if (!response.ok) { 1377 throw new Error(`Failed to fetch plans data: ${response.statusText}`); 1378 } 1379 const data = await response.json(); 1380 return data; 1381 } catch (error) { 1382 console.error("Error fetching plans data:", error); 1383 throw error; 1384 } 1385} 1386 1387/** 1388 * Displays a message in the form's message container. 1389 * 1390 * @param {string} type - The type of message to display (e.g., "error"). 1391 * @param {string} message - The message content to display. 1392 * @param {boolean} [html=false] - Whether the message content is HTML. 1393 * @param {string} [className=''] - An optional class name to add to the message element. 1394 */ 1395function showFormMessage(type, message, html = false, className = false) { 1396 const li = document.createElement("li"); 1397 const liClassNameExists = document.querySelector(`li.${className}`); 1398 if (className && !liClassNameExists) { 1399 li.classList.add(className); 1400 } 1401 if (html) { 1402 li.innerHTML = message; 1403 } else { 1404 li.innerText = message; 1405 } 1406 1407 const messageContainer = document.querySelector(".messageCont ul"); 1408 messageContainer.appendChild(li); 1409 messageContainer.classList.remove("hide"); 1410 1411 if (type === "error") { 1412 console.error("Error: " + message); 1413 } 1414 1415 handleOpenMessageCont(); 1416} 1417 1418/** 1419 * Handles the actions to be taken after a subscription is successfully created. 1420 * 1421 * @function subscription_created 1422 * @param {Object} res - The response object from the subscription creation process. 1423 * @param {Object} data - Additional data related to the subscription. 1424 * @param {string} res.msg - The message returned from the subscription creation process. 1425 * @param {string} res.msg_type - The type of message returned from the subscription creation process. 1426 * 1427 * @description 1428 * This function performs the following actions: 1429 * - Adds a success class to the form to indicate the subscription was successful. 1430 * - Stores the response and data in the form's data attributes. 1431 * - Triggers a custom event 'recurly-success' on the form. 1432 * - Updates the data object with error message and reason. 1433 * - Opens the message container after a short delay. 1434 */ 1435function subscription_created(res, data) { 1436 // Add a success class to the form to indicate the subscription was successful 1437 document.querySelector("form").classList.add("recurly__success"); 1438 1439 // Store the response and data in the form's data attributes and trigger a custom event 1440 const form = document.querySelector("#registerForm"); 1441 form.dataset.res = JSON.stringify(res); 1442 form.dataset.data = JSON.stringify(data); 1443 const event = new CustomEvent("recurly-success", { detail: { res, data } }); 1444 form.dispatchEvent(event); 1445 1446 // Update the data object with error message and reason 1447 data["error-msg"] = res.msg; 1448 data["reason"] = res.msg_type; 1449 1450 // Open the message container after a short delay 1451 setTimeout(() => { 1452 handleOpenMessageCont(); 1453 }, 1000); 1454}
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.