1const app = (function () { 2 const appConfigs = { 3 lazyLoadSelectorName: ".lazyload, img[data-src]", 4 RootPathLawJudg: "/ban-an/", 5 RootPathService: "/khac/", 6 RootPathDoc: "/van-ban/", 7 RootPathLuatVietNam: "/", 8 googleClientId: 9 "430060160652-v6547hpru6paae2boas196b4j7l1qcnr.apps.googleusercontent.com", 10 reCaptchaSiteKey: "6LfhkewpAAAAANgBivSXiilt6-5VbNfvFETkGL2N", 11 cacheKeyExpiry: 3600 * 24 * 365, 12 darkModeExpiry: 3600 * 24 * 30, 13 darkModeKey: "luatvietnam.vn_ban_an_night_mode", 14 }; 15 16 const trustedHostnames = new Set([ 17 "localhost", 18 "betaluat.icsoft.vn", 19 "luat.icsoft.vn", 20 "luatvietnam.vn", 21 ]); 22 23 var messageTimer; 24 const init = () => { 25 handleEvents(); 26 handleMessageListener(); 27 initAuthPopupHeightObserver(); 28 //updateRole(); 29 lazyLoadImage(); 30 }; 31 const baseEventHandler = (event) => { 32 event.preventDefault(); 33 event.stopPropagation(); 34 }; 35 36 // Hà m tiá»n Ãch ghi log qua ClientLogger (nếu có) 37 const logClientError = (message, error) => { 38 if (window.ClientLogger && typeof window.ClientLogger.error === 'function') { 39 window.ClientLogger.error(message, error); 40 } 41 }; 42 const logClientWarn = (message) => { 43 if (window.ClientLogger && typeof window.ClientLogger.warn === 'function') { 44 window.ClientLogger.warn(message); 45 } 46 }; 47 const logClientInfo = (message) => { 48 if (window.ClientLogger && typeof window.ClientLogger.info === 'function') { 49 window.ClientLogger.info(message); 50 } 51 }; 52 53 const handleEvents = () => { 54 $(document).on("click", ".btn-submit", function (event) { 55 app.baseEventHandler(event); 56 const form = $(this).closest("form"), 57 action = $(form).data("action"); 58 59 if (form) { 60 if (!form.valid()) { 61 console.warn('Form không hợp lá» trưá»c khi submit', { formId: form.attr('id') }); 62 63 return; 64 } 65 66 if (!form.attr("submitted")) { 67 if (action) { 68 addRecaptchaV3ToForm( 69 "6Ld_KKgUAAAAALzeQ_4iQZKotSGsnX0EjtP6IjEw", 70 form, 71 action 72 ) 73 .then(function () { 74 form.submit(); 75 }) 76 .catch(function (error) { 77 console.warn('Lá»i khi thêm reCAPTCHA và o form', error); 78 }); 79 } else { 80 form.submit(); 81 } 82 } 83 } 84 }); 85 $(document).on("click", "#gopyv2", function (event) { 86 event.preventDefault(); 87 const self = $(this); 88 app.showPopup({ 89 url: `${app.appConfigs.RootPathLuatVietNam}Ajax/PartialGopY2`, 90 dataType: "html", 91 type: "GET", 92 data: {}, 93 popupId: "GopYv2", 94 target: self, 95 onComplete: function () { 96 app.validatorUnobtrusiveParse($("#FormGopYv2")); 97 }, 98 }); 99 }); 100 101 $(document).on("click", "#ycbsvb", function (event) { 102 event.preventDefault(); 103 const self = $(this); 104 app.showPopup({ 105 url: `${app.appConfigs.RootPathLuatVietNam}van-ban/yeu-cau-bo-sung.html`, 106 dataType: "html", 107 type: "GET", 108 data: {}, 109 popupId: "_ycbsvb", 110 target: self, 111 onComplete: function () { 112 app.validatorUnobtrusiveParse($("#_ycbosungvb_form")); 113 }, 114 }); 115 }); 116 $(document).on("click", ".btn-more-menu", function (event) { 117 event.preventDefault(); 118 const self = $(this), 119 parent = self.closest(".col-mega"), 120 menuItems = parent.find(".ul-mega"), 121 hiddenItems = menuItems.find("li.target-hidden"), 122 availableItems = menuItems.find('li[data-available="1"]'), 123 initialItems = availableItems.length; 124 125 if (hiddenItems.length) { 126 hiddenItems.removeClass("target-hidden"); 127 self.text("Thu gá»n"); 128 } else { 129 menuItems.find("li").addClass("target-hidden"); 130 131 availableItems.slice(0, initialItems).removeClass("target-hidden"); 132 133 if (availableItems.length < initialItems) { 134 menuItems 135 .find("li:not([data-available])") 136 .slice(0, initialItems - availableItems.length) 137 .removeClass("target-hidden"); 138 } 139 140 self.text("Xem thêm"); 141 } 142 }); 143 $(document).on("keyup change", ".input-menulvvb", function () { 144 var self = $(this), 145 searchTerm = self.val().trim(); 146 147 function normalizeString(str) { 148 return str 149 .normalize("NFD") 150 .replace(/[\u0300-\u036f]/g, "") 151 .toLowerCase(); 152 } 153 154 function resetToInitialState() { 155 const parent = self.closest(".col-mega"), 156 menuItems = parent.find(".ul-menu-list li"); 157 var isExpanded = parent.find(".btn-more-menu").text() === "Thu gá»n"; 158 menuItems.each(function () { 159 var li = $(this); 160 li.removeClass("highlight target-hidden"); 161 162 if (!li.data("available") && !isExpanded) { 163 li.addClass("target-hidden"); 164 } 165 }); 166 } 167 168 function highlightText(element, keyword) { 169 var text = element.text(); 170 var normalizedText = normalizeString(text); 171 var normalizedKeyword = normalizeString(keyword); 172 173 if (normalizedKeyword.length > 0) { 174 var keywordIndex = normalizedText.indexOf(normalizedKeyword); 175 if (keywordIndex !== -1) { 176 var originalKeyword = text.substr(keywordIndex, keyword.length); 177 var highlightedText = text.replace( 178 new RegExp(originalKeyword, "gi"), 179 function (match) { 180 return '<span class="highlight">' + match + "</span>"; 181 } 182 ); 183 element.html(highlightedText); 184 } else { 185 element.html(text); 186 } 187 } else { 188 element.html(text); 189 } 190 } 191 192 function highlightAndShowKeyword(keyword) { 193 var keywordExists = keyword.length > 0;
194 195 $(".ul-menu-list li").each(function () { 196 var li = $(this); 197 var link = li.find("a"); 198 199 if (link.length) { 200 link.each(function () { 201 highlightText($(this), keyword); 202 }); 203 } else { 204 highlightText(li, keyword); 205 } 206 207 if (keywordExists && li.find(".highlight").length === 0) { 208 li.addClass("target-hidden"); 209 } else { 210 li.removeClass("target-hidden"); 211 } 212 }); 213 214 if (!keywordExists) { 215 resetToInitialState(); 216 } 217 } 218 219 highlightAndShowKeyword(searchTerm); 220 }); 221 222 $(document) 223 .off("click", "[data-toggle-target]") 224 .on("click", "[data-toggle-target]", function (e) { 225 e.preventDefault(); 226 var self = $(this), 227 target = self.attr("data-toggle-target"); 228 229 if (typeof target != typeof undefined) { 230 $(target).toggleClass("target-expanded").css("display", ""); 231 } 232 }); 233 234 $(document).on("click", ".tabs-login-item", function () { 235 const tab = $(this).data("tab"); 236
237 setActiveSSOTab(tab); 238 239 setTimeout(updateAuthIframeHeight, 100); 240 }); 241 242 $(document).on("click", ".quen-pass", function (event) { 243 app.baseEventHandler(event); 244 const self = $(this); 245 if (!self.attr("clicked")) { 246 app.showPopup({ 247 url: "/khach-hang/sso/quen-mat-khau", 248 dataType: "html", 249 type: "GET", 250 popupId: "_forgotPassword", 251 target: self, 252 onComplete: function () { 253 app.validatorUnobtrusiveParse($("#_forgotPasswordForm")); 254 }, 255 }); 256 } 257 }); 258 259 $(document).on("click", '[data-role="auth-switch-login-code"]', function (e) { 260 e.preventDefault(); 261 262 const authContainer = $(this).closest(".auth-popup-wrapper"); 263 if (!authContainer.length) return; 264 265 authContainer 266 .find('[data-role="auth-form"]') 267 .addClass("target-hidden"); 268 269 authContainer 270 .find('[data-role="auth-form-login-code"]') 271 .removeClass("target-hidden"); 272 273 if (window.parent) { 274 window.parent.postMessage( 275 { 276 action: "authTabSwitch", 277 tabName: 'loginCode', 278 }, 279 "*" 280 ); 281 } 282 }); 283 284 $(document).on("click", '[data-role="auth-back-to-login"]', function (e) { 285 e.preventDefault(); 286 287 const authContainer = $(this).closest(".auth-popup-wrapper"); 288 if (!authContainer.length) return; 289 290 authContainer 291 .find('[data-role="auth-form"]') 292 .removeClass("target-hidden"); 293 294 authContainer 295 .find('[data-role="auth-form-login-code"]') 296 .addClass("target-hidden"); 297 298 if (window.parent) { 299 window.parent.postMessage( 300 { 301 action: "authTabSwitch", 302 tabName: 'login', 303 }, 304 "*" 305 ); 306 } 307 }); 308 309 }; 310 311 const onBegin = (element) => { 312 const target = $(element); 313 if (target) { 314 if (target.is("form")) { 315 target.attr("submitted", ""); 316 let btnSubmit = target.find(".btn-submit").first(); 317 if (!btnSubmit.length) { 318 btnSubmit = target.find(".text-submit").first(); 319 } 320 if (btnSubmit.length > 0) { 321 const ogAttr = btnSubmit.attr("og"); 322 if (!ogAttr) { 323 btnSubmit.attr("og", btnSubmit.html()); 324 } 325 btnSubmit.html( 326 '<svg class="loading" aria-hidden="true" focusable="false" role="presentation" class="icon icon-spinner" viewBox="0 0 20 20"><path d="M7.229 1.173a9.25 9.25 0 1 0 11.655 11.412 1.25 1.25 0 1 0-2.4-.698 6.75 6.75 0 1 1-8.506-8.329 1.25 1.25 0 1 0-.75-2.385z" fill="#919EAB"/></svg>' 327 ); 328 } 329 } else { 330 const ogAttr = target.attr("og"); 331 if (!ogAttr) { 332 target.attr("og", target.html()); 333 } 334 target.html( 335 '<svg class="loading" fill="#fff" aria-hidden="true" focusable="false" role="presentation" class="icon icon-spinner" viewBox="0 0 20 20"><path d="M7.229 1.173a9.25 9.25 0 1 0 11.655 11.412 1.25 1.25 0 1 0-2.4-.698 6.75 6.75 0 1 1-8.506-8.329 1.25 1.25 0 1 0-.75-2.385z" fill="#919EAB"/></svg>' 336 ); 337 } 338 } 339 }; 340 341 const onComplete = (element, reset = true, scrollTo) => { 342 const target = $(element); 343 let timeOut; 344 if (target) { 345 if (target.is("form")) { 346 let btnSubmit = target.find(".btn-submit").first(); 347 if (!btnSubmit.length) { 348 btnSubmit = target.find(".text-submit").first(); 349 } 350 if (btnSubmit.length > 0 && reset) { 351 if (timeOut) { 352 clearTimeout(timeOut); 353 } 354 timeOut = setTimeout(() => { 355 target.removeAttr("submitted"); 356 const ogAttr = btnSubmit.attr("og"); 357 if (ogAttr) { 358 btnSubmit.html(ogAttr); 359 } 360 timeout = null; 361 }, 500); 362 } 363 } else { 364 if (timeOut) { 365 clearTimeout(timeOut); 366 } 367 timeOut = setTimeout(() => { 368 const ogAttr = target.attr("og"); 369 if (ogAttr) { 370 target.html(ogAttr); 371 } 372 timeout = null; 373 }, 500); 374 } 375 } 376 if (scrollTo) { 377 const scrollToElement = $(`#${scrollTo}`); 378 if (scrollToElement) { 379 $("html,body").animate({ scrollTop: 0 }, 600); 380 } 381 } 382 }; 383 384 const onSuccess = (element, xhr) => { 385 if (xhr != null && xhr.message) { 386 showMessage({ 387 autoClose: true, 388 buttons: [ 389 { 390 Name: "Äóng", 391 ClassName: "btn", 392 ClickEvent: function () { 393 $("#_message").remove(); 394 395 if ($(this).attr("clicked")) { 396 return; 397 } 398 399 if (xhr != null && !xhr.succeeded) { 400 return; 401 } 402 403 if ( 404 xhr != null && 405 xhr.callback != null && 406 xhr.callback.length > 0 407 ) { 408 eval(b64DecodeUnicode(xhr.callback)); 409 } 410 411 $(this).attr("clicked", true); 412 413 if (xhr != null && xhr.succeeded && xhr.returnUrl != null) { 414 window.location.href = xhr.returnUrl; 415 return; 416 } 417 }, 418 }, 419 ], 420 messages: [xhr.message], 421 }); 422 } else if (xhr != null && xhr.succeeded && xhr.returnUrl != null) { 423 window.location.href = xhr.returnUrl; 424 return; 425 } 426 if (xhr != null && !xhr.succeeded) { 427 onComplete(element, true); 428 } 429 }; 430 431 const onLoginSuccess = (element, xhr) => { 432 if (xhr != null) { 433 const { 434 message, 435 callback, 436 callback2, 437 succeeded, 438 isAgreedPrivacyPolicy, 439 returnUrl, 440 } = xhr; 441 442 if (succeeded === true && isAgreedPrivacyPolicy === false) { 443 window.parent.postMessage( 444 { 445 action: "requirePrivacyPolicy", 446 returnUrl: returnUrl || window.location.href, 447 }, 448 "*" 449 ); 450 451 return; 452 } 453 454 if (message) { 455 456 //if (callback2) { 457 // handleCallback(callback2); 458 // return; 459 //} 460 461 if (message.indexOf("hiá»n Äang ÄÄng nháºp") > -1) { 462 showMessage({ 463 autoClose: true, 464 buttons: [ 465 { 466 Name: "Tiếp tục", 467 ClassName: "btn", 468 ClickEvent: function () { 469 if ($(this).attr("clicked")) { 470 return; 471 } 472 473 if (!succeeded) { 474 return; 475 } 476 477 if (callback != null && callback.length > 0) { 478 eval(b64DecodeUnicode(callback)); 479 } 480 481 //if (callback2) { 482 // handleCallback(callback2); 483 // return; 484 //} 485 486 $(this).attr("clicked", true); 487 488 if (succeeded) { 489 window.parent.postMessage({ succeeded: true }, "*"); 490 return; 491 } 492 493 $("#login").find("form").submit(); 494 }, 495 }, 496 ], 497 messages: [message], 498 }); 499 } else { 500 showMessage({ 501 autoClose: true, 502 buttons: [ 503 { 504 Name: "Äóng", 505 ClassName: "btn", 506 ClickEvent: function () { 507 if ($(this).attr("clicked")) { 508 return; 509 } 510 511 if (!succeeded) { 512 return; 513 } 514 515 if (callback != null && callback.length > 0) { 516 eval(b64DecodeUnicode(callback)); 517 } 518 519 //if (callback2) { 520 // handleCallback(callback2); 521 // return; 522 //} 523 524 $(this).attr("clicked", true); 525 526 if (succeeded) { 527 window.parent.postMessage({ succeeded: true }, "*"); 528 return; 529 } 530 }, 531 }, 532 ], 533 messages: [message], 534 }); 535 } 536 537 return; 538 } 539 540 if (succeeded) { 541 window.parent.postMessage( 542 { 543 succeeded: true, 544 returnUrl: returnUrl || window.location.href, 545 }, 546 "*" 547 ); 548 549 return; 550 } 551 552 if (!succeeded) { 553 onComplete(element, true); 554 } 555 } 556 }; 557 const onLoginByOpenAuthSuccess = function (succeeded, message) { 558 if (message) { 559 showMessage({ 560 autoClose: true, 561 buttons: [ 562 { 563 Name: "Äóng", 564 ClassName: "btn", 565 ClickEvent: function () { 566 if ($(this).attr("clicked")) { 567 return; 568 } 569 570 if (!succeeded) { 571 return; 572 } 573 574 $(this).attr("clicked", true); 575 576 if (succeeded) { 577 window.parent.postMessage({ succeeded: true }, "*"); 578 return; 579 } 580 }, 581 }, 582 ], 583 messages: [message], 584 }); 585 return; 586 } 587 588 if (succeeded) { 589 window.parent.postMessage({ succeeded: true }, "*"); 590 return; 591 } 592 }; 593 594 const callbackHandlers = { 595 customerRegisterSuccess: (id, userName, email, phone, returnUrl) => { 596 try { 597 const analytics = window.AppAnalytics; 598 599 if (analytics) { 600 if (id) { 601 analytics.identifyUser({ id, userName, email, phone }); 602 } 603
604 analytics.trackEvent("trial_registered", { 605 source: "auth_popup", 606 page_path: window.location.pathname, 607 page_location: window.location.href, 608 }); 609 } 610 } catch (e) { 611 logClientError('Analytics track register success failed', e); 612 } 613 614 if (returnUrl && returnUrl.trim().length > 0) { 615 postMessageToParent("redirect", { redirectUrl: returnUrl }); 616 617 return; 618 } 619 620 setActiveSSOTab("login", true); 621 }, 622 forgotPassword: () => $("#_forgotPassword")?.remove(), 623 }; 624 625 const handleCallback = (callBackRaw) => { 626 if (!callBackRaw) return; 627 628 try { 629 const parsed = 630 typeof callBackRaw === "string" ? JSON.parse(callBackRaw) : callBackRaw; 631 632 const name = parsed?.Name; 633 const args = parsed?.Args || []; 634 635 const fn = callbackHandlers[name]; 636 if (typeof fn === "function") { 637 fn(...args); 638 } 639 } catch (err) { 640 console.error('Lá»i khi parse callback', err); 641 } 642 }; 643 644 const b64DecodeUnicode = (str) => { 645 return decodeURIComponent( 646 atob(str) 647 .split("") 648 .map(function (c) { 649 return "%" + ("00" + c.charCodeAt(0).toString(16)).slice(-2); 650 }) 651 .join("") 652 ); 653 }; 654 655 const debounce = (func, delay = 500) => { 656 let timeOut; 657 return function executedFunc(...args) { 658 if (timeOut) { 659 clearTimeout(timeOut); 660 } 661 timeOut = setTimeout(() => { 662 func(...args); 663 timeOut = null; 664 }, delay); 665 }; 666 }; 667 668 const responseInValid = (response) => { 669 let responseObj; 670 try { 671 responseObj = JSON.parse(response); 672 } catch (e) { } 673 if ( 674 responseObj && 675 responseObj.Message == 676 "Có sá»± cá» trong quá trình xá» lý yêu cầu, quý khách vui lòng liên há» ChÄm sóc khách hà ng Äá» ÄÆ°á»£c há» trợ!" 677 ) { 678 return responseObj; 679 } 680 return undefined; 681 }; 682 683 const showPopup = (options) => { 684 let timeOut; 685 let defaults = { 686 popupId: undefined, 687 url: "", 688 type: "GET", 689 dataType: "html", 690 onComplete: {}, 691 }; 692 options = $.extend(defaults, options); 693 if (!options.popupId) { 694 options.popupId = Math.random().toString(36).substr(2, 9); 695 } 696 fetchData(options).then((response) => { 697 if (response && response.length > 0) { 698 const responseObj = responseInValid(response); 699 if (responseObj != null && responseObj.Message) { 700 showMessage({ autoClose: true, messages: [responseObj.Message] }); 701 } else { 702 if (timeOut) { 703 clearTimeout(timeOut); 704 } 705 timeOut = setTimeout(() => { 706 $(`#${options.popupId}`).remove(); 707 //$('body').css({ 'overflow': 'hidden' }) 708 $("body").append( 709 `<div id="${options.popupId}" class="popup-bg target-hidden target-expanded">${response}</div>` 710 ); 711 if (typeof options.onComplete === "function") { 712 options.onComplete(); 713 } 714 }, 100); 715 } 716 } 717 }); 718 }; 719 720 const showMessage = (options) => { 721 var defaults = { 722 id: "_message", 723 title: "Thông báo", 724 buttons: [ 725 { 726 Name: "Äóng", 727 ClassName: "btn", 728 ClickEvent: {}, 729 }, 730 ], 731 callback: {}, 732 success: true, 733 messages: [], 734 autoClose: true, 735 timeClose: 5, 736 returnUrl: "", 737 }; 738 739 options = $.extend(defaults, options); 740 741 options.messages = $.isArray(options.messages) ? options.messages : []; 742 let popup = `<div id="${options.id}" class="popup-bg target-hidden"> 743 <div class="popup-wrapper popup-340"> <div class="popup-body"> <div class="popup-content"> <div class="form-pad16"> <div class="form-group text-center"> <div class="text-successful color-red">${options.title}</div> </div> <div class="form-group text-center text-content">`; 744 745 if (options.messages.length > 0) { 746 popup += '<div class="form-group text-center text-content">'; 747 for (var i = 0; i < options.messages.length; i++) { 748 popup += "<p>" + options.messages[i] + "</p>"; 749 } 750 popup += `</div> 751 <div class="form-group text-center gap-8">`; 752 if (options.buttons.length > 0) { 753 for (var i = 0; i < options.buttons.length; i++) { 754 if (options.buttons[i].Name == "Äóng") { 755 popup += `<div class="btn-item"><span class="${options.buttons[i].ClassName}" data-toggle-target="#${options.id}" data-close="true">Äóng</span></div>`; 756 } else { 757 popup += `<div class="btn-item"><span class="${options.buttons[i].ClassName}">${options.buttons[i].Name}</span></div>`; 758 } 759 } 760 } 761 popup += "</div>"; 762 } 763 if (messageTimer) { 764 clearInterval(messageTimer); 765 } 766 if (options.success) { 767 $(`#${options.id}`).remove(); 768 $("body").append(popup); 769 for (var i = 0; i < options.buttons.length; i++) { 770 var el = options.buttons[i]; 771 var event = el.ClickEvent; 772 $(`#${options.id}`).on( 773 "click", 774 `.${el.ClassName}`, 775 $.isFunction(event) ? event : $.noop 776 ); 777 } 778 } 779 if (options.autoClose && options.buttons.length == 1) { 780 var autoCloseAfter = options.timeClose, 781 button = $(`.${options.buttons[0].ClassName}`, $(`#${options.id}`)), 782 buttonText = button.text(); 783 messageTimer = setInterval(function () { 784 if (autoCloseAfter > 0) { 785 button.text(`${buttonText} (${autoCloseAfter})`); 786 } else { 787 button.text(buttonText); 788 } 789 if (autoCloseAfter == 0) { 790 clearInterval(messageTimer); 791 792 if (!button.attr("clicked")) { 793 button.trigger("click"); 794 795 $(`#${options.id}`).remove(); 796 } 797 } 798 autoCloseAfter--; 799 }, 1000); 800 } 801 setTimeout(function () { 802 $(`#${options.id}`).addClass("target-expanded").css("display", ""); 803 }, 300); 804 }; 805 806 const fetchData = (options) => { 807 let timeOut, 808 defaults = { 809 target: undefined, 810 beforeSend: function () { 811 if (this.target) { 812 const ogAttr = this.target.attr("og"); 813 if (!ogAttr) { 814 this.target.attr("og", this.target.html()); 815 } 816 this.target.attr("clicked", true); 817 this.target.html( 818 '<svg class="loading" aria-hidden="true" focusable="false" role="presentation" class="icon icon-spinner" viewBox="0 0 20 20"><path d="M7.229 1.173a9.25 9.25 0 1 0 11.655 11.412 1.25 1.25 0 1 0-2.4-.698 6.75 6.75 0 1 1-8.506-8.329 1.25 1.25 0 1 0-.75-2.385z" fill="#919EAB"/></svg>' 819 ); 820 } 821 }, 822 callback: function () { 823 if (this.target) { 824 if (timeOut) { 825 clearTimeout(timeOut); 826 } 827 timeOut = setTimeout(() => { 828 const ogAttr = this.target.attr("og"); 829 if (ogAttr) { 830 this.target.removeAttr("clicked"); 831 this.target.html(ogAttr); 832 this.target.removeAttr("og"); 833 } 834 timeOut = null; 835 }, 200); 836 } 837 }, 838 }; 839 840 options = $.extend(defaults, options); 841 842 if (options.target && options.target.attr("clicked")) { 843 return Promise.resolve(); 844 } 845 846 return new Promise(function (resolve, reject) { 847 $.ajax(options) 848 .done(resolve) 849 .fail(function (jqXHR, textStatus, errorThrown) { 850 // Ghi log lá»i Ajax 851 logClientError('Lá»i Ajax: ' + options.url, { 852 status: jqXHR.status, 853 statusText: jqXHR.statusText, 854 errorThrown: errorThrown 855 }); 856 reject(jqXHR, textStatus, errorThrown); 857 }) 858 .always(options.callback || {}); 859 }); 860 }; 861 862 const handleCustomerStatistics = () => { 863 return new Promise(function (resolver, reject) { 864 let timeOut = setTimeout(function () { 865 fetchData({ 866 url: `${app.appConfigs.APIVersion}customer/get/statistics`, 867 dataType: "json", 868 type: "GET", 869 }).then((response) => { 870 if (response.data) { 871 $("#saved-documents-total").text( 872 `(${response.data.savedDocumentTotal})` 873 ); 874 $("#monitor-document-effects-total").text( 875 `(${response.data.monitorDocumentEffectsTotal})` 876 ); 877 } 878 }); 879 clearTimeout(timeOut); 880 resolver(); 881 }, 3000); 882 }); 883 }; 884 885 const initAuthPopupHeightObserver = () => { 886 updateAuthIframeHeight(); 887 observeAuthPopupResize(); 888 observeTabContentMutation(); 889 }; 890 891 const updateAuthIframeHeight = () => {
892 const popupWrapper = document.querySelector(".auth-popup-wrapper"); 893 if (!popupWrapper) return; 894 895 const height = popupWrapper.scrollHeight; 896 window.parent.postMessage( 897 { 898 action: "authPopupUpdateHeight", 899 height: height, 900 }, 901 "*" 902 ); 903 }; 904 905 const observeAuthPopupResize = () => { 906 const popupWrapper = document.querySelector(".auth-popup-wrapper"); 907 if (!popupWrapper) return; 908 909 const resizeObserver = new ResizeObserver(() => { 910 updateAuthIframeHeight(); 911 }); 912 913 resizeObserver.observe(popupWrapper); 914 }; 915 916 const observeTabContentMutation = () => { 917 const contentWrapper = document.querySelector( 918 ".tabs-login-content-wrapper" 919 ); 920 if (!contentWrapper) return; 921 922 const observer = new MutationObserver(() => { 923 updateAuthIframeHeight(); 924 }); 925 926 observer.observe(contentWrapper, { 927 childList: true, 928 subtree: true, 929 attributes: true, 930 characterData: true, 931 }); 932 }; 933 934 const postMessageToParent = (action, data = {}) => { 935 if (window.parent) { 936 window.parent.postMessage( 937 { 938 action: action, 939 ...data, 940 }, 941 "*" 942 ); 943 } 944 }; 945 946 const handleMessageListener = () => { 947 $(window).on("message", function (event) { 948 const { origin, data } = event.originalEvent; 949 950 if (!isTrustedOrigin(origin, trustedHostnames)) { 951 console.warn('Nháºn message từ origin không tin cáºy: ' + origin); 952 return; 953 } 954 955 if (!data) return; 956 957 switch (data?.action) { 958 case "requestAuthHeight": 959 const iframe = $("#authPopup ._iframe"); 960 if (iframe.length && data.height) { 961 iframe.css("height", data.height); 962 } 963 break; 964 965 case "authTabChange": 966 const tabName = data?.tab; 967 if (tabName) {
968 setActiveSSOTab(tabName); 969 } 970 971 case "authClosePopup": 972 $("body").css("overflow", ""); 973 break; 974 975 case "authTabSwitch": { 976 const $iframe = $("#authPopup ._iframe"); 977 const TAB_HEIGHTS = { login: 500, register: 642, loginCode: 300, }; 978 const newHeight = TAB_HEIGHTS[data.tabName]; 979 980 if ($iframe.length && newHeight) { 981 $iframe[0].style.height = newHeight + "px"; 982 } 983 break; 984 } 985 986 case "redirect": 987 if (data.redirectUrl) { 988 window.location.href = data.redirectUrl; 989 } 990 991 break; 992 993 default: 994 console.warn('Nháºn message không xác Äá»nh từ iframe', { action: data?.action, origin }); 995 break; 996 } 997 }); 998 }; 999 1000 const isTrustedOrigin = (origin, trustedHostnames) => { 1001 try { 1002 const hostname = new URL(origin).hostname; 1003 return trustedHostnames.has(hostname); 1004 } catch (error) { 1005 console.warn("Invalid origin:", origin); 1006 return false; 1007 } 1008 }; 1009 1010 const setActiveSSOTab = (tabName, resetForm = false) => { 1011 const tabItem = $(`.tabs-login-item[data-tab="${tabName}"]`); 1012 const tabContent = $(`#${tabName}`); 1013 1014 // Active tab item 1015 if (tabItem.length) { 1016 $(".tabs-login-item").removeClass("active"); 1017 tabItem.addClass("active"); 1018 } 1019 1020 // Active tab content 1021 if (tabContent.length) { 1022 $(".tabs-login-content").removeClass("active"); 1023 tabContent.addClass("active"); 1024 } 1025 1026 if (resetForm) { 1027 $(".tabs-login-content form").each(function () { 1028 this.reset(); 1029 1030 // Äặt lại xác thá»±c cá»§a jQuery Validate nếu Äã ÄÆ°á»£c áp dụng 1031 const validator = $(this).data("validator"); 1032 if (validator) { 1033 validator.resetForm(); 1034 } 1035 }); 1036 } 1037 1038 if (window.parent) { 1039 window.parent.postMessage( 1040 { 1041 action: "authTabSwitch", 1042 tabName: tabName, 1043 }, 1044 "*" 1045 ); 1046 } 1047 }; 1048 1049 function setCookie(name, value, minutes, secure = true, sameSite = true) { 1050 var expires = ""; 1051 if (minutes) { 1052 var date = new Date(); 1053 date.setTime(date.getTime() + minutes * 60 * 1000); 1054 expires = "; expires=" + date.toUTCString(); 1055 } 1056 var secureFlag = secure ? "; secure" : ""; 1057 var sameSiteFlag = sameSite ? "; SameSite=Strict" : ""; 1058 document.cookie = 1059 name + "=" + value + expires + "; path=/" + secureFlag + sameSiteFlag; 1060 } 1061 function getCookie(name) { 1062 var nameEQ = name + "="; 1063 var cookies = document.cookie.split(";"); 1064 for (var i = 0; i < cookies.length; i++) { 1065 var cookie = cookies[i]; 1066 while (cookie.charAt(0) == " ") { 1067 cookie = cookie.substring(1, cookie.length); 1068 } 1069 if (cookie.indexOf(nameEQ) == 0) { 1070 return cookie.substring(nameEQ.length, cookie.length); 1071 } 1072 } 1073 return null; 1074 } 1075 function isMobile() { 1076 return ( 1077 "ontouchstart" in document.documentElement && 1078 /mobi/i.test(navigator.userAgent) 1079 ); 1080 } 1081 const validatorUnobtrusiveParse = (form) => { 1082 if (form) { 1083 form.removeData("validator"); 1084 form.removeData("unobtrusiveValidation"); 1085 $.validator.unobtrusive.parse(form); 1086 } 1087 }; 1088 const storage = { 1089 getItem: function (key, optionalCallback) { 1090 if (!this.supportsLocalStorage()) { 1091 return null; 1092 } 1093 1094 var callback = function (data) { 1095 data = typeof data !== "undefined" ? data : null; 1096 1097 return typeof optionalCallback === "function" 1098 ? optionalCallback(data) 1099 : data; 1100 }; 1101 1102 var value = localStorage.getItem(key); 1103 1104 if (value !== null) { 1105 value = JSON.parse(value); 1106 1107 if (value.hasOwnProperty("__expiry")) { 1108 var expiry = value.__expiry; 1109 var now = Date.now(); 1110 1111 if (now >= expiry) { 1112 this.removeItem(key); 1113 1114 return callback(); 1115 } else { 1116 return callback(value.__data); 1117 } 1118 } else { 1119 return callback(value); 1120 } 1121 } else { 1122 return callback(); 1123 } 1124 }, 1125 setItem: function (key, value, expiry) { 1126 if ( 1127 !this.supportsLocalStorage() || 1128 typeof value === "undefined" || 1129 key === null || 1130 value === null 1131 ) { 1132 return false; 1133 } 1134
1135 if (typeof expiry === "number") { 1136 value = { 1137 __data: value, 1138 __expiry: Date.now() + parseInt(expiry) * 1000, 1139 }; 1140 } 1141 1142 try { 1143 localStorage.setItem(key, JSON.stringify(value)); 1144 1145 return true; 1146 } catch (e) { 1147 console.log( 1148 "Không thá» lưu dữ liá»u " + key + " trong localStorage: " + e.name 1149 ); 1150 return false; 1151 } 1152 }, 1153 removeItem: function (key) { 1154 if (this.supportsLocalStorage()) { 1155 localStorage.removeItem(key); 1156 } 1157 }, 1158 clear: function () { 1159 if (this.supportsLocalStorage()) { 1160 localStorage.clear(); 1161 } 1162 }, 1163 supportsLocalStorage: function () { 1164 try { 1165 localStorage.setItem("_", "_"); 1166 localStorage.removeItem("_"); 1167 1168 return true; 1169 } catch (e) { 1170 return false; 1171 } 1172 }, 1173 }; 1174 const lazyLoadImage = () => { 1175 const images = document.querySelectorAll(appConfigs.lazyLoadSelectorName); 1176 1177 const lazyLoad = (target) => { 1178 const io = new IntersectionObserver((entries, observer) => { 1179 entries.forEach((entry) => { 1180 if (entry.isIntersecting) { 1181 const img = entry.target; 1182 const src = img.getAttribute("data-src"); 1183 if (src) { 1184 img.setAttribute("src", src); 1185 } 1186 img.classList.add("fade-in"); 1187 observer.disconnect(); 1188 } 1189 }); 1190 }); 1191 1192 io.observe(target); 1193 }; 1194 1195 images.forEach((image) => { 1196 lazyLoad(image); 1197 }); 1198 }; 1199 1200 const loadScript = (src, async = true, type = "text/javascript") => { 1201 return new Promise((resolve, reject) => { 1202 try { 1203 const el = document.createElement("script"); 1204 const container = document.head || document.body; 1205 1206 el.type = type; 1207 el.async = async; 1208 el.src = src; 1209 1210 el.addEventListener("load", () => { 1211 resolve({ status: true }); 1212 }); 1213 1214 el.addEventListener("error", () => { 1215 reject({ 1216 status: false, 1217 message: `Failed to load the script ${src}`, 1218 }); 1219 }); 1220 1221 container.appendChild(el); 1222 } catch (err) { 1223 reject(err); 1224 } 1225 }); 1226 }; 1227 1228 const loadScriptV2 = (src, options = {}) => { 1229 const { async = true, type = "text/javascript", defer = false } = options; 1230 return new Promise((resolve, reject) => { 1231 // Kiá»m tra xem script Äã tá»n tại chưa Äá» tránh tải trùng 1232 const existingScript = document.querySelector(`script[src="${src}"]`); 1233 if (existingScript) { 1234 resolve({ status: true, message: "Script already loaded" }); 1235 return; 1236 } 1237 1238 try { 1239 const el = document.createElement("script"); 1240 const container = document.head || document.body; 1241 1242 // Thiết láºp các thuá»c tÃnh 1243 el.src = src; 1244 el.type = type; 1245 el.async = async; 1246 el.defer = defer; 1247 1248 // Xá» lý sá»± kiá»n load 1249 el.addEventListener("load", () => { 1250 resolve({ status: true, message: "Script loaded successfully" }); 1251 }); 1252 1253 // Xá» lý sá»± kiá»n error chi tiết hÆ¡n 1254 el.addEventListener("error", (error) => { 1255 console.error('Không thá» tải script: ' + src, error); 1256 1257 reject({ 1258 status: false, 1259 message: `Failed to load the script ${src}`, 1260 error, 1261 }); 1262 }); 1263 1264 // Thêm script và o container 1265 container.appendChild(el); 1266 } catch (err) { 1267 reject(err); 1268 } 1269 }); 1270 }; 1271 1272 const loadStyle = (src) => { 1273 return new Promise((resolve, reject) => { 1274 const link = document.createElement("link"); 1275 link.rel = "stylesheet"; 1276 link.href = src; 1277 1278 link.onload = () => resolve(src); 1279 1280 link.onerror = () => { 1281 console.error('Không thá» tải CSS: ' + src); 1282 reject(new Error(`Failed to load CSS file: ${src}`)); 1283 }; 1284 1285 document.head.appendChild(link); 1286 }); 1287 }; 1288 function addRecaptchaV3ToForm(reCAPTCHASiteKey, formSelector, action) { 1289 return new Promise((resolve, reject) => { 1290 grecaptcha.ready(function () { 1291 grecaptcha 1292 .execute(reCAPTCHASiteKey, { action: action }) 1293 .then(function (token) { 1294 var recaptchaResponse = $(formSelector).find( 1295 'input[name="RecaptchaResponse"]' 1296 ); 1297 1298 if (recaptchaResponse.length === 0) { 1299 recaptchaResponse = $("<input>").attr({ 1300 type: "hidden", 1301 name: "RecaptchaResponse", 1302 id: "g-recaptcha-response", 1303 }); 1304 1305 $(formSelector).append(recaptchaResponse); 1306 } 1307 1308 recaptchaResponse.val(token); 1309 1310 resolve(); 1311 }) 1312 .catch(function (error) { 1313 reject(error); 1314 }); 1315 }); 1316 }); 1317 } 1318 function loadGoogleRecaptcha() { 1319 loadScriptV2( 1320 "https://www.google.com/recaptcha/api.js?render=6Ld_KKgUAAAAALzeQ_4iQZKotSGsnX0EjtP6IjEw", 1321 { async: true, defer: true } 1322 ) 1323 .then((data) => { 1324 console.log("reCAPTCHA script loaded successfully"); 1325 }) 1326 .catch((err) => { 1327 console.error(err); 1328 }); 1329 } 1330 function updateRole() { 1331 if ( 1332 typeof IsAuthenticated != typeof undefined && 1333 IsAuthenticated != null && 1334 IsAuthenticated 1335 ) { 1336 var hostName = "/"; 1337 if (window.location.hostname.indexOf("localhost") > -1) { 1338 hostName = "https://localhost:44361/"; 1339 } 1340 var url = hostName + "khach-hang/api/v1/customer/refresh-info"; 1341 1342 fetch(url, { 1343 method: "POST", 1344 headers: { 1345 "Content-Type": "application/json", 1346 }, 1347 }).catch((error) => { 1348 console.error('Lá»i refresh thông tin ngưá»i dùng', error); 1349 }); 1350 } 1351 } 1352 $(document).on("click", ".ic-hidden-pass", function () { 1353 var password = $(this).closest(".form-group").find("input").first(); 1354 $(this).remove(); 1355 password 1356 .closest(".form-group") 1357 .first()
1358 .append('<i class="iconlvn-eye-open ic-show-pass"></i>'); 1359 password.prop("type", "text"); 1360 }); 1361 $(document).on("click", ".ic-show-pass", function () { 1362 var password = $(this).closest(".form-group").find("input").first(); 1363 $(this).remove(); 1364 password 1365 .closest(".form-group") 1366 .first() 1367 .append('<i class="iconlvn-eye-close ic-hidden-pass"></i>'); 1368 password.prop("type", "password"); 1369 }); 1370 var $jQval = $.validator, 1371 adapters, 1372 data_validation = "unobtrusiveValidation"; 1373 1374 function isMethodProxySafe(method) { 1375 return method === "GET" || method === "POST"; 1376 } 1377 1378 function asyncOnBeforeSend(xhr, method) { 1379 if (!isMethodProxySafe(method)) { 1380 xhr.setRequestHeader("X-HTTP-Method-Override", method); 1381 } 1382 } 1383 1384 function asyncOnSuccess(element, data, contentType) { 1385 var mode; 1386 1387 if (contentType.indexOf("application/x-javascript") !== -1) { 1388 // jQuery already executes JavaScript for us 1389 return; 1390 } 1391 1392 mode = (element.getAttribute("data-ajax-mode") || "").toUpperCase(); 1393 $(element.getAttribute("data-ajax-update")).each(function (i, update) { 1394 var top; 1395 1396 switch (mode) { 1397 case "BEFORE": 1398 top = update.firstChild; 1399 $("<div />") 1400 .html(data) 1401 .contents() 1402 .each(function () { 1403 update.insertBefore(this, top); 1404 }); 1405 break; 1406 case "AFTER": 1407 $("<div />") 1408 .html(data) 1409 .contents() 1410 .each(function () { 1411 update.appendChild(this); 1412 }); 1413 break; 1414 default: 1415 $(update).html(data); 1416 break; 1417 } 1418 }); 1419 } 1420 function asyncRequest(element, options) { 1421 var confirm, loading, method, duration; 1422 1423 confirm = element.getAttribute("data-ajax-confirm"); 1424 if (confirm && !window.confirm(confirm)) { 1425 return; 1426 } 1427 1428 loading = $(element.getAttribute("data-ajax-loading")); 1429 duration = element.getAttribute("data-ajax-loading-duration") || 0; 1430 1431 options.data.push({ name: "X-Requested-With", value: "XMLHttpRequest" }); 1432 1433 method = options.type.toUpperCase(); 1434 if (!isMethodProxySafe(method)) { 1435 options.type = "POST"; 1436 options.data.push({ name: "X-HTTP-Method-Override", value: method }); 1437 } 1438 1439 $.ajax(options); 1440 } 1441 1442 function validate(form) { 1443 var validationInfo = $(form).data(data_validation); 1444 return ( 1445 !validationInfo || !validationInfo.validate || validationInfo.validate() 1446 ); 1447 } 1448 //$("form[data-ajax=true]").on("submit", function (evt) { 1449 1450 // evt.preventDefault(); 1451 // if (!validate(this)) { 1452 // return; 1453 // } 1454 // asyncRequest(this, { 1455 // url: this.action, 1456 // type: this.method || "GET", 1457 // data: [] 1458 // }); 1459 //}); 1460 init(); 1461 1462 return { 1463 appConfigs, 1464 baseEventHandler, 1465 debounce, 1466 showPopup, 1467 showMessage, 1468 onBegin, 1469 onComplete, 1470 onSuccess, 1471 onLoginSuccess, 1472 lazyLoadImage, 1473 b64DecodeUnicode, 1474 validatorUnobtrusiveParse, 1475 fetchData, 1476 setCookie, 1477 getCookie, 1478 loadScript, 1479 loadStyle, 1480 storage, 1481 handleCustomerStatistics, 1482 isMobile, 1483 loadGoogleRecaptcha, 1484 responseInValid, 1485 }; 1486})();
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.