Spaces:
Paused
Paused
File size: 5,136 Bytes
bc45e54 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 | import { useState } from "react";
import { apiPost, setToken } from "../lib/api";
import { CLASSLENS_ICON } from "../lib/icon";
interface User {
id: number;
email: string;
display_name: string;
}
interface AuthResponse {
token: string;
user: User;
}
interface LoginFormProps {
onLogin: (user: User) => void;
}
export function LoginForm({ onLogin }: LoginFormProps) {
const [mode, setMode] = useState<"login" | "register">("login");
const [email, setEmail] = useState("");
const [password, setPassword] = useState("");
const [error, setError] = useState("");
const [loading, setLoading] = useState(false);
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
setError("");
setLoading(true);
try {
const endpoint = mode === "login" ? "/api/auth/login" : "/api/auth/register";
const result = await apiPost<AuthResponse>(endpoint, { email, password });
setToken(result.token);
onLogin(result.user);
} catch (err: unknown) {
setError(err instanceof Error ? err.message : "An error occurred");
} finally {
setLoading(false);
}
};
return (
<div className="min-h-screen flex items-center justify-center bg-gradient-to-br from-[#0f1419] via-[#1a2332] to-[#0f1419]">
<div className="relative">
<div className="absolute -top-20 -left-20 w-40 h-40 bg-[var(--color-primary)]/20 rounded-full blur-3xl" />
<div className="absolute -bottom-20 -right-20 w-40 h-40 bg-[var(--color-accent)]/20 rounded-full blur-3xl" />
<div className="relative bg-[var(--color-surface)] border border-[var(--color-border)] rounded-2xl p-8 max-w-md w-full shadow-2xl">
<div className="text-center mb-8">
<img
src={CLASSLENS_ICON}
alt="ClassLens"
className="w-24 h-24 mx-auto mb-4 rounded-2xl shadow-lg"
/>
<h1 className="text-2xl font-bold text-[var(--color-text)] font-display">
ClassLens
</h1>
<p className="text-[var(--color-text-muted)] mt-2">
AI 驅動的考試分析
</p>
</div>
{/* Mode toggle */}
<div className="flex mb-6 bg-[var(--color-background)] rounded-xl p-1">
<button
type="button"
onClick={() => { setMode("login"); setError(""); }}
className={`flex-1 py-2 text-sm font-medium rounded-lg transition-all ${
mode === "login"
? "bg-[var(--color-primary)] text-white shadow"
: "text-[var(--color-text-muted)] hover:text-[var(--color-text)]"
}`}
>
登入
</button>
<button
type="button"
onClick={() => { setMode("register"); setError(""); }}
className={`flex-1 py-2 text-sm font-medium rounded-lg transition-all ${
mode === "register"
? "bg-[var(--color-primary)] text-white shadow"
: "text-[var(--color-text-muted)] hover:text-[var(--color-text)]"
}`}
>
註冊
</button>
</div>
<form onSubmit={handleSubmit} className="space-y-4">
<div>
<label className="block text-sm font-medium text-[var(--color-text-muted)] mb-2">
電子郵件
</label>
<input
type="email"
value={email}
onChange={(e) => setEmail(e.target.value)}
placeholder="your@email.com"
required
className="input"
autoFocus
/>
</div>
<div>
<label className="block text-sm font-medium text-[var(--color-text-muted)] mb-2">
密碼
</label>
<input
type="password"
value={password}
onChange={(e) => setPassword(e.target.value)}
placeholder="••••••••"
required
minLength={4}
className="input"
/>
</div>
{error && (
<p className="text-sm text-red-400 flex items-center gap-1">
<svg className="w-4 h-4 shrink-0" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 8v4m0 4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
</svg>
{error}
</p>
)}
<button
type="submit"
disabled={loading}
className="w-full py-3 px-4 bg-gradient-to-r from-[var(--color-primary)] to-[var(--color-accent)] text-white font-semibold rounded-xl hover:opacity-90 transition-opacity disabled:opacity-50"
>
{loading ? "..." : mode === "login" ? "登入" : "建立帳號"}
</button>
</form>
</div>
</div>
</div>
);
}
|