PageSourceSearch

https://nuagedx.com/wp-content/themes/Avada-Child-Theme/js/recurly-form.js?v=1790902428

js nuagedx.com collected 2026-10-02 00:56:15 UTC 44,791 bytes, 1,454 lines download raw bytes

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.