Make recording guidelines editable from admin panel; v2.0.18
Publish Container / docker (push) Successful in 14m23s

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
nmemmertandClaude Sonnet 4.6 committed 2026-09-09 08:38:02 -04:00
1 parent ef1d9fd8fc
commit 86de45dbc3
5 files changed
+157 -8

No files matched your search

+92 -1
View File
@@ -666,7 +666,7 @@ type AdminView =
| 'about:show-copy' | 'about:bio' | 'about:testimonials'
| 'contact:profile' | 'contact:copy'
| 'podcast:current-series' | 'podcast:highlights' | 'podcast:finished-books' | 'podcast:scripts' | 'podcast:rss'
| 'nuggets:settings' | 'nuggets:hosts' | 'nuggets:upload-portal' | 'nuggets:announcement' | 'nuggets:history'
| 'nuggets:settings' | 'nuggets:hosts' | 'nuggets:upload-portal' | 'nuggets:announcement' | 'nuggets:guidelines' | 'nuggets:history'
| 'downloads' | 'custom-links' | 'content-blocks' | 'books' | 'recommended-resources'
| 'questions' | 'study-comments' | 'analytics' | 'assets' | 'colossians-study' | 'qr-codes'
| 'subscribers' | 'study-users' | 'email-templates' | 'broadcast'
@@ -723,6 +723,7 @@ const ADMIN_NAV: NavGroup[] = [
{ kind: 'leaf', view: 'nuggets:hosts', label: 'Hosts' },
{ kind: 'leaf', view: 'nuggets:upload-portal', label: 'Upload Portal' },
{ kind: 'leaf', view: 'nuggets:announcement', label: 'Announcement' },
{ kind: 'leaf', view: 'nuggets:guidelines', label: 'Recording Guidelines' },
{ kind: 'leaf', view: 'nuggets:history', label: 'Upload History' },
]},
{ kind: 'leaf', view: 'colossians-study', label: 'Studies' },
@@ -1356,6 +1357,93 @@ function NuggetsAnnouncementAdmin() {
)
}
// ── Nuggets Guidelines admin ───────────────────────────────────────────────
const DEFAULT_GUIDELINE_LINES = [
'Export as MP3 or WAV at 44.1 kHz, 128 kbps minimum.',
'File name: YYYY-MM-DD-EpisodeName.mp3 (e.g. 2026-09-09-Romans-5.mp3).',
'Recommended length: 8–15 minutes. Keep a 0.5s lead-in and 1s fade-out.',
'Normalize to -1 dBFS before exporting.',
'Match the episode name here to the file name exactly so search stays clean.',
].join('\n')
function NuggetsGuidelinesAdmin() {
const [lines, setLines] = useState('')
const [loading, setLoading] = useState(true)
const [saving, setSaving] = useState(false)
const [msg, setMsg] = useState('')
useEffect(() => {
fetch('/api/nuggets-upload/guidelines')
.then(r => r.json())
.then(d => setLines(Array.isArray(d.lines) ? d.lines.join('\n') : DEFAULT_GUIDELINE_LINES))
.finally(() => setLoading(false))
}, [])
async function handleSave(e: React.FormEvent) {
e.preventDefault()
setSaving(true)
setMsg('')
try {
const r = await fetch('/api/admin/nuggets-upload/guidelines', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ lines }),
})
const d = await r.json()
if (!r.ok) { setMsg(d.message ?? 'Failed.'); return }
setMsg('✓ Guidelines saved.')
} finally {
setSaving(false)
}
}
async function handleReset() {
setSaving(true)
setMsg('')
try {
await fetch('/api/admin/nuggets-upload/guidelines', { method: 'DELETE' })
setLines(DEFAULT_GUIDELINE_LINES)
setMsg('✓ Reset to defaults.')
} finally {
setSaving(false)
}
}
return (
<section className="admin-panel-section" aria-label="Nuggets recording guidelines">
<div className="admin-panel-head">
<h2>Nuggets — Recording Guidelines</h2>
<p>
One guideline per line. These appear in the collapsible "Recording Guidelines" panel on the host upload portal.
Max 20 lines.
</p>
</div>
{loading ? <p className="admin-stats-note">Loading…</p> : (
<form onSubmit={handleSave}>
<div className="admin-field" style={{ maxWidth: 620 }}>
<label htmlFor="gl-lines">Guidelines (one per line)</label>
<textarea
id="gl-lines"
rows={10}
style={{ fontFamily: 'monospace', fontSize: '0.87rem' }}
value={lines}
onChange={e => setLines(e.target.value)}
/>
<span style={{ fontSize: '0.78rem', color: '#999' }}>
{lines.split('\n').filter(l => l.trim()).length} / 20 lines
</span>
</div>
<div style={{ display: 'flex', gap: 8, marginTop: 10 }}>
<button className="btn-admin-save" type="submit" disabled={saving}>{saving ? 'Saving…' : 'Save'}</button>
<button className="btn-admin-ghost" type="button" onClick={handleReset} disabled={saving}>Reset to defaults</button>
</div>
{msg && <p style={{ color: msg.startsWith('✓') ? '#27ae60' : '#c0392b', marginTop: 6, fontSize: '0.85rem' }}>{msg}</p>}
</form>
)}
</section>
)
}
// ── Nuggets Upload History admin ───────────────────────────────────────────
interface HistoryEntryAdmin {
hostId: string
@@ -5059,6 +5147,9 @@ async function handleAssetUpload(event: ChangeEvent<HTMLInputElement>) {
{/* NUGGETS ANNOUNCEMENT */}
{adminView === 'nuggets:announcement' && <NuggetsAnnouncementAdmin />}
{/* NUGGETS RECORDING GUIDELINES */}
{adminView === 'nuggets:guidelines' && <NuggetsGuidelinesAdmin />}
{/* NUGGETS UPLOAD HISTORY */}
{adminView === 'nuggets:history' && <NuggetsHistoryAdmin />}