Spaces:
Sleeping
Sleeping
| <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; | |
| } | |
| [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; | |
| } | |
| [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; | |
| } | |
| 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; | |
| } | |
| .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-control { | |
| background-color: var(--input-bg); | |
| border: 1px solid var(--input-border); | |
| color: var(--input-text); | |
| 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); | |
| opacity: 0.7; | |
| } | |
| .form-control.is-invalid { | |
| border-color: var(--error-color); | |
| background-image: none; | |
| } | |
| .form-control[readonly] { | |
| color: var(--input-text); | |
| opacity: 0.9; | |
| } | |
| .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; | |
| } | |
| } | |
| .login-container { | |
| background: rgba(13, 27, 42, 0.9); | |
| padding: 2rem; | |
| border-radius: 10px; | |
| width: 100%; | |
| max-width: 400px; | |
| box-shadow: 0 0 20px rgba(0, 0, 0, 0.5); | |
| text-align: center; | |
| } | |
| .login-container h2 { | |
| font-size: 1.5rem; | |
| margin-bottom: 1rem; | |
| font-weight: bold; | |
| } | |
| .login-container p { | |
| font-size: 0.9rem; | |
| color: #adb5bd; | |
| margin-bottom: 1.5rem; | |
| } | |
| .otp-container { | |
| display: flex; | |
| justify-content: space-between; | |
| gap: 10px; | |
| margin-bottom: 1.5rem; | |
| } | |
| .otp-input { | |
| width: 40px; | |
| height: 40px; | |
| text-align: center; | |
| font-size: 1.2rem; | |
| background-color: #2c3e50; | |
| border: none; | |
| color: #fff; | |
| border-radius: 5px; | |
| transition: border-color 0.3s; | |
| } | |
| .otp-input:focus { | |
| outline: none; | |
| border: 2px solid #6366F1; | |
| } | |
| .btn-login { | |
| background-color: #6366F1; | |
| color: #fff; | |
| border: none; | |
| padding: 0.75rem; | |
| width: 100%; | |
| border-radius: 5px; | |
| font-size: 1rem; | |
| font-weight: 500; | |
| transition: background-color 0.3s; | |
| } | |
| .btn-login:hover { | |
| background-color: #4f46e5; | |
| } | |
| .alert { | |
| font-size: 0.9rem; | |
| margin-bottom: 1rem; | |
| } | |
| @media (max-width: 576px) { | |
| .login-container { | |
| padding: 1.5rem; | |
| max-width: 90%; | |
| } | |
| .login-container h2 { | |
| font-size: 1.3rem; | |
| } | |
| .otp-input { | |
| width: 35px; | |
| height: 35px; | |
| font-size: 1rem; | |
| } | |
| } | |
| @keyframes shake { | |
| 0% { transform: translateX(0); } | |
| 25% { transform: translateX(-5px); } | |
| 50% { transform: translateX(5px); } | |
| 75% { transform: translateX(-5px); } | |
| 100% { transform: translateX(0); } | |
| } | |
| .otp-input.shake { | |
| animation: shake 0.3s ease-in-out; | |
| } | |
| </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" role="main"> | |
| <div id="login-message"></div> | |
| <h2>Xác thực OTP</h2> | |
| <p class="description">Vui lòng nhập mã OTP 6 chữ số đã được gửi đến email của bạn.</p> | |
| <div id="otp-message"></div> | |
| <form onsubmit="event.preventDefault(); verifyOtp();"> | |
| <div class="otp-container"> | |
| <input type="tel" class="otp-input" id="otp-1" maxlength="1" inputmode="numeric" pattern="[0-9]*" required | |
| oninput="moveToNext(this, 'otp-2')" onkeydown="handleBackspace(this, 'otp-0')"> | |
| <input type="tel" class="otp-input" id="otp-2" maxlength="1" inputmode="numeric" pattern="[0-9]*" required | |
| oninput="moveToNext(this, 'otp-3')" onkeydown="handleBackspace(this, 'otp-1')"> | |
| <input type="tel" class="otp-input" id="otp-3" maxlength="1" inputmode="numeric" pattern="[0-9]*" required | |
| oninput="moveToNext(this, 'otp-4')" onkeydown="handleBackspace(this, 'otp-2')"> | |
| <input type="tel" class="otp-input" id="otp-4" maxlength="1" inputmode="numeric" pattern="[0-9]*" required | |
| oninput="moveToNext(this, 'otp-5')" onkeydown="handleBackspace(this, 'otp-3')"> | |
| <input type="tel" class="otp-input" id="otp-5" maxlength="1" inputmode="numeric" pattern="[0-9]*" required | |
| oninput="moveToNext(this, 'otp-6')" onkeydown="handleBackspace(this, 'otp-4')"> | |
| <input type="tel" class="otp-input" id="otp-6" maxlength="1" inputmode="numeric" pattern="[0-9]*" required | |
| onkeydown="handleBackspace(this, 'otp-5')"> | |
| </div> | |
| <button type="submit" class="btn btn-login">Xác thực</button> | |
| </form> | |
| <p class="text-center text-muted mt-3"> | |
| Bạn chưa có tài khoản? <a href="/register" style="color: var(--primary-color);">Đăng ký ngay</a> | |
| <br>Hoặc trở về trang chủ <a href="/" style="color: var(--primary-color);">Trang chủ</a> | |
| </p> | |
| </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> | |
| // Function to display 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); | |
| } | |
| // Move to next input field | |
| function moveToNext(current, nextFieldId) { | |
| if (current.value.length === 1) { | |
| const nextField = document.getElementById(nextFieldId); | |
| if (nextField) { | |
| nextField.focus(); | |
| } | |
| } | |
| } | |
| // Handle backspace to move to previous field | |
| function handleBackspace(current, prevFieldId) { | |
| if (event.key === 'Backspace' && current.value.length === 0) { | |
| const prevField = document.getElementById(prevFieldId); | |
| if (prevField) { | |
| prevField.focus(); | |
| prevField.value = ''; // Clear the previous field | |
| } | |
| } | |
| } | |
| // Restrict input to numbers only | |
| document.querySelectorAll('.otp-input').forEach(input => { | |
| input.addEventListener('input', function () { | |
| const value = this.value.replace(/[^0-9]/g, ''); | |
| if (this.value !== value) { | |
| this.value = value; | |
| this.classList.add('shake'); | |
| setTimeout(() => this.classList.remove('shake'), 300); | |
| } | |
| }); | |
| }); | |
| // Handle OTP verification | |
| function verifyOtp() { | |
| const user_id = new URLSearchParams(window.location.search).get('user_id'); | |
| const otp = Array.from({ length: 6 }, (_, i) => document.getElementById(`otp-${i + 1}`).value).join(''); | |
| if (otp.length !== 6) { | |
| showMessage('otp-message', 'Vui lòng nhập đủ 6 chữ số OTP', 'danger'); | |
| return; | |
| } | |
| 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', data.message, 'success'); | |
| setTimeout(() => window.location.href = '/login', 2000); | |
| } | |
| }) | |
| .catch(error => { | |
| showMessage('otp-message', 'Lỗi hệ thống, vui lòng thử lại', 'danger'); | |
| console.error('Error:', error); | |
| }); | |
| } | |
| </script> | |
| </body> | |
| </html> |