- 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
215 lines
6.9 KiB
PHP
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>
|