aboutsummaryrefslogtreecommitdiffhomepage
path: root/src/client/pages/LoginPage.tsx
diff options
context:
space:
mode:
authornsfisis <nsfisis@gmail.com>2025-12-06 18:38:57 +0900
committernsfisis <nsfisis@gmail.com>2025-12-06 18:38:57 +0900
commit516e26f5ca72f2db724fd68584663c0732c77f77 (patch)
tree9bc589cb64c2c8b6f2f3c1e8b6ff00f868fec15b /src/client/pages/LoginPage.tsx
parenta2569837aa07ef48f27884fc2869b5be47087a4e (diff)
downloadkioku-516e26f5ca72f2db724fd68584663c0732c77f77.tar.gz
kioku-516e26f5ca72f2db724fd68584663c0732c77f77.tar.zst
kioku-516e26f5ca72f2db724fd68584663c0732c77f77.zip
feat(client): implement Login page with form validation
Add login form with username/password fields, error handling, and automatic redirect when already authenticated. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
Diffstat (limited to 'src/client/pages/LoginPage.tsx')
-rw-r--r--src/client/pages/LoginPage.tsx76
1 files changed, 75 insertions, 1 deletions
diff --git a/src/client/pages/LoginPage.tsx b/src/client/pages/LoginPage.tsx
index 459e2ce..f72a6da 100644
--- a/src/client/pages/LoginPage.tsx
+++ b/src/client/pages/LoginPage.tsx
@@ -1,8 +1,82 @@
+import { type FormEvent, useEffect, useState } from "react";
+import { Link, useLocation } from "wouter";
+import { ApiClientError, useAuth } from "../stores";
+
export function LoginPage() {
+ const [, navigate] = useLocation();
+ const { login, isAuthenticated } = useAuth();
+ const [username, setUsername] = useState("");
+ const [password, setPassword] = useState("");
+ const [error, setError] = useState<string | null>(null);
+ const [isSubmitting, setIsSubmitting] = useState(false);
+
+ // Redirect if already authenticated
+ useEffect(() => {
+ if (isAuthenticated) {
+ navigate("/", { replace: true });
+ }
+ }, [isAuthenticated, navigate]);
+
+ const handleSubmit = async (e: FormEvent) => {
+ e.preventDefault();
+ setError(null);
+
+ setIsSubmitting(true);
+
+ try {
+ await login(username, password);
+ navigate("/", { replace: true });
+ } catch (err) {
+ if (err instanceof ApiClientError) {
+ setError(err.message);
+ } else {
+ setError("Login failed. Please try again.");
+ }
+ } finally {
+ setIsSubmitting(false);
+ }
+ };
+
return (
<div>
<h1>Login</h1>
- <p>Login page coming soon</p>
+ <form onSubmit={handleSubmit}>
+ {error && (
+ <div role="alert" style={{ color: "red" }}>
+ {error}
+ </div>
+ )}
+ <div>
+ <label htmlFor="username">Username</label>
+ <input
+ id="username"
+ type="text"
+ value={username}
+ onChange={(e) => setUsername(e.target.value)}
+ required
+ autoComplete="username"
+ disabled={isSubmitting}
+ />
+ </div>
+ <div>
+ <label htmlFor="password">Password</label>
+ <input
+ id="password"
+ type="password"
+ value={password}
+ onChange={(e) => setPassword(e.target.value)}
+ required
+ autoComplete="current-password"
+ disabled={isSubmitting}
+ />
+ </div>
+ <button type="submit" disabled={isSubmitting}>
+ {isSubmitting ? "Logging in..." : "Login"}
+ </button>
+ </form>
+ <p>
+ Don't have an account? <Link href="/register">Register</Link>
+ </p>
</div>
);
}