import { useState, useRef, useEffect } from "react"; import { LoginRequest, LoginResponse } from "../items"; interface LoginProps { onLogin: (token: string) => void; } export function Login({ onLogin }: LoginProps) { const usernameInputRef = useRef(null); const [username, setUsername] = useState(""); const [password, setPassword] = useState(""); const [error, setError] = useState(""); const [isSubmitting, setIsSubmitting] = useState(false); const [fieldErrors, setFieldErrors] = useState<{ username?: string, password?: string }>({}); const [isSuccess, setIsSuccess] = useState(false); const [shakeCard, setShakeCard] = useState(false); useEffect(() => { usernameInputRef.current?.focus(); }, []); const validateForm = () => { const errors: { username?: string, password?: string } = {}; if (!username.trim()) errors.username = "Username is required"; if (!password) errors.password = "Password is required"; setFieldErrors(errors); return Object.keys(errors).length === 0; }; const handleSubmit = async (e: React.FormEvent) => { e.preventDefault(); setError(""); if (!validateForm()) { setShakeCard(true); setTimeout(() => setShakeCard(false), 500); return; } setIsSubmitting(true); try { const req = LoginRequest.create({ username: username.trim(), password }); const body = LoginRequest.encode(req).finish(); const res = await fetch("/auth/login", { method: "POST", body, headers: { "Content-Type": "application/octet-stream", }, }); const buffer = await res.arrayBuffer(); const response = LoginResponse.decode(new Uint8Array(buffer)); if (response.success) { setIsSuccess(true); onLogin(response.token); } else { setError(response.message || "Login failed"); setShakeCard(true); setTimeout(() => setShakeCard(false), 500); } } catch (err) { console.error("Login error:", err); setError("An unexpected error occurred. Please try again."); setShakeCard(true); setTimeout(() => setShakeCard(false), 500); } finally { setIsSubmitting(false); } }; const handleKeyDown = (e: React.KeyboardEvent) => { if (e.key === "Enter") { handleSubmit(e); } }; return (
{isSubmitting && (
)} {isSuccess && (
Success!
)}

🎮 Login

Welcome back! Please sign in to continue.

👤 { setUsername(e.target.value); if (fieldErrors.username) setFieldErrors({ ...fieldErrors, username: undefined }); }} onKeyDown={handleKeyDown} placeholder="Enter your username" aria-label="Username" aria-invalid={!!fieldErrors.username} aria-describedby={fieldErrors.username ? "username-error" : undefined} style={{ width: "100%", paddingLeft: "2.75rem", paddingRight: "0.75rem", paddingTop: "0.75rem", paddingBottom: "0.75rem", borderColor: fieldErrors.username ? "#f44336" : undefined, borderWidth: fieldErrors.username ? "2px" : "1px", fontSize: "1rem" }} />
{fieldErrors.username && ( ⚠️ {fieldErrors.username} )}
🔒 { setPassword(e.target.value); if (fieldErrors.password) setFieldErrors({ ...fieldErrors, password: undefined }); }} onKeyDown={handleKeyDown} placeholder="Enter your password" aria-label="Password" aria-invalid={!!fieldErrors.password} aria-describedby={fieldErrors.password ? "password-error" : undefined} style={{ width: "100%", paddingLeft: "2.75rem", paddingRight: "0.75rem", paddingTop: "0.75rem", paddingBottom: "0.75rem", borderColor: fieldErrors.password ? "#f44336" : undefined, borderWidth: fieldErrors.password ? "2px" : "1px", fontSize: "1rem" }} />
{fieldErrors.password && ( ⚠️ {fieldErrors.password} )}
{error && (
⚠️ {error}
)}
); }