Add simple auth for admin access
This commit is contained in:
+93
-1
@@ -600,6 +600,98 @@ function LegalPage({ title, body }: { title: string; body: string[] }) {
|
||||
)
|
||||
}
|
||||
|
||||
function AdminShell({ content, onSave }: { content: SiteContent; onSave: (c: SiteContent) => void }) {
|
||||
const [status, setStatus] = useState<'checking' | 'authenticated' | 'unauthenticated' | 'misconfigured'>('checking')
|
||||
const [password, setPassword] = useState('')
|
||||
const [errorMsg, setErrorMsg] = useState('')
|
||||
const [submitting, setSubmitting] = useState(false)
|
||||
|
||||
useEffect(() => {
|
||||
fetch('/api/admin-auth/status')
|
||||
.then(r => (r.ok ? r.json() : Promise.reject(new Error('Status failed'))))
|
||||
.then(data => {
|
||||
const next = data as { authenticated?: boolean; configured?: boolean }
|
||||
if (next.configured === false) {
|
||||
setStatus('misconfigured')
|
||||
return
|
||||
}
|
||||
setStatus(next.authenticated ? 'authenticated' : 'unauthenticated')
|
||||
})
|
||||
.catch(() => {
|
||||
setStatus('unauthenticated')
|
||||
})
|
||||
}, [])
|
||||
|
||||
async function handleLogin(e: React.FormEvent) {
|
||||
e.preventDefault()
|
||||
setSubmitting(true)
|
||||
setErrorMsg('')
|
||||
try {
|
||||
const res = await fetch('/api/admin-auth/login', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ password }),
|
||||
})
|
||||
if (!res.ok) {
|
||||
const data = await res.json().catch(() => ({}))
|
||||
setErrorMsg((data as { message?: string }).message ?? 'Login failed.')
|
||||
setSubmitting(false)
|
||||
return
|
||||
}
|
||||
setStatus('authenticated')
|
||||
setPassword('')
|
||||
} catch {
|
||||
setErrorMsg('Login failed.')
|
||||
} finally {
|
||||
setSubmitting(false)
|
||||
}
|
||||
}
|
||||
|
||||
async function handleLogout() {
|
||||
try {
|
||||
await fetch('/api/admin-auth/logout', { method: 'POST' })
|
||||
} finally {
|
||||
setStatus('unauthenticated')
|
||||
}
|
||||
}
|
||||
|
||||
if (status === 'authenticated') {
|
||||
return <AdminPage content={content} onSave={onSave} onLogout={handleLogout} />
|
||||
}
|
||||
|
||||
return (
|
||||
<main className="admin-auth-page" aria-label="Admin sign in">
|
||||
<div className="admin-auth-card">
|
||||
<p className="eyebrow">Admin Access</p>
|
||||
<h1>{status === 'misconfigured' ? 'Admin Not Configured' : 'Sign in to Admin'}</h1>
|
||||
{status === 'checking' && <p className="admin-auth-note">Checking session...</p>}
|
||||
{status === 'misconfigured' && (
|
||||
<p className="admin-auth-note">Set the ADMIN_PASSWORD environment variable on the server to enable admin login.</p>
|
||||
)}
|
||||
{status === 'unauthenticated' && (
|
||||
<form className="admin-auth-form" onSubmit={handleLogin}>
|
||||
<label>
|
||||
Password
|
||||
<input
|
||||
type="password"
|
||||
value={password}
|
||||
onChange={e => setPassword(e.target.value)}
|
||||
autoComplete="current-password"
|
||||
required
|
||||
/>
|
||||
</label>
|
||||
{errorMsg && <p className="admin-auth-error">{errorMsg}</p>}
|
||||
<button type="submit" className="btn-primary" disabled={submitting}>
|
||||
{submitting ? 'Signing In…' : 'Sign In'}
|
||||
</button>
|
||||
<Link to="/" className="btn-secondary">Back to Site</Link>
|
||||
</form>
|
||||
)}
|
||||
</div>
|
||||
</main>
|
||||
)
|
||||
}
|
||||
|
||||
export default function App() {
|
||||
const [content, setContent] = useState<SiteContent>(DEFAULTS)
|
||||
|
||||
@@ -618,7 +710,7 @@ export default function App() {
|
||||
<Routes>
|
||||
<Route path="/" element={<LandingPage content={content} />} />
|
||||
<Route path="/thanks" element={<ThankYouPage />} />
|
||||
<Route path="/admin" element={<AdminPage content={content} onSave={setContent} />} />
|
||||
<Route path="/admin" element={<AdminShell content={content} onSave={setContent} />} />
|
||||
<Route
|
||||
path="/privacy"
|
||||
element={(
|
||||
|
||||
Reference in New Issue
Block a user