Legalmind / templates /verify_otp.html
Nguyendat92929's picture
upload file
8a15958 verified
<!DOCTYPE html>
<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 !important;
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>