QLVB / login.html
hoangthiencm's picture
Upload 14 files
ef54470 verified
<!DOCTYPE html>
<html lang="vi">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Đăng Nhập - Hệ thống QLVB</title>
<script src="https://cdn.tailwindcss.com"></script>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
<style>
body {
background: linear-gradient(135deg, #f0f4f8 0%, #d9e2ec 100%);
min-height: 100vh;
display: flex;
align-items: center;
justify-content: center;
font-family: 'Segoe UI', sans-serif;
}
</style>
</head>
<body class="p-4">
<div class="bg-white p-8 rounded-2xl shadow-xl w-full max-w-md border border-slate-200 relative">
<!-- Loading Overlay -->
<div id="loading" class="hidden absolute inset-0 bg-white/80 z-10 flex items-center justify-center rounded-2xl">
<i class="fas fa-spinner fa-spin text-blue-600 text-3xl"></i>
</div>
<div class="text-center mb-8">
<div class="inline-block p-3 rounded-full bg-blue-600 text-white mb-3">
<i class="fas fa-file-contract text-3xl"></i>
</div>
<h2 class="text-2xl font-bold text-slate-800">Hệ Thống QLVB</h2>
<p class="text-slate-500 text-sm">Vui lòng đăng nhập để truy cập dữ liệu</p>
</div>
<!-- TABS -->
<div class="flex mb-6 border-b border-gray-200">
<button id="tabLogin" onclick="switchTab('login')" class="flex-1 py-2 text-blue-600 font-bold border-b-2 border-blue-600">Đăng Nhập</button>
<button id="tabRegister" onclick="switchTab('register')" class="flex-1 py-2 text-slate-500 font-medium hover:text-blue-500">Đăng Ký</button>
</div>
<!-- FORM AUTH -->
<form id="authForm" onsubmit="handleAuth(event)">
<div class="mb-4">
<label class="block text-sm font-medium text-slate-700 mb-1">Email</label>
<div class="relative">
<span class="absolute left-3 top-2.5 text-slate-400"><i class="fas fa-envelope"></i></span>
<input type="email" id="email" required class="pl-10 w-full p-2 border border-slate-300 rounded focus:ring-2 focus:ring-blue-500 outline-none" placeholder="email@example.com">
</div>
</div>
<div class="mb-2">
<label class="block text-sm font-medium text-slate-700 mb-1">Mật khẩu</label>
<div class="relative">
<span class="absolute left-3 top-2.5 text-slate-400"><i class="fas fa-lock"></i></span>
<input type="password" id="password" required class="pl-10 w-full p-2 border border-slate-300 rounded focus:ring-2 focus:ring-blue-500 outline-none" placeholder="••••••••">
</div>
</div>
<!-- Link Quên mật khẩu (Chỉ hiện khi ở tab Login) -->
<div id="forgotPasswordLink" class="text-right mb-6">
<a href="#" onclick="showResetModal(event)" class="text-xs text-blue-600 hover:text-blue-800 font-medium">Quên mật khẩu?</a>
</div>
<!-- Spacer cho tab Register để layout không nhảy -->
<div id="registerSpacer" class="mb-6 hidden"></div>
<button type="submit" id="submitBtn" class="w-full bg-blue-600 hover:bg-blue-700 text-white font-bold py-2.5 rounded shadow transition flex justify-center items-center">
<span>Đăng Nhập</span>
</button>
<p id="message" class="text-center text-sm mt-4 hidden font-medium px-2 py-2 rounded border"></p>
</form>
<!-- FORM RESET PASSWORD (MODAL ẨN) -->
<div id="resetModal" class="hidden absolute inset-0 bg-white z-20 p-8 rounded-2xl flex flex-col justify-center">
<h3 class="text-xl font-bold text-center mb-4 text-slate-800">Đặt lại mật khẩu</h3>
<p class="text-sm text-center text-slate-500 mb-6">Nhập email của bạn để nhận liên kết đặt lại mật khẩu.</p>
<form onsubmit="handleResetPassword(event)">
<div class="mb-4">
<label class="block text-sm font-medium text-slate-700 mb-1">Email đăng ký</label>
<div class="relative">
<span class="absolute left-3 top-2.5 text-slate-400"><i class="fas fa-envelope"></i></span>
<input type="email" id="resetEmail" required class="pl-10 w-full p-2 border border-slate-300 rounded focus:ring-2 focus:ring-blue-500 outline-none">
</div>
</div>
<button type="submit" class="w-full bg-yellow-500 hover:bg-yellow-600 text-white font-bold py-2.5 rounded shadow transition mb-3">
Gửi yêu cầu
</button>
<button type="button" onclick="hideResetModal()" class="w-full bg-slate-100 hover:bg-slate-200 text-slate-600 font-medium py-2.5 rounded transition">
Quay lại
</button>
</form>
<p id="resetMessage" class="text-center text-sm mt-4 hidden font-medium px-2 py-2 rounded border"></p>
</div>
</div>
<!-- Import Config (Cùng cấp) -->
<script src="config.js"></script>
<!-- Firebase SDK -->
<script type="module">
import { initializeApp } from "https://www.gstatic.com/firebasejs/10.7.1/firebase-app.js";
import { getAuth, signInWithEmailAndPassword, createUserWithEmailAndPassword, signOut, sendPasswordResetEmail } from "https://www.gstatic.com/firebasejs/10.7.1/firebase-auth.js";
import { getFirestore, doc, setDoc, getDoc, serverTimestamp } from "https://www.gstatic.com/firebasejs/10.7.1/firebase-firestore.js";
const app = initializeApp(CONFIG.FIREBASE_CONFIG);
const auth = getAuth(app);
const db = getFirestore(app);
const USERS_COL = CONFIG.USERS_COLLECTION || "users";
window.isLoginMode = true;
window.switchTab = function(mode) {
window.isLoginMode = (mode === 'login');
const tabLogin = document.getElementById('tabLogin');
const tabRegister = document.getElementById('tabRegister');
const btnSpan = document.querySelector('#submitBtn span');
const msg = document.getElementById('message');
const forgotLink = document.getElementById('forgotPasswordLink');
const spacer = document.getElementById('registerSpacer');
msg.classList.add('hidden');
document.getElementById('authForm').reset();
if(window.isLoginMode) {
tabLogin.className = "flex-1 py-2 text-blue-600 font-bold border-b-2 border-blue-600";
tabRegister.className = "flex-1 py-2 text-slate-500 font-medium hover:text-blue-500";
if(btnSpan) btnSpan.textContent = "Đăng Nhập";
forgotLink.classList.remove('hidden');
spacer.classList.add('hidden');
} else {
tabRegister.className = "flex-1 py-2 text-blue-600 font-bold border-b-2 border-blue-600";
tabLogin.className = "flex-1 py-2 text-slate-500 font-medium hover:text-blue-500";
if(btnSpan) btnSpan.textContent = "Đăng Ký Tài Khoản";
forgotLink.classList.add('hidden');
spacer.classList.remove('hidden');
}
}
// --- AUTH MAIN LOGIC ---
window.handleAuth = async function(e) {
e.preventDefault();
const email = document.getElementById('email').value;
const password = document.getElementById('password').value;
const msg = document.getElementById('message');
const loading = document.getElementById('loading');
loading.classList.remove('hidden');
msg.classList.add('hidden');
try {
if (window.isLoginMode) {
// --- XỬ LÝ ĐĂNG NHẬP ---
const userCredential = await signInWithEmailAndPassword(auth, email, password);
const user = userCredential.user;
// Kiểm tra Firestore
const userRef = doc(db, USERS_COL, user.uid);
const userSnap = await getDoc(userRef);
if (userSnap.exists()) {
const userData = userSnap.data();
if (userData.status === 'pending') {
await signOut(auth);
throw new Error("Tài khoản đang chờ Admin duyệt!");
} else if (userData.status === 'blocked') {
await signOut(auth);
throw new Error("Tài khoản đã bị khóa!");
}
} else {
// User tồn tại trong Auth nhưng mất trong Firestore -> Tự động khôi phục
await setDoc(userRef, {
email: user.email,
status: 'pending',
created_at: serverTimestamp()
});
await signOut(auth);
throw new Error("Đã khôi phục tài khoản cũ. Vui lòng chờ Admin duyệt lại.");
}
window.location.href = 'index.html';
} else {
// --- XỬ LÝ ĐĂNG KÝ ---
try {
const userCredential = await createUserWithEmailAndPassword(auth, email, password);
const user = userCredential.user;
await setDoc(doc(db, USERS_COL, user.uid), {
email: user.email,
status: 'pending',
created_at: serverTimestamp()
});
await signOut(auth);
showMessage("Đăng ký thành công! Vui lòng chờ Admin kích hoạt.", "success");
setTimeout(() => window.switchTab('login'), 2000);
} catch (regError) {
if (regError.code === 'auth/email-already-in-use') {
// Thử đăng nhập ngầm để check pass
try {
const userCredential = await signInWithEmailAndPassword(auth, email, password);
const user = userCredential.user;
const userRef = doc(db, USERS_COL, user.uid);
const userSnap = await getDoc(userRef);
if (!userSnap.exists()) {
await setDoc(userRef, {
email: user.email,
status: 'pending',
created_at: serverTimestamp()
});
await signOut(auth);
showMessage("Tài khoản cũ đã được khôi phục. Vui lòng chờ Admin.", "success");
setTimeout(() => window.switchTab('login'), 3000);
return;
} else {
await signOut(auth);
throw new Error("Email này đã được sử dụng.");
}
} catch (loginErr) {
if(loginErr.code === 'auth/wrong-password' || loginErr.code === 'auth/invalid-credential') {
// Gợi ý reset pass
throw new Error("Email đã tồn tại & sai mật khẩu. Vui lòng dùng 'Quên mật khẩu?'.");
} else {
throw loginErr;
}
}
} else {
throw regError;
}
}
}
} catch (error) {
console.error(error);
let text = error.message;
if(error.code === 'auth/invalid-credential' || error.code === 'auth/wrong-password') text = "Email hoặc mật khẩu không đúng.";
if(error.code === 'auth/email-already-in-use') text = "Email này đã được đăng ký.";
if(error.code === 'auth/weak-password') text = "Mật khẩu quá yếu (tối thiểu 6 ký tự).";
if(error.code === 'auth/too-many-requests') text = "Quá nhiều lần thử sai. Vui lòng thử lại sau.";
showMessage(text, "error");
} finally {
loading.classList.add('hidden');
}
}
// --- RESET PASSWORD LOGIC ---
window.showResetModal = function(e) {
e.preventDefault();
// Tự điền email nếu đã nhập ở form chính
document.getElementById('resetEmail').value = document.getElementById('email').value;
document.getElementById('resetModal').classList.remove('hidden');
document.getElementById('resetMessage').classList.add('hidden');
}
window.hideResetModal = function() {
document.getElementById('resetModal').classList.add('hidden');
}
window.handleResetPassword = async function(e) {
e.preventDefault();
const email = document.getElementById('resetEmail').value;
const msgBox = document.getElementById('resetMessage');
const loading = document.getElementById('loading');
if(!email) return;
loading.classList.remove('hidden');
msgBox.classList.add('hidden');
try {
await sendPasswordResetEmail(auth, email);
msgBox.textContent = "Đã gửi email! Hãy kiểm tra hộp thư (cả mục Spam) để đặt lại mật khẩu.";
msgBox.className = "text-center text-sm mt-4 text-green-600 bg-green-50 px-2 py-2 rounded border border-green-200 block";
} catch (error) {
console.error(error);
let text = error.message;
if(error.code === 'auth/user-not-found') text = "Email này chưa được đăng ký trong hệ thống.";
if(error.code === 'auth/invalid-email') text = "Email không hợp lệ.";
msgBox.textContent = text;
msgBox.className = "text-center text-sm mt-4 text-red-600 bg-red-50 px-2 py-2 rounded border border-red-200 block";
} finally {
loading.classList.add('hidden');
}
}
function showMessage(text, type) {
const msg = document.getElementById('message');
msg.textContent = text;
msg.className = "text-center text-sm mt-4 font-medium px-2 py-2 rounded border block";
if (type === 'error') {
msg.classList.add('text-red-600', 'bg-red-50', 'border-red-200');
} else if (type === 'success') {
msg.classList.add('text-green-600', 'bg-green-50', 'border-green-200');
} else {
msg.classList.add('text-yellow-600', 'bg-yellow-50', 'border-yellow-200');
}
}
auth.onAuthStateChanged(async (user) => {
if (user) {
const userSnap = await getDoc(doc(db, USERS_COL, user.uid));
if (userSnap.exists() && userSnap.data().status === 'active') {
window.location.href = 'index.html';
}
}
});
</script>
</body>
</html>