"use client"; import { useCallback, useEffect, useState } from "react"; import { c2api } from "@/lib/c2api"; import { useAuth } from "@/components/AuthProvider"; import type { UserRecord, UserRole } from "@/lib/types"; import { Card } from "@/components/ui/Card"; import { Badge } from "@/components/ui/Badge"; import { Button } from "@/components/ui/Button"; import { EmptyState, ErrorBanner } from "@/components/ui/EmptyState"; import { SkeletonRow } from "@/components/ui/Skeleton"; const ROLE_TONE: Record = { admin: "brand", operator: "success", viewer: "neutral", }; const ROLE_LABEL: Record = { admin: "Admin", operator: "Operator", viewer: "Viewer" }; function InviteModal({ onClose, onCreated }: { onClose: () => void; onCreated: (u: UserRecord) => void }) { const [email, setEmail] = useState(""); const [role, setRole] = useState("viewer"); const [saving, setSaving] = useState(false); const [error, setError] = useState(null); const [inviteLink, setInviteLink] = useState(null); async function handleSubmit(e: React.FormEvent) { e.preventDefault(); setSaving(true); setError(null); try { const created = await c2api.createUser({ email, role }); onCreated(created); if (created.invite_link) setInviteLink(created.invite_link); else onClose(); } catch (err) { setError(err instanceof Error ? err.message : String(err)); } finally { setSaving(false); } } if (inviteLink) { return (

Member invited

Share this one-time invite link so they can set their password. It expires after use.

{inviteLink}

); } return (

Invite a member

setEmail(e.target.value)} className="w-full bg-gray-800 border border-gray-700 rounded-lg px-3 py-2 text-white text-sm focus:outline-none focus:border-indigo-500" placeholder="teammate@example.com" />
{error &&

{error}

}
); } export default function MembersSettingsPage() { const { user } = useAuth(); const [users, setUsers] = useState([]); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); const [showInvite, setShowInvite] = useState(false); const [savingUid, setSavingUid] = useState(null); const load = useCallback(async () => { try { setUsers(await c2api.listUsers()); } catch (e) { setError(e instanceof Error ? e.message : String(e)); } finally { setLoading(false); } }, []); useEffect(() => { load(); }, [load]); async function handleRoleChange(u: UserRecord, role: UserRole) { setSavingUid(u.uid); try { const updated = await c2api.updateUser(u.uid, { role, owned_node_ids: role === "operator" ? u.owned_node_ids : [] }); setUsers((prev) => prev.map((x) => (x.uid === u.uid ? { ...x, ...updated } : x))); } catch (e) { setError(e instanceof Error ? e.message : String(e)); } finally { setSavingUid(null); } } return (
{showInvite && ( setShowInvite(false)} onCreated={(u) => setUsers((prev) => [...prev, u])} /> )}

{loading ? "Loading members…" : `${users.length} member${users.length !== 1 ? "s" : ""}`}

{error && } {!loading && users.length === 0 ? ( ) : ( {loading ? Array.from({ length: 3 }).map((_, i) => ) : users.map((u) => ( ))}
Member Role Owned nodes Status

{u.display_name || u.email}

{u.display_name &&

{u.email}

}
{u.uid === user?.uid ? ( {ROLE_LABEL[u.role]} ) : ( )} {u.role === "operator" ? u.owned_node_ids.length : "—"} {u.disabled ? Disabled : Active}
)}

Need to disable or delete a member? Use the full user admin panel under Admin → Users.

); }