PageSourceSearch

https://seaboats.net/public/js/account.js?v5?14980131

js seaboats.net collected 2026-10-02 11:02:21 UTC 42,187 bytes, 1,262 lines download raw bytes

1"use strict";
2
3var profile = {
4   postLoginActionUrl: '',
5   data: {},
6   isLoggedIn: function() {
7      var me = this;
8      if (app.ls.profile.guid) {
9         if (app.ls.profile.guid.length > 5) {
10            return true;
11         } 
12      }
13      return false; 
14   },
15   modalAnimate: function($newForm, $oldForm) {
16      var me = this;
17      
18      var $newH = $newForm.height();
19      if ($newH < 150) {
20         $newH = 290;
21      } 
22      if ($newForm.data("height")) {
23         $newH = $newForm.data("height");
24      }
25      if (me.$oldForm) {
26         var $oldH = me.$oldForm.height();
27         me.$divForms.css("height", $oldH);
28
29         me.$oldForm.fadeToggle(me.$modalAnimateTime, function() {
30            me.$divForms.animate({
31               height: $newH
32            }, me.$modalAnimateTime, function() {
33               $newForm.fadeToggle(me.$modalAnimateTime);
34            });
35         });
36      } else {
37         me.$divForms.animate({
38            height: $newH
39         }, me.$modalAnimateTime, function() {
40            $newForm.fadeIn(me.$modalAnimateTime);
41         });
42      }
43   
44      me.$oldForm = $newForm;
45   },
46   msgFade: function($msgId, $msgText) {
47      var me = this;
48      $msgId.fadeOut(me.$msgAnimateTime, function() {
49         $(this).text($msgText).fadeIn(me.$msgAnimateTime);
50      });
51   },
52   msgChange: function($divTag, $iconTag, $textTag, $divClass, $iconClass, $msgText) {
53      var $msgOld = $divTag.text();
54      var me = this;
55      profile.msgFade($textTag, $msgText);
56      $divTag.addClass($divClass);
57      $iconTag.removeClass("glyphicon-chevron-right");
58      $iconTag.addClass($iconClass + " " + $divClass);
59      setTimeout(function() {
60         profile.msgFade($textTag, $msgOld);
61         $divTag.removeClass($divClass);
62         $iconTag.addClass("glyphicon-chevron-right");
63         $iconTag.removeClass($iconClass + " " + $divClass);
64      }, me.$msgShowTime);
65   },
66   init: function() {
67      var me = this;
68      me.$formLogin = $('#login-form');
69      me.$formLost = $('#lost-form');
70      me.$formRegister = $('#register-form');
71      me.$formCartLogin = $('#cart-login');
72      me.$divForms = $('#div-forms');
73      me.$modalAnimateTime = 300;
74      me.$msgAnimateTime = 150;
75      me.$msgShowTime = 2000;
76      me.$oldForm = null;
77   
78      $(".fnc-show-account-login").off().on("click", function(e) {
79         profile.modalAnimate(profile.$formLogin);
80         if ($(this).attr("data-login-success-url")) {
81            profile.postLoginActionUrl = $(this).attr("data-login-success-url");
82         } else {
83            profile.postLoginActionUrl = "";
84         }
85         if (utils.isBS5()) {
86            var loginModal = new bootstrap.Modal(document.getElementById('login-modal'), {});
87            loginModal.show();
88         } else {
89            $("#login-modal").modal();
90         }
91         app.loadPickupSelectLists();
92         e.preventDefault();
93         return false;         
94      });
95
96      $(".fnc-show-account-register").off().on("click", function(e) {
97         profile.modalAnimate(profile.$formRegister);
98         if (utils.isBS5()) {
99            var loginModal = new bootstrap.Modal(document.getElementById('login-modal'), {});
100            loginModal.show();
101         } else {
102            $("#login-modal").modal();
103         }
104         e.preventDefault();
105         return false; 
106      });
107
108      $(".fnc-account-logout").off().on("click", function(e) {
109         if (utils.isBS5()) {
110            var logoutModal = new bootstrap.Modal(document.getElementById('dialog-logout'), {});
111            logoutModal.show();
112         } else {
113            $("#dialog-logout").modal();
114         }
115         e.preventDefault();
116         return false;
117      });
118
119      $(".fnc-account-logout-confirm").off().on("click", function(e) {
120         profile.logout();
121         return false;
122      });
123
124      $(".fnc-account-index").off().on("click", function() {
125         window.location = "/account/welcome";
126      });
127
128      $(".fnc-account-save-profile").parents("form").off().on("submit", function(e) {
129         e.preventDefault();
130         me.saveProfile("#account-profile");
131         $('.fnc-account-save-profile').prop('disabled', true);
132      });
133      
134      $(".fnc-account-save-profile").off().on("click", function(e) {
135         $(".fnc-account-save-profile").parents("form").submit();   
136      });
137
138      $(".fnc-account-login").parents("form").off().on("submit", function(e) {
139         var input = this;
140         var loginForm = corridor(input);
141
142         if ($(this).attr("data-login-success-url")) { 
143            profile.postLoginActionUrl = $(this).attr("data-login-success-url");
144         } 
145         
146         $.ajax({
147            type: "POST",
148            url: "/account/login",
149            data: JSON.stringify(loginForm),
150            headers: {
151               'Content-Type': 'application/json',
152               'x-csrf-token': csrf_token
153            },
154            success: function(data) {
155               profile.login(data, app.loginCallback);
156            },
157            failure: function(data) {
158               console.log("Login failed", JSON.stringify(data));
159            }
160         });
161         e.preventDefault();
162         return false; 
163      });
164      
165      $(".fnc-account-impersonate").parents("form").off().on("submit", function(e) {
166         var input = this; 
167         var loginForm = corridor(input);
168         loginForm._crsf = csrf_token;
169         $.ajax({
170            type: "POST",  
171            url: "/api/impersonate",
172            data: JSON.stringify(loginForm),
173            headers: {
174               'Content-Type': 'application/json',
175               'x-csrf-token': csrf_token
176            },
177            success: function(data) {
178               var currentState = app.getLocalState("s");
179               app.setLocalState(currentState, "imp");
180               profile.login(data, function() {
181                  //console.log("impersonate complete")
182               });
183            }
184         });
185         e.preventDefault();
186         return false;
187      });
188      
189       $(".fnc-account-undo-impersonate").off().on("click", function(e) {
190         var currentState = app.getLocalState("imp");
191         app.setLocalState(currentState, "s");
192         if (currentState) {
193            var impersonateReq = {
194               impersonateGuid: currentState.profile.guid
195            }
196   
197            $.ajax({
198               type: "POST",  
199               url: "/api/impersonate",
200               data: JSON.stringify(impersonateReq),
201               headers: {
202                  'Content-Type': 'application/json',
203                  'x-csrf-token': csrf_token
204               },
205               success: function(data) {
206                  var currentState = app.getLocalState("s");
207                  app.setLocalState(currentState, "imp");
208                  Cookies.set("imp", null); 
209                  profile.login(data, function() {
210                     //console.log("impersonate undo complete")
211                  });
212               }
213            });
214         }
215         e.preventDefault();
216         return false;
217      });
218
219      $(".fnc-account-forgot-password").parents("form").off().on("submit", function(e) {
220         var input = this;
221         var passwordForm = corridor(input);
222         passwordForm._crsf = csrf_token;
223         $.ajax({
224            type: "POST",
225            url: "/account/passwordreset",
226            data: JSON.stringify(passwordForm),
227            headers: {
228               'Content-Type': 'application/json',
229               'x-csrf-token': csrf_token
230            },
231            success: function(data) {
232               if (data.success) {
233                  app.notify("Please check your email for the password reset link.", "info");
234               } else {
235                  app.notify(data.message, "error");
236               }
237               $("#login-modal").modal("hide");
238            }
239         });
240         e.preventDefault();
241         return false;
242      });
243
244      $(".fnc-account-reset-password").on("click", function(e) {
245         var input = document.querySelector('#reset-password');
246         var passwordForm = corridor(input);
247         passwordForm._crsf = csrf_token;
248         $.ajax({
249            type: "POST",
250            url: "/account/submitpasswordreset",
251            data: JSON.stringify(passwordForm),
252            headers: {
253               'Content-Type': 'application/json',
254               'x-csrf-token': csrf_token
255            },
256            success: function(data) {
257               app.notify("Your password was updated successfully.", "info");
258               $("#login-modal").modal("hide");
259               setTimeout(function() {
260                  window.location = "/login";
261               }, 1000);
262            }
263         });
264         e.preventDefault();
265      });
266
267      $(".fnc-account-register").parents("form").off().on("submit", function(e) {
268         var input = this; //document.querySelector('#register-form');
269         var registerForm = {};
270         $.each($(input).serializeArray(), function(i, field) {
271               registerForm[field.name] = field.value;
272         });
273         // var registerForm = corridor(input);
274         registerForm._crsf = csrf_token;
275         
276         if (profile.validateRegisterForm(input.id)) {
277            // everything looks good!
278            registerForm.code = profile.formatLinkName(registerForm.email);
279            profile.registerProfile(registerForm, function(err, result) {
280               //display result
281               if (err) {
282                  app.notify("Registration error: " + result.message, "error");
283               }
284               if (result) {
285                  if (result.success) {
286                     window.location = "/";
287                  } else {
288                     app.notify("Registration error: " + result.message, "error");
289                  }
290               }
291            });
292         }
293         e.preventDefault();
294         return false;
295      });
296
297      $(".fnc-account-submit-login-form").parents("form").off().on("submit", function() {
298         var input = document.querySelector('#contact-form');
299         var docId = "history_" + app.g(0);
300         var contactForm = corridor(input);
301         contactForm._id = docId;
302         contactForm.pdb = m.sessionDbName;
303         contactForm._crsf = csrf_token;
304         var doc = {
305            _id: docId,
306            url: window.location.host,
307            contactEmail: contactForm.email,
308            subject: contactForm.subject,
309            type: 7, // custom form
310            metadata: JSON.stringify(contactForm)
311         }
312         app.db.put(doc, function callback(err, result) {
313            if (!err) {
314               app.sync();
315            }
316         });
317         return false;
318      });
319
320      $("#login-modal form").submit(function() {
321         switch (this.id) {
322            case "login-form":
323               var $lg_username = $('#login_username').val();
324               var $lg_password = $('#login_password').val();
325               
326               return false;
327               break;
328            case "lost-form":
329               var $ls_email = $('#lost_email').val();
330               if ($ls_email == "ERROR") {
331                  //profile.msgChange($('#div-lost-msg'), $('#icon-lost-msg'), $('#text-lost-msg'), "error", "glyphicon-remove", "Send error");
332               } else {
333                  //profile.msgChange($('#div-lost-msg'), $('#icon-lost-msg'), $('#text-lost-msg'), "success", "glyphicon-ok", "Send OK");
334               }
335               return false;
336               break;
337            case "register-form":
338               var $rg_username = $('#register_username').val();
339               var $rg_email = $('#register_email').val();
340               var $rg_password = $('#register_password').val();
341               if ($rg_username == "ERROR") {
342                  //profile.msgChange($('#div-register-msg'), $('#icon-register-msg'), $('#text-register-msg'), "error", "glyphicon-remove", "Register error");
343               } else {
344                  //profile.msgChange($('#div-register-msg'), $('#icon-register-msg'), $('#text-register-msg'), "success", "glyphicon-ok", "Register OK");
345               }
346               return false;
347               break;
348            default:
349               return false;
350         }
351         profile.renderWatchlistDropdown();
352         return false;
353      });
354
355      $('#login_register_btn').click(function() {
356         $("#acc-country").val(app.ls.geoCountry);
357         profile.modalAnimate(me.$formRegister)
358      });
359      $('#register_login_btn').click(function() {
360         profile.modalAnimate(me.$formLogin);
361      });
362      $('#login_lost_btn').click(function() {
363         profile.modalAnimate(me.$formLost);
364      });
365      $('#lost_login_btn').click(function() {
366         profile.modalAnimate(me.$formLogin);
367      });
368      $('#lost_register_btn').click(function() {
369         $("#acc-country").val(app.ls.geoCountry);
370         profile.modalAnimate(me.$formRegister);
371      });
372      $('#register_lost_btn').click(function() {
373         profile.modalAnimate(me.$formLost);
374      });
375
376      if (app.ls.syncDue) {
377         setTimeout(function() {
378            app.sync(function() {
379               app.ls.syncDue = false;
380               me.refreshLocalProfile();
381            });
382         }, 2000);
383      }
384
385      if (app.ls.profileDbName) {
386         app.pdb = new PouchDB(app.ls.profileDbName);
387         app.pdb.setMaxListeners(20);
388      }
389      
390      // WATCHLIST SECTION
391      $(".fnc-add-to-watchlist").off().on("click", function(e) {
392         var product = app.resolveItemToSaleline($(this));
393         app.requireLogin("To add this item to your watchlist, please login or register.", function(userProfile) {
394            profile.addWatchListItem(product, function(msg, type) {
395               app.notify(msg, type);
396            });
397         });
398         e.preventDefault();
399         return false;
400      });
401
402
403      // ITEMLIST SECTION
404      $(".fnc-itemlink-delete").off().on("click", function(e) { 
405         var itlId = $(this).attr("data-itl-id").split("_")[2];
406         if (itlId > 0) {
407            $.ajax({
408               type: "POST",
409               url: '/account/itemlink',
410               data: JSON.stringify({
411                  itlId: itlId,
412                  action: "delete"
413               }),
414               headers: {
415                  'Content-Type': 'application/json',
416                  'x-csrf-token': csrf_token
417               },
418               success: function(data) {
419                  if (data.success) {
420                     app.notify("The link was deleted. Please refresh the page to see updated information.", "info");
421                     
422                  } else {
423                     app.notify(data.message, "error");
424                  }
425               }
426            });
427         }
428      });
429      
430      
431       $(".add-itemlink-form").off().on("submit", function(e) {
432         var input = this; //document.querySelector('#lost-form');
433         var linkForm = corridor(input);
434         
435         if (!linkForm.sourceId) {
436            linkForm.sourceId = app.ls.profile.custId;
437         }
438         
439         var ipToInt = function(ip) {
440             // Split IP into octets and convert to numbers
441             const octets = ip.split('.').map(Number);
442             
443             // Combine octets using bitwise operations
444             return octets[0] << 24 | octets[1] << 16 | octets[2] << 8 | octets[3];
445         }
446
447         if (parseInt(linkForm.type) == 45) {
448            linkForm.metadata = {"notes":linkForm.notes,"ipaddress":linkForm.description};
449            linkForm.targetId = ipToInt(linkForm.description);
450         }
451
452         $.ajax({
453            type: "POST",
454            url: '/account/itemlink',
455            data: JSON.stringify(linkForm),
456            headers: {
457               'Content-Type': 'application/json',
458               'x-csrf-token': csrf_token
459            },
460            success: function(data) {
461               if (data.success) {
462                  app.notify("The link was added.  Please refresh the page to see updated information.", "info");
463               } else {
464                  app.notify(data.message, "error");
465               }
466            }
467         });
468         e.preventDefault();
469         return false;
470      });
471      profile.refreshLocalProfile();
472      profile.renderWatchlistDropdown();
473   },
474   refreshLocalProfile: function() {
475      var options = {
476         startkey: "user",
477         endkey: "user",
478         include_docs: true,
479         attachments: true
480      };
481
482      if (app.pdb) {
483         app.pdb.allDocs(options).then(function(result) {
484            if (result.rows[0]) {
485               var profileDoc = result.rows[0].doc;
486               app.ls.profile = profileDoc;
487               if(typeof store !== 'undefined'){
488                  store.profile.set(app.ls.profile);
489               }
490               app.setLocalState(app.ls);
491            }
492            profile.updateProfileDisplay();
493         }).catch(function(err) {
494            console.log(err);
495         });
496      } else {
497         profile.updateProfileDisplay();
498      }
499   },
500   formatLinkName: function(st) {
501      var lIndex = 0;
502      var sOut = [];
503      var cTemp = "";
504      var lChar = 0;
505      var bskip = false;
506
507      st = st.toLowerCase().trim();
508      if (st.length > 0) {
509         for (lIndex = 0; lIndex < st.length; lIndex++) {
510            cTemp = st.substr(lIndex, 1);
511            lChar = cTemp.charCodeAt(0);
512            switch (lChar) {
513               // convert space
514               case 32:
515                  {
516                     if (!bskip) {
517                        if (lIndex > 2) {
518                           sOut.push("-");
519                        }
520                        bskip = true;
521                     }
522                     break;
523                  }
524                  // convert dash
525               case 45:
526                  {
527                     if (!bskip) {
528                        if (lIndex > 2) {
529                           sOut.push("-");
530                        }
531                        bskip = true;
532                     }
533                     break;
534                  }
535                  // convert forwardslash
536               case 47:
537                  {
538                     if (!bskip) {
539                        if (lIndex > 2) {
540                           sOut.push("-");
541                        }
542                        bskip = true;
543                     }
544                     break;
545                  }
546               case 46:
547                  { // .
548                     if (!bskip) {
549                        if (lIndex < 3) {} else {
550                           sOut.push(cTemp);
551                        }
552                     }
553                     bskip = true;
554                     break;
555                  }
556                  // allow a-z, 0-9, @
557               default:
558                  {
559                     if (lChar == 64) {
560                        sOut.push('_at_');
561                        bskip = false;
562                     }
563                     if ((lChar >= 97) && (lChar <= 122)) {
564                        sOut.push(cTemp);
565                        bskip = false;
566                     }
567                     if ((lChar >= 48) && (lChar <= 57)) {
568                        sOut.push(cTemp);
569                        bskip = false;
570                     }
571                  }
572            }
573         }
574         return sOut.join("");
575      } else {
576         return "";
577      }
578   },
579
580   updateProfileDisplay: function() {
581      var pitems = document.querySelectorAll(".account-status");
582      var loggedIn = false;
583      if (app.ls.profile) {
584         if (app.ls.profile.guid) {
585            loggedIn = true;
586         }
587      }
588     // debugger;
589      for (var i = 0; i < pitems.length; i++) {
590         switch (pitems[i].getAttribute("data-type")) {
591            case "public": 
592               if (!app.ls.profile.guid) {
593                  if (pitems[i].getAttribute("data-style")) {
594                     pitems[i].style.display = pitems[i].getAttribute("data-style");
595                  } else {
596                     pitems[i].style.display = "inline-block";
597                  }
598                  pitems[i].classList.remove("hidden-default");
599               } else {
600                  pitems[i].style.display = "none";
601               }
602               break;
603            case "authenticated":
604               if (loggedIn) {
605                  if (pitems[i].getAttribute("data-style")) {
606                     pitems[i].style.display = pitems[i].getAttribute("data-style");
607                  } else {
608                     pitems[i].style.display = "inline-block";
609                  }
610                  pitems[i].classList.remove("hidden-default");
611               } else {
612                  //debugger;
613                  if (pitems[i].getAttribute("data-login-page-id")) {
614                     $(pitems[i]).children("a").attr("href","/?pageid=" + pitems[i].getAttribute("data-login-page-id"));
615                  } else {
616                     pitems[i].style.display = "none";
617                  }
618               }
619               break;
620            case "impersonate":
621               if (app.getLocalState("imp")) {
622                  if (pitems[i].getAttribute("data-style")) {
623                     pitems[i].style.display = pitems[i].getAttribute("data-style");
624                  } else {
625                     pitems[i].style.display = "inline-block";
626                  }
627                  pitems[i].classList.remove("hidden-default");
628               } else {
629                  pitems[i].style.display = "none";
630               }
631               break;
632         }
633      }
634      if (loggedIn) {
635         var pflds = $(".profile");
636         for (var i = 0; i < pflds.length; i++) {
637            var fld = $(pflds[i]);
638            if (fld.attr("name")) {
639               if (fld.val() == "") {
640                  if (app.ls.profile[fld.attr("name")]) {
641                     fld.val(app.ls.profile[fld.attr("name")]);
642                  }
643               }
644            } else {
645               if (app.ls.profile[fld.attr("data-field")]) {
646                  fld.text(app.ls.profile[fld.attr("data-field")]);
647               }
648            }
649         }
650      }
651   },
652   //updateLocalProfile: function () {
653   //    app.sync(function () {
654   //        var options = {
655   //            startkey: "user",
656   //            endkey: "user",
657   //            include_docs: true,
658   //            attachments: true
659   //        };
660
661   //        app.pdb.allDocs(options).then(function (result) {
662   //            var profileDoc = result.rows[0].doc;
663   //            app.ls.profile = JSON.parse(sjcl.decrypt(app.ls.key, profileDoc.content)); // JSON.parse(profileDoc.content); // 
664   //            app.setLocalState(app.ls);
665   //        }).catch(function (err) {
666   //            console.log(err);
667   //        });
668   //    });
669   //},
670
671
672   // PROFILE SECTION
673   validateRegisterForm: function(formId) {
674      var form = document.querySelector('#' + formId);
675      $("#" + formId + " .error").removeClass("error");
676      var cf = null;
677      var isValid = true;
678
679      var invalidField = function(formId, name, note) {
680         var fld = $("#" + formId + " input[name='" + name + "']");
681         if (fld) {
682            fld.addClass("error");
683            fld.attr("title", note);
684            isValid = false;
685         } else {
686            fld = $("#" + formId + " select[name='" + name + "']");
687            if (fld) {
688               fld.addClass("error");
689               fld.attr("title", note);
690               isValid = false;
691            }
692         }
693      };
694
695      var validateEmail = function(value) {
696         var input = document.createElement('input');
697         input.type = 'email';
698         input.value = value;
699         return typeof input.checkValidity == 'function' ? input.checkValidity() : /\S+@\S+\.\S+/.test(value);
700      };
701
702      if (form) {
703         cf = corridor(form);
704         if (cf.firstname.length < 2)
705            invalidField(formId, "firstname", "Please enter your full first name.");
706
707         if (cf.lastname.length < 2)
708            invalidField(formId, "lastname", "Please enter your full last name.");
709
710         if (!validateEmail(cf.email) || cf.email.length < 3)
711            invalidField(formId, "email", "Please enter a valid email address.");
712
713         if (cf.address1) if (cf.address1.length < 2) invalidField(formId, "address1", "Please enter an address.");
714         
715         if (cf.phone) if (cf.phone.length < 2) invalidField(formId, "phone", "Please enter a phone number.");
716
717         if (cf.city) if (cf.city.length < 2) invalidField(formId, "city", "Please enter a city name.");
718
719         if (cf.postcode) if (cf.postcode.length < 4) invalidField(formId, "postcode", "Please enter a valid post code.");
720
721         if (cf.country) if (cf.country.length < 2) invalidField(formId, "country", "Please choose a valid country.");
722
723         if (String(cf.password).length > 4) {
724            if (cf.password != cf.password2) {
725               invalidField(formId, "password", "The two password fields must match.");
726               invalidField(formId, "password2", "The two password fields must match.");
727            }
728         } else {
729            invalidField(formId, "password", "The password must be longer than 4 letters.");
730         }
731         return isValid;
732      }
733      return false;
734   },
735
736
737   registerProfile: function(registerForm, callback) {
738      registerForm._crsf = csrf_token;
739      $.ajax({
740         type: "POST",
741         url: "/account/register",
742         data: JSON.stringify(registerForm),
743         headers: {
744            'Content-Type': 'application/json',
745            'x-csrf-token': csrf_token
746         },
747         success: function(data) {
748            if (data.success) {
749               window.location = "/";
750            } else {
751               if (data.message) {
752                  callback("Registration error", data);
753               } else {
754                  callback("Registration error", {
755                     success: false,
756                     message: "Registration failed, please try again."
757                  });
758               }
759            }
760         }
761      });
762   },
763   registerContact: function(cust) {
764
765   },
766   
767   completeLogin: function(callback) {
768      var options = {
769         startkey: "user", 
770         endkey: "user",
771         include_docs: true,
772         attachments: true
773      };
774
775      app.pdb.allDocs(options).then(function(result) {
776         if (result.rows) {
777            profile.msgChange($('#div-login-msg'), $('#icon-login-msg'), $('#text-login-msg'), "success", "glyphicon-ok", "Login OK");
778         } else {
779            profile.msgChange($('#div-login-msg'), $('#icon-login-msg'), $('#text-login-msg'), "error", "glyphicon-remove", "Login error.");
780         }
781         
782         if (result.rows[0]) {
783            app.ls.profile = result.rows[0].doc;
784            if (app.ls.profile) {
785               if (!app.ls.profile.priceLevel) app.ls.profile.priceLevel = app.ls.profile.pricelevel;
786            }
787            app.setLocalState(app.ls);
788            try {
789               $("#login-modal").modal("hide");
790            }
791            catch(e) {}
792
793            app.applyProfileDetailsToSale('shopping-cart-details', function() {
794               profile.updateProfileDisplay();
795               if (callback) {
796                  callback(app.ls.profile);
797               }
798
799               app.checkCartDraftOrders(function(err, res) {
800                  if (profile.postLoginActionUrl != "") {
801                     setTimeout(function() {
802                        switch (profile.postLoginActionUrl) {
803                           case "_current":
804                              profile.postLoginActionUrl = window.location.pathname + '?nc=1';
805                              break;
806                           default: 
807                              // it's already a URL
808                              break;
809                        }
810                        window.location = profile.postLoginActionUrl;
811                        profile.postLoginActionUrl = "";
812                     }, 500);
813                     $("body").fadeOut(1000);
814                  } else {
815                     $("body").fadeOut(1000);
816                     setTimeout(function() {
817                        window.location = '/account/welcome?_dc='+Math.random();
818                     }, 500);
819                  }
820               });
821            });
822         } else {
823            console.log("User record not found");
824         }
825      }).catch(function(err) {
826            console.log(err);
827      });
828   },
829   
830   login: function(data, callback) {
831      var dbName = "";
832     
833      if (data.success) {
834         if (data.key) {
835            app.ls.key = data.key;
836            app.ls.profileDbName = data.profile;
837         }
838         app.ls.currency = data.data.currency;
839         app.ls.baseCurrency = data.data.currency;
840
841         app.setLocalState(app.ls);
842         if (PouchDB) {
843            app.pdb = new PouchDB(app.ls.profileDbName); 
844            app.pdb.setMaxListeners(20);
845            setTimeout(function() {
846               app.pdb.info(function(err, info) {
847                  if (!err) {
848                     if (app.ls.profileDbName) {
849                        app.pdb.get('user').then(function(doc) {
850                          data.data._rev = doc._rev;
851                          return app.pdb.put(data.data);
852                        }).then(function(response) {
853                           profile.completeLogin(callback);
854                        }).catch(function (err) {
855                           if (err) console.log("ERROR: ", err);
856                           app.pdb.put(data.data).then(function (response) {
857                              profile.completeLogin(callback);
858                           });
859                        });
860                     }
861                  } else {
862                     console.log("login PDB error: " + err)
863                  }
864               }); 
865            }, 200);
866            return dbName;
867         } else {
868            profile.completeLogin(callback);
869         }
870      } else {
871         profile.msgChange($('#div-login-msg'), $('#icon-login-msg'), $('#text-login-msg'), "error", "glyphicon-remove", "Login error - please try again");
872         return "";
873      }
874   },
875   forgotPassword: function(email) {},
876   
877   logout: function() {
878      app.ls.profileDbName = "";
879      app.ls.profile = {};
880      $.ajax({
881         type: "POST",
882         url: "/account/logout", 
883         headers: {
884            'Content-Type': 'application/json',
885            'x-csrf-token': csrf_token
886         },
887         contentType: 'application/json',
888         success: function(data) {
889           // console.log("Logout resp", JSON.stringify(data));
890            app.cartClear(true, function() {
891               app.setLocalState(app.ls);
892               window.location = "/?_dc="+Math.random();
893            });
894         }
895      });
896   },
897   
898   refreshProfile: function() {
899      console.log("Profile Refresh");
900   },
901   
902   loadProfile: function() {
903      // return cust obj
904   },
905   saveProfile: function(formId) {
906      var profileForm = corridor(document.querySelector(formId));
907      profileForm._csrf = csrf_token;
908      $.ajax({
909         type: "POST",
910         url: "/account/saveprofile",
911         headers: {
912            'Content-Type': 'application/json',
913            'x-csrf-token': csrf_token
914         },
915         data: JSON.stringify(profileForm),
916         success: function(data) {
917            app.notify("Your profile was updated successfully.", "info");
918            delete profileForm._csrf;
919            if (data.success) {
920               var options = {
921                  startkey: "user",
922                  endkey: "user", 
923                  include_docs: true,
924                  attachments: true
925               };
926         
927               app.pdb = new PouchDB(app.ls.profileDbName);
928               app.pdb.allDocs(options).then(function(result) {
929                  var profileDoc = result.rows[0].doc;
930                  var profileRec = $.extend({}, profileDoc, profileForm); // merge plaintext profile with form data
931                  // update local profile
932                  app.ls.profile = profileRec;
933
934                  // encrypt for sync
935                  app.pdb.put(profileRec, function callback(err, result) {
936                     if (!err) {
937                        setTimeout(function() {
938                           window.location = "/account/welcome";
939                        }, 3000);
940                     } else {
941                        console.log(err);
942                     }
943                  });
944               });
945            } else {
946               if (data.message) {
947                  callback("Registration error", data);
948               } else {
949                  callback("Registration error", {
950                     success: false,
951                     message: "Registration failed, please try again."
952                  });
953               }
954            }
955         }
956      });
957   },
958   deleteProfile: function() {
959
960   },
961
962   startImpersonate: function(impersonateCustGuid) {
963
964   },
965   stopImpersonate: function() {
966
967   },
968
969   // SALES SECTION
970   saveCurrentSale: function() {},
971   loadSavedSale: function(saleGuid) {},
972   renderPastSales: function() {
973      var m = app;
974      var options = {
975         startkey: "sale",
976         endkey: "sale",
977         include_docs: true
978      };
979
980      m.pdb.allDocs(options, function(err, doc) {
981         if (!err) {
982            m.renderTotals();
983            dust.render("templates/components/client/account/sales/history/report", doc, function(err, out) {
984               if (err) {
985                  console.error(err);
986               } else {
987                  $(".sales-report").html(out);
988                  prices.initPrices(m.ls.currency);
989                  m.registerCartEvents();
990               }
991            });
992         }
993      });
994   },
995
996   // WATCHLIST SECTION
997   addWatchListItem: function(product, cb) {
998      var m = this;
999      var itemlinkGuid = app.g(0);
1000      var currline = {
1001         "_id": "itemlink_" + itemlinkGuid,
1002         "contentType": "itemlink",
1003         "url": window.location.href,
1004         "prdId": product.prdId,
1005         "pmmId": product.pmmId,
1006         "picId": product.picId,
1007         "custId": app.ls.profile.custId,
1008         "outletId": product.outletId,
1009         "picFileName": product.picFileName,
1010         "schema": product.schema,
1011         "grpIdTree": product.grpIdTree,
1012         "code": product.code,
1013         "name": product.name,
1014         "type": product.type,
1015         "description": product.description,
1016         "loyaltyCost": 0,
1017         "loyaltyReward": 0,
1018         "price": product.price,
1019         "priceCurr": product.priceCurr,
1020         "priceTax": product.priceTax,
1021         "rrp": product.rrp,
1022         "discount": product.discount
1023      };
1024      
1025      $.ajax({
1026         url: '/account/itemlink',
1027         type: 'POST',
1028         headers: {
1029            'Content-Type': 'application/json',
1030            'x-csrf-token': csrf_token
1031         },
1032         data: JSON.stringify({
1033            _csrf: csrf_token,
1034            targetId: product.prdId, 
1035            sourceId: currline.custId,
1036            metadata: currline,
1037            type: "2",
1038            description: product.code + ": " + product.name,
1039            flags: 0,
1040            action: "save"
1041         }),
1042         dataType: 'json',
1043         success: function(d) {
1044            if (cb) {
1045               cb("Added " + product.name + " to your watchlist.", "info");
1046               setTimeout(function() {
1047                  profile.renderWatchlistDropdown();
1048               }, 10000);
1049            }
1050         },
1051         failure: function(d) {
1052            if (cb) {
1053               //cb(product.name + "<br>was already in your watchlist", "info");
1054            }
1055         }
1056      });
1057   },
1058   removeWatchListItem: function(domEl) {
1059
1060   },
1061   registerWatchlistEvents: function() {
1062      $(".fnc-watchlist-delete-line").off().on("click", function(e) {
1063         var $elm = $($(this).parents(".watchlist-item")[0]);
1064         var prdId = $elm.attr("data-prd-id");
1065         var pmmId = $elm.attr("data-pmm-id");
1066         
1067         $.ajax({
1068            url: '/account/itemlink',
1069            type: 'POST',
1070            headers: {
1071               'Content-Type': 'application/json',
1072               'x-csrf-token': csrf_token
1073            },
1074            data: JSON.stringify({
1075               _csrf: csrf_token,
1076               itlId: 0, //$elm.attr("data-id").split("_")[2],
1077               targetId: prdId, 
1078               sourceId: app.ls.profile.custId,
1079               metadata: null, 
1080               type: "2",
1081               description: "",
1082               flags: 0,
1083               action: "delete"
1084            }),
1085            dataType: 'json',
1086            success: function(d) {
1087               $elm.css("display", "none");
1088               setTimeout(function() {
1089                  profile.renderWatchlistDropdown();
1090               }, 10000);
1091            },
1092            failure: function(d) {
1093            }
1094         });
1095
1096         e.preventDefault();
1097         return false;
1098      });
1099   },
1100   
1101   renderWatchlistDropdown: function() {
1102      var m = app;
1103      $.ajax({
1104         url: '/account/itemlinks?type=2&_dc=' + Math.random(),
1105         type: 'GET',
1106         contentType: 'application/json',
1107         dataType: 'json',
1108         success: function(d) {
1109            let doc = {
1110               rows: {
1111                  doc: d.data
1112               }
1113            }
1114            
1115            dust.render("templates/components/client/watchlist/dropdown", doc, function(err, out) {
1116               if (err) {
1117                  console.error(err)
1118               } else {
1119                  $(".watchlist-item-list").html(out);
1120
1121                  if (app.ls.total) {
1122                     if (doc.rows.doc) {
1123                        app.ls.total.watchlistCount = doc.rows.doc.length;
1124                     } else {
1125                        app.ls.total.watchlistCount = 0;
1126                     }
1127                  }
1128                  
1129                  // hide or statuses based on if there are things in the watchlist
1130                  var pitems = document.querySelectorAll(".watchlist-status");
1131                  for (var i = 0; i < pitems.length; i++) {
1132                     var type = pitems[i].getAttribute("data-type");
1133      
1134                     if (type == "empty") {
1135                        if (m.ls.total.watchlistCount > 0) {
1136                           pitems[i].style.display = "none";
1137                        } else {
1138                           if (pitems[i].getAttribute("data-style")) {
1139                              pitems[i].style.display = pitems[i].getAttribute("data-style");
1140                           } else {
1141                              pitems[i].style.display = "block";
1142                           }
1143                        }
1144                     } else {
1145                        if (m.ls.total.watchlistCount > 0) {
1146                           if (pitems[i].getAttribute("data-style")) {
1147                              pitems[i].style.display = pitems[i].getAttribute("data-style");
1148                           } else {
1149                              pitems[i].style.display = "block";
1150                           }
1151                        } else {
1152                           pitems[i].style.display = "none";
1153                        }
1154                     }
1155                  }
1156                  
1157                  profile.registerWatchlistEvents();
1158               }
1159            });
1160            
1161         },
1162         failure: function(d) {
1163         }
1164      });
1165   },
1166
1167   findWatchlistLine: function(prod, cb) {
1168      var currline = null;
1169      var options = {
1170         startkey: "itemlink_",
1171         endkey: "itemlink_\uffff",
1172         include_docs: true
1173      };
1174
1175      app.pdb.allDocs(options, function(err, doc) {
1176         if (!err) {
1177            if (doc.rows.length == 0) {
1178               cb(err, null);
1179               return;
1180            }
1181            doc.rows.forEach(function(item) {
1182               var wll = item.doc;
1183               if (wll._id.indexOf("itemlink_") > -1) {
1184                  if (wll.prdId == prod.prdId) {
1185                     if (typeof prod.pmmId != "undefined") {
1186                        if (wll.pmmId == prod.pmmId) {
1187                           currline = item.doc;
1188                        }
1189                     } else {
1190                        // found item line
1191                        currline = item.doc;
1192                     }
1193                  }
1194               }
1195            });
1196         }
1197
1198         if (currline) {
1199            cb(err, currline);
1200         } else {
1201            cb(err, null);
1202         }
1203         return; 
1204      });
1205   },
1206   itemLinkAction: function(item, cb) {
1207      // set the "item.action" property to "save" or "delete"
1208      /* obj:
1209            targetId:  
1210            sourceId: 
1211            metadata: 
1212            type: 
1213            description: 
1214            flags: 
1215            action:
1216      */
1217
1218      $.ajax({
1219         url: '/account/itemlink',
1220         type: 'POST',
1221         headers: {
1222            'Content-Type': 'application/json',
1223            'x-csrf-token': csrf_token
1224         },
1225         data: JSON.stringify(item),
1226         dataType: 'json',
1227         success: function(d) {
1228            if (cb) {
1229               cb(false, "OK");
1230            }
1231         },
1232         failure: function(d) {
1233            if (cb) {
1234               cb("ERR");
1235            }
1236         }
1237      });
1238   },
1239   sd: function() {
1240      app.db.allDocs({
1241         include_docs: true
1242      }).then(function(res) {
1243         console.dir(res)
1244      });
1245   },
1246   pd: function() {
1247      app.pdb.allDocs({
1248         include_docs: true
1249      }).then(function(res) {
1250         console.dir(res)
1251      });
1252   },
1253   dr: function(id) {
1254      app.pdb.get(id).then(function(doc) {
1255         return app.pdb.remove(doc);
1256      }).then(function(result) {
1257         console.log("Deleted " + id);
1258      }).catch(function(err) {
1259         console.log(err);
1260      });
1261   }
1262}

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.