feat: web frontend; middleware; serde (WIP?)

This commit is contained in:
2025-11-30 09:41:37 +08:00
parent be35040e26
commit 531ac029af
45 changed files with 6806 additions and 82 deletions

View File

@@ -0,0 +1,87 @@
import { Component, createSignal } from 'solid-js';
interface LoginProps {
onLoginSuccess: (token: string) => void;
}
const Login: Component<LoginProps> = (props) => {
const [password, setPassword] = createSignal('');
const [error, setError] = createSignal('');
const [isLoading, setIsLoading] = createSignal(false);
const handleSubmit = async (e: Event) => {
e.preventDefault();
setError('');
setIsLoading(true);
try {
const response = await fetch('/admin/api/login', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ password: password() }),
});
if (!response.ok) {
const errorData = await response.json().catch(() => ({ error: 'Login failed' }));
throw new Error(errorData.error || 'Invalid password');
}
const data = await response.json();
props.onLoginSuccess(data.token);
} catch (err) {
setError(err instanceof Error ? err.message : 'Login failed');
} finally {
setIsLoading(false);
}
};
return (
<div class="min-h-screen bg-gray-100 flex items-center justify-center">
<div class="bg-white p-8 rounded-lg shadow-md w-96">
<h1 class="text-2xl font-bold text-gray-900 mb-6 text-center">
My Linspirer Admin Login
</h1>
<form onSubmit={handleSubmit}>
<div class="mb-4">
<label class="block text-gray-700 text-sm font-medium mb-2" for="password">
Password
</label>
<input
id="password"
type="password"
value={password()}
onInput={(e) => setPassword(e.currentTarget.value)}
class="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-indigo-500"
placeholder="Enter admin password"
disabled={isLoading()}
required
/>
</div>
{error() && (
<div class="mb-4 p-3 bg-red-50 border border-red-200 rounded-md">
<p class="text-sm text-red-600">{error()}</p>
</div>
)}
<button
type="submit"
disabled={isLoading()}
class="w-full bg-indigo-600 text-white py-2 px-4 rounded-md hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-indigo-500 disabled:bg-gray-400"
>
{isLoading() ? 'Logging in...' : 'Login'}
</button>
</form>
<div class="mt-4 text-center text-sm text-gray-500">
<p>Default password: admin123</p>
</div>
</div>
</div>
);
};
export default Login;