function AdminPanel({ go, user, token }) { const wide = useWide(); // Redirect if not admin React.useEffect(() => { if (user === null) go('login'); else if (user && user.role !== 'admin') go('landing'); }, [user]); const [view, setView] = React.useState('list'); // 'list' | 'edit' const [memos, setMemos] = React.useState([]); const [editing, setEditing] = React.useState(null); // memo object or null (new) const [saving, setSaving] = React.useState(false); const [msg, setMsg] = React.useState(''); const headers = { 'Content-Type': 'application/json', Authorization: 'Bearer ' + token }; const loadMemos = () => fetch('/api/memos', { headers }).then(r => r.json()).then(setMemos); React.useEffect(() => { loadMemos(); }, []); const newMemo = () => { setEditing({ slug: '', title: '', date: new Date().toLocaleDateString('en-US',{month:'short',day:'numeric',year:'numeric'}), category: 'MARKETS', read_min: 7, free: false, summary: '', body: [], published: false }); setView('edit'); }; const editMemo = (m) => { setEditing({ ...m, body: m.body || [] }); setView('edit'); }; const deleteMemo = async (slug) => { if (!confirm('Delete ' + slug + '?')) return; await fetch('/api/memos/' + slug, { method: 'DELETE', headers }); loadMemos(); }; const togglePublish = async (m) => { await fetch('/api/memos/' + m.slug, { method: 'PATCH', headers, body: JSON.stringify({ published: !m.published }) }); loadMemos(); }; const saveMemo = async () => { setSaving(true); setMsg(''); const isNew = !memos.find(m => m.slug === editing.slug); const method = isNew ? 'POST' : 'PATCH'; const url = isNew ? '/api/memos' : '/api/memos/' + editing.slug; try { const res = await fetch(url, { method, headers, body: JSON.stringify({ ...editing, body: editing.body }) }); const data = await res.json(); if (!res.ok) { setMsg('Error: ' + (data.error || 'Save failed')); return; } setMsg('Saved!'); loadMemos(); setTimeout(() => { setView('list'); setEditing(null); setMsg(''); }, 800); } catch { setMsg('Network error'); } finally { setSaving(false); } }; const addBlock = (type) => setEditing(e => ({ ...e, body: [...e.body, { t: type, s: type === 'bullets' ? [''] : '' }] })); const updateBlock = (i, val) => setEditing(e => { const b = [...e.body]; b[i] = { ...b[i], s: val }; return { ...e, body: b }; }); const removeBlock = (i) => setEditing(e => ({ ...e, body: e.body.filter((_, j) => j !== i) })); const moveBlock = (i, dir) => setEditing(e => { const b = [...e.body]; const j = i + dir; if (j < 0 || j >= b.length) return e; [b[i], b[j]] = [b[j], b[i]]; return { ...e, body: b }; }); const updateBullet = (bi, li, val) => setEditing(e => { const b = [...e.body]; const bullets = [...b[bi].s]; bullets[li] = val; b[bi] = { ...b[bi], s: bullets }; return { ...e, body: b }; }); const addBullet = (bi) => setEditing(e => { const b = [...e.body]; b[bi] = { ...b[bi], s: [...b[bi].s, ''] }; return { ...e, body: b }; }); const removeBullet = (bi, li) => setEditing(e => { const b = [...e.body]; b[bi] = { ...b[bi], s: b[bi].s.filter((_,j)=>j!==li) }; return { ...e, body: b }; }); const inp = (style={}) => ({ style: { width: '100%', background: '#0d0d0a', border: '1px solid #2a2a22', color: '#e8e4d6', fontFamily: 'JetBrains Mono, monospace', fontSize: 12, padding: '8px 10px', outline: 'none', boxSizing: 'border-box', ...style } }); const lbl = (text) =>