Spaces:
Running
Running
| <html lang="vi" data-theme="dark"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <meta name="description" content="Đăng nhập tài khoản của bạn một cách dễ dàng và an toàn"> | |
| <title>Đăng Nhập</title> | |
| <link href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css" rel="stylesheet"> | |
| <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600&display=swap" rel="stylesheet"> | |
| <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.2/css/all.min.css"> | |
| <link href="https://cdnjs.cloudflare.com/ajax/libs/jarallax/2.2.1/jarallax.min.css" rel="stylesheet"> | |
| <style> | |
| :root { | |
| --primary-color: #6366F1; | |
| --error-color: #dc3545; | |
| --success-color: #28a745; | |
| --text-muted: #adb5bd; | |
| --input-text: #000000; /* Default input text color */ | |
| } | |
| [data-theme="dark"] { | |
| --card-bg: rgba(13, 27, 42, 0.95); | |
| --input-bg: #2c3e50; | |
| --input-border: #3b4a5e; | |
| --input-focus-bg: #34495e; | |
| --alert-bg: #1b263b; | |
| --input-text: #ffffff; /* White text for dark mode */ | |
| } | |
| [data-theme="light"] { | |
| --card-bg: rgba(255, 255, 255, 0.95); | |
| --input-bg: #ffffff; | |
| --input-border: #ced4da; | |
| --input-focus-bg: #f8f9fa; | |
| --alert-bg: #e9ecef; | |
| --text-muted: #6c757d; | |
| --input-text: #000000; /* Black text for light mode */ | |
| } | |
| body { | |
| background-color: #000000; | |
| min-height: 100vh; | |
| margin: 0; | |
| display: flex; | |
| justify-content: center; | |
| align-items: center; | |
| } | |
| .form-label { | |
| color: var(--text-muted); | |
| font-size: 0.85rem; | |
| font-weight: 500; | |
| margin-bottom: 0.5rem; | |
| } | |
| .jarallax { | |
| position: relative; | |
| z-index: 0; | |
| background-color: #000000; | |
| min-height: 100vh; | |
| width: 100%; | |
| display: flex; | |
| justify-content: center; | |
| align-items: center; | |
| } | |
| .text-muted { | |
| color: #cbd2d7 ; | |
| font-size: 0.85rem; | |
| } | |
| .text-muted, .form-check-label { | |
| color: #cbd2d7 ; | |
| font-size: 0.85rem; | |
| } | |
| .btn-google { | |
| background-color: #6366F1; | |
| color: #fff; | |
| border: none; | |
| padding: 0.5rem; | |
| width: 100%; | |
| margin-bottom: 1rem; | |
| display: flex; | |
| align-items: center; | |
| justify-content: center; | |
| } | |
| .btn-google img { | |
| width: 20px; | |
| margin-right: 0.5rem; | |
| } | |
| .jarallax-img { | |
| position: absolute; | |
| object-fit: cover; | |
| top: 0; | |
| left: 0; | |
| width: 100%; | |
| height: 100%; | |
| z-index: -1; | |
| } | |
| .login-container { | |
| background: var(--card-bg); | |
| padding: 2.5rem; | |
| border-radius: 12px; | |
| width: 100%; | |
| max-width: 450px; | |
| box-shadow: 0 4px 20px rgba(0, 0, 0, 0.3); | |
| backdrop-filter: blur(5px); | |
| transition: transform 0.3s ease; | |
| position: relative; | |
| z-index: 1; | |
| } | |
| .login-container:hover { | |
| transform: translateY(-2px); | |
| } | |
| .login-container h2 { | |
| font-size: 1.75rem; | |
| font-weight: 600; | |
| margin-bottom: 1rem; | |
| text-align: center; | |
| color: #fff; | |
| } | |
| .description { | |
| font-size: 0.9rem; | |
| text-align: center; | |
| margin-bottom: 2rem; | |
| line-height: 1.5; | |
| color: white; | |
| } | |
| .form-label { | |
| font-size: 0.85rem; | |
| font-weight: 500; | |
| margin-bottom: 0.5rem; | |
| } | |
| .form-control { | |
| background-color: var(--input-bg); | |
| border: 1px solid var(--input-border); | |
| color: var(--input-text); /* Use theme-based text color */ | |
| border-radius: 6px; | |
| padding: 0.75rem; | |
| transition: all 0.2s ease; | |
| } | |
| .form-control:focus { | |
| background-color: var(--input-focus-bg); | |
| border-color: var(--primary-color); | |
| box-shadow: 0 0 0 3px rgba(99, 102, 241, 0.2); | |
| outline: none; | |
| } | |
| .form-control::placeholder { | |
| color: var(--input-text); /* White placeholder text in dark mode */ | |
| opacity: 0.7; /* Slightly reduce opacity for better UX */ | |
| } | |
| .form-control.is-invalid { | |
| border-color: var(--error-color); | |
| background-image: none; | |
| } | |
| .form-control[readonly] { | |
| color: var(--input-text); /* Ensure readonly input text is white in dark mode */ | |
| opacity: 0.9; /* Slightly reduce opacity for readonly fields */ | |
| } | |
| .invalid-feedback { | |
| font-size: 0.8rem; | |
| color: var(--error-color); | |
| } | |
| .alert { | |
| background-color: var(--alert-bg); | |
| color: var(--text-muted); | |
| border: none; | |
| margin-bottom: 1rem; | |
| font-size: 0.9rem; | |
| border-radius: 6px; | |
| } | |
| .alert-success { | |
| background-color: var(--success-color); | |
| color: #fff; | |
| } | |
| .alert-danger { | |
| background-color: var(--error-color); | |
| color: #fff; | |
| } | |
| .btn-submit, | |
| .btn-login, | |
| .btn-google { | |
| background: var(--primary-color); | |
| color: #fff; | |
| border: none; | |
| padding: 0.85rem; | |
| border-radius: 6px; | |
| width: 100%; | |
| font-weight: 500; | |
| transition: all 0.2s ease; | |
| position: relative; | |
| display: flex; | |
| align-items: center; | |
| justify-content: center; | |
| } | |
| .btn-submit:hover:not(:disabled), | |
| .btn-login:hover:not(:disabled), | |
| .btn-google:hover:not(:disabled) { | |
| background: #4f46e5; | |
| transform: translateY(-1px); | |
| } | |
| .btn-login:hover { | |
| color: #fff; | |
| } | |
| .btn-submit:disabled, | |
| .btn-login:disabled, | |
| .btn-google:disabled { | |
| opacity: 0.6; | |
| cursor: not-allowed; | |
| } | |
| .btn-google img { | |
| width: 20px; | |
| margin-right: 0.5rem; | |
| } | |
| .text-muted { | |
| font-size: 0.85rem; | |
| } | |
| .theme-toggle-btn { | |
| position: absolute; | |
| top: 1rem; | |
| right: 1rem; | |
| background: transparent; | |
| border: none; | |
| color: var(--text-muted); | |
| font-size: 1.2rem; | |
| cursor: pointer; | |
| transition: color 0.2s ease; | |
| } | |
| .theme-toggle-btn:hover { | |
| color: var(--primary-color); | |
| } | |
| .loading-spinner { | |
| display: none; | |
| border: 3px solid #f3f3f3; | |
| border-top: 3px solid var(--primary-color); | |
| border-radius: 50%; | |
| width: 20px; | |
| height: 20px; | |
| animation: spin 1s linear infinite; | |
| position: absolute; | |
| right: 10px; | |
| top: 50%; | |
| transform: translateY(-50%); | |
| } | |
| @keyframes spin { | |
| 0% { | |
| transform: translateY(-50%) rotate(0deg); | |
| } | |
| 100% { | |
| transform: translateY(-50%) rotate(360deg); | |
| } | |
| } | |
| @media (max-width: 576px) { | |
| .login-container { | |
| margin: 1.5rem; | |
| padding: 1.5rem; | |
| } | |
| .login-container h2 { | |
| font-size: 1.5rem; | |
| } | |
| .description { | |
| font-size: 0.85rem; | |
| } | |
| .btn-submit, | |
| .btn-login, | |
| .btn-google { | |
| padding: 0.75rem; | |
| } | |
| } | |
| .sr-only { | |
| position: absolute; | |
| width: 1px; | |
| height: 1px; | |
| padding: 0; | |
| margin: -1px; | |
| overflow: hidden; | |
| clip: rect(0, 0, 0, 0); | |
| border: 0; | |
| } | |
| .form-control{ | |
| color: white ; /* Ensure input text is white in dark mode */ | |
| } | |
| </style> | |
| </head> | |
| <body> | |
| <div class="jarallax" data-jarallax data-speed="0.2"> | |
| <img class="jarallax-img" src="https://silicon.createx.studio/assets/img/landing/saas-5/hero-bg-pattern.png" | |
| alt="Background"> | |
| <div class="login-container"> | |
| <h2>Quên mật khẩu</h2> | |
| <p class="description">Nhập email và 4 số cuối của số điện thoại để nhận mật khẩu mới.</p> | |
| <div id="forgot-password-message"></div> | |
| <form onsubmit="event.preventDefault(); forgotPassword();"> | |
| <div class="form-group mb-3"> | |
| <label for="email" class="form-label">Email</label> | |
| <input type="email" class="form-control" id="email" required> | |
| </div> | |
| <div class="form-group mb-3"> | |
| <label for="masked_phone" class="form-label">Số điện thoại đã đăng ký (Tự động)</label> | |
| <input type="text" class="form-control" id="masked_phone" readonly | |
| aria-describedby="maskedPhoneFeedback"> | |
| <div id="maskedPhoneFeedback" class="invalid-feedback"></div> | |
| </div> | |
| <div class="form-group mb-3"> | |
| <label for="last_four_digits" class="form-label">4 số cuối của số điện thoại</label> | |
| <input type="text" class="form-control" id="last_four_digits" maxlength="4" | |
| aria-describedby="lastThreeDigitsFeedback"> | |
| <div id="lastThreeDigitsFeedback" class="invalid-feedback">Vui lòng nhập đúng 3 số cuối của số điện | |
| thoại.</div> | |
| </div> | |
| <div class="mb-3 form-group "> | |
| <a href="/register" class="float-start text-muted">Đăng ký ngay</a> | |
| <a href="/login" class="float-end text-muted">Đăng nhập ngay</a> | |
| </div> | |
| <br> | |
| <button type="submit" class="btn btn-login" id="submit-btn"> | |
| Gửi yêu cầu | |
| <span class="loading-spinner"></span> | |
| </button> | |
| </form> | |
| </div> | |
| </div> | |
| <script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/js/bootstrap.bundle.min.js"></script> | |
| <script src="https://cdnjs.cloudflare.com/ajax/libs/jarallax/2.2.1/jarallax.min.js"></script> | |
| <script> | |
| // Show messages | |
| function showMessage(elementId, message, type = 'success') { | |
| const messageDiv = document.getElementById(elementId); | |
| messageDiv.innerHTML = `<div class="alert alert-${type}">${message}</div>`; | |
| setTimeout(() => messageDiv.innerHTML = '', 5000); | |
| } | |
| // Fetch masked phone number | |
| async function fetchMaskedPhone(emailInput, maskedPhoneInput) { | |
| const email = emailInput.value.trim(); | |
| if (!email || !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email)) { | |
| maskedPhoneInput.value = ''; | |
| document.getElementById('maskedPhoneFeedback').textContent = 'Vui lòng nhập email hợp lệ'; | |
| maskedPhoneInput.classList.add('is-invalid'); | |
| return; | |
| } | |
| try { | |
| document.getElementById('submit-btn').disabled = true; | |
| document.querySelector('.loading-spinner').classList.add('active'); | |
| const response = await fetch('/get_masked_phone', { | |
| method: 'POST', | |
| headers: { 'Content-Type': 'application/json' }, | |
| body: JSON.stringify({ email }) | |
| }); | |
| const data = await response.json(); | |
| if (data.error) { | |
| maskedPhoneInput.value = ''; | |
| document.getElementById('maskedPhoneFeedback').textContent = data.error; | |
| maskedPhoneInput.classList.add('is-invalid'); | |
| } else { | |
| maskedPhoneInput.classList.remove('is-invalid'); | |
| document.getElementById('maskedPhoneFeedback').textContent = ''; | |
| maskedPhoneInput.value = data.masked_phone; | |
| } | |
| } catch (error) { | |
| maskedPhoneInput.value = ''; | |
| document.getElementById('maskedPhoneFeedback').textContent = _action | |
| 'Lỗi hệ thống, vui lòng thử lại'; | |
| maskedPhoneInput.classList.add('is-invalid'); | |
| console.error('Error:', error); | |
| } finally { | |
| document.getElementById('submit-btn').disabled = false; | |
| document.querySelector('.loading-spinner').classList.remove('active'); | |
| } | |
| } | |
| // Check session on page load | |
| function checkSession() { | |
| fetch('/check_session', { | |
| method: 'GET', | |
| headers: { 'Content-Type': 'application/json' } | |
| }) | |
| .then(response => response.json()) | |
| .then(data => { | |
| const authNav = document.getElementById('auth-nav'); | |
| if (data.logged_in) { | |
| authNav.innerHTML = ` | |
| <li class="nav-item"> | |
| <span class="nav-link">Xin chào, ${data.username}</span> | |
| </li> | |
| <li class="nav-item"> | |
| <a class="nav-link" href="/change_password">Đổi mật khẩu</a> | |
| </li> | |
| <li class="nav-item"> | |
| <a class="nav-link" href="#" onclick="logout()">Đăng xuất</a> | |
| </li> | |
| `; | |
| } else { | |
| authNav.innerHTML = ` | |
| <li class="nav-item"> | |
| <a class="nav-link" href="/login">Đăng nhập</a> | |
| </li> | |
| <li class="nav-item"> | |
| <a class="nav-link" href="/register">Đăng ký</a> | |
| </li> | |
| `; | |
| } | |
| }) | |
| .catch(error => console.error('Error checking session:', error)); | |
| } | |
| // Register | |
| function register() { | |
| const username = document.getElementById('username')?.value; | |
| const email = document.getElementById('email')?.value; | |
| const password = document.getElementById('password')?.value; | |
| const phone = document.getElementById('phone')?.value; | |
| fetch('/register', { | |
| method: 'POST', | |
| headers: { 'Content-Type': 'application/json' }, | |
| body: JSON.stringify({ username, email, password, phone }) | |
| }) | |
| .then(response => response.json()) | |
| .then(data => { | |
| if (data.error) { | |
| showMessage('register-message', data.error, 'danger'); | |
| } else { | |
| showMessage('register-message', data.message, 'success'); | |
| setTimeout(() => { | |
| window.location.href = `/verify_otp?user_id=${data.user_id}`; | |
| }, 2000); | |
| } | |
| }) | |
| .catch(error => { | |
| showMessage('register-message', 'Lỗi hệ thống, vui lòng thử lại', 'danger'); | |
| console.error('Error:', error); | |
| }); | |
| } | |
| // Verify OTP | |
| function verifyOtp() { | |
| const user_id = new URLSearchParams(window.location.search).get('user_id'); | |
| const otp = document.getElementById('otp')?.value; | |
| fetch('/verify_otp', { | |
| method: 'POST', | |
| headers: { 'Content-Type': 'application/json' }, | |
| body: JSON.stringify({ user_id, otp }) | |
| }) | |
| .then(response => response.json()) | |
| .then(data => { | |
| if (data.error) { | |
| showMessage('otp-message', data.error, 'danger'); | |
| } else { | |
| showMessage('otp-message', 'Xác minh thành công', 'success'); | |
| setTimeout(() => window.location.href = '/login', 2000); | |
| } | |
| }) | |
| .catch(error => { | |
| showMessage('otp-message', 'Lỗi hệ thống, vui lòng thử lại', ' Hambackend'); | |
| console.error('Error:', error); | |
| }); | |
| } | |
| // Login | |
| function login() { | |
| const email = document.getElementById('email')?.value; | |
| const password = document.getElementById('password')?.value; | |
| fetch('/login', { | |
| method: 'POST', | |
| headers: { 'Content-Type': 'application/json' }, | |
| body: JSON.stringify({ email, password }) | |
| }) | |
| .then(response => response.json()) | |
| .then(data => { | |
| if (data.error) { | |
| showMessage('login-message', data.error, 'danger'); | |
| } else { | |
| showMessage('login-message', 'Đăng nhập thành công', 'success'); | |
| setTimeout(() => window.location.href = '/home', 2000); | |
| } | |
| }) | |
| .catch(error => { | |
| showMessage('login-message', 'Lỗi hệ thống, vui lòng thử lại', 'danger'); | |
| console.error('Error:', error); | |
| }); | |
| } | |
| // Forgot Password | |
| function forgotPassword() { | |
| const email = document.getElementById('email').value; | |
| const last_four_digits = document.getElementById('last_four_digits').value; | |
| if (!/^[0-9]{4}$/.test(last_four_digits)) { | |
| document.getElementById('last_four_digits').classList.add('is-invalid'); | |
| return; | |
| } | |
| document.getElementById('submit-btn').disabled = true; | |
| document.querySelector('.loading-spinner').classList.add('active'); | |
| fetch('/forgot_password', { | |
| method: 'POST', | |
| headers: { 'Content-Type': 'application/json' }, | |
| body: JSON.stringify({ email, last_four_digits }) | |
| }) | |
| .then(response => response.json()) | |
| .then(data => { | |
| if (data.error) { | |
| showMessage('forgot-password-message', data.error, 'danger'); | |
| } else { | |
| showMessage('forgot-password-message', data.message, 'success'); | |
| setTimeout(() => window.location.href = '/login', 2000); | |
| } | |
| }) | |
| .catch(error => { | |
| showMessage('forgot-password-message', 'Lỗi hệ thống, vui lòng thử lại', 'danger'); | |
| console.error('Error:', error); | |
| }) | |
| .finally(() => { | |
| document.getElementById('submit-btn').disabled = false; | |
| document.querySelector('.loading-spinner').classList.remove('active'); | |
| }); | |
| } | |
| // Change Password | |
| function changePassword() { | |
| const current_password = document.getElementById('current_password').value; | |
| const new_password = document.getElementById('new_password').value; | |
| fetch('/change_password', { | |
| method: 'POST', | |
| headers: { 'Content-Type': 'application/json' }, | |
| body: JSON.stringify({ current_password, new_password }) | |
| }) | |
| .then(response => response.json()) | |
| .then(data => { | |
| if (data.error) { | |
| showMessage('change-password-message', data.error, 'danger'); | |
| } else { | |
| showMessage('change-password-message', data.message, 'success'); | |
| setTimeout(() => window.location.href = '/home', 2000); | |
| } | |
| }) | |
| .catch(error => { | |
| showMessage('change-password-message', 'Lỗi hệ thống, vui lòng thử lại', 'danger'); | |
| console.error('Error:', error); | |
| }); | |
| } | |
| // Logout | |
| function logout() { | |
| fetch('/logout', { | |
| method: 'POST', | |
| headers: { 'Content-Type': 'application/json' } | |
| }) | |
| .then(response => response.json()) | |
| .then(data => { | |
| showMessage('auth-message', data.message, 'success'); | |
| setTimeout(() => window.location.href = '/', 1000); | |
| }) | |
| .catch(error => { | |
| showMessage('auth-message', 'Lỗi hệ thống, vui lòng thử lại', 'danger'); | |
| console.error('Error:', error); | |
| }); | |
| } | |
| // Event listener for email input | |
| document.getElementById('email').addEventListener('input', () => { | |
| const emailInput = document.getElementById('email'); | |
| const maskedPhoneInput = document.getElementById('masked_phone'); | |
| fetchMaskedPhone(emailInput, maskedPhoneInput); | |
| }); | |
| </script> | |
| </body> | |
| </html> |