171 lines
7.8 KiB
PHP
171 lines
7.8 KiB
PHP
|
|
<!DOCTYPE html>
|
||
|
|
<!DOCTYPE html>
|
||
|
|
<html lang="en" data-theme="autumn">
|
||
|
|
|
||
|
|
<head>
|
||
|
|
<meta charset="UTF-8">
|
||
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||
|
|
<title>Login - TinyQC</title>
|
||
|
|
<link href="https://cdn.jsdelivr.net/npm/daisyui@5" rel="stylesheet" type="text/css" />
|
||
|
|
<link href="https://cdn.jsdelivr.net/npm/daisyui@5/themes.css" rel="stylesheet" type="text/css" />
|
||
|
|
<script src="https://cdn.jsdelivr.net/npm/@tailwindcss/browser@4"></script>
|
||
|
|
<script defer src="https://cdn.jsdelivr.net/npm/alpinejs@3.14.1/dist/cdn.min.js"></script>
|
||
|
|
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@fortawesome/fontawesome-free@6.7.2/css/all.min.css">
|
||
|
|
<script>
|
||
|
|
const BASEURL = '<?= base_url(''); ?>';
|
||
|
|
</script>
|
||
|
|
</head>
|
||
|
|
|
||
|
|
<body class="bg-base-200 min-h-screen flex items-center justify-center p-4" x-data="loginData()">
|
||
|
|
<div class="w-full max-w-md">
|
||
|
|
<div class="card bg-base-100 shadow-xl border border-base-300">
|
||
|
|
<div class="card-body p-8">
|
||
|
|
<div class="text-center mb-8">
|
||
|
|
<div class="inline-flex items-center justify-center w-16 h-16 rounded-2xl bg-primary mb-4 shadow-lg shadow-primary/20">
|
||
|
|
<i class="fa-solid fa-flask text-white text-2xl"></i>
|
||
|
|
</div>
|
||
|
|
<h1 class="text-2xl font-bold text-base-content">TinyQC</h1>
|
||
|
|
<p class="text-sm text-base-content/60 mt-1">QC Management System</p>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<form @submit.prevent="handleSubmit" class="space-y-4">
|
||
|
|
<label class="input input-bordered w-full flex items-center gap-2" :class="{ 'input-error': errors.username }">
|
||
|
|
<i class="fa-solid fa-user text-sm opacity-70"></i>
|
||
|
|
<input type="text" x-model="form.username" placeholder="Username" class="grow" required autofocus>
|
||
|
|
</label>
|
||
|
|
<span x-show="errors.username" x-text="errors.username" class="text-error text-xs mt-1"></span>
|
||
|
|
|
||
|
|
<label class="input input-bordered w-full flex items-center gap-2" :class="{ 'input-error': errors.password }">
|
||
|
|
<i class="fa-solid fa-lock text-sm opacity-70"></i>
|
||
|
|
<input :type="showPassword ? 'text' : 'password'" x-model="form.password" placeholder="Password" class="grow" required>
|
||
|
|
<button type="button" @click="showPassword = !showPassword" class="opacity-70 hover:opacity-100">
|
||
|
|
<i class="fa-solid" :class="showPassword ? 'fa-eye-slash' : 'fa-eye'"></i>
|
||
|
|
</button>
|
||
|
|
</label>
|
||
|
|
<span x-show="errors.password" x-text="errors.password" class="text-error text-xs mt-1"></span>
|
||
|
|
|
||
|
|
<div class="flex items-center justify-between">
|
||
|
|
<label class="label cursor-pointer flex items-center gap-2">
|
||
|
|
<input type="checkbox" x-model="form.remember" class="checkbox checkbox-primary checkbox-sm">
|
||
|
|
<span class="label-text text-sm">Remember me</span>
|
||
|
|
</label>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<div x-show="errorMessage" x-transition
|
||
|
|
class="alert alert-error alert-sm text-sm py-2">
|
||
|
|
<i class="fa-solid fa-circle-exclamation text-sm"></i>
|
||
|
|
<span x-text="errorMessage"></span>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<button type="submit" class="btn btn-primary w-full" :class="{ 'loading': loading }"
|
||
|
|
:disabled="loading">
|
||
|
|
<span x-show="!loading">
|
||
|
|
<i class="fa-solid fa-sign-in-alt text-sm mr-2"></i>Sign In
|
||
|
|
</span>
|
||
|
|
<span x-show="loading">Signing in...</span>
|
||
|
|
</button>
|
||
|
|
</form>
|
||
|
|
|
||
|
|
<div class="divider text-xs text-base-content/50">or</div>
|
||
|
|
|
||
|
|
<div class="text-center">
|
||
|
|
<button @click="toggleTheme()" class="btn btn-ghost btn-sm">
|
||
|
|
<i class="fa-solid fa-sun text-lg text-warning" x-show="currentTheme === 'autumn'"></i>
|
||
|
|
<i class="fa-solid fa-moon text-lg text-neutral-content" x-show="currentTheme === 'dracula'"></i>
|
||
|
|
<span class="ml-2" x-text="currentTheme === 'autumn' ? 'Dark Mode' : 'Light Mode'"></span>
|
||
|
|
</button>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<p class="text-center text-sm text-base-content/50 mt-6">
|
||
|
|
© <?= date('Y') ?> TinyQC - PT.Summit
|
||
|
|
</p>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<script>
|
||
|
|
document.addEventListener('alpine:init', () => {
|
||
|
|
Alpine.data('loginData', () => ({
|
||
|
|
loading: false,
|
||
|
|
showPassword: false,
|
||
|
|
errorMessage: '',
|
||
|
|
errors: {
|
||
|
|
username: '',
|
||
|
|
password: ''
|
||
|
|
},
|
||
|
|
form: {
|
||
|
|
username: '',
|
||
|
|
password: '',
|
||
|
|
remember: false
|
||
|
|
},
|
||
|
|
get currentTheme() {
|
||
|
|
return localStorage.getItem('theme') || 'autumn';
|
||
|
|
},
|
||
|
|
set currentTheme(value) {
|
||
|
|
localStorage.setItem('theme', value);
|
||
|
|
document.documentElement.setAttribute('data-theme', value);
|
||
|
|
},
|
||
|
|
init() {
|
||
|
|
document.documentElement.setAttribute('data-theme', this.currentTheme);
|
||
|
|
},
|
||
|
|
toggleTheme() {
|
||
|
|
this.currentTheme = this.currentTheme === 'autumn' ? 'dracula' : 'autumn';
|
||
|
|
},
|
||
|
|
validateForm() {
|
||
|
|
this.errors = { username: '', password: '' };
|
||
|
|
let isValid = true;
|
||
|
|
|
||
|
|
if (!this.form.username.trim()) {
|
||
|
|
this.errors.username = 'Username is required';
|
||
|
|
isValid = false;
|
||
|
|
}
|
||
|
|
|
||
|
|
if (!this.form.password) {
|
||
|
|
this.errors.password = 'Password is required';
|
||
|
|
isValid = false;
|
||
|
|
}
|
||
|
|
|
||
|
|
return isValid;
|
||
|
|
},
|
||
|
|
async handleSubmit() {
|
||
|
|
this.errorMessage = '';
|
||
|
|
|
||
|
|
if (!this.validateForm()) {
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
this.loading = true;
|
||
|
|
|
||
|
|
try {
|
||
|
|
const response = await fetch(`${BASEURL}login`, {
|
||
|
|
method: 'POST',
|
||
|
|
headers: {
|
||
|
|
'Content-Type': 'application/json',
|
||
|
|
'X-Requested-With': 'XMLHttpRequest'
|
||
|
|
},
|
||
|
|
body: JSON.stringify({
|
||
|
|
username: this.form.username,
|
||
|
|
password: this.form.password,
|
||
|
|
remember: this.form.remember
|
||
|
|
})
|
||
|
|
});
|
||
|
|
|
||
|
|
const data = await response.json();
|
||
|
|
|
||
|
|
if (data.status === 'success') {
|
||
|
|
window.location.href = data.redirect || BASEURL;
|
||
|
|
} else {
|
||
|
|
this.errorMessage = data.message || 'Invalid username or password';
|
||
|
|
}
|
||
|
|
} catch (error) {
|
||
|
|
this.errorMessage = 'An error occurred. Please try again.';
|
||
|
|
} finally {
|
||
|
|
this.loading = false;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}));
|
||
|
|
});
|
||
|
|
</script>
|
||
|
|
</body>
|
||
|
|
|
||
|
|
</html>
|