"use client"; import { useState } from "react"; import { useRouter } from "next/navigation"; import { Badge, Button, Card, ErrorBanner, Field, Input, Modal, PageHeader, Select } from "@/components/ui"; import { apiFetch, ApiClientError } from "@/lib/client-api"; interface UserRow { id: string; role: string; name: string; email: string | null; active: boolean; lastLoginAt: string | null; createdAt: string; } export default function UsersClient({ initial }: { initial: UserRow[] }) { const router = useRouter(); const [open, setOpen] = useState(false); const [editOpen, setEditOpen] = useState(null); return (
setOpen(true)}>New user} /> {initial.map((u) => ( ))} {initial.length === 0 && ( )}
Name Role Email Last login Status
{u.name} {u.role} {u.email ?? "—"} {u.lastLoginAt ? new Date(u.lastLoginAt).toLocaleString() : "—"} {u.active ? "active" : "inactive"}
No users yet.
{open && setOpen(false)} onSaved={() => router.refresh()} />} {editOpen && ( setEditOpen(null)} onSaved={() => router.refresh()} /> )}
); } function NewUserModal({ onClose, onSaved }: { onClose: () => void; onSaved: () => void }) { const [role, setRole] = useState<"admin" | "operator">("operator"); const [name, setName] = useState(""); const [email, setEmail] = useState(""); const [password, setPassword] = useState(""); const [pin, setPin] = useState(""); const [busy, setBusy] = useState(false); const [error, setError] = useState(null); async function submit(e: React.FormEvent) { e.preventDefault(); setBusy(true); setError(null); try { const body = role === "admin" ? { role, name, email, password } : { role, name, pin }; await apiFetch("/api/v1/users", { method: "POST", body: JSON.stringify(body) }); onSaved(); onClose(); } catch (err) { setError(err instanceof ApiClientError ? err.message : "Failed to create user"); } finally { setBusy(false); } } return ( } >
setName(e.target.value)} required maxLength={200} /> {role === "admin" ? ( <> setEmail(e.target.value)} required /> setPassword(e.target.value)} minLength={8} required /> ) : ( setPin(e.target.value.replace(/\D/g, ""))} required /> )}
); } function EditUserModal({ user, onClose, onSaved, }: { user: UserRow; onClose: () => void; onSaved: () => void; }) { const [name, setName] = useState(user.name); const [email, setEmail] = useState(user.email ?? ""); const [password, setPassword] = useState(""); const [pin, setPin] = useState(""); const [active, setActive] = useState(user.active); const [busy, setBusy] = useState(false); const [error, setError] = useState(null); async function submit(e: React.FormEvent) { e.preventDefault(); setBusy(true); setError(null); const body: Record = {}; if (name !== user.name) body.name = name; if (active !== user.active) body.active = active; if (user.role === "admin") { if (email && email !== user.email) body.email = email; if (password) body.password = password; } else { if (pin) body.pin = pin; } try { if (Object.keys(body).length > 0) { await apiFetch(`/api/v1/users/${user.id}`, { method: "PATCH", body: JSON.stringify(body) }); } onSaved(); onClose(); } catch (err) { setError(err instanceof ApiClientError ? err.message : "Failed to update user"); } finally { setBusy(false); } } async function deactivate() { if (!confirm(`Deactivate ${user.name}? Their sessions will be revoked immediately.`)) return; setBusy(true); setError(null); try { await apiFetch(`/api/v1/users/${user.id}`, { method: "DELETE" }); onSaved(); onClose(); } catch (err) { setError(err instanceof ApiClientError ? err.message : "Failed to deactivate"); setBusy(false); } } return ( {user.active && ( )}
} >
setName(e.target.value)} /> {user.role === "admin" ? ( <> setEmail(e.target.value)} /> setPassword(e.target.value)} minLength={password.length > 0 ? 8 : undefined} /> ) : ( setPin(e.target.value.replace(/\D/g, ""))} /> )} ); }