|
|
@@ -1,13 +1,9 @@
|
|
|
@{
|
|
|
- ViewData["Title"] = "Test Login";
|
|
|
- var returnUrl = ViewBag.ReturnUrl as string ?? "/";
|
|
|
+ ViewData["Title"] = "Login";
|
|
|
var currentMsisdn = ViewBag.CurrentMsisdn as string;
|
|
|
- var pendingOtpMsisdn = ViewBag.PendingOtpMsisdn as string;
|
|
|
- var pendingOtpTransactionId = ViewBag.PendingOtpTransactionId as string;
|
|
|
- var isOtpPending = !string.IsNullOrWhiteSpace(pendingOtpTransactionId);
|
|
|
}
|
|
|
|
|
|
-<section class="kitty-app login-screen" aria-label="Test login">
|
|
|
+<section class="kitty-app login-screen" aria-label="login">
|
|
|
<img class="kitty-bg" src="~/kitty/assets/kitty-fall/bg.png" alt="">
|
|
|
<div class="kitty-bg-fade"></div>
|
|
|
|
|
|
@@ -15,7 +11,7 @@
|
|
|
<img class="login-logo" src="~/kitty/assets/kitty-fall/logo.png" alt="Kitty Fall">
|
|
|
<img class="login-cat" src="~/kitty/assets/kitty-fall/cat.png" alt="">
|
|
|
|
|
|
- <div class="login-card" data-login-card>
|
|
|
+ <div class="login-card" data-login-card data-login-step="msisdn">
|
|
|
<div class="login-card-head">
|
|
|
<strong>Login</strong>
|
|
|
</div>
|
|
|
@@ -32,150 +28,230 @@
|
|
|
</div>
|
|
|
}
|
|
|
|
|
|
- <div class="login-alert login-alert-info @(isOtpPending ? "" : "login-hidden")" data-login-message>
|
|
|
- @(isOtpPending ? "Enter OTP from message." : "")
|
|
|
- </div>
|
|
|
+ <div id="loginMessage" class="login-alert login-hidden" data-login-message></div>
|
|
|
|
|
|
- <div class="login-form">
|
|
|
- <input type="hidden" data-return-url value="@returnUrl">
|
|
|
- <input type="hidden" data-transaction-id value="@pendingOtpTransactionId">
|
|
|
+ <div class="login-form" data-login-form>
|
|
|
+ <div id="loginSendPanel" class="login-step-panel login-send-step">
|
|
|
+ <label class="login-field">
|
|
|
+ <span>MSISDN</span>
|
|
|
+ <input id="phonenumber" type="text" autocomplete="tel" placeholder="959xxxxxxxx">
|
|
|
+ </label>
|
|
|
+ </div>
|
|
|
|
|
|
- <label class="login-field">
|
|
|
- <span>MSISDN</span>
|
|
|
- <input data-msisdn type="text" autocomplete="tel" placeholder="959xxxxxxxx" value="@pendingOtpMsisdn" readonly="@isOtpPending">
|
|
|
- </label>
|
|
|
+ <div id="loginOtpPanel" class="login-step-panel login-otp-step" style="display: none;">
|
|
|
+ <label class="login-field">
|
|
|
+ <span>MSISDN</span>
|
|
|
+ <input id="otp_phonenumber" type="text" autocomplete="tel" placeholder="959xxxxxxxx" readonly>
|
|
|
+ </label>
|
|
|
|
|
|
- <label class="login-field @(isOtpPending ? "" : "login-hidden")" data-otp-field>
|
|
|
- <span>OTP</span>
|
|
|
- <input data-otp type="text" inputmode="numeric" autocomplete="one-time-code" placeholder="Enter OTP">
|
|
|
- </label>
|
|
|
+ <label class="login-field">
|
|
|
+ <span>OTP</span>
|
|
|
+ <input id="otp" type="text" inputmode="numeric" autocomplete="one-time-code" placeholder="Enter OTP">
|
|
|
+ </label>
|
|
|
+ </div>
|
|
|
|
|
|
- <button class="login-submit @(isOtpPending ? "login-hidden" : "")" type="button" data-send-otp onclick="sendOtp()">
|
|
|
+ <button type="button" id="btnLoginConfirm" class="login-submit">
|
|
|
<img src="~/kitty/assets/kitty-fall/play-icon.svg" alt="">
|
|
|
<span>Send OTP</span>
|
|
|
</button>
|
|
|
|
|
|
- <button class="login-submit @(isOtpPending ? "" : "login-hidden")" type="button" data-confirm-otp onclick="confirmOtp()">
|
|
|
- <img src="~/kitty/assets/kitty-fall/play-icon.svg" alt="">
|
|
|
- <span>Confirm OTP</span>
|
|
|
- </button>
|
|
|
+ <div id="changePhoneWrapper" class="change-phone-wrapper" style="display: none; text-align: center; margin-top: 15px;">
|
|
|
+ <button type="button" id="btnChangePhone" style="background: none; border: none; color: #5721ea; text-decoration: underline; cursor: pointer; font-size: 14px; font-weight: 800;">
|
|
|
+ Change phone number
|
|
|
+ </button>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div class="login-loading login-hidden" data-login-loading>
|
|
|
+ <span class="login-spinner" aria-hidden="true"></span>
|
|
|
</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
</section>
|
|
|
|
|
|
-@section Scripts {
|
|
|
- <script>
|
|
|
- function getLoginElements() {
|
|
|
- const card = document.querySelector("[data-login-card]");
|
|
|
- return {
|
|
|
- card: card,
|
|
|
- message: card?.querySelector("[data-login-message]"),
|
|
|
- msisdnInput: card?.querySelector("[data-msisdn]"),
|
|
|
- otpInput: card?.querySelector("[data-otp]"),
|
|
|
- otpField: card?.querySelector("[data-otp-field]"),
|
|
|
- transactionInput: card?.querySelector("[data-transaction-id]"),
|
|
|
- returnUrlInput: card?.querySelector("[data-return-url]"),
|
|
|
- sendButton: card?.querySelector("[data-send-otp]"),
|
|
|
- confirmButton: card?.querySelector("[data-confirm-otp]")
|
|
|
- };
|
|
|
- }
|
|
|
+<script>
|
|
|
+ (function() {
|
|
|
+ var currentStep = "msisdn";
|
|
|
+ var transactionId = "";
|
|
|
+ var loginBusy = false;
|
|
|
|
|
|
- function showLoginMessage(text, isInfo) {
|
|
|
- const el = getLoginElements();
|
|
|
- if (!el.message) return;
|
|
|
- el.message.textContent = text || "";
|
|
|
- el.message.classList.toggle("login-hidden", !text);
|
|
|
- el.message.classList.toggle("login-alert-info", !!isInfo);
|
|
|
+ var btnConfirm = document.getElementById("btnLoginConfirm");
|
|
|
+ var btnChangePhone = document.getElementById("btnChangePhone");
|
|
|
+ var changePhoneWrapper = document.getElementById("changePhoneWrapper");
|
|
|
+ var inputPhone = document.getElementById("phonenumber");
|
|
|
+ var inputOtpPhone = document.getElementById("otp_phonenumber");
|
|
|
+ var inputOtp = document.getElementById("otp");
|
|
|
+ var panelSend = document.getElementById("loginSendPanel");
|
|
|
+ var panelOtp = document.getElementById("loginOtpPanel");
|
|
|
+ var alertMessage = document.getElementById("loginMessage");
|
|
|
+ var loadingSpinner = document.querySelector("[data-login-loading]");
|
|
|
+ var card = document.querySelector("[data-login-card]");
|
|
|
+
|
|
|
+ function showMessage(text, isInfo) {
|
|
|
+ if (!alertMessage) return;
|
|
|
+ if (text) {
|
|
|
+ alertMessage.textContent = text;
|
|
|
+ alertMessage.className = "login-alert" + (isInfo ? " login-alert-info" : "");
|
|
|
+ alertMessage.style.display = "block";
|
|
|
+ } else {
|
|
|
+ alertMessage.style.display = "none";
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
- function setLoginLoading(button, isLoading, text) {
|
|
|
- button.disabled = isLoading;
|
|
|
- button.querySelector("span").textContent = text;
|
|
|
+ function setLoading(isLoading, text) {
|
|
|
+ loginBusy = isLoading;
|
|
|
+ if (btnConfirm) {
|
|
|
+ btnConfirm.disabled = isLoading;
|
|
|
+ var span = btnConfirm.querySelector("span");
|
|
|
+ if (span && text) span.textContent = text;
|
|
|
+ }
|
|
|
+ if (loadingSpinner) {
|
|
|
+ loadingSpinner.classList.toggle("login-hidden", !isLoading);
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
- function showOtpStep(res) {
|
|
|
- const el = getLoginElements();
|
|
|
- el.transactionInput.value = res.transactionId || "";
|
|
|
- el.msisdnInput.value = res.msisdn || el.msisdnInput.value.trim();
|
|
|
- el.msisdnInput.readOnly = true;
|
|
|
- el.otpField.classList.remove("login-hidden");
|
|
|
- el.sendButton.classList.add("login-hidden");
|
|
|
- el.confirmButton.classList.remove("login-hidden");
|
|
|
- showLoginMessage(res.message || "Enter OTP from message.", true);
|
|
|
- el.otpInput.focus();
|
|
|
+ function setStep(step) {
|
|
|
+ currentStep = step;
|
|
|
+ if (card) {
|
|
|
+ card.setAttribute("data-login-step", step);
|
|
|
+ }
|
|
|
+ if (panelSend) {
|
|
|
+ panelSend.style.display = step === "otp" ? "none" : "grid";
|
|
|
+ }
|
|
|
+ if (panelOtp) {
|
|
|
+ panelOtp.style.display = step === "otp" ? "grid" : "none";
|
|
|
+ }
|
|
|
+ if (changePhoneWrapper) {
|
|
|
+ changePhoneWrapper.style.display = step === "otp" ? "block" : "none";
|
|
|
+ }
|
|
|
+ if (btnConfirm) {
|
|
|
+ var span = btnConfirm.querySelector("span");
|
|
|
+ if (span) {
|
|
|
+ span.textContent = step === "otp" ? "Confirm OTP" : "Send OTP";
|
|
|
+ }
|
|
|
+ }
|
|
|
+ if (step === "otp" && inputOtp) {
|
|
|
+ inputOtp.value = "";
|
|
|
+ setTimeout(function() { inputOtp.focus(); }, 50);
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
- function sendOtp() {
|
|
|
- const el = getLoginElements();
|
|
|
- if (!el.card || !window.jQuery) return;
|
|
|
+ function handleSendOtp() {
|
|
|
+ var msisdn = (inputPhone ? inputPhone.value : "").trim();
|
|
|
+ if (!msisdn) {
|
|
|
+ showMessage("Please enter MSISDN.", false);
|
|
|
+ if (inputPhone) inputPhone.focus();
|
|
|
+ return;
|
|
|
+ }
|
|
|
|
|
|
- const msisdn = el.msisdnInput.value.trim();
|
|
|
- showLoginMessage("", false);
|
|
|
- setLoginLoading(el.sendButton, true, "Sending...");
|
|
|
+ showMessage("Sending OTP...", true);
|
|
|
+ setLoading(true, "Sending...");
|
|
|
|
|
|
$.ajax({
|
|
|
url: "/Login/SendOtpAction",
|
|
|
type: "POST",
|
|
|
data: { msisdn: msisdn },
|
|
|
- success: function (res) {
|
|
|
- console.log("GetOtp Response:", res);
|
|
|
- setLoginLoading(el.sendButton, false, "Send OTP");
|
|
|
-
|
|
|
- if (res && (res.errorCode === 0 || res.errorCode === 200)) {
|
|
|
- showOtpStep(res);
|
|
|
- return;
|
|
|
+ success: function(res) {
|
|
|
+ setLoading(false);
|
|
|
+ if (typeof res === "string") {
|
|
|
+ try { res = JSON.parse(res); } catch(e) {}
|
|
|
+ }
|
|
|
+
|
|
|
+ var errorCode = res && (res.errorCode ?? res.ErrorCode);
|
|
|
+ if (errorCode === 0 || errorCode === 200) {
|
|
|
+ transactionId = res.transactionId || res.TransactionId;
|
|
|
+ if (inputOtpPhone) inputOtpPhone.value = res.msisdn || res.Msisdn || msisdn;
|
|
|
+ setStep("otp");
|
|
|
+ showMessage(res.message || "Enter OTP from message.", true);
|
|
|
+ } else {
|
|
|
+ showMessage(res.message || "Send OTP failed.", false);
|
|
|
+ setStep("msisdn");
|
|
|
}
|
|
|
-
|
|
|
- showLoginMessage((res && res.message) || "Send OTP failed.", false);
|
|
|
},
|
|
|
- error: function (err) {
|
|
|
- console.error("GetOtp Error:", err);
|
|
|
- setLoginLoading(el.sendButton, false, "Send OTP");
|
|
|
- showLoginMessage("Send OTP failed. Please check API service.", false);
|
|
|
+ error: function() {
|
|
|
+ setLoading(false);
|
|
|
+ showMessage("Send OTP failed. Please check network connection.", false);
|
|
|
}
|
|
|
});
|
|
|
}
|
|
|
|
|
|
- function confirmOtp() {
|
|
|
- const el = getLoginElements();
|
|
|
- if (!el.card || !window.jQuery) return;
|
|
|
+ function handleConfirmOtp() {
|
|
|
+ var msisdn = (inputOtpPhone ? inputOtpPhone.value : "").trim();
|
|
|
+ var otp = (inputOtp ? inputOtp.value : "").trim();
|
|
|
|
|
|
- showLoginMessage("", false);
|
|
|
- if (!el.otpInput.value.trim()) {
|
|
|
- showLoginMessage("Please enter OTP.", false);
|
|
|
- el.otpInput.focus();
|
|
|
+ if (!otp) {
|
|
|
+ showMessage("Please enter OTP.", false);
|
|
|
+ if (inputOtp) inputOtp.focus();
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
- setLoginLoading(el.confirmButton, true, "Checking...");
|
|
|
+ showMessage("Checking OTP...", true);
|
|
|
+ setLoading(true, "Checking...");
|
|
|
|
|
|
$.ajax({
|
|
|
url: "/Login/ConfirmOtpAction",
|
|
|
type: "POST",
|
|
|
data: {
|
|
|
- msisdn: el.msisdnInput.value.trim(),
|
|
|
- transactionId: el.transactionInput.value,
|
|
|
- otp: el.otpInput.value.trim(),
|
|
|
- returnUrl: el.returnUrlInput.value
|
|
|
+ msisdn: msisdn,
|
|
|
+ transactionId: transactionId,
|
|
|
+ otp: otp
|
|
|
},
|
|
|
- success: function (res) {
|
|
|
- console.log("ConfirmOtp Response:", res);
|
|
|
- setLoginLoading(el.confirmButton, false, "Confirm OTP");
|
|
|
+ success: function(res) {
|
|
|
+ if (typeof res === "string") {
|
|
|
+ try { res = JSON.parse(res); } catch(e) {}
|
|
|
+ }
|
|
|
|
|
|
- if (res && (res.errorCode === 0 || res.errorCode === 200)) {
|
|
|
+ var errorCode = res && (res.errorCode ?? res.ErrorCode);
|
|
|
+ if (errorCode === 0 || errorCode === 200) {
|
|
|
+ showMessage("Login successful! Redirecting...", true);
|
|
|
window.location.href = res.redirectUrl || "/";
|
|
|
- return;
|
|
|
+ } else {
|
|
|
+ setLoading(false);
|
|
|
+ showMessage(res.message || "Confirm OTP failed.", false);
|
|
|
}
|
|
|
-
|
|
|
- showLoginMessage((res && res.message) || "Confirm OTP failed.", false);
|
|
|
},
|
|
|
- error: function (err) {
|
|
|
- console.error("ConfirmOtp Error:", err);
|
|
|
- setLoginLoading(el.confirmButton, false, "Confirm OTP");
|
|
|
- showLoginMessage("Confirm OTP failed. Please check API service and OTP.", false);
|
|
|
+ error: function() {
|
|
|
+ setLoading(false);
|
|
|
+ showMessage("Confirm OTP failed. Please check network connection.", false);
|
|
|
}
|
|
|
});
|
|
|
}
|
|
|
- </script>
|
|
|
-}
|
|
|
+
|
|
|
+ if (btnConfirm) {
|
|
|
+ btnConfirm.addEventListener("click", function(e) {
|
|
|
+ e.preventDefault();
|
|
|
+ if (loginBusy) return;
|
|
|
+ if (currentStep === "otp") {
|
|
|
+ handleConfirmOtp();
|
|
|
+ } else {
|
|
|
+ handleSendOtp();
|
|
|
+ }
|
|
|
+ });
|
|
|
+ }
|
|
|
+
|
|
|
+ if (btnChangePhone) {
|
|
|
+ btnChangePhone.addEventListener("click", function(e) {
|
|
|
+ e.preventDefault();
|
|
|
+ setStep("msisdn");
|
|
|
+ showMessage("");
|
|
|
+ });
|
|
|
+ }
|
|
|
+
|
|
|
+ if (inputPhone) {
|
|
|
+ inputPhone.addEventListener("keypress", function(e) {
|
|
|
+ if (e.key === "Enter") {
|
|
|
+ e.preventDefault();
|
|
|
+ if (!loginBusy) handleSendOtp();
|
|
|
+ }
|
|
|
+ });
|
|
|
+ }
|
|
|
+ if (inputOtp) {
|
|
|
+ inputOtp.addEventListener("keypress", function(e) {
|
|
|
+ if (e.key === "Enter") {
|
|
|
+ e.preventDefault();
|
|
|
+ if (!loginBusy) handleConfirmOtp();
|
|
|
+ }
|
|
|
+ });
|
|
|
+ }
|
|
|
+ })();
|
|
|
+</script>
|