444 lines
24 KiB
PHP
444 lines
24 KiB
PHP
<?php
|
|
ini_set("display_errors", 1);
|
|
error_reporting(E_ALL);
|
|
// views/login.php
|
|
require_once("../core/security.php");
|
|
require_once("../core/settings.php");
|
|
require_once("../config/db.php");
|
|
configure_secure_session();
|
|
session_start();
|
|
send_security_headers();
|
|
|
|
// Handle Logout
|
|
if (isset($_GET['action']) && $_GET['action'] == 'logout') {
|
|
if (isset($_SESSION['account'])) {
|
|
require_once("../core/utils.php");
|
|
system_log($conn2, $_SESSION['account'], 'LOGOUT', ['reason' => 'User initiated']);
|
|
}
|
|
session_destroy();
|
|
header("Location: login.php");
|
|
exit();
|
|
}
|
|
|
|
// Handle Auto Logout (Timeout)
|
|
if (isset($_GET['action']) && $_GET['action'] == 'timeout') {
|
|
if (isset($_SESSION['account'])) {
|
|
require_once("../core/utils.php");
|
|
system_log($conn2, $_SESSION['account'], 'AUTO_LOGOUT', ['reason' => 'Inactivity timeout']);
|
|
}
|
|
session_destroy();
|
|
header("Location: login.php?error=timeout");
|
|
exit();
|
|
}
|
|
|
|
// Redirect if already logged in
|
|
if (!empty($_SESSION['sess_userid']) && $_SESSION['sess_userid'] === session_id()) {
|
|
header("Location: dashboard.php");
|
|
exit();
|
|
}
|
|
|
|
$error_msg = "";
|
|
if (isset($_GET['error']) && $_GET['error'] === 'timeout') {
|
|
$error_msg = "เซสชั่นหมดอายุ<br><span class='text-[11px] font-normal text-rose-400 mt-1 block'>เนื่องจากไม่มีการใช้งานระบบเป็นเวลานาน กรุณาล็อกอินใหม่อีกครั้ง</span>";
|
|
}
|
|
|
|
if ($_SERVER["REQUEST_METHOD"] == "POST") {
|
|
require_once("../core/utils.php");
|
|
require_once("../inc/rfc6238.php");
|
|
|
|
// CSRF Check
|
|
if (!verify_csrf_token($_POST['csrf_token'] ?? '')) {
|
|
die("CSRF Token Validation Failed");
|
|
}
|
|
|
|
$username = $_POST['username'] ?? '';
|
|
$password = $_POST['password'] ?? '';
|
|
$ip_address = $_SERVER['REMOTE_ADDR'];
|
|
|
|
$otp_arr = $_POST['otp'] ?? [];
|
|
$otp_val = implode('', $otp_arr);
|
|
|
|
if (empty($username) || empty($password)) {
|
|
$error_msg = "ข้อมูลไม่ครบถ้วน<br><span class='text-[11px] font-normal text-rose-400 mt-1 block'>กรุณากรอก Username และ Password ให้ครบถ้วน</span>";
|
|
} else if (!check_rate_limit($conn2, $ip_address)) {
|
|
$error_msg = "บัญชีถูกระงับชั่วคราว<br><span class='text-[11px] font-normal text-rose-400 mt-1 block'>ท่านพยายามเข้าสู่ระบบผิดพลาดหลายครั้ง กรุณารอ 15 นาที</span>";
|
|
system_log($conn2, $username, 'LOGIN_BLOCKED', ['reason' => 'Rate limit exceeded']);
|
|
} else {
|
|
$stmt = $conn1->prepare("SELECT * FROM opduser WHERE loginname = ?");
|
|
$stmt->bind_param("s", $username);
|
|
$stmt->execute();
|
|
$result = $stmt->get_result();
|
|
$stmt->close();
|
|
|
|
if ($result->num_rows == 1) {
|
|
$row = $result->fetch_assoc();
|
|
if (isset($row['account_disable']) && $row['account_disable'] === 'Y') {
|
|
$error_msg = "บัญชีนี้ถูกยกเลิกการใช้งาน<br><span class='text-[11px] font-normal text-rose-400 mt-1 block'>กรุณาติดต่อผู้ดูแลระบบ (Admin) เพื่อขอเปิดสิทธิ์</span>";
|
|
system_log($conn2, $username, 'LOGIN_FAILED', ['reason' => 'Account disabled']);
|
|
} else if (md5($password) === $row['passweb']) {
|
|
|
|
// Check 2FA if user has secret key
|
|
$secretkey = '';
|
|
$stmt2 = $conn2->prepare("SELECT secret_key FROM sys_user_2fa WHERE loginname = ?");
|
|
if ($stmt2) {
|
|
$stmt2->bind_param("s", $username);
|
|
$stmt2->execute();
|
|
$res2 = $stmt2->get_result();
|
|
if ($res2->num_rows > 0) {
|
|
$secretkey = $res2->fetch_assoc()['secret_key'];
|
|
}
|
|
$stmt2->close();
|
|
}
|
|
$pass_2fa = true;
|
|
if (!empty($secretkey)) {
|
|
if (empty($otp_val)) {
|
|
$pass_2fa = false;
|
|
$error_msg = "ตรวจสอบสิทธิ์ล้มเหลว (2FA)<br><span class='text-[11px] font-normal text-rose-400 mt-1 block'>กรุณากรอกรหัสยืนยันตัวตน 6 หลัก จากแอปพลิเคชัน</span>";
|
|
system_log($conn2, $username, 'LOGIN_FAILED', ['reason' => 'Missing 2FA code']);
|
|
record_failed_login($conn2, $ip_address, $username);
|
|
} else {
|
|
$pass_2fa = TokenAuth6238::verify($secretkey, $otp_val);
|
|
if (!$pass_2fa) {
|
|
$error_msg = "รหัส 2FA ไม่ถูกต้อง<br><span class='text-[11px] font-normal text-rose-400 mt-1 block'>กรุณาตรวจสอบรหัสจาก Google Authenticator และลองอีกครั้ง</span>";
|
|
system_log($conn2, $username, 'LOGIN_FAILED', ['reason' => 'Invalid 2FA code']);
|
|
record_failed_login($conn2, $ip_address, $username);
|
|
}
|
|
}
|
|
}
|
|
|
|
if ($pass_2fa) {
|
|
session_regenerate_id();
|
|
$_SESSION['sess_userid'] = session_id();
|
|
$_SESSION['account'] = $row['loginname'];
|
|
$_SESSION['name'] = $row['name'];
|
|
$_SESSION['position'] = 'เจ้าหน้าที่'; // simplified
|
|
$_SESSION['ip'] = $_SERVER['REMOTE_ADDR'];
|
|
|
|
$client_os = getOS();
|
|
$client_browser = getBrowser();
|
|
system_log($conn2, $username, 'LOGIN_SUCCESS', [
|
|
'message' => 'Logged in successfully',
|
|
'os' => $client_os,
|
|
'browser' => $client_browser
|
|
]);
|
|
clear_failed_logins($conn2, $ip_address);
|
|
|
|
header("Location: dashboard.php");
|
|
exit();
|
|
}
|
|
} else {
|
|
record_failed_login($conn2, $ip_address, $username);
|
|
$error_msg = "รหัสผ่านไม่ถูกต้อง<br><span class='text-[11px] font-normal text-rose-400 mt-1 block'>กรุณาตรวจสอบตัวสะกด พิมพ์เล็ก/ใหญ่ แล้วลองอีกครั้ง</span>";
|
|
system_log($conn2, $username, 'LOGIN_FAILED', ['reason' => 'Invalid password']);
|
|
}
|
|
} else {
|
|
record_failed_login($conn2, $ip_address, $username);
|
|
$error_msg = "ไม่พบชื่อผู้ใช้งานนี้<br><span class='text-[11px] font-normal text-rose-400 mt-1 block'>ไม่มีชื่อผู้ใช้งานนี้ในระบบ HOSxP กรุณาตรวจสอบ Username อีกครั้ง</span>";
|
|
system_log($conn2, $username, 'LOGIN_FAILED', ['reason' => 'Username not found']);
|
|
}
|
|
}
|
|
}
|
|
?>
|
|
<!DOCTYPE html>
|
|
<html lang="th">
|
|
|
|
<head>
|
|
<?php require_once("../components/head.php"); ?>
|
|
<style>
|
|
/* Hide number arrows */
|
|
input[type=number]::-webkit-inner-spin-button,
|
|
input[type=number]::-webkit-outer-spin-button {
|
|
-webkit-appearance: none;
|
|
margin: 0;
|
|
}
|
|
|
|
input[type=number] {
|
|
-moz-appearance: textfield;
|
|
}
|
|
|
|
/* Custom animations */
|
|
@keyframes blob {
|
|
0% {
|
|
transform: translate(0px, 0px) scale(1);
|
|
}
|
|
|
|
33% {
|
|
transform: translate(30px, -50px) scale(1.1);
|
|
}
|
|
|
|
66% {
|
|
transform: translate(-20px, 20px) scale(0.9);
|
|
}
|
|
|
|
100% {
|
|
transform: translate(0px, 0px) scale(1);
|
|
}
|
|
}
|
|
|
|
.animate-blob {
|
|
animation: blob 7s infinite;
|
|
}
|
|
|
|
.animation-delay-2000 {
|
|
animation-delay: 2s;
|
|
}
|
|
|
|
.animation-delay-4000 {
|
|
animation-delay: 4s;
|
|
}
|
|
|
|
@keyframes shine {
|
|
0% {
|
|
left: -100%;
|
|
opacity: 0;
|
|
}
|
|
|
|
20% {
|
|
opacity: 1;
|
|
}
|
|
|
|
50% {
|
|
left: 100%;
|
|
opacity: 0;
|
|
}
|
|
|
|
100% {
|
|
left: 100%;
|
|
opacity: 0;
|
|
}
|
|
}
|
|
|
|
.animate-shine {
|
|
position: absolute;
|
|
top: 0;
|
|
width: 50%;
|
|
height: 100%;
|
|
background: linear-gradient(to right, rgba(255, 255, 255, 0) 0%, rgba(255, 255, 255, 0.8) 50%, rgba(255, 255, 255, 0) 100%);
|
|
animation: shine 4s infinite;
|
|
transform: skewX(-20deg);
|
|
z-index: 20;
|
|
pointer-events: none;
|
|
}
|
|
</style>
|
|
</head>
|
|
|
|
<body class="flex items-center justify-center min-h-screen bg-[#cfe6d8] text-slate-800 font-sans antialiased relative overflow-hidden">
|
|
|
|
<!-- Background Blobs -->
|
|
<div class="absolute inset-0 overflow-hidden pointer-events-none z-0">
|
|
<div class="absolute top-[-10%] left-[-10%] w-[50vw] h-[50vw] rounded-full bg-emerald-200/60 mix-blend-multiply filter blur-[100px] opacity-70 animate-blob"></div>
|
|
<div class="absolute top-[20%] right-[-10%] w-[40vw] h-[40vw] rounded-full bg-teal-200/60 mix-blend-multiply filter blur-[100px] opacity-70 animate-blob animation-delay-2000"></div>
|
|
<div class="absolute bottom-[-20%] left-[20%] w-[60vw] h-[60vw] rounded-full bg-green-200/60 mix-blend-multiply filter blur-[100px] opacity-70 animate-blob animation-delay-4000"></div>
|
|
</div>
|
|
|
|
<!-- Main Card -->
|
|
<div class="relative w-full max-w-[460px] bg-white/40 backdrop-blur-2xl border-2 border-white/70 shadow-[0_8px_32px_rgba(16,185,129,0.15)] rounded-[2.5rem] p-8 sm:p-12 text-center m-4 z-10 before:absolute before:inset-0 before:bg-gradient-to-b before:from-white/50 before:to-transparent before:rounded-[2.5rem] before:pointer-events-none">
|
|
|
|
<!-- Logo -->
|
|
<div class="relative w-28 h-28 mx-auto mb-6 group">
|
|
<div class="absolute inset-0 bg-emerald-400/30 rounded-full filter blur-xl group-hover:bg-emerald-400/40 transition-colors duration-500"></div>
|
|
<div class="relative w-full h-full bg-white/80 backdrop-blur-md rounded-full shadow-[0_4px_20px_rgba(16,185,129,0.2)] border-[3px] border-white flex items-center justify-center p-0.5 overflow-hidden">
|
|
<img src="../img/logo.png" alt="Logo" class="w-full h-full object-contain drop-shadow-sm rounded-full relative z-10">
|
|
<div class="animate-shine"></div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Titles -->
|
|
<h2 class="text-3xl font-extrabold text-slate-800 mb-1 tracking-tight">โรงพยาบาลเกาะสมุย</h2>
|
|
<p class="text-slate-500 font-medium tracking-wide mb-8">ระบบฐานข้อมูล HOSxP Data</p>
|
|
|
|
<div class="mb-8" style="display: none;">
|
|
<p class="text-emerald-600 font-bold text-[15px] tracking-wide mb-0.5">ยินดีต้อนรับเข้าสู่ระบบ</p>
|
|
<p class="text-slate-400 text-sm">กรุณาเข้าสู่ระบบเพื่อใช้งาน</p>
|
|
</div>
|
|
|
|
<?php if (!empty($error_msg)): ?>
|
|
<div class="bg-rose-50 text-rose-600 px-4 py-3 rounded-2xl mb-6 text-sm text-left font-medium flex gap-3 items-start border border-rose-100 shadow-sm animate-enter">
|
|
<svg class="w-5 h-5 shrink-0 mt-0.5 text-rose-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z"></path>
|
|
</svg>
|
|
<div>
|
|
<?php echo $error_msg; ?>
|
|
</div>
|
|
</div>
|
|
<?php endif; ?>
|
|
|
|
|
|
<form id="loginForm" method="post" action="login.php" class="text-left space-y-5" autocomplete="off">
|
|
<input type="hidden" name="csrf_token" value="<?php echo generate_csrf_token(); ?>">
|
|
|
|
<!-- Honeypot / Fake inputs to defeat browser autofill -->
|
|
<input type="text" style="display:none" name="fake_username" autocomplete="username">
|
|
<input type="password" style="display:none" name="fake_password" autocomplete="current-password">
|
|
|
|
<!-- Username Input -->
|
|
<div>
|
|
<label class="block text-[13px] font-bold text-slate-600 mb-2">ชื่อผู้ใช้งาน (Username)</label>
|
|
<div class="relative">
|
|
<div class="absolute inset-y-0 left-0 pl-4 flex items-center pointer-events-none text-emerald-600">
|
|
<svg class="w-5 h-5" fill="currentColor" viewBox="0 0 20 20">
|
|
<path fill-rule="evenodd" d="M10 9a3 3 0 100-6 3 3 0 000 6zm-7 9a7 7 0 1114 0H3z" clip-rule="evenodd"></path>
|
|
</svg>
|
|
</div>
|
|
<input type="text" name="username" class="w-full pl-11 pr-4 py-3.5 bg-white/80 border border-white focus:border-emerald-400 focus:ring-4 focus:ring-emerald-500/20 rounded-2xl outline-none transition-all font-medium text-slate-700 placeholder-slate-400 shadow-[inset_0_3px_8px_rgba(0,0,0,0.06),0_1px_2px_rgba(255,255,255,0.9)]" placeholder="บัญชีผู้ใช้" required autocomplete="new-password">
|
|
</div>
|
|
<p id="usernameWarning" class="text-rose-500 text-[11px] mt-1.5 ml-1 hidden font-medium animate-pulse">⚠️ ตรวจพบภาษาไทย กรุณาเปลี่ยนแป้นพิมพ์เป็นภาษาอังกฤษ</p>
|
|
</div>
|
|
|
|
<!-- Password Input -->
|
|
<div>
|
|
<label class="block text-[13px] font-bold text-slate-600 mb-2">รหัสผ่าน (Password)</label>
|
|
<div class="relative">
|
|
<div class="absolute inset-y-0 left-0 pl-4 flex items-center pointer-events-none text-emerald-600">
|
|
<svg class="w-5 h-5" fill="currentColor" viewBox="0 0 20 20">
|
|
<path fill-rule="evenodd" d="M5 9V7a5 5 0 0110 0v2a2 2 0 012 2v5a2 2 0 01-2 2H5a2 2 0 01-2-2v-5a2 2 0 012-2zm8-2v2H7V7a3 3 0 016 0z" clip-rule="evenodd"></path>
|
|
</svg>
|
|
</div>
|
|
<input type="password" id="pwdInput" name="password" class="w-full pl-11 pr-12 py-3.5 bg-white/80 border border-white focus:border-emerald-400 focus:ring-4 focus:ring-emerald-500/20 rounded-2xl outline-none transition-all font-medium text-slate-700 placeholder-slate-400 shadow-[inset_0_3px_8px_rgba(0,0,0,0.06),0_1px_2px_rgba(255,255,255,0.9)]" placeholder="รหัสผ่าน" required autocomplete="new-password">
|
|
<button type="button" tabindex="-1" onclick="const p = document.getElementById('pwdInput'); p.type = p.type === 'password' ? 'text' : 'password';" class="absolute inset-y-0 right-0 pr-4 flex items-center text-emerald-600 hover:text-emerald-700 transition-colors">
|
|
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"></path>
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z"></path>
|
|
</svg>
|
|
</button>
|
|
</div>
|
|
<p id="pwdWarning" class="text-rose-500 text-[11px] mt-1.5 ml-1 hidden font-medium animate-pulse">⚠️ ตรวจพบภาษาไทย กรุณาเปลี่ยนแป้นพิมพ์เป็นภาษาอังกฤษ</p>
|
|
</div>
|
|
|
|
<!-- 2FA Input -->
|
|
<div>
|
|
<label class="flex items-center text-[13px] font-bold text-slate-600 mb-2">
|
|
รหัส 2FA
|
|
<span class="ml-1.5 bg-white rounded-full shadow-[0_1px_3px_rgba(0,0,0,0.1)] p-0.5 inline-flex" title="Google Authenticator">
|
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100" class="w-[18px] h-[18px] animate-spin" style="animation-duration: 6s; animation-timing-function: linear;">
|
|
<line x1="50" y1="12" x2="50" y2="88" stroke="#34A853" stroke-width="16" stroke-linecap="round" />
|
|
<line x1="23" y1="23" x2="77" y2="77" stroke="#FBBC05" stroke-width="16" stroke-linecap="round" />
|
|
<line x1="12" y1="50" x2="88" y2="50" stroke="#4285F4" stroke-width="16" stroke-linecap="round" />
|
|
<line x1="77" y1="23" x2="23" y2="77" stroke="#EA4335" stroke-width="16" stroke-linecap="round" />
|
|
<circle cx="50" cy="50" r="14" fill="#ffffff" />
|
|
</svg>
|
|
</span>
|
|
</label>
|
|
<div class="flex gap-2 justify-between" id="otp-container">
|
|
<?php for ($i = 0; $i < 6; $i++): ?>
|
|
<input type="number" name="otp[]" maxlength="1" class="w-[50px] h-[56px] text-center text-2xl font-extrabold text-emerald-700 rounded-2xl border border-white bg-white/80 focus:outline-none focus:border-emerald-400 focus:ring-4 focus:ring-emerald-500/20 transition-all shadow-[inset_0_3px_8px_rgba(0,0,0,0.06),0_1px_2px_rgba(255,255,255,0.9)] otp-input" autocomplete="off" oninput="if(this.value.length > 1) this.value = this.value.slice(0,1);">
|
|
<?php endfor; ?>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Submit Button -->
|
|
<button type="submit" id="submitBtn" class="w-full bg-gradient-to-r from-emerald-500 to-green-600 hover:from-emerald-600 hover:to-green-700 text-white font-bold py-4 px-6 rounded-full shadow-[0_8px_20px_rgba(16,185,129,0.4),inset_0_2px_0_rgba(255,255,255,0.3)] hover:shadow-[0_10px_25px_rgba(16,185,129,0.5),inset_0_2px_0_rgba(255,255,255,0.4)] transition-all flex items-center justify-center gap-2 mt-8 transform hover:-translate-y-0.5 border border-emerald-400/50">
|
|
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M11 16l-4-4m0 0l4-4m-4 4h14m-5 4v1a3 3 0 01-3 3H6a3 3 0 01-3-3V7a3 3 0 013-3h7a3 3 0 013 3v1"></path>
|
|
</svg>
|
|
เข้าสู่ระบบ
|
|
</button>
|
|
</form>
|
|
|
|
<!-- Footer in Card -->
|
|
<div class="mt-8 pt-6 border-t border-emerald-500/10 flex items-center justify-center gap-2 text-[11px] font-semibold text-emerald-600/80">
|
|
<svg class="w-4 h-4" fill="currentColor" viewBox="0 0 20 20">
|
|
<path fill-rule="evenodd" d="M2.166 4.999A11.954 11.954 0 0010 1.944 11.954 11.954 0 0017.834 5c.11.65.166 1.32.166 2.001 0 5.225-3.34 9.67-8 11.317C5.34 16.67 2 12.225 2 7c0-.682.057-1.35.166-2.001zm11.541 3.708a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z" clip-rule="evenodd"></path>
|
|
</svg>
|
|
ระบบมีความปลอดภัยสูง ข้อมูลของท่านได้รับการปกป้อง
|
|
</div>
|
|
|
|
</div>
|
|
|
|
<!-- Absolute Footer -->
|
|
<?php
|
|
$app_settings = get_app_settings();
|
|
$changelogs = get_changelogs();
|
|
$latest_version = !empty($changelogs) ? $changelogs[0]['version'] : '';
|
|
$footer_html = str_replace('{YEAR}', date('Y'), $app_settings['footer_text'] ?? '');
|
|
?>
|
|
<div class="absolute bottom-6 left-0 w-full text-center text-xs text-emerald-800/50 font-medium z-10 flex flex-col sm:flex-row items-center justify-center gap-2 sm:gap-3">
|
|
<?php if (!empty($latest_version)): ?>
|
|
<span><?php echo htmlspecialchars($latest_version); ?></span>
|
|
<span class="text-emerald-800/30 hidden sm:inline">|</span>
|
|
<?php endif; ?>
|
|
<span><?php echo $footer_html; ?></span>
|
|
</div>
|
|
|
|
<script>
|
|
document.addEventListener('DOMContentLoaded', function() {
|
|
const otpInputs = document.querySelectorAll('.otp-input');
|
|
const loginForm = document.getElementById('loginForm');
|
|
|
|
// Language check for Username and Password
|
|
const checkLang = (inputEl, warningEl) => {
|
|
inputEl.addEventListener('input', (e) => {
|
|
// Check if value contains non-ASCII characters (e.g. Thai)
|
|
if (/[^\x00-\x7F]/.test(e.target.value)) {
|
|
warningEl.classList.remove('hidden');
|
|
} else {
|
|
warningEl.classList.add('hidden');
|
|
}
|
|
});
|
|
};
|
|
const usernameInput = document.querySelector('input[name="username"]');
|
|
const usernameWarning = document.getElementById('usernameWarning');
|
|
if (usernameInput && usernameWarning) checkLang(usernameInput, usernameWarning);
|
|
|
|
const pwdInput = document.getElementById('pwdInput');
|
|
const pwdWarning = document.getElementById('pwdWarning');
|
|
if (pwdInput && pwdWarning) checkLang(pwdInput, pwdWarning);
|
|
|
|
function checkAndSubmit() {
|
|
let allFilled = true;
|
|
otpInputs.forEach(inp => {
|
|
if (!inp.value) allFilled = false;
|
|
});
|
|
const uname = document.querySelector('input[name="username"]').value;
|
|
const pwd = document.querySelector('input[name="password"]').value;
|
|
if (allFilled && uname && pwd) {
|
|
// Optional: show a loading state on the button
|
|
const btn = document.getElementById('submitBtn');
|
|
if (btn) {
|
|
btn.innerHTML = '<svg class="animate-spin -ml-1 mr-2 h-5 w-5 text-white" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"><circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle><path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path></svg> กำลังเข้าสู่ระบบ...';
|
|
btn.disabled = true;
|
|
}
|
|
loginForm.submit();
|
|
}
|
|
}
|
|
|
|
otpInputs.forEach((input, index) => {
|
|
input.addEventListener('input', (e) => {
|
|
if (e.target.value.length === 1) {
|
|
if (index < otpInputs.length - 1) {
|
|
otpInputs[index + 1].focus();
|
|
} else {
|
|
checkAndSubmit();
|
|
}
|
|
}
|
|
});
|
|
|
|
input.addEventListener('keydown', (e) => {
|
|
if (e.key === 'Backspace' && !e.target.value && index > 0) {
|
|
otpInputs[index - 1].focus();
|
|
}
|
|
});
|
|
|
|
// Allow pasting 6 digits
|
|
input.addEventListener('paste', (e) => {
|
|
e.preventDefault();
|
|
const pastedData = e.clipboardData.getData('text').slice(0, 6);
|
|
if (/^\d+$/.test(pastedData)) {
|
|
pastedData.split('').forEach((char, i) => {
|
|
if (i < otpInputs.length) {
|
|
otpInputs[i].value = char;
|
|
}
|
|
});
|
|
const focusIndex = Math.min(pastedData.length, 5);
|
|
otpInputs[focusIndex].focus();
|
|
if (pastedData.length === 6) {
|
|
checkAndSubmit();
|
|
}
|
|
}
|
|
});
|
|
});
|
|
});
|
|
</script>
|
|
</body>
|
|
|
|
</html>
|