import { useState } from "react"; import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query"; import { Loader2, AlertCircle, Users, Plus, X, Check, UserX, UserCheck, Edit, } from "lucide-react"; import { getUsers, createUser, updateUser, type UserOut, type UserCreatePayload } from "../api/users"; import { PasswordPolicyChecklist } from "../components/ChangePasswordModal"; const ROLES = [ { value: "viewer", label: "Viewer" }, { value: "red_tech", label: "Red Tech" }, { value: "blue_tech", label: "Blue Tech" }, { value: "red_lead", label: "Red Lead" }, { value: "blue_lead", label: "Blue Lead" }, { value: "admin", label: "Admin" }, ]; const roleBadgeColors: Record = { admin: "bg-purple-900/50 text-purple-400 border-purple-500/30", red_tech: "bg-red-900/50 text-red-400 border-red-500/30", blue_tech: "bg-blue-900/50 text-blue-400 border-blue-500/30", red_lead: "bg-orange-900/50 text-orange-400 border-orange-500/30", blue_lead: "bg-cyan-900/50 text-cyan-400 border-cyan-500/30", viewer: "bg-gray-800/50 text-gray-400 border-gray-600/30", }; export default function UsersPage() { const queryClient = useQueryClient(); const [showCreateModal, setShowCreateModal] = useState(false); const [editingUser, setEditingUser] = useState(null); const { data: users, isLoading, error, } = useQuery({ queryKey: ["users"], queryFn: getUsers, }); const createMutation = useMutation({ mutationFn: createUser, onSuccess: () => { queryClient.invalidateQueries({ queryKey: ["users"] }); setShowCreateModal(false); }, }); const updateMutation = useMutation({ mutationFn: ({ userId, payload }: { userId: string; payload: Parameters[1] }) => updateUser(userId, payload), onSuccess: () => { queryClient.invalidateQueries({ queryKey: ["users"] }); setEditingUser(null); }, }); const toggleUserActive = (user: UserOut) => { updateMutation.mutate({ userId: user.id, payload: { is_active: !user.is_active }, }); }; const formatDate = (dateStr: string | null) => { if (!dateStr) return "Never"; return new Date(dateStr).toLocaleDateString("en-US", { year: "numeric", month: "short", day: "numeric", }); }; if (isLoading) { return (
); } if (error) { return (

Failed to load users

); } return (
{/* Header */}

User Management

Manage user accounts and permissions

{/* Users Table */}
{users?.map((user) => ( ))}
Username Email Role Status Created Last Login Actions
{user.username} {user.email || "—"} {user.role.replace(/_/g, " ")} {user.is_active ? ( Active ) : ( Inactive )} {formatDate(user.created_at)} {formatDate(user.last_login)}
{users?.length === 0 && (
No users found
)}
{/* Create User Modal */} {showCreateModal && ( setShowCreateModal(false)} onSubmit={(data) => createMutation.mutate(data)} isSubmitting={createMutation.isPending} error={createMutation.error as Error | null} /> )} {/* Edit User Modal */} {editingUser && ( setEditingUser(null)} onSubmit={(payload) => updateMutation.mutate({ userId: editingUser.id, payload }) } isSubmitting={updateMutation.isPending} error={updateMutation.error as Error | null} /> )}
); } // ── Create User Modal ────────────────────────────────────────────── interface CreateUserModalProps { onClose: () => void; onSubmit: (data: UserCreatePayload) => void; isSubmitting: boolean; error: Error | null; } function CreateUserModal({ onClose, onSubmit, isSubmitting, error }: CreateUserModalProps) { const [formData, setFormData] = useState({ username: "", email: "", password: "", role: "viewer", }); const [errors, setErrors] = useState>({}); const validate = () => { const newErrors: Record = {}; if (!formData.username.trim()) newErrors.username = "Username is required"; if (!formData.password) newErrors.password = "Password is required"; if (formData.password.length < 6) newErrors.password = "Password must be at least 6 characters"; setErrors(newErrors); return Object.keys(newErrors).length === 0; }; const handleSubmit = (e: React.FormEvent) => { e.preventDefault(); if (validate()) { onSubmit({ username: formData.username, email: formData.email || undefined, password: formData.password, role: formData.role, }); } }; return (

Create New User

{error && (

{error.message}

)}
setFormData({ ...formData, username: e.target.value })} className={`mt-1 w-full rounded-lg border bg-gray-800 px-3 py-2 text-gray-200 ${ errors.username ? "border-red-500" : "border-gray-700" }`} /> {errors.username &&

{errors.username}

}
setFormData({ ...formData, email: e.target.value })} className="mt-1 w-full rounded-lg border border-gray-700 bg-gray-800 px-3 py-2 text-gray-200" />
setFormData({ ...formData, password: e.target.value })} className={`mt-1 w-full rounded-lg border bg-gray-800 px-3 py-2 text-gray-200 ${ errors.password ? "border-red-500" : "border-gray-700" }`} /> {errors.password &&

{errors.password}

}

The user will be required to change this password on first login.

); } // ── Edit User Modal ────────────────────────────────────────────── interface EditUserModalProps { user: UserOut; onClose: () => void; onSubmit: (payload: Parameters[1]) => void; isSubmitting: boolean; error: Error | null; } function EditUserModal({ user, onClose, onSubmit, isSubmitting, error }: EditUserModalProps) { const [formData, setFormData] = useState({ email: user.email || "", role: user.role, password: "", }); const handleSubmit = (e: React.FormEvent) => { e.preventDefault(); const payload: Parameters[1] = { email: formData.email || undefined, role: formData.role, }; if (formData.password) { payload.password = formData.password; } onSubmit(payload); }; return (

Edit User: {user.username}

{error && (

{error.message}

)}
setFormData({ ...formData, email: e.target.value })} className="mt-1 w-full rounded-lg border border-gray-700 bg-gray-800 px-3 py-2 text-gray-200" />
setFormData({ ...formData, password: e.target.value })} className="mt-1 w-full rounded-lg border border-gray-700 bg-gray-800 px-3 py-2 text-gray-200" placeholder="••••••••" /> {formData.password.length > 0 && ( )}
); }