clqms-be/app/Views/auth/login.php
mahdahar eb883cf059 feat: Add V2 UI with JWT auth, DaisyUI 5, and theme system
- Implement JWT authentication with HTTP-only cookies
- Create /v2/* namespace to avoid conflicts with existing frontend
- Upgrade to DaisyUI 5 + Tailwind CSS 4
- Add light/dark theme toggle with smooth transitions
- Build login page, dashboard, and patient list UI
- Protect V2 routes with auth middleware
- Add comprehensive documentation

No breaking changes - all new features under /v2/* namespace
2025-12-30 08:48:13 +07:00

324 lines
10 KiB
PHP

<!DOCTYPE html>
<html lang="en" data-theme="corporate">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Login - CLQMS</title>
<!-- TailwindCSS 4 + DaisyUI 5 CDN -->
<link href="https://cdn.jsdelivr.net/npm/daisyui@5" rel="stylesheet" type="text/css" />
<script src="https://cdn.jsdelivr.net/npm/@tailwindcss/browser@4"></script>
<!-- FontAwesome -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/js/all.min.js"></script>
<!-- Alpine.js -->
<script defer src="https://cdn.jsdelivr.net/npm/alpinejs@3.x.x/dist/cdn.min.js"></script>
<style>
[x-cloak] { display: none !important; }
/* Smooth theme transition */
* {
transition: background-color 0.3s ease, color 0.3s ease, border-color 0.3s ease;
}
/* Animated gradient background */
.gradient-bg {
background: linear-gradient(-45deg, #0ea5e9, #3b82f6, #6366f1, #8b5cf6);
background-size: 400% 400%;
animation: gradient 15s ease infinite;
}
@keyframes gradient {
0% { background-position: 0% 50%; }
50% { background-position: 100% 50%; }
100% { background-position: 0% 50%; }
}
</style>
</head>
<body class="min-h-screen flex items-center justify-center gradient-bg" x-data="loginApp()">
<!-- Login Card -->
<div class="w-full max-w-md p-4">
<div class="card bg-base-100 shadow-2xl">
<div class="card-body">
<!-- Logo & Title -->
<div class="text-center mb-6">
<div class="w-20 h-20 bg-primary/20 rounded-2xl flex items-center justify-center mx-auto mb-4">
<i class="fa-solid fa-flask text-primary text-4xl"></i>
</div>
<h1 class="text-3xl font-bold text-base-content">CLQMS</h1>
<p class="text-base-content/60 mt-2">Clinical Laboratory Queue Management System</p>
</div>
<!-- Alert Messages -->
<div x-show="errorMessage" x-cloak class="alert alert-error mb-4">
<i class="fa-solid fa-exclamation-circle"></i>
<span x-text="errorMessage"></span>
</div>
<div x-show="successMessage" x-cloak class="alert alert-success mb-4">
<i class="fa-solid fa-check-circle"></i>
<span x-text="successMessage"></span>
</div>
<!-- Login Form -->
<form @submit.prevent="login">
<!-- Username -->
<div class="form-control mb-4">
<label class="label">
<span class="label-text font-medium">Username</span>
</label>
<div class="relative">
<span class="absolute inset-y-0 left-0 flex items-center pl-3">
<i class="fa-solid fa-user text-base-content/40"></i>
</span>
<input
type="text"
placeholder="Enter your username"
class="input input-bordered w-full pl-10"
x-model="form.username"
required
:disabled="loading"
/>
</div>
</div>
<!-- Password -->
<div class="form-control mb-6">
<label class="label">
<span class="label-text font-medium">Password</span>
</label>
<div class="relative">
<span class="absolute inset-y-0 left-0 flex items-center pl-3">
<i class="fa-solid fa-lock text-base-content/40"></i>
</span>
<input
:type="showPassword ? 'text' : 'password'"
placeholder="Enter your password"
class="input input-bordered w-full pl-10 pr-10"
x-model="form.password"
required
:disabled="loading"
/>
<button
type="button"
@click="showPassword = !showPassword"
class="absolute inset-y-0 right-0 flex items-center pr-3"
tabindex="-1"
>
<i :class="showPassword ? 'fa-solid fa-eye-slash' : 'fa-solid fa-eye'" class="text-base-content/40"></i>
</button>
</div>
</div>
<!-- Remember Me -->
<div class="form-control mb-6">
<label class="label cursor-pointer justify-start gap-3">
<input type="checkbox" class="checkbox checkbox-primary checkbox-sm" x-model="form.remember" />
<span class="label-text">Remember me</span>
</label>
</div>
<!-- Submit Button -->
<button
type="submit"
class="btn btn-primary w-full"
:disabled="loading"
>
<span x-show="loading" class="loading loading-spinner loading-sm"></span>
<span x-show="!loading">
<i class="fa-solid fa-sign-in-alt mr-2"></i>
Login
</span>
</button>
</form>
<!-- Footer -->
<div class="divider">OR</div>
<div class="text-center">
<p class="text-sm text-base-content/60">
Don't have an account?
<button @click="showRegister = true" class="link link-primary">Register here</button>
</p>
</div>
</div>
</div>
<!-- Copyright -->
<div class="text-center mt-6 text-white/80">
<p class="text-sm">© 2025 5Panda. All rights reserved.</p>
</div>
</div>
<!-- Register Modal -->
<dialog class="modal" :class="showRegister && 'modal-open'">
<div class="modal-box">
<h3 class="font-bold text-lg mb-4">
<i class="fa-solid fa-user-plus mr-2 text-primary"></i>
Create Account
</h3>
<form @submit.prevent="register">
<!-- Username -->
<div class="form-control mb-4">
<label class="label">
<span class="label-text font-medium">Username</span>
</label>
<input
type="text"
placeholder="Choose a username"
class="input input-bordered w-full"
x-model="registerForm.username"
required
/>
</div>
<!-- Password -->
<div class="form-control mb-4">
<label class="label">
<span class="label-text font-medium">Password</span>
</label>
<input
type="password"
placeholder="Choose a password"
class="input input-bordered w-full"
x-model="registerForm.password"
required
/>
</div>
<!-- Confirm Password -->
<div class="form-control mb-6">
<label class="label">
<span class="label-text font-medium">Confirm Password</span>
</label>
<input
type="password"
placeholder="Confirm your password"
class="input input-bordered w-full"
x-model="registerForm.confirmPassword"
required
/>
</div>
<div class="modal-action">
<button type="button" class="btn btn-ghost" @click="showRegister = false">Cancel</button>
<button type="submit" class="btn btn-primary" :disabled="registering">
<span x-show="registering" class="loading loading-spinner loading-sm"></span>
<span x-show="!registering">Register</span>
</button>
</div>
</form>
</div>
<div class="modal-backdrop bg-black/50" @click="showRegister = false"></div>
</dialog>
<!-- Scripts -->
<script>
window.BASEURL = "<?= base_url() ?>";
function loginApp() {
return {
loading: false,
registering: false,
showPassword: false,
showRegister: false,
errorMessage: '',
successMessage: '',
form: {
username: '',
password: '',
remember: false
},
registerForm: {
username: '',
password: '',
confirmPassword: ''
},
async login() {
this.errorMessage = '';
this.successMessage = '';
this.loading = true;
try {
const res = await fetch(`${BASEURL}api/auth/login`, {
method: 'POST',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
body: new URLSearchParams({
username: this.form.username,
password: this.form.password
})
});
const data = await res.json();
if (res.ok && data.status === 'success') {
this.successMessage = 'Login successful! Redirecting...';
setTimeout(() => {
window.location.href = `${BASEURL}v2/`;
}, 1000);
} else {
this.errorMessage = data.message || 'Login failed. Please try again.';
}
} catch (err) {
console.error(err);
this.errorMessage = 'Network error. Please try again.';
} finally {
this.loading = false;
}
},
async register() {
this.errorMessage = '';
this.successMessage = '';
if (this.registerForm.password !== this.registerForm.confirmPassword) {
this.errorMessage = 'Passwords do not match!';
return;
}
this.registering = true;
try {
const res = await fetch(`${BASEURL}api/auth/register`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
username: this.registerForm.username,
password: this.registerForm.password
})
});
const data = await res.json();
if (res.ok && data.status === 'success') {
this.successMessage = 'Registration successful! You can now login.';
this.showRegister = false;
this.registerForm = { username: '', password: '', confirmPassword: '' };
} else {
this.errorMessage = data.message || 'Registration failed. Please try again.';
}
} catch (err) {
console.error(err);
this.errorMessage = 'Network error. Please try again.';
} finally {
this.registering = false;
}
}
}
}
</script>
</body>
</html>