92 lines
3.0 KiB
JavaScript
92 lines
3.0 KiB
JavaScript
import { useEffect, useState } from 'react'
|
|
import { Loading, ErrorState } from '../../../components/PageState.jsx'
|
|
import { api } from '../../../api/client.js'
|
|
import { useSite } from '../../../contexts/SiteContext.jsx'
|
|
|
|
// Editable settings shown on this screen (key -> label + control type).
|
|
const FIELDS = [
|
|
{ key: 'site_title', label: 'Site title' },
|
|
{ key: 'homepage_teaser', label: 'Homepage teaser', long: true },
|
|
{ key: 'maintenance_message', label: 'Maintenance message', long: true },
|
|
{ key: 'status_message', label: 'Status message' },
|
|
{ key: 'contact_email', label: 'Contact email' },
|
|
]
|
|
|
|
export default function SettingsAdmin() {
|
|
const { refresh: refreshSite } = useSite()
|
|
const [values, setValues] = useState(null)
|
|
const [initial, setInitial] = useState({})
|
|
const [loading, setLoading] = useState(true)
|
|
const [error, setError] = useState('')
|
|
const [busy, setBusy] = useState(false)
|
|
const [saved, setSaved] = useState(false)
|
|
|
|
useEffect(() => {
|
|
let active = true
|
|
api.admin
|
|
.getSettings()
|
|
.then((all) => {
|
|
if (!active) return
|
|
const v = {}
|
|
FIELDS.forEach((f) => (v[f.key] = all[f.key] ?? ''))
|
|
setValues(v)
|
|
setInitial(v)
|
|
})
|
|
.catch(() => active && setError('Could not load settings.'))
|
|
.finally(() => active && setLoading(false))
|
|
return () => {
|
|
active = false
|
|
}
|
|
}, [])
|
|
|
|
if (loading) return <Loading />
|
|
if (error) return <ErrorState message={error} />
|
|
|
|
const set = (k) => (e) => {
|
|
setValues((v) => ({ ...v, [k]: e.target.value }))
|
|
setSaved(false)
|
|
}
|
|
|
|
async function save() {
|
|
setBusy(true)
|
|
setError('')
|
|
try {
|
|
await api.admin.updateSettings(values)
|
|
setInitial(values)
|
|
setSaved(true)
|
|
await refreshSite()
|
|
} catch (err) {
|
|
setError(err.message || 'Could not save settings.')
|
|
} finally {
|
|
setBusy(false)
|
|
}
|
|
}
|
|
|
|
return (
|
|
<section style={{ maxWidth: 620 }}>
|
|
<div style={{ display: 'flex', flexDirection: 'column', gap: 18 }}>
|
|
{FIELDS.map((f) => (
|
|
<label key={f.key} style={{ display: 'block' }}>
|
|
<span className="field-label">{f.label}</span>
|
|
{f.long ? (
|
|
<textarea value={values[f.key]} onChange={set(f.key)} className="textarea" style={{ minHeight: 90 }} />
|
|
) : (
|
|
<input type="text" value={values[f.key]} onChange={set(f.key)} className="input" />
|
|
)}
|
|
</label>
|
|
))}
|
|
<div style={{ display: 'flex', gap: 10, marginTop: 6, alignItems: 'center' }}>
|
|
<button onClick={save} disabled={busy} className="btn btn-primary btn-sq">
|
|
{busy ? 'Saving…' : 'Save changes'}
|
|
</button>
|
|
<button onClick={() => setValues(initial)} disabled={busy} className="pill">
|
|
Reset
|
|
</button>
|
|
{saved && <span className="sans" style={{ color: '#7fd0a4', fontSize: '0.85rem' }}>Saved.</span>}
|
|
{error && <span className="sans" style={{ color: '#d98b84', fontSize: '0.85rem' }}>{error}</span>}
|
|
</div>
|
|
</div>
|
|
</section>
|
|
)
|
|
}
|