import { useState } from 'react'; import { Plus, Pencil, Trash2, RefreshCw, Copy, Check } from 'lucide-react'; import { toast } from 'sonner'; import Layout from '../../components/Layout'; import Modal from '../../components/Modal'; import { User, Role } from '../../types'; import { useAuth } from '../../contexts/AuthContext'; import { useUsers, useCreateUser, useUpdateUser, useDeleteUser, } from '../../api/queries'; import { AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle, } from '@/components/ui/alert-dialog'; interface UserForm { username: string; email: string; displayName: string; password: string; role: Role; } const BLANK_FORM: UserForm = { username: '', email: '', displayName: '', password: '', role: 'AGENT', }; const ROLE_LABELS: Record = { ADMIN: 'Admin', AGENT: 'Agent', USER: 'User', SERVICE: 'Service', }; const ROLE_BADGE: Record = { ADMIN: 'bg-purple-500/20 text-purple-400 border-purple-500/30', AGENT: 'bg-blue-500/20 text-blue-400 border-blue-500/30', USER: 'bg-gray-500/20 text-gray-400 border-gray-500/30', SERVICE: 'bg-orange-500/20 text-orange-400 border-orange-500/30', }; const ROLE_DESCRIPTIONS: Record = { ADMIN: 'Full access — manage users, CTI config, close and delete tickets', AGENT: 'Manage tickets — create, update, assign, comment, change status', USER: 'Basic access — view tickets and add comments only', SERVICE: 'Automation account — authenticates via API key, no password login', }; export default function AdminUsers() { const { user: authUser } = useAuth(); const { data: users = [] } = useUsers(); const createUser = useCreateUser(); const updateUser = useUpdateUser(); const deleteUser = useDeleteUser(); const [modal, setModal] = useState<'add' | 'edit' | null>(null); const [selected, setSelected] = useState(null); const [form, setForm] = useState(BLANK_FORM); const [error, setError] = useState(''); const [copiedKey, setCopiedKey] = useState(null); const [newApiKey, setNewApiKey] = useState(null); const [deleting, setDeleting] = useState(null); const [rotating, setRotating] = useState(null); const submitting = createUser.isPending || updateUser.isPending; const openAdd = () => { setForm(BLANK_FORM); setError(''); setNewApiKey(null); setModal('add'); }; const openEdit = (u: User) => { setSelected(u); setForm({ username: u.username, email: u.email, displayName: u.displayName, password: '', role: u.role, }); setError(''); setNewApiKey(null); setModal('edit'); }; const closeModal = () => { setModal(null); setSelected(null); setNewApiKey(null); }; const handleAdd = async (e: React.FormEvent) => { e.preventDefault(); setError(''); try { const payload: Record = { username: form.username, email: form.email, displayName: form.displayName, role: form.role, }; if (form.password) payload.password = form.password; const created = await createUser.mutateAsync(payload); if (created.apiKey) setNewApiKey(created.apiKey); else closeModal(); } catch (err: unknown) { const e = err as { response?: { data?: { error?: string } } }; setError(e.response?.data?.error ?? 'Failed to create user'); } }; const handleEdit = async (e: React.FormEvent) => { e.preventDefault(); if (!selected) return; setError(''); try { const payload: Record = { email: form.email, displayName: form.displayName, role: form.role, }; if (form.password) payload.password = form.password; await updateUser.mutateAsync({ id: selected.id, data: payload }); closeModal(); } catch (err: unknown) { const e = err as { response?: { data?: { error?: string } } }; setError(e.response?.data?.error ?? 'Failed to update user'); } }; const confirmDelete = async () => { if (!deleting) return; try { await deleteUser.mutateAsync(deleting.id); toast.success(`Deleted ${deleting.displayName}`); } catch (e) { toast.error((e as Error).message || 'Failed to delete user'); } setDeleting(null); }; const confirmRegenerate = async () => { if (!rotating) return; try { const updated = await updateUser.mutateAsync({ id: rotating.id, data: { regenerateApiKey: true }, }); setNewApiKey(updated.apiKey ?? null); setSelected(rotating); setModal('edit'); } catch (e) { toast.error((e as Error).message || 'Failed to rotate key'); } setRotating(null); }; const copyToClipboard = (key: string) => { navigator.clipboard.writeText(key); setCopiedKey(key); setTimeout(() => setCopiedKey(null), 2000); }; const inputClass = 'w-full bg-gray-800 border border-gray-700 text-gray-100 placeholder-gray-500 rounded-lg px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent'; const labelClass = 'block text-sm font-medium text-gray-300 mb-1'; return ( Add User } >
{users.map((u) => ( ))}
User Username Role Email
{u.displayName} {u.username} {ROLE_LABELS[u.role]} {u.email}
{u.role === 'SERVICE' && ( )} {u.id !== authUser?.id && ( )}
{/* Add / Edit Modal */} {modal && ( {newApiKey ? (

API Key — copy it now, it won't be shown again

{newApiKey}
) : (
{error && (
{error}
)} {modal === 'add' && (
setForm((f) => ({ ...f, username: e.target.value }))} required className={inputClass} />
)}
setForm((f) => ({ ...f, displayName: e.target.value }))} required className={inputClass} />
setForm((f) => ({ ...f, email: e.target.value }))} required className={inputClass} />
setForm((f) => ({ ...f, password: e.target.value }))} required={modal === 'add' && form.role !== 'SERVICE'} className={inputClass} placeholder={modal === 'edit' ? '••••••••' : ''} />

{ROLE_DESCRIPTIONS[form.role]}

)}
)} !o && setDeleting(null)}> Delete {deleting?.displayName}? This user will be permanently removed. Their tickets and comments are preserved. Cancel Delete !o && setRotating(null)}> Regenerate API key for {rotating?.displayName}? The old key will stop working immediately. You'll see the new key once — make sure whatever uses it can be updated. Cancel Rotate key
); }