/* global React, PR */ const { useState, useEffect, useCallback } = React; const { I, Avatar } = PR; const ROLE_LABELS = { admin: "Admin", lead: "R&D Lead", core: "Designer", orbit: "Analyst", viewer: "Viewer", }; const ROLE_DESCS = { admin: "Full access including user management and system settings.", lead: "Full access. Approves pitch sessions and stage gates.", core: "Authors hypotheses. Edits own projects, comments on others.", orbit: "Owns telemetry and Cold Test data. Attaches reports.", viewer: "Read-only. Procurement uses this to receive scaled packages.", }; const ROLE_PERMS = { admin: ["Edit registry", "Move stages", "Kill / Pivot / Scale", "Manage users", "System settings"], lead: ["Edit registry", "Move stages", "Decide Kill / Pivot / Scale", "Manage users"], core: ["Create hypothesis", "Edit own", "Comment", "Move own to next stage"], orbit: ["Edit metrics", "Attach reports", "Comment"], viewer: ["Read", "Export passport"], }; function fmtLast(s) { if (!s) return "—"; const d = new Date(s); if (isNaN(d.getTime())) return "—"; const diff = Math.floor((Date.now() - d.getTime()) / 86400000); if (diff <= 0) return "today"; if (diff === 1) return "1d ago"; if (diff < 30) return diff + "d ago"; return Math.floor(diff / 30) + "mo ago"; } function InviteModal({ onClose, onSuccess }) { const [form, setForm] = useState({ username: "", email: "", full_name: "", role: "core", password: "" }); const [saving, setSaving] = useState(false); const [error, setError] = useState(""); const set = (k, v) => setForm(f => ({ ...f, [k]: v })); const handleSubmit = async (e) => { e.preventDefault(); setSaving(true); setError(""); try { const resp = await fetch("/api/users", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(form), }); if (!resp.ok) { const err = await resp.json(); setError(err.detail || "Failed to create user"); return; } onSuccess(); onClose(); } finally { setSaving(false); } }; return (
e.stopPropagation()} style={{ maxWidth: 440 }}>
Invite member
{error &&
{error}
}
set("full_name", e.target.value)} placeholder="Jane Doe" />
set("username", e.target.value)} placeholder="jdoe" />
set("email", e.target.value)} placeholder="jane@studio.dev" />
set("password", e.target.value)} placeholder="••••••••" />
); } function UsersView({ data }) { const [users, setUsers] = useState([]); const [loading, setLoading] = useState(true); const [filter, setFilter] = useState("all"); const [showInvite, setShowInvite] = useState(false); const load = useCallback(async () => { const resp = await fetch("/api/users"); if (resp.ok) setUsers(await resp.json()); setLoading(false); }, []); useEffect(() => { load(); }, [load]); const toggleActive = async (u) => { await fetch(`/api/users/${u.id}`, { method: "PUT", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ is_active: !u.is_active }), }); load(); }; if (loading) return
Loading…
; const active = users.filter(u => u.is_active).length; const deactivated = users.filter(u => !u.is_active).length; const byRole = {}; users.forEach(u => { const r = u.role || "core"; byRole[r] = (byRole[r] || 0) + 1; }); const allRoles = Object.keys(ROLE_LABELS); const rows = filter === "all" ? users : filter === "active" ? users.filter(u => u.is_active) : filter === "deactivated" ? users.filter(u => !u.is_active) : users.filter(u => u.role === filter); return (
Members
{users.length}
{active} active · {deactivated} deactivated
Seats used
{active} / 20
Pilot plan
Roles defined
{allRoles.length}
Admin can edit
Teams
{[...new Set(users.map(u => u.role).filter(Boolean))].length}
Active roles in use
Roles & permissions
{allRoles.map(r => (
{ROLE_LABELS[r]} {byRole[r] || 0}

{ROLE_DESCS[r]}

{(ROLE_PERMS[r] || []).map(p => (
{I.check} {p}
))}
))}
Members
setFilter("all")}>All {users.length} setFilter("active")}>Active {active} setFilter("deactivated")}>Deactivated {deactivated} {allRoles.map(r => ( setFilter(r)}> {ROLE_LABELS[r]} {byRole[r] || 0} ))}
{rows.map(u => { const author = { name: u.full_name || u.username, color: u.color || "#7C8BA1" }; return ( ); })}
Name Role Username Status Actions
{u.full_name || u.username}
{u.email}
{ROLE_LABELS[u.role] || u.role} {u.username} {u.is_active ? "Active" : "Deactivated"}
{u.is_active ? ( ) : ( )}
{showInvite && setShowInvite(false)} onSuccess={load} />}
); } window.PRUsers = { UsersView };