diff options
| author | Adam Mathes <adam@adammathes.com> | 2026-02-12 21:50:56 -0800 |
|---|---|---|
| committer | Adam Mathes <adam@adammathes.com> | 2026-02-12 21:50:56 -0800 |
| commit | 42f1b4de384bcbbdab3b80d8e5cc53b36fcffd50 (patch) | |
| tree | 3a5aab90607131231ec68367f8cc00425d7dc516 /frontend/src/components/Login.tsx | |
| parent | 9db2500fb340ef304c0f15f4379bc33589df9a63 (diff) | |
| download | neko-42f1b4de384bcbbdab3b80d8e5cc53b36fcffd50.tar.gz neko-42f1b4de384bcbbdab3b80d8e5cc53b36fcffd50.tar.bz2 neko-42f1b4de384bcbbdab3b80d8e5cc53b36fcffd50.zip | |
Implement frontend login logic with >90% coverage
Diffstat (limited to 'frontend/src/components/Login.tsx')
| -rw-r--r-- | frontend/src/components/Login.tsx | 54 |
1 files changed, 54 insertions, 0 deletions
diff --git a/frontend/src/components/Login.tsx b/frontend/src/components/Login.tsx new file mode 100644 index 0000000..2e8bbf7 --- /dev/null +++ b/frontend/src/components/Login.tsx @@ -0,0 +1,54 @@ +import { useState, type FormEvent } from 'react'; +import { useNavigate } from 'react-router-dom'; +import './Login.css'; + +export default function Login() { + const [password, setPassword] = useState(''); + const [error, setError] = useState(''); + const navigate = useNavigate(); + + const handleSubmit = async (e: FormEvent) => { + e.preventDefault(); + setError(''); + + try { + // Use URLSearchParams to send as form-urlencoded, matching backend expectation + const params = new URLSearchParams(); + params.append('password', password); + + const res = await fetch('/api/login', { + method: 'POST', + body: params, + }); + + if (res.ok) { + navigate('/'); + } else { + const data = await res.json(); + setError(data.message || 'Login failed'); + } + } catch (err) { + setError('Network error'); + } + }; + + return ( + <div className="login-container"> + <form onSubmit={handleSubmit} className="login-form"> + <h1>neko rss mode</h1> + <div className="form-group"> + <label htmlFor="password">password</label> + <input + id="password" + type="password" + value={password} + onChange={(e) => setPassword(e.target.value)} + autoFocus + /> + </div> + {error && <div className="error-message">{error}</div>} + <button type="submit">login</button> + </form> + </div> + ); +} |
