PageSourceSearch

https://www.any.do/v5/js/main.js?v=2026-07-6

js any.do collected 2026-09-24 08:32:44 UTC 57,082 bytes, 1,829 lines download raw bytes

1/* global ga */
2
3/**
4 * @param {string} key
5 * @param {string} [defaultValue]
6 * @param {string|null}
7 */
8function getQueryParam(key, defaultValue) {
9  if (typeof URLSearchParams === "undefined") return null;
10
11  var q = new URLSearchParams(location.search);
12  var value = q.get(key);
13
14  return value == null ? (defaultValue == null ? null : defaultValue.toLocaleLowerCase()) : value.toLocaleLowerCase();
15}
16
17//#region Google one tap login
18window.onGoogleLibraryLoad = () => {
19  if (!isMobile.any) {
20    google.accounts.id.initialize({
21      client_id: Auth().GOOGLE_CLIENT_ID,
22      prompt_parent_id: "google-one-tap-login-container",
23      callback: handleGoogleLoginSuccess,
24      cancel_on_tap_outside: false,
25      use_fedcm_for_prompt: true,
26    });
27    google.accounts.id.prompt();
28  }
29};
30
31function handleGoogleLoginSuccess(event) {
32  Auth()
33    .loginGoogle(event.credential)
34    .then((token) => {
35      Auth().redirectToDesktop(token);
36    });
37}
38//#endregion
39
40/**
41 * @typedef {Object} IsMobile
42 * @property {boolean} any
43 * @property {{ phone: boolean, tablet: boolean, device: boolean }} android
44 * @property {{ phone: boolean, ipod: boolean, tablet: boolean, device: boolean }} apple
45 */
46
47// general functionality
48
49jQuery(function ($) {
50  "use strict";
51
52  /**
53   * Detect mobile
54   */
55
56  if (isMobile.apple.device) {
57    document.documentElement.classList.add("ios-device");
58  }
59  if (isMobile.android.device) {
60    document.documentElement.classList.add("android-device");
61  }
62
63  (function shoHideGetStarted() {
64    // get started button show/hide
65
66    if ($("#get-started-btn").length > 0) {
67      $("#get-started-btn")
68        .scrollSpy({
69          offsetTop: $(".site-header").outerHeight(),
70          offsetBottom: 0,
71        })
72        .on("scrollSpy:enter", function () {
73          $("body").addClass("hide-get-started");
74        })
75        .on("scrollSpy:exit", function () {
76          $("body").removeClass("hide-get-started");
77        });
78    }
79  })();
80
81  //simple tabs
82  (function simpleTabs() {
83    $(".tabs .tabs-nav").on("click", "li:not(.active)", function (e) {
84      e.preventDefault();
85      var $this = $(this);
86      $this.addClass("active").siblings().removeClass("active");
87      var $pane = $this
88        .closest(".tabs")
89        .find(".tabs-pane")
90        .hide()
91        .removeClass("active")
92        .eq($this.index())
93        .addClass("active");
94      $pane.fadeIn(150);
95    });
96  })();
97
98  //scroll to element function
99  $.fn.scrollView = function () {
100    var siteHeader = ".fixed-header",
101      offset = 0,
102      headerHeight = 0;
103
104    if ($(siteHeader).length > 0) {
105      headerHeight = $(siteHeader).outerHeight();
106    }
107    offset = offset + headerHeight;
108    return this.each(function () {
109      $("html, body").animate(
110        {
111          scrollTop: $(this).offset().top - offset,
112        },
113        600,
114      );
115    });
116  };
117
118  // scroll to element via data scroll-to
119  function scrollToElement(e) {
120    e.preventDefault();
121    var $this = $(this),
122      target = $this.data("scroll-to");
123    $(target).scrollView();
124  }
125
126  $("body").on("click", "[data-scroll-to]", scrollToElement);
127
128  // add classes to body according to page scroll
129  function handleScroll() {
130    $("body").toggleClass("page-top", $(window).scrollTop() == 0);
131    $("body").toggleClass(
132      "show-back-to-top",
133      $(window).scrollTop() / ($(document).height() - $(window).height()) > 0.4,
134    );
135  }
136  $(window).on("load scroll touchmove", function () {
137    handleScroll();
138  });
139
140  if ($().scrollSpy) {
141    var $sidebox = $(".sticky-side-box-container");
142
143    $(".sticky-sidebox-top-trigger-show")
144      .scrollSpy({
145        offsetTop: 0,
146        offsetBottom: 0,
147      })
148      .on("scrollSpy:enter", function () {
149        $sidebox.addClass("show");
150      })
151      .on("scrollSpy:exit", function () {
152        $sidebox.removeClass("show");
153      });
154
155    $(".sticky-sidebox-top-trigger-hide")
156      .scrollSpy({
157        offsetTop: 0,
158        offsetBottom: 0,
159      })
160      .on("scrollSpy:enter", function () {
161        $sidebox.removeClass("show");
162      });
163
164    $(".site-footer")
165      .scrollSpy({
166        offsetTop: 0,
167        offsetBottom: 0,
168      })
169      .on("scrollSpy:enter", function () {
170        $sidebox.removeClass("show");
171      })
172      .on("scrollSpy:exit", function () {
173        $sidebox.addClass("show");
174      });
175  }
176
177  $(".sticky-side-box-container").on("click", ".sticky-side-box-close", function () {
178    $(".sticky-side-box-container").addClass("close");
179  });
180
181  //Main nav
182
183  $(".mobile-nav-btn").on("click", function () {
184    $("body").toggleClass("open-nav").removeClass("open-user-nav open-languages");
185    $(".language-selector .languages-list").slideUp();
186    $(".main-nav .site-menu").slideToggle();
187  });
188
189  $(".language-selector").on("click", ".current-language", function () {
190    $("body").toggleClass("open-languages").removeClass("open-user-nav open-nav");
191    $(".main-nav .site-menu").slideUp();
192    $(".language-selector .languages-list").slideToggle();
193  });
194
195  var $mainNav = $(".main-nav");
196
197  $mainNav.on("click", ".subdrop-arrow", function (e) {
198    e.preventDefault();
199    var $subdropContainer = $(this).closest(".subdrop-container");
200    $subdropContainer.siblings(".subdrop-container").find(".sub-dropdown").slide
200Up();
201    $subdropContainer.toggleClass("open-dropdown").find(".sub-dropdown").slideToggle();
202  });
203  $mainNav.on("click", ".drop-arrow, .dummy-link", function (e) {
204    e.preventDefault();
205    var $dropdownContainer = $(this).closest("li");
206    $dropdownContainer.siblings("li").find(".sub-dropdown").slideUp();
207    $dropdownContainer.toggleClass("open-dropdown").find(".dropdown").slideToggle();
208  });
209
210  //premium plan selectors
211  function updatePlanSelector(plan) {
212    $("#premium-plan-selector").find(".payment-plan").removeClass("-selected").eq(plan).addClass("-selected");
213  }
214
215  (function planSelector() {
216    $("#premium-plan-selector").on("click", ".payment-plan", function () {
217      var $this = $(this),
218        plan = $this.data("plan");
219      updatePlanSelector(plan);
220    });
221
222    $("#premium-plans-list")
223      .on("click", ".button", function () {
224        var plan = $(this).data("plan");
225        updatePlanSelector(plan);
226      })
227      .on("click", ".payment-plan", function () {
228        var $this = $(this),
229          plan = $this.data("plan");
230        $this.closest(".plan-selector").find(".payment-plan").removeClass("-selected").eq(plan).addClass("-selected");
231        updatePlanSelector(plan);
232      });
233  })();
234
235  // sideboxregister-form
236
237  $(".sideboxregister-form").on("submit", function (e) {
238    e.preventDefault();
239    var email = $(this).find("input[type='text']").val();
240    console.log(email);
241    location.href = "https://app.any.do/?website_registration_flow=team&website_registration_email=" + email;
242  });
243
244  if ($().slick) {
245    $(".teams-slider").slick({
246      autoplay: true,
247      slidesToShow: 1,
248      slidesToScroll: 1,
249      swipeToSlide: true,
250      dots: true,
251      arrows: false,
252      infinite: true,
253      focusOnSelect: false,
254      variableWidth: false,
255      adaptiveHeight: false,
256    });
257
258    $(".quotes-slider").slick({
259      autoplay: true,
260      autoplaySpeed: 500000,
261      slidesToShow: 1,
262      slidesToScroll: 1,
263      swipeToSlide: true,
264      dots: false,
265      arrows: true,
266      infinite: true,
267      focusOnSelect: false,
268      variableWidth: false,
269      adaptiveHeight: false,
270    });
271
272    $(".marketing-carousel").slick({
273      autoplay: true,
274      autoplaySpeed: 5000,
275      slidesToShow: 5,
276      slidesToScroll: 5,
277      swipeToSlide: true,
278      dots: false,
279      arrows: true,
280      infinite: true,
281      focusOnSelect: false,
282      variableWidth: false,
283      adaptiveHeight: false,
284      responsive: [
285        {
286          breakpoint: 1920,
287          settings: {
288            slidesToShow: 4,
289            slidesToScroll: 4,
290          },
291        },
292        {
293          breakpoint: 1700,
294          settings: {
295            slidesToShow: 3,
296            slidesToScroll: 3,
297          },
298        },
299        {
300          breakpoint: 1025,
301          settings: {
302            slidesToShow: 1,
303            slidesToScroll: 1,
304            centerMode: true,
305            centerPadding: "25%",
306          },
307        },
308        {
309          breakpoint: 768,
310          settings: {
311            slidesToShow: 1,
312            slidesToScroll: 1,
313          },
314        },
315      ],
316    });
317
318    $(".live-feed-slider").slick({
319      autoplay: true,
320      vertical: true,
321      pauseOnFocus: false,
322      autoplaySpeed: 3000,
323      slidesToShow: 3,
324      slidesToScroll: 1,
325      swipe: false,
326      swipeToSlide: false,
327      dots: false,
328      arrows: false,
329      infinite: true,
330      focusOnSelect: false,
331      variableWidth: false,
332      adaptiveHeight: true,
333    });
334  } //end if slick
335
336  (function playTeamsVideos() {
337    // trigger play on teams page videos on page scroll
338
339    if ($(".block-video-container").length > 0) {
340      $(".block-video-container")
341        .scrollSpy({
342          offsetTop: $(".site-header").outerHeight(),
343          offsetBottom: 0,
344        })
345        .on("scrollSpy:enter", function () {
346          var $video = $(this).find("video");
347          $video.prop("loop");
348          $video.get(0).play();
349        });
350    }
351  })();
352
353  (function teamsGetStarted() {
354    // trigger play on teams page videos on page scroll
355
356    if ($(".teams-get-started-button").length > 0) {
357      $(".teams-get-started-button").on("click", function () {
358        var $teamsForm = $("#teams-signup-form");
359        $teamsForm.scrollView($(".site-header").outerHeight() + 30);
360        $teamsForm.find("input").focus();
361      });
362    }
363  })();
364
365  // faq
366
367  $(".faq").on("click", ".faq-title", function () {
368    $(this).toggleClass("open").next(".faq-description").slideToggle();
369  });
370
371  // contact sales form submit
372
373  $("#contact-sales-form").submit(function (e) {
374    e.preventDefault();
375
376    const SERVER_DOMAIN = __DEV__ ? "sm-dev-teams.any.do" : "sm-prod4.any.do";
377
378    var formData = {
379      firstname: $("#first-name").val(),
380      lastname: $("#last-name").val(),
381      email: $("#email").val(),
382      teamsize: $("#teamsize").val(),
383    };
384
385    return new Promise(function (resolve, reject) {
386      grecaptcha.enterprise.ready(function () {
387        const siteKey = "6Le0784oAAAAAKlcpM4NH00ZAnGJSrra5Cd-RTQc";
388
389        return grecaptcha.enterprise
390          .execute(siteKey, { action: "submit_contact_sales_form" })
391          .then(function (token) {
392            formData["captcha_method"] = "google_recaptcha";
393            formData["captcha_params"] = {
394              token,
395              site_key: siteKey,
396              action: "submit_contact_sales_form",
397            };
398            resolve(
399              $.ajax({
400                type: "POST",
401                url: "https://" + SERVER_DOMAIN + "/submit_contact_sales_form",
402                contentType: "application/json",
403                data: JSON.stringify(formData),
404                encode: true,
405                success: function (data) {
406                  console.log(data);
407                  $("#contacSalesModal").modal("toggle");
408                },
409              }).fail(function (jqxhr, textStatus, error) {
410                var err = textStatus + ", " + error;
411                console.log("Failed: " + err);
412              }),
413            );
414          })
415          .catch(reject);
416      });
417    });
418  });
419
420  $.ajax({
421    url: "https://sm-dev-teams.any.do/api/board_templates",
422    type: "GET",
423    //contentType: "application/json; charset=utf-8",
424    dataType: "json",
425    data: "PROJECT_MANAGMENT",
426    success: function (data) {
427      console.log(data);
428    },
429  }).fail(function (jqxhr, textStatus, error) {
430    var err = textStatus + ", " + error;
431    console.log("Failed: " + err);
432  });
433
434  /**
435   * Open todo tracking
436   */
437
438  Array.prototype.slice.apply(document.querySelectorAll("[data-open-todo]")).forEac
438h(
439    /** @param {HTMLAnchorElement} $e */
440    function ($e) {
441      var eventValue = Number($e.dataset.openTodo);
442      if (isNaN(eventValue)) return;
443
444      $e.addEventListener(
445        "click",
446        function onClick(event) {
447          event.preventDefault();
448          $e.removeEventListener("click", onClick);
449
450          if (typeof ga !== "undefined") {
451            ga("send", {
452              hitType: "event",
453              eventCategory: "Open todo",
454              eventAction: "click",
455              eventLabel: $e.href,
456              eventValue: eventValue,
457              transport: "beacon",
458            });
459          }
460
461          setTimeout(function () {
462            $e.click();
463          }, 300);
464        },
465        false,
466      );
467    },
468  );
469
470  /** handle open todo click */
471  Array.prototype.slice.apply(document.querySelectorAll(".js-open-todo")).forEach(
472    /** @param {HTMLAnchorElement} $e */
473    function ($e) {
474      $e.addEventListener("click", function (e) {
475        e.preventDefault();
476        Auth().redirectToDesktop();
477      });
478    },
479  );
480
481  /** handle open checkout click */
482  Array.prototype.slice.apply(document.querySelectorAll(".js-open-checkout")).forEach(
483    /** @param {HTMLAnchorElement} $e */
484    function ($e) {
485      $e.addEventListener("click", function (e) {
486        Auth().redirectToCheckout();
487      });
488    },
489  );
490
491  /**
492   * Sticky header
493   */
494  /*
495  var $header = $("header.site-header");
496  var $headerSticky = $header.clone();
497
498  $headerSticky
499    .attr("tabindex", "-1")
500    .css({
501      opacity: 0,
502      visibility: "hidden"
503    })
504    .find("#mobile-nav-btn")
505    .removeAttr("id");
506
507  $header.css({
508    position: "fixed",
509    top: 0,
510    left: 0,
511    width: "100%",
512    background: "white"
513  });
514
515  $headerSticky.insertAfter($header);
516
517*/
518
519  //#region Teams
520  function simpleTabs() {
521    $(".js-tab-underline").css("width", $(".js-active-tab .js-tab-link").outerWidth());
522
523    $(".tabs .tabs-nav").on("click", "li:not(.js-active-tab)", function (e) {
524      e.preventDefault();
525      var $this = $(this),
526        position = $this.position(),
527        newIndex = $this.index(),
528        oldIndex = $this.closest(".tabs-nav").find(".js-active-tab").index(),
529        animateClass = newIndex < oldIndex ? "animate-from-right" : "animate-from-left";
530
531      $(".js-tab-underline").css({ left: position.left, width: $(this).outerWidth() });
532
533      $this.addClass("js-active-tab").siblings().removeClass("js-active-tab");
534      var $pane = $this
535        .closest(".tabs")
536        .find(".tabs-pane")
537        .removeClass("js-active-pane animate-from-left animate-from-right")
538        .eq($this.index() - 1)
539        .addClass(animateClass);
540
541      setTimeout(function () {
542        $pane.addClass("js-active-pane");
543      }, 10);
544    });
545  }
546
547  /**
548   * @param {Array.<string>} keys
549   * @returns {*}
550   */
551  function createEnum(keys) {
552    var ENUM = {};
553
554    for (var i = 0; i < keys.length; i++) {
555      var key = keys[i];
556      var value = i + 1;
557
558      ENUM[key] = value;
559      ENUM[value] = key;
560    }
561
562    return Object.freeze(ENUM);
563  }
564
565  /**
566   * @param {HTMLDivElement} $container
567   */
568  function initTeamForm($container) {
569    var STATE = createEnum([
570      //
571      "LOCK",
572      "CHECK_EMAIL",
573      "CHECK_EMAIL_LOADING",
574      "JOIN",
575      "JOIN_LOADING",
576      "LOGIN",
577      "LOGIN_LOADING",
578    ]);
579
580    var auth = Auth();
581    /** @type {HTMLInputElement} */
582    var $tfEmail = $container.querySelector("input[name='email']");
583    /** @type {HTMLInputElement} */
584    var $tfPasswordLogin = $container.querySelector("input[name='password_login']");
585    /** @type {HTMLInputElement} */
586    var $tfPasswordJoin = $container.querySelector("input[name='password_join']");
587    /** @type {HTMLButtonElement} */
588    var $emailButton = $container.querySelector(".button-spinner");
589    /** @type {HTMLFormElement} */
590    var $loginForm = $container.querySelector(".teams-register-pass");
591    /** @type {HTMLFormElement} */
592    var $joinForm = $container.querySelector(".teams-create-account");
593
594    var state = STATE.CHECK_EMAIL;
595
596    Array.prototype.slice.apply($container.querySelectorAll("form")).forEac
596h(function ($form) {
597      $form.addEventListener("submit", function (event) {
598        event.preventDefault();
599        render();
600      });
601    });
602
603    render();
604
605    function render() {
606      var email = $tfEmail.value;
607      var passwordLogin = $tfPasswordLogin.value;
608      var passwordJoin = $tfPasswordJoin.value;
609
610      switch (state) {
611        case STATE.LOCK:
612          break;
613
614        case STATE.CHECK_EMAIL:
615          state = STATE.CHECK_EMAIL_LOADING;
616          $tfEmail.removeAttribute("disabled");
617          $emailButton.classList.remove("running");
618          $emailButton.removeAttribute("disabled");
619          break;
620
621        case STATE.CHECK_EMAIL_LOADING:
622          state = STATE.LOCK;
623          $tfEmail.setAttribute("disabled", "disabled");
624          $emailButton.classList.add("running");
625          $emailButton.setAttribute("disabled", "disabled");
626          auth.checkEmail(email).then(
627            function (isExisted) {
628              var $el = isExisted ? $loginForm : $joinForm;
629
630              if (!isMobile.any) $($el).slideDown();
631
632              setTimeout(function () {
633                state = isExisted ? STATE.LOGIN : STATE.JOIN;
634
635                $($el).show();
636                $emailButton.classList.add("form-success");
637                $el.querySelector("input").focus();
638
639                render();
640              }, 300);
641            },
642            function () {
643              alert("There is a connectivity issue. Please check if you're network");
644            },
645          );
646          break;
647
648        case STATE.JOIN:
649          state = STATE.JOIN_LOADING;
650          break;
651
652        case STATE.LOGIN:
653          state = STATE.LOGIN_LOADING;
654          break;
655
656        case STATE.JOIN_LOADING:
657          state = STATE.LOCK;
658          $joinForm.querySelector("input").readOnly = true;
659          $joinForm.querySelector("button").classList.add("running");
660
661          auth
662            .join(email, getUserNameFromEmail(email), passwordJoin)
663            .then(function (token) {
664              auth.redirectToTeams(token);
665            })
666            .catch(function (error) {
667              state = STATE.JOIN;
668              // eslint-disable-next-line no-console
669              console.error(error);
670              alert("Something went wrong");
671
672              $joinForm.querySelector("input").readOnly = false;
673              $joinForm.querySelector("button").classList.remove("running");
674            });
675          break;
676
677        case STATE.LOGIN_LOADING:
678          state = STATE.LOCK;
679          $loginForm.querySelector("input").readOnly = true;
680          $loginForm.querySelector("button").classList.add("running");
681
682          auth
683            .login(email, passwordLogin)
684            .then(function (token) {
685              auth.redirectToTeams(token);
686            })
687            .catch(function (error) {
688              state = STATE.LOGIN;
689              // eslint-disable-next-line no-console
690              console.error(error);
691              alert("Password is incorrect");
692
693              $loginForm.querySelector("input").readOnly = false;
694              $loginForm.querySelector("button").classList.remove("running");
695            });
696          break;
697      }
698    }
699
700    /**
701     * @param {string} email
702     */
703    function getUserNameFromEmail(email) {
704      var name = email.split("@")[0];
705
706      return name
707        .replace(/[+-.]/g, " ")
708        .toLowerCase()
709        .replace(/\b([a-z])/g, (m) => m.toUpperCase());
710    }
711  }
712
713  function initTeamsForm() {
714    var formsContainers = Array.prototype.slice.apply(document.querySelectorAll(".js-teams"));
715
716    formsContainers.forEach(initTeamForm);
717  }
718
719  $(function () {
720    if (document.querySelector(".js-teams")) {
721      initTeamsForm();
722    }
723  });
724  simpleTabs();
725
726  //#endregion
727
728  // #region assistant-lp
729  function initAssistantLp($container) {
730    var stripeHelper = new StripeHelper();
731    stripeHelper.initStripeSingleInput(stripeHelper.ASSISTANT_STRIPE_KEY);
732    var assistantSubscriptionHelper = new AssistantSubscriptionHelper();
733
734    var $paymentForm = $container.querySelector("#assistant-form");
735    var $errorContainer = $container.querySelector(".error");
736    var $paymentSubmit = $paymentForm.querySelector(".button.submit");
737
738    // set up show + hide of checkout popup
739    [...$container.querySelectorAll(".js-cta")].forEach((e) => {
740      e.addEventListener("click", showCheckout);
741    });
742
743    [...$container.querySelectorAll(".js-hide-checkout")].forEach((e) => {
744      e.addEventListener("click", hideCheckout);
745    });
746
747    // switch between sign in / sign up in checkout
748    $container.querySelector(".js-switch-to-signup").addEventListener("click", switchToSignUp);
749    $container.querySelector(".js-switch-to-signin").addEventListener("click", switchToSignIn);
750
751    // plan selection radio button UI set up
752    $(".js-plan-selector").click(() => {
753      $(".js-plan-selector").each((i, e) => {
754        if (e.checked) {
755          e.closest(".assistant-plan-option").classList.add("selected");
756        } else {
757          e.closest(".assistant-plan-option").classList.remove("selected");
758        }
759      });
760    });
761
762    // checkout submit
763    $paymentForm.addEventListener("submit", function (event) {
764      event.preventDefault();
765
766      enterButtonLoading($paymentSubmit);
767
768      var isSignUpMode = $paymentForm.classList.contains("signup");
769
770      var formData = new FormData($paymentForm);
771      var email = formData.get("email");
772      var password = formData.get("password");
773      var selectedPlan = formData.get("assistant-plan");
774
775      Auth()
776        .checkEmail(email)
777        .then(function (doesUserExist) {
778          if (isAuthStateMismatch(doesUserExist, isSignUpMode, $errorContainer)) {
779            exitButtonLoading($paymentSubmit);
780            return;
781          }
782          stripeHelper.stripe.createToken(stripeHelper.card).then(function (stripeResponse) {
783            if (stripeResponse.error || !stripeResponse.token) {
784              $errorContainer.textContent = stripeResponse.error && stripeResponse.error.message;
785              exitButtonLoading($paymentSubmit);
786              return;
787            }
788
789            var authPromise = isSignUpMode
790              ? Auth().join(
791                  email,
792                  formData.get("name"),
793                  password,
794                  assistantSubscriptionHelper.ASSISTANT_APPLICATION_NAME,
795                )
796              : Auth().login(email, password);
797
798            authPromise.then(subscribeToAssistant.bind(null, stripeResponse.token.id)).catch(handleSignUpError);
799          });
800        });
801
802      function subscribeToAssistant(stripeToken) {
803        assistantSubscriptionHelper
804          .createCustomer(stripeToken)
805          .then(() => {
806            assistantSubscriptionHelper
807              .subscribe(selectedPlan)
808              .then(redirectToAssistantForm)
809              .catch(handleAssistantSubscribeError);
810          })
811          .catch(handleAssistantSubscribeError);
812      }
813
814      function handleAssistantSubscribeError(errorMessage) {
815        console.error(errorMessage);
816        exitButtonLoading($paymentSubmit);
817        switchToSignIn();
818        $errorContainer.textContent = errorMessage || "An error has occurred";
819      }
820
821      function redirectToAssistantForm() {
822        location.href = "https://anydo.typeform.com/to/dZBHwSX5#puid=" + encodeURIComponent(Auth().getUserGlobalId());
823      }
824    });
825
826    function switchToSignUp() {
827      $paymentForm.classList.add("signup");
828      $paymentForm.classList.remove("signin");
829      $errorContainer.textContent = "";
830    }
831
832    function switchToSignIn() {
833      $paymentForm.classList.add("signin");
834      $paymentForm.classList.remove("signup");
835      $errorContainer.textContent = "";
836    }
837
838    function hideCheckout() {
839      $container.querySelector("#checkout").classList.add("hidden");
840      $container.querySelector("#overlay").classList.add("hidden");
841      $container.querySelector("#info").classList.remove("hide-on-mobile");
842    }
843
844    function showCheckout() {
845      $container.querySelector("#checkout").classList.remove("hidden");
846      $container.querySelector("#overlay").classList.remove("hidden");
847      $container.querySelector("#info").classList.add("hide-on-mobile");
848    }
849  }
850
851  $(function () {
852    var $formContainer = document.querySelector(".js-assistant-lp");
853    if ($formContainer) {
854      initAssistantLp($formContainer);
855    }
856  });
857
858  function AssistantSubscriptionHelper() {
859    this.ASSISTANT_APPLICATION_NAME = "assistant";
860    this._SERVER_DOMAIN = __DEV__ ? "sm-dev2.any.do" : "sm-prod2.any.do";
861    this._ASSISTANT_CUSTOMER_URI = "https://" + this._SERVER_DOMAIN + "/me/assistant/create_customer";
862    this._ASSISTANT_SUB_URI = "https://" + this._SERVER_DOMAIN + "/me/assistant/packages/subscribe";
863  }
864
865  AssistantSubscriptionHelper.prototype.createCustomer = function createCustomer(stripeToken) {
866    return fetch(this._ASSISTANT_CUSTOMER_URI, {
867      method: "POST",
868      headers: Auth().createHeaders(true),
869      body: JSON.stringify({
870        token: stripeToken,
871        sendEmail: true,
872      }),
873    }).then(function (response) {
874      return Promise.resolve(response);
875    });
876  };
877
878  AssistantSubscriptionHelper.prototype.subscribe = function subscribe(stripePlan) {
879    return fetch(this._ASSISTANT_SUB_URI, {
880      method: "POST",
881      headers: Auth().createHeaders(true),
882      body: JSON.stringify({
883        plan: stripePlan,
884      }),
885    }).then(function (response) {
886      return response.ok
887        ? Promise.resolve()
888        : Promise.reject(
889            new Error(`AssistantSubscriptionHelper.subscribe(): Any.do server status "${response.status}"`),
890          );
891    });
892  };
893
894  // #endregion assistant-lp
895
896  // #region checkout
897  function handleCheckoutSubmit(event, stripeHelper, subscribeFunction, login_app_name) {
898    event.preventDefault();
899
900    var $paymentForm = event.target;
901    var $paymentSubmit = $paymentForm.querySelector(".button.submit");
902    var $errorContainer = $paymentForm.querySelector(".error");
903
904    enterButtonLoading($paymentSubmit);
905
906    var isSignUpMode = $paymentForm.classList.contains("signup");
907
908    var formData = new FormData($paymentForm);
909    var email = formData.get("email");
910    var password = formData.get("password");
911    var selectedPlan = $paymentForm.querySelector(".payment-plan.-selected").getAttribute("data-plan-id");
912
913    stripeHelper.stripe.createToken(stripeHelper.card).then(function (stripeResponse) {
914      if (stripeResponse.error || !stripeResponse.token) {
915        $errorContainer.textContent = stripeResponse.error && stripeResponse.error.message;
916        exitButtonLoading($paymentSubmit);
917        return;
918      }
919      if (Auth().getAuthToken()) {
920        subscribeFunction(stripeResponse, selectedPlan, $paymentForm);
921      } else {
922        Auth()
923          .checkEmail(email)
924          .then(function (doesUserExist) {
925            if (isAuthStateMismatch(doesUserExist, isSignUpMode, $errorContainer)) {
926              exitButtonLoading($paymentSubmit);
927              return;
928            }
929
930            var authPromise = isSignUpMode
931              ? Auth().join(email, formData.get("name"), password, login_app_name)
932              : Auth().login(email, password);
933
934            authPromise
935              .then(subscribeFunction.bind(null, stripeResponse, selectedPlan, $paymentForm))
936              .catch(handleSignUpError.bind(null, $paymentForm));
937          });
938      }
939    });
940  }
941
942  function handleSignUpError($form, errorMessage) {
943    console.error(errorMessage);
944    var $submitButton = $form.querySelector(".button.submit");
945    var $errorContainer = $form.querySelector(".error");
946    var isSignUpMode = $form.classList.contains("signup");
947
948    exitButtonLoading($submitButton);
949    $errorContainer.textContent = isSignUpMode ? "Something went wrong." : "Password is incorrect";
950  }
951
952  function switchToSignUp($form) {
953    $form.classList.add("signup");
954    $form.classList.remove("signin");
955    $form.querySelector(".error").textContent = "";
956  }
957
958  function switchToSignIn($form) {
959    $form.classList.add("signin");
960    $form.classList.remove("signup");
961    $form.querySelector(".error").textContent = "";
962  }
963  // #endregion checkout
964
965  // #region premium
966  function subscribeToPremium(stripeResponse, plan, $paymentForm) {
967    var $submitButton = $paymentForm.querySelector(".button.submit");
968    // check for active subscription
969    Auth()
970      .getPremiumData()
971      .then(function (premiumData) {
972        if (premiumData) {
973          alert("You are already subscribed to Any.do Premium! press OK to go to Any.do.");
974          Auth().redirectToDesktop();
975        } else {
976          Auth()
977            .subscribeToPremium(stripeResponse, plan)
978            .then(function () {
979              $submitButton.classList.remove("running");
980              $submitButton.textContent = "Success! Sending you to Any.do...";
981              setTimeout(function () {
982                Auth().redirectToDesktop(Auth().getAuthToken());
983              }, 3000);
984            })
985            .catch(function (message) {
986              exitButtonLoading($submitButton);
987              $paymentForm.querySelector(".error").textContent = message || "An error occurred";
988            });
989        }
990      })
991      .catch(function (message) {
992        exitButtonLoading($submitButton);
993        $paymentForm.querySelector(".error").textContent = message || "An error occurred";
994      });
995  }
996
997  async function initPremiumCheckoutModal($checkoutContainer) {
998    var access_token = null;
999    var google_login_success = null;
1000    var fb_login_success = null;
1001
1002    new URLSearchParams(location.search).forEach(function (value, key) {
1003      if (key == "google-login-result" && value == "1") {
1004        google_login_success = true;
1005      }
1006      if (key == "fb-login-result" && value == "1") {
1007        fb_login_success = true;
1008      }
1009    });
1010
1011    new URLSearchParams(location.hash.slice(1)).forEach(function (value, key) {
1012      if (key == "access_token") {
1013        access_token = value;
1014      }
1015    });
1016
1017    if (Auth().getAuthToken() || google_login_success || fb_login_success || (await authPromise)) {
1018      $('#go-premium input[type="email"]').removeAttr("required");
1019      $('#go-premium input[type="password"]').removeAttr("required");
1020      $("#go-premium .hide-if-logged-in").hide();
1021      $("#go-premium").modal("show");
1022    }
1023
1024    if (google_login_success && access_token) {
1025      await Auth().loginGoogle(null, access_token);
1026    }
1027    if (fb_login_success && access_token) {
1028      await Auth().loginFacebook(access_token);
1029    }
1030
1031    $(".js-google-button").on("click", () => Auth().redirectLoginWithGoogle());
1032    $(".login .button.facebook").on("click", () => Auth().redirectLoginWithFacebook());
1033
1034    var stripeHelper = new StripeHelper();
1035    stripeHelper.initStripeSingleInput(stripeHelper.ANYDO_STRIPE_KEY);
1036    var $paymentForm = $checkoutContainer.querySelector(".js-payment-form");
1037
1038    // switch between sign in / sign up in checkout
1039    $checkoutContainer
1040      .querySelector(".js-switch-to-signup")
1041      .addEventListener("click", () => switchToSignUp($paymentForm));
1042    $checkoutContainer
1043      .querySelector(".js-switch-to-signin")
1044      .addEventListener("click", () => switchToSignIn($paymentForm));
1045
1046    $("#go-premium").on("hide.bs.modal", () => $("#go-premium").removeClass("hide-unselected-plans"));
1047
1048    $paymentForm.addEventListener("submit", function () {
1049      handleCheckoutSubmit(event, stripeHelper, subscribeToPremium, "web");
1050    });
1051  }
1052
1053  $(function () {
1054    var $checkoutContainer = document.querySelector(".js-go-premium");
1055    if ($checkoutContainer) {
1056      initPremiumCheckoutModal($checkoutContainer);
1057    }
1058  });
1059  // #endregion premium
1060
1061  //#region Teams Checkout
1062  function Business() {
1063    this._SERVER_DOMAIN = __DEV__ ? "sm-dev-teams.any.do" : "sm-prod4.any.do";
1064    this._BUSINESS_URI = "https://" + this._SERVER_DOMAIN + "/business";
1065  }
1066
1067  Business.prototype.createBusiness = function createBusiness(options) {
1068    var stripeHelper = options.stripeHelper;
1069    var stripePlan = options.stripePlan;
1070    var businessName = options.businessName;
1071    var members = options.members;
1072    var self = this;
1073
1074    return stripeHelper.stripe
1075      .createToken(stripeHelper.cardNumber)
1076      .then(function (stripeResponse) {
1077        if (stripeResponse.error || !stripeResponse.token) {
1078          return Promise.reject(stripeResponse.error && stripeResponse.error.message);
1079        }
1080
1081        return fetch(self._BUSINESS_URI, {
1082          method: "POST",
1083          headers: Auth().createHeaders(true),
1084          body: JSON.stringify({
1085            name: businessName,
1086            members,
1087            is_trial: true,
1088            provider_data: stripeResponse.token,
1089            stripe_plan: stripePlan,
1090            currency: "usd",
1091            payment_provider: "Stripe",
1092          }),
1093        });
1094      })
1095      .then(function (response) {
1096        if (response.ok) {
1097          return Auth().getPremiumData();
1098        }
1099
1100        return Promise.reject(new Error(`createBusiness(): Any.do server status "${response.status}"`));
1101      })
1102      .then(function (data) {
1103        return {
1104          businessId: data.businessId,
1105          members,
1106        };
1107      });
1108  };
1109
1110  Business.prototype.updateBusiness = async function updateBusiness(businessId, businessName, members) {
1111    var response = await fetch(this._BUSINESS_URI + "/" + businessId, {
1112      method: "PUT",
1113      headers: Auth().createHeaders(true),
1114      body: JSON.stringify({
1115        name: businessName,
1116        members,
1117      }),
1118    });
1119    if (!response.ok) {
1120      throw new Error(`updateBusiness(): Any.do server status "${response.status}"`);
1121    } else {
1122      return {
1123        businessId,
1124        members,
1125      };
1126    }
1127  };
1128
1129  Business.prototype.getBusinessData = async function getBusinessData(businessId) {
1130    var response = await fetch(this._BUSINESS_URI + "/" + businessId, {
1131      method: "GET",
1132      headers: Auth().createHeaders(true),
1133    });
1134    if (response.ok) {
1135      var data = await response.json();
1136      return data;
1137    } else {
1138      throw new Error(`getBusinessData(): Any.do server status "${response.status}"`);
1139    }
1140  };
1141
1142  Business.prototype.buildMembersObject = function buildMembersObject(adminEmail, membersEmails) {
1143    return membersEmails
1144      .map(function (email) {
1145        return {
1146          email,
1147          is_admin: false,
1148        };
1149      })
1150      .concat({
1151        email: adminEmail,
1152        is_admin: true,
1153      });
1154  };
1155
1156  function StripeHelper() {
1157    this.ANYDO_STRIPE_KEY = __DEV__ ? "pk_test_ibci0S0N18vCEROPRoY786nk" : "pk_live_PpUdKgmIH1mJ388r4uOIqJPb";
1158    this.ASSISTANT_STRIPE_KEY = __DEV__ ? "pk_test_crYdbmMDSFbqzEOgD4wAQDCN" : "pk_live_MDMT2N9MUCxGKoamBrmA79BT";
1159    this.STRIPE_TEAMS_PLAN = "anydo_teams_yearly";
1160    this.TEAMS_PLAN_USD_PRICE_IN_CENTS = 5999;
1161    this.TEAMS_PLAN_USD_PRICE_MONTHLY_IN_CENTS = 499;
1162    this.TEAMS_FREE_TRIAL_LENGTH = 14;
1163    this.stripeLoadingPromise = undefined;
1164  }
1165
1166  StripeHelper.prototype.initStripeMultiInput = async function initStripeMultiInput(key) {
1167    this.stripe = Stripe(key);
1168    const elements = this.stripe.elements({
1169      locale: "auto",
1170    });
1171
1172    const elementStyles = {
1173      base: {
1174        fontSize: "16px",
1175
1176        "::placeholder": {
1177          color: "#afafaf",
1178        },
1179        ":-webkit-autofill": {
1180          color: "#e39f48",
1181        },
1182      },
1183      invalid: {
1184        color: "#E25950",
1185
1186        "::placeholder": {
1187          color: "#FFCCA5",
1188        },
1189      },
1190    };
1191
1192    const elementClasses = {
1193      focus: "focused",
1194      empty: "empty",
1195      invalid: "invalid",
1196    };
1197
1198    this.cardNumber = elements.create("cardNumber", {
1199      style: elementStyles,
1200      classes: elementClasses,
1201    });
1202    this.cardNumber.mount("#card-number");
1203    this.cardNumber.on("change", this.handleStripeChange.bind(this));
1204
1205    this.cardExpiry = elements.create("cardExpiry", {
1206      style: elementStyles,
1207      classes: elementClasses,
1208    });
1209    this.cardExpiry.mount("#card-expiry");
1210    this.cardExpiry.on("change", this.handleStripeChange.bind(this));
1211
1212    this.cardCvc = elements.create("cardCvc", {
1213      style: elementStyles,
1214      classes: elementClasses,
1215    });
1216    this.cardCvc.mount("#card-cvc");
1217    this.cardCvc.on("change", this.handleStripeChange.bind(this));
1218
1219    this.cardNumber.on("change", (event) => event && event.complete && this.cardExpiry.focus());
1220    this.cardExpiry.on("change", (event) => event && event.complete && this.cardCvc.focus());
1221  };
1222
1223  StripeHelper.prototype.initStripeSingleInput = async function initStripeSingleInput(key) {
1224    this.stripe = Stripe(key);
1225    const elements = this.stripe.elements({
1226      locale: "auto",
1227    });
1228
1229    const elementStyles = {
1230      base: {
1231        "::placeholder": {
1232          color: "#cfcfcf",
1233        },
1234      },
1235      invalid: {
1236        color: "#E25950",
1237
1238        "::placeholder": {
1239          color: "#FFCCA5",
1240        },
1241      },
1242    };
1243
1244    const elementClasses = {
1245      focus: "focused",
1246      empty: "empty",
1247      invalid: "invalid",
1248    };
1249
1250    this.card = elements.create("card", {
1251      style: elementStyles,
1252      classes: elementClasses,
1253    });
1254    this.card.mount("#card");
1255    this.card.on("change", this.handleStripeChange.bind(this));
1256  };
1257
1258  StripeHelper.prototype.handleStripeChange = function handleStripeChange(event) {
1259    let $errorContainer = document.querySelector(".stripe-error");
1260
1261    if (event && event.error) {
1262      $errorContainer.textContent = event.error.message || "";
1263    } else {
1264      $errorContainer.textContent = "";
1265    }
1266  };
1267
1268  /**
1269   * @param {HTMLDivElement} $container
1270   * @package {String} flowExprGroup - group name of the flow experiment
1271   */
1272  async function initTeamsCheckoutForm($container, flowExprGroup) {
1273    var stripeHelper = new StripeHelper();
1274    stripeHelper.initStripeMultiInput(stripeHelper.ANYDO_STRIPE_KEY);
1275    var businessHelper = new Business();
1276
1277    var STATE = createEnum([
1278      "LOCK",
1279      "INIT",
1280      "LOAD_PAYMENT",
1281      "PAY",
1282      "LOAD_MANAGE_TEAM",
1283      "VALIDATE_TEAM_MEMBERS",
1284      "MANAGE_TEAM",
1285      "LOAD_SUCCESS",
1286      "REDIRECT_TO_ANYDO",
1287    ]);
1288
1289    var userEmail = Auth().getUserEmail();
1290    var businessId = null;
1291    var serverMemberCount = null; // for analytics
1292    var premiumData = await Auth().getPremiumData();
1293
1294    /** @type {HTMLInputElement} */
1295    var $tcTeamName = $container.querySelector("input#team-name");
1296
1297    /** @type {HTMLSectionElement} */
1298    var $tcInfoSection = $container.querySelector("section#tc-info");
1299    /** @type {HTMLSectionElement} */
1300    var $tcTeamNameSection = $container.querySelector("section#tc-team-name");
1301    /** @type {HTMLSectionElement} */
1302    var $tcPaymentSection = $container.querySelector("section#tc-payment");
1303    /** @type {HTMLSectionElement} */
1304    var $tcTeamManagementSection = $container.querySelector("section#tc-team-management");
1305    /** @type {HTMLSectionElement} */
1306    var $tcSuccessSection = $container.querySelector("section#tc-success");
1307
1308    /** @type {HTMLElement} */
1309    var $teamManageErrorContainers = $container.querySelectorAll(".js-team-member-form div.error");
1310    /** @type {HTMLElement} */
1311    var $successMessageContainers = $container.querySelectorAll(".js-team-member-form .success");
1312    /** @type {HTMLButtonElement} */
1313    var $teamManageSubmit = $container.querySelectorAll(".js-team-member-form .js-tc-submit");
1314    /** @type {HTMLButtonElement} */
1315    var $tcPaymentSubmit = $tcPaymentSection.querySelector(".js-tc-submit");
1316
1317    setLayoutPerFlowExprGroup(flowExprGroup);
1318    var state = await determineStateByPremiumStatus(premiumData);
1319
1320    [...$container.querySelectorAll(".js-team-member.insert input")].forEac
1320h(function ($field) {
1321      $field.addEventListener("input", handleMemberInputChange);
1322    });
1323
1324    [...$container.querySelectorAll(".js-teams-checkout-form")].forEach(function ($form) {
1325      $form.addEventListener("submit", function (event) {
1326        event.preventDefault();
1327        render();
1328      });
1329    });
1330
1331    render();
1332
1333    function render() {
1334      switch (state) {
1335        case STATE.LOCK:
1336          break;
1337
1338        case STATE.REDIRECT_TO_ANYDO:
1339          window.location.replace("/open-todo");
1340          break;
1341
1342        case STATE.INIT:
1343          state = STATE.LOCK;
1344          loadTeamNameView();
1345          state = flowExprGroup == "invite-first" ? STATE.LOAD_MANAGE_TEAM : STATE.LOAD_PAYMENT;
1346          break;
1347
1348        case STATE.LOAD_PAYMENT:
1349          state = STATE.LOCK;
1350          loadPaymentView();
1351          state = STATE.PAY;
1352          break;
1353
1354        case STATE.PAY:
1355          state = STATE.LOCK;
1356          toggleButtonRunning($tcPaymentSubmit, true);
1357          performBusinessCreate()
1358            .then((responseData) => {
1359              handleCreateBusinessSuccess(responseData);
1360              toggleButtonRunning($tcPaymentSubmit, false);
1361              state = flowExprGroup == "invite-first" ? STATE.LOAD_SUCCESS : STATE.LOAD_MANAGE_TEAM;
1362              render();
1363            })
1364            .catch((message) => {
1365              handleCreateBusinessFailure(message);
1366              toggleButtonRunning($tcPaymentSubmit, false);
1367              state = STATE.PAY;
1368            });
1369          break;
1370
1371        case STATE.LOAD_MANAGE_TEAM:
1372          state = STATE.LOCK;
1373          loadTeamManageView();
1374          state = STATE.VALIDATE_TEAM_MEMBERS;
1375          break;
1376
1377        case STATE.VALIDATE_TEAM_MEMBERS:
1378          state = STATE.LOCK;
1379          setElementsText([...$successMessageContainers, ...$teamManageErrorContainers], "");
1380          if (validateInvitees()) {
1381            state = flowExprGroup == "invite-first" && !businessId ? STATE.LOAD_PAYMENT : STATE.MANAGE_TEAM;
1382            render();
1383          } else {
1384            setElementsText([...$teamManageErrorContainers], "Some of the emails you have entered are invalid");
1385            state = STATE.VALIDATE_TEAM_MEMBERS;
1386          }
1387          break;
1388
1389        case STATE.MANAGE_TEAM:
1390          state = STATE.LOCK;
1391          toggleButtonsRunning([...$teamManageSubmit], true);
1392          performBusinessUpdate()
1393            .then((responseData) => {
1394              toggleButtonsRunning([...$teamManageSubmit], false);
1395              handleBusinessUpdateSuccess(responseData);
1396              state = STATE.VALIDATE_TEAM_MEMBERS;
1397            })
1398            .catch(() => {
1399              toggleButtonsRunning([...$teamManageSubmit], false);
1400              handleBusinessUpdateFailure();
1401              state = STATE.VALIDATE_TEAM_MEMBERS;
1402            });
1403          break;
1404
1405        case STATE.LOAD_SUCCESS:
1406          state = STATE.LOCK;
1407          loadSuccessView();
1408          state = STATE.VALIDATE_TEAM_MEMBERS;
1409          break;
1410      }
1411    }
1412
1413    async function determineStateByPremiumStatus(premiumData) {
1414      if (premiumData && premiumData.serverExpirationDate > Date.now()) {
1415        // user is premium
1416        if (premiumData.activePlan == "BUSINESS") {
1417          // business plan
1418          if (premiumData.isBusinessAdmin) {
1419            // business admin => direct to team management section
1420            await fillBusinessData(premiumData.businessId);
1421            return STATE.LOAD_SUCCESS;
1422          } else {
1423            // business team member => can't use, direct to anydo
1424            alert(
1425              "You are already a member of an existing team. Please contact your admin in order to manage the team.",
1426            );
1427            return STATE.REDIRECT_TO_ANYDO;
1428          }
1429        } else {
1430          // other premium plan => treatment TBD
1431          return STATE.INIT;
1432        }
1433      } else {
1434        // not premium
1435        return STATE.INIT;
1436      }
1437    }
1438
1439    async function fillBusinessData(businessIdArg) {
1440      businessId = businessIdArg;
1441      var businessData = await businessHelper.getBusinessData(businessId);
1442      $tcTeamName.value = businessData.name;
1443      var $teamMemberElement = $container.querySelector("#team-members .js-team-member.insert");
1444      businessData.members.forEach((member) => {
1445        if (!member.is_admin) addTeamMember($teamMemberElement, member.email);
1446      });
1447      serverMemberCount = businessData.members.length;
1448    }
1449
1450    function setLayoutPerFlowExprGroup(group) {
1451      if (group == "invite-first") {
1452        $tcTeamManagementSection.querySelector(".js-tc-submit .button-text").textContent = "Continue";
1453      }
1454    }
1455
1456    function loadTeamNameView() {
1457      $tcTeamNameSection.querySelector(".disclaimer .date").textContent = addToToday(
1458        stripeHelper.TEAMS_FREE_TRIAL_LENGTH,
1459      )
1460        .toDateString()
1461        .substring(4);
1462      showElement($tcTeamNameSection);
1463      hideElement($tcPaymentSection);
1464      hideElement($tcTeamManagementSection);
1465
1466      ga("send", {
1467        hitType: "event",
1468        eventCategory: "teams_checkout",
1469        eventAction: "team_name_step_shown",
1470      });
1471    }
1472
1473    function loadPaymentView() {
1474      showElement($tcPaymentSection);
1475      hideElement($tcTeamNameSection);
1476      hideElement($tcTeamManagementSection);
1477      stripeHelper.cardNumber.focus();
1478
1479      ga("send", {
1480        hitType: "event",
1481        eventCategory: "teams_checkout",
1482        eventAction: "enter_cc_step_shown",
1483      });
1484    }
1485
1486    function loadTeamManageView() {
1487      $tcTeamManagementSection.querySelector(".admin input").value = userEmail + " (That's you)";
1488      $(".hide-team-manage").hide();
1489      hideElement($tcPaymentSection);
1490      showElement($tcTeamNameSection);
1491      showElement($tcTeamManagementSection);
1492
1493      ga("send", {
1494        hitType: "event",
1495        eventCategory: "teams_checkout",
1496        eventAction: "manage_team_step_shown",
1497      });
1498    }
1499
1500    function loadSuccessView() {
1501      $tcSuccessSection.querySelector(".js-instruction").textContent = SUCCESS_DESCRIPTION.replace(
1502        "(#team-name)",
1503        teamNameFromContainer(),
1504      );
1505
1506      hideElement($tcInfoSection);
1507      hideElement($tcPaymentSection);
1508      showElement($tcSuccessSection);
1509    }
1510
1511    function performBusinessCreate() {
1512      var membersObject = businessHelper.buildMembersObject(userEmail, getInviteesFromContainer());
1513      return businessHelper.createBusiness({
1514        stripeHelper,
1515        stripePlan: stripeHelper.STRIPE_TEAMS_PLAN,
1516        businessName: teamNameFromContainer(),
1517        members: membersObject,
1518      });
1519    }
1520
1521    function performBusinessUpdate() {
1522      var membersObject = businessHelper.buildMembersObject(userEmail, getInviteesFromContainer());
1523      return businessHelper.updateBusiness(businessId, teamNameFromContainer(), membersObject);
1524    }
1525
1526    function handleCreateBusinessSuccess(businessData) {
1527      businessId = businessData.businessId;
1528      serverMemberCount = businessData.members.length;
1529      var purchaseValueInCents = serverMemberCount * stripeHelper.TEAMS_PLAN_USD_PRICE_IN_CENTS;
1530
1531      ga("send", {
1532        hitType: "event",
1533        eventCategory: "teams_checkout",
1534        eventAction: "purchase",
1535        eventValue: purchaseValueInCents,
1536      });
1537
1538      if (!__DEV__) {
1539        fbq("track", "StartTrial", {
1540          currency: "USD",
1541          value: purchaseValueInCents / 100,
1542        });
1543      }
1544    }
1545
1546    function handleCreateBusinessFailure(errorMessage) {
1547      $tcPaymentSection.querySelector(".error").textContent = errorMessage || "An error occurred";
1548
1549      ga("send", {
1550        hitType: "event",
1551        eventCategory: "teams_checkout",
1552        eventAction: "purchase_error",
1553        eventLabel: errorMessage || "unknown error",
1554      });
1555    }
1556
1557    function handleBusinessUpdateSuccess(businessData) {
1558      setElementsText([...$successMessageContainers], "Invitations have been sent to your team!");
1559
1560      var updatedMemberCount = businessData.members.length;
1561
1562      // report to ga
1563      ga("send", {
1564        hitType: "event",
1565        eventCategory: "teams_checkout",
1566        eventAction: "teams_plan_updated",
1567        eventValue: Math.max(updatedMemberCount - serverMemberCount, 0) * stripeHelper.TEAMS_PLAN_USD_PRICE_IN_CENTS,
1568      }); // eventValue must be non-negative. negative value events will be reported with value = 0 for lack of a better option
1569
1570      if (updatedMemberCount != serverMemberCount) {
1571        var manageEvent =
1572          "team_manage_" + (updatedMemberCount < serverMemberCount ? "removed_members" : "added_members");
1573        ga("send", {
1574          hitType: "event",
1575          eventCategory: "teams_checkout",
1576          eventAction: manageEvent,
1577          eventValue: Math.abs(serverMemberCount - updatedMemberCount),
1578        });
1579      }
1580
1581      // report to fb
1582      if (!__DEV__ && updatedMemberCount > serverMemberCount) {
1583        // no concept of "negative conversions" exists on fb
1584        fbq("track", "StartTrial", {
1585          currency: "USD",
1586          value: ((updatedMemberCount - serverMemberCount) * stripeHelper.TEAMS_PLAN_USD_PRICE_IN_CENTS) / 100,
1587        });
1588      }
1589
1590      serverMemberCount = updatedMemberCount;
1591    }
1592
1593    function handleBusinessUpdateFailure() {
1594      setElementsText([...$teamManageErrorContainers], "An error has occurred");
1595      toggleButtonsRunning([...$teamManageSubmit], false);
1596
1597      ga("send", {
1598        hitType: "event",
1599        eventCategory: "teams_checkout",
1600        eventAction: "teams_plan_update_failed",
1601      });
1602    }
1603
1604    function tmUpdateTotalPricePerYear() {
1605      var memberCount = getTeamMembersFromContainer().length;
1606      var total_price_per_year = (memberCount * stripeHelper.TEAMS_PLAN_USD_PRICE_IN_CENTS) / 100;
1607      $tcTeamManagementSection.querySelector(".plan-details").textContent = TM_PAYMENT_TOTAL.replace(
1608        "(#yearly)",
1609        total_price_per_year,
1610      ).replace("(#monthly)", stripeHelper.TEAMS_PLAN_USD_PRICE_MONTHLY_IN_CENTS / 100);
1611    }
1612
1613    function handleMemberInputChange(event) {
1614      var $targetTeamMember = event.target.closest(".js-team-member");
1615      if ($targetTeamMember.classList.contains("insert") && event.target.value) {
1616        var $newMember = $targetTeamMember.cloneNode(true);
1617        var $newMemberInput = $newMember.querySelector("input");
1618        $newMemberInput.value = "";
1619        $newMemberInput.addEventListener("input", handleMemberInputChange);
1620
1621        $targetTeamMember.classList.remove("insert");
1622        $targetTeamMember.classList.add("invitee");
1623        $targetTeamMember.querySelector("input").addEventListener("blur", (e) => {
1624          if (!e.target.value) {
1625            e.target.closest(".js-team-member").remove();
1626            tmUpdateTotalPricePerYear();
1627          }
1628        });
1629        $targetTeamMember.querySelector("button").addEventListener("click", (e) => {
1630          e.target.closest(".js-team-member").remove();
1631          tmUpdateTotalPricePerYear();
1632        });
1633        $targetTeamMember.onkeypress = (event) => {
1634          if (event.keyCode == 13) {
1635            event.preventDefault;
1636            event.target.closest("ul.js-team-member-list").querySelector(".insert input").focus();
1637            return false;
1638          }
1639        };
1640
1641        $targetTeamMember.closest("ul.js-team-member-list").appendChild($newMember);
1642        tmUpdateTotalPricePerYear();
1643        $targetTeamMember.closest("form").querySelector(".js-tc-submit").removeAttribute("disabled");
1644      }
1645    }
1646
1647    function addTeamMember($teamMemberElement, newMemberEmail) {
1648      var $insertedTeamMember = $teamMemberElement.cloneNode(true);
1649      var $insertedTeamMemberInput = $insertedTeamMember.querySelector("input");
1650      var $insertedTeamMemberButton = $insertedTeamMember.querySelector(".member-button");
1651      if (newMemberEmail) $insertedTeamMemberInput.value = newMemberEmail;
1652
1653      $insertedTeamMemberInput.setAttribute("disabled", "disabled");
1654      $insertedTeamMember.classList.remove("insert");
1655      $insertedTeamMember.classList.add("invitee");
1656      $teamMemberElement.closest("ul.js-team-member-list").insertBefore($insertedTeamMember, $teamMemberElement);
1657      $insertedTeamMemberButton.addEventListener("click", (e) => e.target.closest(".js-team-member").remove());
1658      $insertedTeamMemberButton.removeAttribute("disabled");
1659      tmUpdateTotalPricePerYear();
1660    }
1661
1662    function getInviteeElementsFromContainer() {
1663      return [...$container.querySelectorAll(".js-team-member.invitee input")];
1664    }
1665
1666    function getInviteesFromContainer() {
1667      return getInviteeElementsFromContainer().map((e) => e.value);
1668    }
1669
1670    function validateInvitees() {
1671      var is_valid = true;
1672      getInviteeElementsFromContainer().forEach((e) => {
1673        var $teamMember = e.closest(".js-team-member");
1674        if (isValidTeamsEmail(e.value)) {
1675          $teamMember.classList.remove("error");
1676        } else {
1677          $teamMember.classList.add("error");
1678          is_valid = false;
1679        }
1680      });
1681      return is_valid;
1682    }
1683
1684    function getTeamMembersFromContainer() {
1685      var teamMembers = getInviteesFromContainer();
1686      teamMembers.push(userEmail); // add admin
1687      return teamMembers;
1688    }
1689
1690    function teamNameFromContainer() {
1691      return $tcTeamName.value;
1692    }
1693
1694    function isValidTeamsEmail(str) {
1695      if (!isValidEmail(str)) {
1696        return false;
1697      }
1698      var teamMembers = getTeamMembersFromContainer();
1699      return teamMembers.reduce((n, member) => n + (member === str), 0) == 1;
1700    }
1701  }
1702
1703  $(function () {
1704    var formContainer = document.querySelector(".js-teams-checkout");
1705    if (formContainer) {
1706      updateAuthFromQueryParams();
1707      if (!Auth().getAuthToken()) window.location.replace("/teams");
1708
1709      var flowExprGroup = getQueryParam("flow") === "b" ? "invite-first" : "payment-first";
1710
1711      initTeamsCheckoutForm(formContainer, flowExprGroup);
1712    }
1713
1714    function updateAuthFromQueryParams() {
1715      if (typeof URLSearchParams === "undefined") return null;
1716
1717      var q = new URLSearchParams(location.search);
1718      var token = q.get("token");
1719      var email = q.get("email");
1720
1721      if (token && email) Auth().setAuth(token, email);
1722
1723      // remove the auth params from the url
1724      q.delete("token");
1725      q.delete("email");
1726      const url = `${location.href.split("?")[0]}?${q.toString()}`;
1727      history.replaceState({}, document.title, url);
1728    }
1729  });
1730
1731  // utils
1732  function addToToday(days) {
1733    var res = new Date();
1734    res.setDate(res.getDate() + days);
1735    return res;
1736  }
1737
1738  function isValidEmail(str) {
1739    const re =
1740      /^(([^<>()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;
1741    return re.test(str);
1742  }
1743
1744  function setElementsText(elements, text) {
1745    elements.forEach((e) => (e.textContent = text));
1746  }
1747
1748  function toggleButtonRunning(button, buttonState) {
1749    var runningClass = "running";
1750    if (buttonState) button.classList.add(runningClass);
1751    else button.classList.remove(runningClass);
1752  }
1753
1754  function toggleButtonsRunning(buttons, buttonState) {
1755    buttons.forEach((button) => toggleButtonRunning(button, buttonState));
1756  }
1757
1758  function hideElement(e) {
1759    e.style.display = "none";
1760    e.style.visibility = "hidden";
1761  }
1762
1763  function showElement(e) {
1764    e.style.display = "block";
1765    e.style.visibility = "visible";
1766  }
1767
1768  function enterButtonLoading($el) {
1769    $el.classList.add("running");
1770    $el.setAttribute("disabled", "disabled");
1771  }
1772
1773  function exitButtonLoading($el) {
1774    $el.classList.remove("running");
1775    $el.removeAttribute("disabled");
1776  }
1777
1778  function isAuthStateMismatch(doesUserExist, isSignUpMode, $errorContainer) {
1779    if (doesUserExist && isSignUpMode) {
1780      $errorContainer.textContent = "A user with this email address is already registered, please sign in.";
1781    } else if (!doesUserExist && !isSignUpMode) {
1782      $errorContainer.textContent = "Oops.. we don't recognize this email address, please sign up.";
1783    } else {
1784      $errorContainer.textContent = "";
1785      return false;
1786    }
1787    return true;
1788  }
1789
1790  $(function () {
1791    document.querySelector(".js-book-demo-redirect").addEventListener("click", function () {
1792      window.location.href = "https://app.any.do/?website_registration_flow=team";
1793    });
1794
1795    window.addEventListener("message", (evt) => {
1796      if (
1797        //
1798        evt.origin !== "https://calendly.com" ||
1799        evt.data === null ||
1800        !("event" in evt.data)
1801      ) {
1802        return;
1803      }
1804
1805      if (evt.data.event === "calendly.event_scheduled") {
1806        window.location.href = "https://app.any.do/?website_registration_flow=team";
1807      }
1808    });
1809
1810    const bookDemo = document.querySelector(".js-book-demo-schedule");
1811
1812    bookDemo.addEventListener("click", function () {
1813      const params = new URLSearchParams({
1814        hide_gdpr_banner: "1",
1815
1816        // theme
1817        background_color: "fffff",
1818        text_color: "030303",
1819        primary_color: "0083ff",
1820      });
1821
1822      window.Calendly.initPopupWidget({
1823        url: `${bookDemo.dataset.calendlyLink}?${params.toString()}`,
1824        utm: {},
1825      });
1826    });
1827  });
1828  //#endregion
1829}); //end jQuery

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.