Private
Public Access
Renames the app's display name everywhere (page titles, PWA manifest, TopBar, email subject lines, HMAC signature header) and its internal technical slug from chatapp to ds-chat/ds_chat: the Python package name and console script, the systemd unit and its user/group/paths, the deploy scripts, the Docker container names, and the Postgres database name. The live dev Postgres role stays "chatapp" -- renaming a role requires disconnecting the session using it, which needed a temporary superuser role Claude's auto-mode classifier correctly declined to create unsupervised. Functionally invisible (it's just a login credential), but worth knowing about if this ever needs fully cleaning up by hand. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
71 lines
2.1 KiB
TypeScript
71 lines
2.1 KiB
TypeScript
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>DS Chat</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>
|
|
)
|
|
}
|