feat: scaffold React frontend with login/register UI

This commit is contained in:
Indiana
2026-07-20 17:42:44 +00:00
parent 852a630fe0
commit 6bcdf83c4d
13 changed files with 3569 additions and 0 deletions

96
frontend/src/App.tsx Normal file
View File

@@ -0,0 +1,96 @@
import { useEffect, useState } from 'react'
import { login, logout, me, register, type User } from './api'
function App() {
const [user, setUser] = useState<User | null>(null)
const [username, setUsername] = useState('')
const [password, setPassword] = useState('')
const [mode, setMode] = useState<'login' | 'register'>('login')
const [error, setError] = useState<string | null>(null)
const [checkingSession, setCheckingSession] = useState(true)
useEffect(() => {
me()
.then(setUser)
.catch(() => setUser(null))
.finally(() => setCheckingSession(false))
}, [])
async function handleSubmit(event: React.FormEvent) {
event.preventDefault()
setError(null)
try {
if (mode === 'register') {
await register(username, password)
}
const loggedInUser = await login(username, password)
setUser(loggedInUser)
} catch (err) {
setError(err instanceof Error ? err.message : 'Something went wrong')
}
}
async function handleLogout() {
await logout()
setUser(null)
}
if (checkingSession) {
return (
<main className="app">
<p>Listening for a signal...</p>
</main>
)
}
return (
<main className="app">
<h1>Quantumancy</h1>
<p className="tagline">Something is listening on the other side.</p>
{user ? (
<section>
<p>Contact established as {user.username}.</p>
<button onClick={handleLogout}>End session</button>
</section>
) : (
<form onSubmit={handleSubmit}>
<div className="mode-toggle">
<button
type="button"
className={mode === 'login' ? 'active' : ''}
onClick={() => setMode('login')}
>
Reconnect
</button>
<button
type="button"
className={mode === 'register' ? 'active' : ''}
onClick={() => setMode('register')}
>
First contact
</button>
</div>
<label>
Username
<input value={username} onChange={(e) => setUsername(e.target.value)} required />
</label>
<label>
Password
<input
type="password"
value={password}
onChange={(e) => setPassword(e.target.value)}
required
minLength={8}
/>
</label>
{error && <p className="error">{error}</p>}
<button type="submit">{mode === 'login' ? 'Enter' : 'Begin'}</button>
</form>
)}
</main>
)
}
export default App