buckets/frontend/src/auth/RegisterPage.tsx

100 lines
3.2 KiB
TypeScript

import { useState, type FormEvent } from 'react'
import { Link, useNavigate } from 'react-router'
import AppLayout from '../components/layout/AppLayout'
import Button from '../components/ui/Button'
import FieldError from '../components/ui/FieldError'
import Input from '../components/ui/Input'
import Panel from '../components/ui/Panel'
import { api } from '../lib/api'
import { parseFieldErrors, type FieldErrors } from '../lib/fieldErrors'
export default function RegisterPage() {
const navigate = useNavigate()
const [email, setEmail] = useState('')
const [password, setPassword] = useState('')
const [fieldErrors, setFieldErrors] = useState<FieldErrors>({})
const [formError, setFormError] = useState<string | null>(null)
const [pending, setPending] = useState(false)
async function handleSubmit(event: FormEvent<HTMLFormElement>) {
event.preventDefault()
setFieldErrors({})
setFormError(null)
setPending(true)
try {
await api.postJson('/register', { email, password })
// Register does not establish a session — send the user to sign in.
navigate('/login')
} catch (err: unknown) {
const errors = parseFieldErrors(err)
if (errors !== null) {
setFieldErrors(errors)
} else {
setFormError('Registration failed. Please try again.')
}
} finally {
setPending(false)
}
}
const emailErrors = fieldErrors.email
const passwordErrors = fieldErrors.password
return (
<AppLayout>
<Panel className="mx-auto max-w-sm space-y-4 mt-32">
<h2 className="text-primary font-bold uppercase tracking-wider">
Register
</h2>
<form className="space-y-4" onSubmit={handleSubmit}>
<div className="space-y-1">
<Input
label="Email"
type="email"
autoComplete="email"
required
value={email}
onChange={(e) => setEmail(e.target.value)}
aria-invalid={emailErrors !== undefined}
aria-describedby={emailErrors ? 'email-errors' : undefined}
/>
<FieldError id="email-errors" messages={emailErrors} />
</div>
<div className="space-y-1">
<Input
label="Password"
type="password"
autoComplete="new-password"
required
value={password}
onChange={(e) => setPassword(e.target.value)}
aria-invalid={passwordErrors !== undefined}
aria-describedby={passwordErrors ? 'password-errors' : undefined}
/>
<FieldError id="password-errors" messages={passwordErrors} />
</div>
{formError !== null && (
<p role="alert" className="text-destructive font-mono text-sm">
{formError}
</p>
)}
<Button type="submit" glow disabled={pending} className="w-full">
{pending ? 'Registering…' : 'Register'}
</Button>
</form>
<p className="text-muted-foreground font-mono text-sm">
Already have an account?{' '}
<Link to="/login" className="text-primary underline">
Log In
</Link>
</p>
</Panel>
</AppLayout>
)
}