import { useState, useEffect, useCallback } from 'react' import { useAuth } from '../contexts/AuthContext' function apiHeaders(token) { return { 'Content-Type': 'application/json', Authorization: `Bearer ${token}` } } async function apiFetch(url, options, token) { const res = await fetch(`/api${url}`, { ...options, headers: { ...apiHeaders(token), ...options?.headers } }) if (!res.ok) { const err = await res.json().catch(() => ({})) throw new Error(err.detail || 'Request failed') } if (res.status === 204) return null return res.json() } function UserModal({ user, onClose, onSave, token }) { const isNew = !user.id const [form, setForm] = useState({ username: user.username || '', full_name: user.full_name || '', email: user.email || '', password: '', is_admin: user.is_admin || false, }) const [saving, setSaving] = useState(false) const [error, setError] = useState(null) const set = (k, v) => setForm(f => ({ ...f, [k]: v })) const handleSave = async () => { if (isNew && !form.password) { setError('Password is required for new users.'); return } if (form.password && form.password.length < 6) { setError('Password must be at least 6 characters.'); return } setSaving(true) setError(null) try { if (isNew) { await apiFetch('/admin/users', { method: 'POST', body: JSON.stringify(form) }, token) } else { const { password, username, ...updateData } = form await apiFetch(`/admin/users/${user.id}`, { method: 'PUT', body: JSON.stringify(updateData) }, token) } onSave() } catch (e) { setError(e.message) } finally { setSaving(false) } } return (

{isNew ? 'Add New User' : `Edit: ${user.username}`}

{error &&
{error}
}
set('username', e.target.value)} disabled={!isNew} />
set('full_name', e.target.value)} placeholder="Optional" />
set('email', e.target.value)} placeholder="Optional" />
set('password', e.target.value)} placeholder={isNew ? 'Min 6 characters' : 'Leave blank to keep unchanged'} />
) } function ResetPasswordModal({ user, onClose, token }) { const [password, setPassword] = useState('') const [confirm, setConfirm] = useState('') const [saving, setSaving] = useState(false) const [success, setSuccess] = useState(false) const [error, setError] = useState(null) const handleReset = async () => { if (password.length < 6) { setError('Password must be at least 6 characters.'); return } if (password !== confirm) { setError('Passwords do not match.'); return } setSaving(true) setError(null) try { await apiFetch(`/admin/users/${user.id}/reset-password`, { method: 'POST', body: JSON.stringify({ new_password: password }) }, token) setSuccess(true) } catch (e) { setError(e.message) } finally { setSaving(false) } } return (

Reset Password

Set a new password for {user.username}.

{success ?
✅ Password reset successfully!
: ( <> {error &&
{error}
}
setPassword(e.target.value)} placeholder="Min 6 characters" autoFocus />
setConfirm(e.target.value)} placeholder="Repeat new password" />
)}
{!success && ( )}
) } export default function Admin() { const { token, user: currentUser } = useAuth() const [users, setUsers] = useState([]) const [loading, setLoading] = useState(true) const [error, setError] = useState(null) const [modal, setModal] = useState(null) // { type: 'edit'|'reset', user } const fetchUsers = useCallback(async () => { setLoading(true) try { const data = await apiFetch('/admin/users', {}, token) setUsers(data) } catch (e) { setError(e.message) } finally { setLoading(false) } }, [token]) useEffect(() => { fetchUsers() }, [fetchUsers]) const deleteUser = async (u) => { if (!window.confirm(`Delete user "${u.username}"? This cannot be undone.`)) return try { await apiFetch(`/admin/users/${u.id}`, { method: 'DELETE' }, token) fetchUsers() } catch (e) { alert(e.message) } } const toggleActive = async (u) => { try { await apiFetch(`/admin/users/${u.id}`, { method: 'PUT', body: JSON.stringify({ is_active: !u.is_active }) }, token) fetchUsers() } catch (e) { alert(e.message) } } return (
{modal?.type === 'edit' && ( setModal(null)} onSave={() => { setModal(null); fetchUsers() }} /> )} {modal?.type === 'reset' && ( { setModal(null); fetchUsers() }} /> )}

Admin Panel — User Management

Tip: Make sure to change the default admin password after first login! You are logged in as {currentUser?.username}.
{error &&
{error}
}
{loading ? (
Loading users...
) : ( {users.map(u => ( ))}
Username Full Name Email Role Status Created Actions
{u.username} {u.id === currentUser?.id && (you)} {u.full_name || '—'} {u.email || '—'} {u.is_admin ? '👑 Admin' : '👤 User'} {u.is_active ? '● Active' : '○ Disabled'} {u.created_at ? new Date(u.created_at).toLocaleDateString() : '—'}
)}
) }