//rt (function ($) { "use strict"; // ========================================================= // Initialize OTP Inputs / 2FA // ========================================================= function initOtpInputs(containerSelector, inputSelector, hiddenFieldSelector) { const $container = $(containerSelector); const $inputs = $container.find(inputSelector); const $hidden = $(hiddenFieldSelector); let justPasted = false; // track paste event function updateHidden() { let otp = ""; $inputs.each(function () { otp += $(this).val(); }); $hidden.val(otp); // Dynamic error handling const $firstEmpty = $inputs.filter(function () { return !$(this).val().trim(); }).first(); // Hide errors on filled inputs $inputs.each(function () { const $input = $(this); if ($input.val().trim()) { showError($input, null, "req-error"); } }); // Show error only on the first empty input if any if ($firstEmpty.length) { const msg = $firstEmpty.attr("data-msg") || "Please complete your 6-digit OTP"; showError($firstEmpty, msg, "req-error"); } } // Input: move forward $container.on("input", inputSelector, function () { const $this = $(this); // Filter out non-digits let val = $this.val().replace(/\D/g, ""); $this.val(val); // Move focus to next input if one digit entered if (val.length === 1) { const idx = $inputs.index($this); if (idx < $inputs.length - 1) $inputs.eq(idx + 1).focus(); } updateHidden(); }); // Backspace: move backward $container.on("keydown", inputSelector, function (e) { const $this = $(this); const idx = $inputs.index($this); if (e.key === "Backspace" && !$this.val() && idx > 0) { $inputs.eq(idx - 1).focus(); } }); // Focus: only select if not after paste $container.on("focus", inputSelector, function () { if (!justPasted) $(this).select(); }); // Paste: fill all $container.on("paste", inputSelector, function (e) { e.preventDefault(); let pasteData = (e.originalEvent || e).clipboardData.getData("text").replace(/\D/g, ""); if (!pasteData) return; justPasted = true; // temporarily block focus-select $inputs.each((i, el) => $(el).val(pasteData[i] || "")); updateHidden(); // Focus next empty or blur last const focusIdx = pasteData.length < $inputs.length ? pasteData.length : $inputs.length - 1; $inputs.eq(focusIdx).focus(); setTimeout(() => { justPasted = false; }, 300); // reset flag after 300ms }); } //function validateOTP() { // const $otpInputs = $(".otp-input"); // const emptyInputs = $otpInputs.filter(function () { return !$(this).val().trim(); }); // if (emptyInputs.length) { // // Use the data-msg from the first empty input // const $firstEmpty = emptyInputs.first(); // const customMsg = $firstEmpty.attr("data-msg") || "Please complete your 6-digit OTP"; // showError($firstEmpty, customMsg, "req-error"); // return false; // } // // Clear errors on all filled inputs // $otpInputs.each(function () { // showError($(this), null, "req-error"); // }); // return true; //} function validateOTP() { const $otpInputs = $(".otp-input"); // Check if all boxes are filled const allFilled = $otpInputs.toArray().every(input => $(input).val().trim() !== ""); if (!allFilled) { // Find the first empty input and show the error const $firstEmpty = $otpInputs.filter(function () { return !$(this).val().trim(); }).first(); const customMsg = $firstEmpty.attr("data-msg") || "Please complete your 6-digit OTP"; showError($firstEmpty, customMsg, "req-error"); return false; } // Clear errors if all filled $otpInputs.each(function () { showError($(this), null, "req-error"); }); return true; } // ========================================================= // Global Password Settings // ========================================================= window.PasswordOptions = { minLength: 2, requireUppercase: { min: 0 }, requireLowercase: { min: 0 }, requireNumber: { min: 0 }, requireSpecialChar: { min: 0 } }; // ========================================================= // Password Validation Function // ========================================================= function validatePassword(value, opts = {}) { const o = { ...PasswordOptions, ...opts }; if (!value || !value.trim()) return "Password is required."; if (value.length < o.minLength) return `Password must be at least ${o.minLength} characters long.`; if (o.requireUppercase && (value.match(/[A-Z]/g) || []).length < (o.requireUppercase.min ?? 1)) return `Password must contain at least ${o.requireUppercase.min ?? 1} uppercase letter(s).`; if (o.requireLowercase && (value.match(/[a-z]/g) || []).length < (o.requireLowercase.min ?? 1)) return `Password must contain at least ${o.requireLowercase.min ?? 1} lowercase letter(s).`; if (o.requireNumber && (value.match(/[0-9]/g) || []).length < (o.requireNumber.min ?? 1)) return `Password must contain at least ${o.requireNumber.min ?? 1} number(s).`; if (o.requireSpecialChar && (value.match(/[!@#$%^&*(),.?":{}|<>]/g) || []).length < (o.requireSpecialChar.min ?? 1)) return `Password must contain at least ${o.requireSpecialChar.min ?? 1} special character(s).`; return null; } // ========================================================= // Show error below input // ========================================================= function showError($input, message, className = "field-error") { const $container = $input.parent(); let $error = $container.children("." + className); if ($error.length === 0) { $error = $('
'); $container.append($error); } $error.text(message || ""); } // ========================================================= // Required Text Validation // ========================================================= function validateRequiredText($input, customMsg) { const value = $input.val().trim(); const msg = value ? null : (customMsg || "This field is required."); showError($input, msg, "req-error"); // your existing function return !msg; } // ========================================================= // Password Field Validation (with match support) // ========================================================= function validatePasswordField($input, opts = {}, force = false) { const $container = $input.parent(); let $error = $container.children(".pw-error"); if ($error.length === 0) { $error = $(''); $container.append($error); } // If not force and not touched, skip inline validation if (!force && !$input.data("touched")) return true; let msg = validatePassword($input.val(), opts); const matchSelector = $input.data("match"); if (!msg && matchSelector) { const $other = $(matchSelector); if ($other.length && $input.val() !== $other.val()) { msg = "Passwords do not match."; } } $error.text(msg || ""); return !msg; } // ========================================================= // Initialize Clearable Inputs (.has-x) // ========================================================= function initClearableInputs(selector = ".has-x") { $(selector).each(function () { const $input = $(this); if (!$input.parent().hasClass("input-with-clear")) { $input.wrap(''); $input.after('×'); } const $btn = $input.parent().find(".clear-btn"); function toggleClear() { $btn.css("display", $input.val().length > 0 ? "block" : "none"); } $input.on("input focus blur", toggleClear); $btn.on("click", function () { $input.val("").trigger("input").focus(); toggleClear(); }); toggleClear(); }); } // ========================================================= // Initialize Password Toggle (.pw-toggle) // ========================================================= function initPasswordToggle(selector = ".pw-toggle") { $(selector).each(function () { const $input = $(this); if (!$input.parent().hasClass("input-with-toggle")) { $input.wrap(''); $input.after('๐๏ธ'); } const $btn = $input.parent().find(".toggle-btn"); $btn.off("click").on("click", function () { const type = $input.attr("type"); if (type === "password") { $input.attr("type", "text"); $btn.text("๐"); } else { $input.attr("type", "password"); $btn.text("๐๏ธ"); } }); }); } // ========================================================= // Initialize Password Validation (.pw-validate) // ========================================================= function initPasswordValidation(selector = ".pw-validate", localOpts = {}) { $(selector).each(function () { const $input = $(this); $input.data("touched", false); $input.on("blur", function () { $input.data("touched", true); validatePasswordField($input, localOpts); }); $input.on("input", function () { validatePasswordField($input, localOpts); }); }); } // ========================================================= // Validate All Passwords On Submit // ========================================================= function validateAllPasswordsOnSubmit() { let isValid = true; $(".pw-validate").each(function () { const $input = $(this); if (!validatePasswordField($input, {}, true)) isValid = false; }); return isValid; } // ========================================================= // Validate All Required Text Fields On Submit // ========================================================= function validateAllRequiredOnSubmit() { let isValid = true; // Validate all non-OTP fields $(".req-text:not(.otp-input)").each(function () { const $input = $(this); if ($input.hasClass("pw-validate")) return; const customMsg = $input.attr("data-msg"); if (!validateRequiredText($input, customMsg)) isValid = false; }); // Validate OTP if (!validateOTP()) isValid = false; return isValid; } // ========================================================= // Button Validation + Loader // ========================================================= $(document).on("click", ".validate-btn", function (e) { const $btn = $(this); // Validate all required fields including OTP hidden field const requiredOk = validateAllRequiredOnSubmit(); const passwordOk = validateAllPasswordsOnSubmit(); if (!requiredOk || !passwordOk) { e.preventDefault(); e.stopImmediatePropagation(); return false; } // Button loading state if ($btn.hasClass("btn-loading") && !$btn.hasClass("loading")) { $btn.addClass("loading"); if ($btn.find(".spinner").length === 0) $btn.prepend(''); $btn.prop("disabled", true); } // Let ASP.NET postback continue return true; }); // Generic loader for buttons without validation $(document).on("click", ".btn-loading:not(.validate-btn)", function () { const $btn = $(this); if ($btn.hasClass("loading")) return; $btn.addClass("loading"); if ($btn.find(".spinner").length === 0) $btn.prepend(''); $btn.prop("disabled", true); }); // ========================================================= // Restore Loader After Partial Postbacks (UpdatePanel) // ========================================================= if (typeof Sys !== "undefined" && Sys.WebForms) { const prm = Sys.WebForms.PageRequestManager.getInstance(); prm.add_endRequest(function () { $(".btn-loading").removeClass("loading").prop("disabled", false).find(".spinner").remove(); }); } // ========================================================= // Initialize All on DOM Ready // ========================================================= $(function () { initClearableInputs(); initPasswordToggle(); initPasswordValidation(); // initCookieNotice(); }); // ========================================================= // Expose Globals // ========================================================= window.validatePassword = validatePassword; window.validatePasswordField = validatePasswordField; window.validateAllPasswordsOnSubmit = validateAllPasswordsOnSubmit; window.validateAllRequiredOnSubmit = validateAllRequiredOnSubmit; window.initPasswordValidation = initPasswordValidation; window.initPasswordToggle = initPasswordToggle; window.initClearableInputs = initClearableInputs; window.initOtpInputs = initOtpInputs; })(jQuery); //window.initCookieNotice = initCookieNotice; // ========================================================= // Initialize Cookie footer Message // ========================================================= //function initCookieNotice() { // const banner = document.getElementById('cookieNotice'); // const acceptBtn = document.getElementById('acceptCookies'); // // If elements donโt exist, do nothing // if (!banner || !acceptBtn) return; // // Show banner only if not previously accepted // if (!localStorage.getItem('gApp_cookie_notice_accepted')) { // banner.style.display = 'block'; // } // // Hide banner and remember user choice // acceptBtn.addEventListener('click', function () { // localStorage.setItem('gApp_cookie_notice_accepted', 'true'); // banner.style.display = 'none'; // }); //} window.LoginGlobals = { _VGLID: "2034EE0F-7A94-4165-B8E6-E94907AF8B7F", //Store ID _Login_Page: "https://easycustomerbookings.com/PUB/Login.aspx", //Set the login page _LoginOTP_Page: "", //Set the login page /AtestAPI/LoginMemOTP.html _Login_Redirect_Page: "https://easycustomerbookings.com/Member/MDashboard.aspx", //Set the redirected page after user log-in _Logout_Redirect_Page: "https://easycustomerbookings.com/PUB/Login.aspx", //Set the redirected page after user log-out _UnlockUser_Page: "https://easycustomerbookings.com/Member/UnlockYourAccount.html", //Set the redirected page after user log-out _cookieName: "Calendar_SID", _cookieLifeTimeMin: 30, _Register_ConfirmActivation_URL: "https://easycustomerbookings.com/PUB/RegisterNewUserConfirm.html", }; (function ($) { "use strict"; // ========================================================= // Initialize Cookie footer Message // ========================================================= function initCookieNotice() { const banner = document.getElementById('cookieNotice'); const acceptBtn = document.getElementById('acceptCookies'); // If elements donโt exist, do nothing if (!banner || !acceptBtn) return; // Show banner only if not previously accepted if (!localStorage.getItem('gApp_cookie_notice_accepted')) { banner.style.display = 'block'; } // Hide banner and remember user choice acceptBtn.addEventListener('click', function () { localStorage.setItem('gApp_cookie_notice_accepted', 'true'); banner.style.display = 'none'; }); } // ========================================================= // Initialize All on DOM Ready // ========================================================= $(function () { initCookieNotice(); }); // ========================================================= // Expose Globals // ========================================================= window.initCookieNotice = initCookieNotice; })(jQuery); /** * BookingDetailsBackBTN * Handles back navigation for booking details pages. * If the previous page was ManageBookings.aspx, go there. * Otherwise, redirect to ViewBusCalendar.aspx. */ function BookingDetailsBackBTN() { const targetPage = '/Member/Calendar/ManageBookings.aspx'; const fallbackPage = '/Member/Calendar/ViewBusCalendar.aspx'; // Check if referrer exists and contains target page if (document.referrer && document.referrer.indexOf(targetPage) !== -1) { window.location.href = targetPage; } else { window.location.href = fallbackPage; } } // Smoothly scrolls to a collapse target once it opens. // @param { string } collapseId - The ID of the collapse target(without #) function scrollToCollapse(collapseId) { const collapseElement = document.getElementById(collapseId); if (!collapseElement) return; // Listen for the Bootstrap "shown.bs.collapse" event collapseElement.addEventListener('shown.bs.collapse', function handler() { // Smooth scroll into view collapseElement.scrollIntoView({ behavior: 'smooth', block: 'start' }); // Remove this handler so it only triggers once collapseElement.removeEventListener('shown.bs.collapse', handler); }); }