import { useState, useEffect, useCallback } from 'react' import { adminApi } from './api' import styles from './Admin.module.css' // Owner-only — the server enforces this too (requireOwner on every /api/admin/instructors* // route); this component is only ever mounted for an owner, but the backend never trusts that. export default function InstructorsView({ currentInstructorId }) { const [instructors, setInstructors] = useState(null) const [selected, setSelected] = useState(null) const [error, setError] = useState(null) const reload = useCallback(() => { adminApi.instructors().then(setInstructors).catch((e) => setError(e.message)) }, []) useEffect(reload, [reload]) if (selected) return { setSelected(null); reload() }} /> return ( <> {error &&
{error}
}

Add instructor

Instructors

{!instructors ?

Loading…

: ( {instructors.map((i) => ( ))}
NameEmailRoleStatusLast login
{i.display_name} {i.email} {i.role} {i.status} {i.last_login_at ? new Date(i.last_login_at).toLocaleString() : 'never'}
)}
) } function CreateInstructorForm({ onCreated }) { const [form, setForm] = useState({ email: '', display_name: '', role: 'instructor', password: '' }) const [error, setError] = useState(null) const [busy, setBusy] = useState(false) const submit = async (e) => { e.preventDefault() setBusy(true); setError(null) try { await adminApi.createInstructor(form) setForm({ email: '', display_name: '', role: 'instructor', password: '' }) onCreated() } catch (e) { setError(e.message) } finally { setBusy(false) } } return (
{error &&
{error}
}
setForm({ ...form, display_name: e.target.value })} required />
setForm({ ...form, email: e.target.value })} required />
setForm({ ...form, password: e.target.value })} required minLength={12} />
) } function InstructorDetail({ instructorId, isSelf, onBack }) { const [instructor, setInstructor] = useState(null) const [error, setError] = useState(null) const [notice, setNotice] = useState(null) const [busy, setBusy] = useState(false) const [newPassword, setNewPassword] = useState('') const reload = useCallback(() => { adminApi.instructor(instructorId).then(setInstructor).catch((e) => setError(e.message)) }, [instructorId]) useEffect(reload, [reload]) const updateRole = async (role) => { setBusy(true); setError(null); setNotice(null) try { await adminApi.updateInstructor(instructorId, { role }); reload() } catch (e) { setError(e.message) } finally { setBusy(false) } } const toggleStatus = async () => { const status = instructor.status === 'active' ? 'inactive' : 'active' setBusy(true); setError(null); setNotice(null) try { await adminApi.updateInstructor(instructorId, { status }); reload() } catch (e) { setError(e.message) } finally { setBusy(false) } } const revokeSessions = async () => { setBusy(true); setError(null) try { await adminApi.revokeInstructorSessions(instructorId); setNotice('All sessions revoked.'); reload() } catch (e) { setError(e.message) } finally { setBusy(false) } } const resetPassword = async (e) => { e.preventDefault() setBusy(true); setError(null); setNotice(null) try { await adminApi.resetInstructorPassword(instructorId, newPassword); setNewPassword(''); setNotice('Password reset. All of their sessions were revoked.') } catch (e) { setError(e.message) } finally { setBusy(false) } } if (!instructor) return error ?
{error}
:

Loading…

return ( <>

{instructor.display_name} ({instructor.email})

{error &&
{error}
} {notice &&

{notice}

}

Role & status

{isSelf &&

This is your own account — the platform refuses to remove the last active owner, whichever account that is.

}

Sessions

Last login: {instructor.last_login_at ? new Date(instructor.last_login_at).toLocaleString() : 'never'}

{instructor.sessions?.length ? ( {instructor.sessions.map((s) => ( ))}
CreatedLast activeExpiresStatus
{new Date(s.created_at).toLocaleString()} {new Date(s.last_seen_at).toLocaleString()} {new Date(s.expires_at).toLocaleString()} {s.revoked_at ? revoked : active}
) :

No sessions on record.

}

Reset password

setNewPassword(e.target.value)} minLength={12} required />
) }