dptxa-proxy / src /pages /dang-nhap.astro
TXAVLOG
Deploy DPTXA to Hugging Face Spaces
4bea261
Raw
History Blame Contribute Delete
17.7 kB
---
import Layout from '../layouts/Layout.astro';
const title = "Đăng nhập | DPTXA - Thiên đường điện ảnh";
const description = "Đăng nhập vào tài khoản DPTXA để trải nghiệm kho phim khổng lồ và lưu lại những bộ phim yêu thích của bạn.";
---
<Layout title={title} description={description}>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css" />
<div class="relative min-h-[calc(100vh-64px)] w-full overflow-hidden flex items-center justify-center py-12 px-4">
<div class="absolute inset-0 z-0">
<img
src="/images/auth/bg.png"
alt="Background"
class="h-full w-full object-cover scale-105 blur-[2px]"
/>
<div class="absolute inset-0 bg-gradient-to-b from-[#0f0f0f]/80 via-[#0f0f0f]/60 to-[#0f0f0f] z-10"></div>
</div>
<div class="relative z-20 w-full max-w-[450px] glass rounded-[32px] p-8 md:p-12 shadow-[0_0_50px_rgba(0,0,0,0.5)] ring-1 ring-white/10 backdrop-blur-2xl animate-in fade-in zoom-in duration-700">
<div class="owl-container mb-6 flex justify-center">
<div class="owl relative w-24 h-24 bg-[#1a1a1a] rounded-full border-2 border-white/10 flex items-center justify-center shadow-lg">
<div class="owl-ears absolute top-[-5px] left-0 right-0 flex justify-between px-3">
<div class="w-4 h-4 bg-[#1a1a1a] rotate-45 rounded-tl-md border-l-2 border-t-2 border-white/10"></div>
<div class="w-4 h-4 bg-[#1a1a1a] rotate-45 rounded-tr-md border-r-2 border-t-2 border-white/10"></div>
</div>
<div class="owl-eyes flex gap-4 z-10 transition-transform duration-300">
<div class="eye w-7 h-7 bg-white rounded-full flex items-center justify-center relative overflow-hidden">
<div class="pupil w-3 h-3 bg-black rounded-full absolute transition-all duration-100"></div>
</div>
<div class="eye w-7 h-7 bg-white rounded-full flex items-center justify-center relative overflow-hidden">
<div class="pupil w-3 h-3 bg-black rounded-full absolute transition-all duration-100"></div>
</div>
</div>
<div class="owl-wings absolute inset-0 flex justify-between px-2 items-center pointer-events-none z-20">
<div class="wing-left w-6 h-12 bg-[#2a2a2a] rounded-full border border-white/10 origin-bottom transition-all duration-300 translate-y-12 opacity-0"></div>
<div class="wing-right w-6 h-12 bg-[#2a2a2a] rounded-full border border-white/10 origin-bottom transition-all duration-300 translate-y-12 opacity-0"></div>
</div>
</div>
</div>
<div class="text-center mb-8">
<h1 class="text-3xl font-black tracking-tighter text-white mb-2">Mừng bạn trở lại!</h1>
<p class="text-gray-400 text-sm font-medium">Đăng nhập để tiếp tục xem phim nào</p>
</div>
<form id="login-form" class="space-y-5" novalidate>
<div class="space-y-1">
<label for="identity" class="text-[10px] font-black uppercase tracking-widest text-gray-500 ml-1">Tài khoản hoặc Email</label>
<div class="relative group input-container">
<i class="fas fa-user absolute left-5 top-1/2 -translate-y-1/2 text-gray-500 group-focus-within:text-primary transition-colors z-10"></i>
<input
type="text"
id="identity"
name="identity"
placeholder="Nhập tên tài khoản hoặc email..."
class="w-full bg-white/5 border border-white/10 rounded-2xl py-4 pl-12 pr-10 text-sm text-white placeholder:text-gray-600 focus:outline-none focus:ring-2 focus:ring-primary/50 transition-all z-0"
required
/>
<i class="validation-icon fas absolute right-4 top-1/2 -translate-y-1/2 opacity-0 transition-opacity pointer-events-none"></i>
</div>
<p class="error-msg text-[11px] text-red-500 font-medium ml-1 h-4 opacity-0 transition-opacity"></p>
</div>
<div class="space-y-1">
<div class="flex items-center justify-between ml-1">
<label for="password" class="text-[10px] font-black uppercase tracking-widest text-gray-500">Mật khẩu</label>
<a href="#" class="text-[10px] font-black uppercase tracking-widest text-primary hover:underline transition-all">Quên mật khẩu?</a>
</div>
<div class="relative group input-container">
<i class="fas fa-lock absolute left-5 top-1/2 -translate-y-1/2 text-gray-500 group-focus-within:text-primary transition-colors z-10"></i>
<input
type="password"
id="password"
name="password"
placeholder="••••••••"
class="w-full bg-white/5 border border-white/10 rounded-2xl py-4 pl-12 pr-24 text-sm text-white placeholder:text-gray-600 focus:outline-none focus:ring-2 focus:ring-primary/50 transition-all z-0"
required
/>
<div class="absolute right-4 top-1/2 -translate-y-1/2 flex items-center gap-3 z-10">
<i class="validation-icon fas opacity-0 transition-opacity pointer-events-none"></i>
<button
type="button"
id="toggle-password"
class="text-gray-500 hover:text-white transition-colors"
>
<i class="fas fa-eye"></i>
</button>
</div>
</div>
<p class="error-msg text-[11px] text-red-500 font-medium ml-1 h-4 opacity-0 transition-opacity"></p>
</div>
<label class="flex items-center gap-3 cursor-pointer group w-fit pt-1">
<div class="relative flex items-center justify-center shrink-0">
<input type="checkbox" class="peer sr-only" />
<div class="h-5 w-5 rounded-md border border-white/10 bg-white/5 transition-all peer-checked:bg-primary peer-checked:border-primary"></div>
<i class="fas fa-check absolute text-[10px] text-white opacity-0 peer-checked:opacity-100 transition-opacity"></i>
</div>
<span class="text-xs font-medium text-gray-400 group-hover:text-gray-200 transition-colors select-none">Ghi nhớ đăng nhập</span>
</label>
<button
type="submit"
class="w-full bg-primary hover:bg-red-700 text-white font-black uppercase tracking-widest py-4 rounded-2xl shadow-[0_0_20px_rgba(229,9,20,0.3)] hover:shadow-[0_0_30px_rgba(229,9,20,0.5)] transition-all active:scale-95 flex items-center justify-center gap-3 mt-2"
>
<span>Đăng nhập ngay</span>
<i class="fas fa-arrow-right text-xs"></i>
</button>
<div class="relative flex items-center py-2">
<div class="flex-grow border-t border-white/5"></div>
<span class="flex-shrink mx-4 text-[10px] font-black uppercase tracking-widest text-gray-600">Hoặc tiếp tục với</span>
<div class="flex-grow border-t border-white/5"></div>
</div>
<div class="grid grid-cols-2 gap-4">
<button
type="button"
id="google-login-btn"
data-txatooltip="Đăng nhập với Google"
class="flex items-center justify-center gap-3 bg-white/5 hover:bg-white/10 border border-white/10 py-3 rounded-2xl transition-all group"
>
<i class="fab fa-google text-red-500 group-hover:scale-110 transition-transform"></i>
<span class="text-xs font-black uppercase tracking-widest text-white">Google</span>
</button>
<button
type="button"
id="facebook-login-btn"
data-txatooltip="Đăng nhập với Facebook"
class="flex items-center justify-center gap-3 bg-white/5 hover:bg-white/10 border border-white/10 py-3 rounded-2xl transition-all group"
>
<i class="fab fa-facebook text-blue-500 group-hover:scale-110 transition-transform"></i>
<span class="text-xs font-black uppercase tracking-widest text-white">Facebook</span>
</button>
</div>
</form>
<div class="mt-8 text-center">
<p class="text-gray-500 text-xs font-medium">
Chưa có tài khoản?
<a href="/dang-ky" class="text-primary font-black uppercase tracking-widest ml-1 hover:underline">Đăng ký tại đây</a>
</p>
</div>
</div>
</div>
</Layout>
<script is:inline>
function initLoginPage() {
const form = document.getElementById('login-form');
const togglePass = document.getElementById('toggle-password');
const passInput = document.getElementById('password');
const identityInput = document.getElementById('identity');
const pupils = document.querySelectorAll('.pupil');
const wingLeft = document.querySelector('.wing-left');
const wingRight = document.querySelector('.wing-right');
// --- OWL ADAPTIVE TRACKING ANIMATION ---
const setupTracking = (input) => {
if (!input) return;
input.addEventListener('input', (e) => {
const length = e.target.value.length;
const moveX = Math.min(Math.max(length * 0.5 - 5, -6), 6);
pupils.forEach(pupil => {
pupil.style.transform = `translate(${moveX}px, 3px)`;
});
});
input.addEventListener('focus', () => {
const length = input.value.length;
const moveX = Math.min(Math.max(length * 0.5 - 5, -6), 6);
pupils.forEach(pupil => pupil.style.transform = `translate(${moveX}px, 3px)`);
});
input.addEventListener('blur', () => {
pupils.forEach(pupil => pupil.style.transform = 'translate(0px, 0px)');
});
};
setupTracking(identityInput);
const setupPasswordOwlEffect = (input) => {
if (!input) return;
input.addEventListener('focus', () => {
wingLeft.style.opacity = '1';
wingRight.style.opacity = '1';
wingLeft.style.transform = 'translateY(-35px) scaleY(-1) rotate(15deg)';
wingRight.style.transform = 'translateY(-35px) scaleY(-1) rotate(-15deg)';
pupils.forEach(pupil => pupil.style.transform = 'translate(0px, -10px)');
});
input.addEventListener('blur', () => {
if (passInput.getAttribute('type') === 'password') {
wingLeft.style.opacity = '0';
wingRight.style.opacity = '0';
wingLeft.style.transform = 'translateY(48px)';
wingRight.style.transform = 'translateY(48px)';
}
pupils.forEach(pupil => pupil.style.transform = 'translate(0px, 0px)');
});
};
setupPasswordOwlEffect(passInput);
// Toggle Password Visibility
if (togglePass && passInput) {
togglePass.onclick = () => {
const isShowing = passInput.getAttribute('type') === 'text';
const type = isShowing ? 'password' : 'text';
passInput.setAttribute('type', type);
togglePass.querySelector('i').classList.toggle('fa-eye');
togglePass.querySelector('i').classList.toggle('fa-eye-slash');
if (!isShowing) { // Now showing text
wingLeft.style.opacity = '0';
wingRight.style.opacity = '0';
wingLeft.style.transform = 'translateY(48px)';
wingRight.style.transform = 'translateY(48px)';
pupils.forEach(pupil => pupil.style.transform = 'translate(0px, 0px)');
} else { // Now hidden
wingLeft.style.opacity = '1';
wingRight.style.opacity = '1';
wingLeft.style.transform = 'translateY(-35px) scaleY(-1) rotate(15deg)';
wingRight.style.transform = 'translateY(-35px) scaleY(-1) rotate(-15deg)';
pupils.forEach(pupil => pupil.style.transform = 'translate(0px, -10px)');
}
};
}
// --- REAL-TIME INPUT VALIDATION ---
const validateInput = (input, rules) => {
if (!input) return true;
const container = input.closest('.input-container');
const icon = container.querySelector('.validation-icon');
const errorMsg = container.parentElement.querySelector('.error-msg');
let isValid = true;
let message = '';
const val = input.value.trim();
for (const rule of rules) {
const check = rule(val);
if (check !== true) {
isValid = false;
message = check;
break;
}
}
if (!val && input.hasAttribute('required')) {
isValid = false;
message = 'Trường này không được để trống nhé!';
}
if (isValid && val) {
input.style.borderColor = 'rgba(16, 185, 129, 0.4)';
if (icon) {
icon.className = 'validation-icon fas fa-check-circle absolute text-emerald-500 opacity-100 transition-all pointer-events-none scale-110';
if (input.id === 'identity') icon.style.right = '16px';
if (input.id === 'password') icon.style.right = '48px';
icon.style.top = '50%';
icon.style.transform = 'translateY(-50%)';
}
if (errorMsg) {
errorMsg.style.opacity = '0';
errorMsg.textContent = '';
}
} else if (!isValid) {
input.style.borderColor = 'rgba(239, 68, 68, 0.4)';
if (icon) {
icon.className = 'validation-icon fas fa-exclamation-circle absolute text-red-500 opacity-100 transition-all pointer-events-none scale-110';
if (input.id === 'identity') icon.style.right = '16px';
if (input.id === 'password') icon.style.right = '48px';
icon.style.top = '50%';
icon.style.transform = 'translateY(-50%)';
}
if (errorMsg) {
errorMsg.textContent = message;
errorMsg.style.opacity = '1';
}
} else {
input.style.borderColor = '';
if (icon) icon.style.opacity = '0';
if (errorMsg) errorMsg.style.opacity = '0';
}
return isValid;
};
[identityInput, passInput].forEach(el => {
if (!el) return;
el.addEventListener('input', () => {
if (el === identityInput) {
validateInput(el, [v => v.length >= 3 || 'Tối thiểu 3 ký tự!']);
} else if (el === passInput) {
validateInput(el, [v => v.length >= 6 || 'Tối thiểu 6 ký tự!']);
}
});
});
// Form Handle Submit
if (form) {
form.onsubmit = async (e) => {
e.preventDefault();
const { txatoast } = window;
const isIdentityValid = validateInput(identityInput, [v => v.length >= 3]);
const isPasswordValid = validateInput(passInput, [v => v.length >= 6]);
if (!isIdentityValid || !isPasswordValid) {
if (txatoast) txatoast.error('Kiểm tra lại thông tin đăng nhập!');
return;
}
const submitBtn = form.querySelector('button[type="submit"]');
const originalBtnContent = submitBtn.innerHTML;
try {
const formData = new FormData(form);
const data = Object.fromEntries(formData.entries());
submitBtn.disabled = true;
submitBtn.style.opacity = '0.5';
submitBtn.innerHTML = '<i class="fas fa-circle-notch fa-spin"></i> <span>Đang xác thực...</span>';
if (txatoast) txatoast.info('Đang kiểm tra...', 2000, 'top-center');
// Tạo độ trễ 800ms tối thiểu để nút xoay mượt mà đậm chất điện ảnh
const [response] = await Promise.all([
fetch('/api/auth/login', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(data)
}),
new Promise(resolve => setTimeout(resolve, 800))
]);
const result = await response.json();
if (!response.ok) {
if (txatoast) txatoast.error(result.error || 'Sai tài khoản hoặc mật khẩu!');
submitBtn.disabled = false;
submitBtn.style.opacity = '1';
submitBtn.innerHTML = originalBtnContent;
return;
}
if (txatoast) txatoast.success('Đăng nhập thành công! Đang chuyển hướng...', 2000);
setTimeout(() => window.location.href = '/', 1000);
} catch (error) {
if (txatoast) txatoast.error('Lỗi kết nối!');
submitBtn.disabled = false;
submitBtn.style.opacity = '1';
submitBtn.innerHTML = originalBtnContent;
}
};
}
// Social Logins Click
const googleBtn = document.getElementById('google-login-btn');
const facebookBtn = document.getElementById('facebook-login-btn');
if (googleBtn) {
googleBtn.onclick = () => {
if (window.txatoast) {
window.txatoast.info('Tính năng đăng nhập qua Google đang được phát triển!');
}
};
}
if (facebookBtn) {
facebookBtn.onclick = () => {
if (window.txatoast) {
window.txatoast.info('Tính năng đăng nhập qua Facebook đang được phát triển!');
}
};
}
}
document.addEventListener('astro:page-load', initLoginPage);
</script>
<style>
.glass {
background: rgba(255, 255, 255, 0.02);
backdrop-filter: blur(25px);
-webkit-backdrop-filter: blur(25px);
border: 1px solid rgba(255, 255, 255, 0.08);
}
.text-primary {
color: #e50914;
}
.bg-primary {
background-color: #e50914;
}
input:focus {
box-shadow: 0 0 20px rgba(229, 9, 20, 0.15);
}
</style>