clqms-be/app/Views/v2/auth/login.php
mahdahar a94df3b5f7 **feat: migrate to v2 frontend with Alpine.js pattern**
- Introduce v2 views directory with Alpine.js-based UI components
- Add AuthV2 controller for v2 authentication flow
- Update PagesController for v2 routing
- Refactor ValueSet module with v2 dialogs and nested CRUD views
- Add organization management views (accounts, departments, disciplines, sites, workstations)
- Add specimen management views (containers, preparations)
- Add master views for tests and valuesets
- Migrate patient views to v2 pattern
- Update Routes and Exceptions config for v2 support
- Enhance CORS configuration
- Clean up legacy files (check_db.php, llms.txt, sanity.php, old views)
- Update agent workflow patterns for PHP Alpine.js
2025-12-30 14:30:35 +07:00

215 lines
6.9 KiB
PHP

<!DOCTYPE html>
<html lang="en" data-theme="light">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Login - CLQMS</title>
<!-- Google Fonts - Inter -->
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700;800&display=swap" rel="stylesheet">
<!-- TailwindCSS 4 CDN -->
<script src="https://cdn.jsdelivr.net/npm/@tailwindcss/browser@4"></script>
<!-- Custom Styles -->
<link rel="stylesheet" href="<?= base_url('css/v2/styles.css') ?>">
<!-- 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>
/* Animated gradient background */
.gradient-bg {
background: linear-gradient(-45deg, #1e3a8a, #1e40af, #2563eb, #3b82f6);
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%; }
}
/* Floating animation for logo */
@keyframes float {
0%, 100% { transform: translateY(0px); }
50% { transform: translateY(-10px); }
}
.float-animation {
animation: float 3s ease-in-out infinite;
}
</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-glass p-8 animate-fadeIn">
<!-- Logo & Title -->
<div class="text-center mb-8">
<div class="w-20 h-20 mx-auto mb-4 rounded-3xl bg-gradient-to-br from-blue-600 to-blue-900 flex items-center justify-center shadow-xl float-animation">
<i class="fa-solid fa-flask text-white text-4xl"></i>
</div>
<h1 class="text-3xl font-bold mb-2 text-gradient">CLQMS</h1>
<p class="text-sm" style="color: rgb(var(--color-text-muted));">Clinical Laboratory Quality Management System</p>
</div>
<!-- Alert Messages -->
<div x-show="errorMessage" x-cloak class="alert alert-error mb-4 animate-slideInUp">
<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 animate-slideInUp">
<i class="fa-solid fa-check-circle"></i>
<span x-text="successMessage"></span>
</div>
<!-- Login Form -->
<form @submit.prevent="login" class="space-y-4">
<!-- Username -->
<div>
<label class="label">
<span class="label-text font-medium">Username</span>
</label>
<div class="relative">
<input
type="text"
placeholder="Enter your username"
class="input !pl-10"
x-model="form.username"
required
:disabled="loading"
/>
<span class="absolute inset-y-0 left-0 flex items-center pl-3 pointer-events-none" style="color: rgb(var(--color-text-muted));">
<i class="fa-solid fa-user"></i>
</span>
</div>
</div>
<!-- Password -->
<div>
<label class="label">
<span class="label-text font-medium">Password</span>
</label>
<div class="relative">
<input
:type="showPassword ? 'text' : 'password'"
placeholder="Enter your password"
class="input !pl-10 !pr-10"
x-model="form.password"
required
:disabled="loading"
/>
<span class="absolute inset-y-0 left-0 flex items-center pl-3 pointer-events-none" style="color: rgb(var(--color-text-muted));">
<i class="fa-solid fa-lock"></i>
</span>
<button
type="button"
@click="showPassword = !showPassword"
class="absolute inset-y-0 right-0 flex items-center pr-3 z-10"
style="color: rgb(var(--color-text-muted));"
tabindex="-1"
>
<i :class="showPassword ? 'fa-solid fa-eye-slash' : 'fa-solid fa-eye'"></i>
</button>
</div>
</div>
<!-- Remember Me -->
<div class="flex items-center gap-2">
<input type="checkbox" class="checkbox checkbox-sm" x-model="form.remember" id="remember" />
<label for="remember" class="label-text cursor-pointer">Remember me</label>
</div>
<!-- Submit Button -->
<button
type="submit"
class="btn btn-primary w-full !rounded-full"
:disabled="loading"
>
<span x-show="loading" class="spinner spinner-sm"></span>
<span x-show="!loading">
<i class="fa-solid fa-sign-in-alt mr-2"></i>
Login
</span>
</button>
</form>
</div>
<!-- Copyright -->
<div class="text-center mt-6 text-white/90">
<p class="text-sm drop-shadow-lg">© 2025 5Panda. All rights reserved.</p>
</div>
</div>
<!-- Scripts -->
<script>
window.BASEURL = "<?= base_url() ?>";
function loginApp() {
return {
loading: false,
showPassword: false,
errorMessage: '',
successMessage: '',
form: {
username: '',
password: '',
remember: false
},
async login() {
this.errorMessage = '';
this.successMessage = '';
this.loading = true;
try {
const formData = new URLSearchParams({
username: this.form.username,
password: this.form.password,
remember: this.form.remember ? '1' : '0'
});
const res = await fetch(`${BASEURL}v2/auth/login`, {
method: 'POST',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
body: formData,
credentials: 'include'
});
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;
}
}
}
}
</script>
</body>
</html>