Private
Public Access
Add self-service password change, forgot-password flow, and fix admin UI bugs
Users can change their own password from the profile modal, and a "forgot password" link sends a 15-minute expiring reset link (same hashed-token pattern as site invites). The forgot-password response is always generic so it never reveals which emails are registered. Also fixes two admin-page display bugs found while testing: table row divider lines that didn't line up across a row (the actions column had `display: flex` on the <td> itself, breaking it out of normal table-cell layout -- moved to a child <div>), and the pending-invites list floating with no visual grouping (now boxed with a label and per-status badges).
This commit is contained in:
@@ -0,0 +1,70 @@
|
||||
import { useState, type FormEvent } from 'react'
|
||||
import { Link, Navigate } from 'react-router-dom'
|
||||
import { requestPasswordReset } from '../api/auth'
|
||||
import { useAuth } from '../context/AuthContext'
|
||||
import logo from '../assets/logo.png'
|
||||
import './LoginPage.css'
|
||||
|
||||
export function ForgotPasswordPage() {
|
||||
const { user } = useAuth()
|
||||
const [email, setEmail] = useState('')
|
||||
const [submitting, setSubmitting] = useState(false)
|
||||
const [sent, setSent] = useState(false)
|
||||
|
||||
if (user) return <Navigate to="/rooms" replace />
|
||||
|
||||
async function handleSubmit(e: FormEvent) {
|
||||
e.preventDefault()
|
||||
setSubmitting(true)
|
||||
try {
|
||||
await requestPasswordReset(email)
|
||||
} catch {
|
||||
// Fall through to the generic message regardless -- the request
|
||||
// itself never reveals whether the email is registered.
|
||||
} finally {
|
||||
setSubmitting(false)
|
||||
setSent(true)
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="login-screen">
|
||||
<div className="login-card">
|
||||
<div className="login-brand">
|
||||
<img src={logo} alt="" />
|
||||
<span>KeepItTalking</span>
|
||||
</div>
|
||||
|
||||
{sent ? (
|
||||
<p className="login-copy">
|
||||
If an account exists for that email, a password reset link is on its way. The link
|
||||
expires in 15 minutes.
|
||||
</p>
|
||||
) : (
|
||||
<>
|
||||
<p className="login-copy">Enter your account email and we'll send a reset link.</p>
|
||||
<form className="login-form" onSubmit={handleSubmit}>
|
||||
<label>
|
||||
Email
|
||||
<input
|
||||
type="email"
|
||||
value={email}
|
||||
onChange={(e) => setEmail(e.target.value)}
|
||||
required
|
||||
autoFocus
|
||||
/>
|
||||
</label>
|
||||
<button type="submit" className="btn-primary" disabled={submitting}>
|
||||
{submitting ? 'Sending…' : 'Send reset link'}
|
||||
</button>
|
||||
</form>
|
||||
</>
|
||||
)}
|
||||
|
||||
<Link to="/login" className="login-link">
|
||||
Back to log in
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user