Dark theme, roles overhaul, modal New Ticket, My Tickets page, and more
- Dark UI across all pages and components (gray-950/900/800 palette) - New Ticket is now a centered modal (triggered from sidebar), not a separate page - Add USER role: view and comment only; AGENT and SERVICE can create/edit tickets - Only admins can set ticket status to CLOSED (enforced server + UI) - Add My Tickets page (/my-tickets) showing tickets assigned to current user - Add queue (category) filter to Dashboard - Audit log entries are clickable to expand detail; comment body shown as markdown - Resolved date now includes time (HH:mm) in ticket sidebar - Store comment body in audit log detail for COMMENT_ADDED and COMMENT_DELETED - Clarify role descriptions in Admin Users modal - Remove CI/CD section from README; add full API reference documentation Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -25,9 +25,24 @@ const BLANK_FORM: UserForm = {
|
||||
const ROLE_LABELS: Record<Role, string> = {
|
||||
ADMIN: 'Admin',
|
||||
AGENT: 'Agent',
|
||||
USER: 'User',
|
||||
SERVICE: 'Service',
|
||||
}
|
||||
|
||||
const ROLE_BADGE: Record<Role, string> = {
|
||||
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<Role, string> = {
|
||||
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 [users, setUsers] = useState<User[]>([])
|
||||
@@ -133,8 +148,8 @@ export default function AdminUsers() {
|
||||
}
|
||||
|
||||
const inputClass =
|
||||
'w-full border border-gray-300 rounded-lg px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-blue-500'
|
||||
const labelClass = 'block text-sm font-medium text-gray-700 mb-1'
|
||||
'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 (
|
||||
<Layout
|
||||
@@ -149,9 +164,9 @@ export default function AdminUsers() {
|
||||
</button>
|
||||
}
|
||||
>
|
||||
<div className="bg-white border border-gray-200 rounded-xl overflow-hidden">
|
||||
<div className="bg-gray-900 border border-gray-800 rounded-xl overflow-hidden">
|
||||
<table className="w-full text-sm">
|
||||
<thead className="bg-gray-50 border-b border-gray-200">
|
||||
<thead className="border-b border-gray-800">
|
||||
<tr>
|
||||
<th className="text-left px-5 py-3 text-xs font-semibold text-gray-500 uppercase tracking-wide">
|
||||
User
|
||||
@@ -168,31 +183,23 @@ export default function AdminUsers() {
|
||||
<th className="px-5 py-3" />
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody className="divide-y divide-gray-100">
|
||||
<tbody className="divide-y divide-gray-800">
|
||||
{users.map((u) => (
|
||||
<tr key={u.id} className="hover:bg-gray-50">
|
||||
<td className="px-5 py-3 font-medium text-gray-900">{u.displayName}</td>
|
||||
<tr key={u.id} className="hover:bg-gray-800/50">
|
||||
<td className="px-5 py-3 font-medium text-gray-100">{u.displayName}</td>
|
||||
<td className="px-5 py-3 text-gray-500 font-mono text-xs">{u.username}</td>
|
||||
<td className="px-5 py-3">
|
||||
<span
|
||||
className={`inline-flex px-2 py-0.5 rounded text-xs font-medium ${
|
||||
u.role === 'ADMIN'
|
||||
? 'bg-purple-100 text-purple-700'
|
||||
: u.role === 'SERVICE'
|
||||
? 'bg-orange-100 text-orange-700'
|
||||
: 'bg-gray-100 text-gray-600'
|
||||
}`}
|
||||
>
|
||||
<span className={`inline-flex px-2 py-0.5 rounded text-xs font-medium border ${ROLE_BADGE[u.role]}`}>
|
||||
{ROLE_LABELS[u.role]}
|
||||
</span>
|
||||
</td>
|
||||
<td className="px-5 py-3 text-gray-500">{u.email}</td>
|
||||
<td className="px-5 py-3 text-gray-400">{u.email}</td>
|
||||
<td className="px-5 py-3">
|
||||
<div className="flex items-center justify-end gap-2">
|
||||
{u.role === 'SERVICE' && (
|
||||
<button
|
||||
onClick={() => handleRegenerateKey(u)}
|
||||
className="text-gray-400 hover:text-gray-700 transition-colors"
|
||||
className="text-gray-600 hover:text-gray-300 transition-colors"
|
||||
title="Regenerate API key"
|
||||
>
|
||||
<RefreshCw size={14} />
|
||||
@@ -200,14 +207,14 @@ export default function AdminUsers() {
|
||||
)}
|
||||
<button
|
||||
onClick={() => openEdit(u)}
|
||||
className="text-gray-400 hover:text-gray-700 transition-colors"
|
||||
className="text-gray-600 hover:text-gray-300 transition-colors"
|
||||
>
|
||||
<Pencil size={14} />
|
||||
</button>
|
||||
{u.id !== authUser?.id && (
|
||||
<button
|
||||
onClick={() => handleDelete(u)}
|
||||
className="text-gray-400 hover:text-red-600 transition-colors"
|
||||
className="text-gray-600 hover:text-red-400 transition-colors"
|
||||
>
|
||||
<Trash2 size={14} />
|
||||
</button>
|
||||
@@ -228,17 +235,17 @@ export default function AdminUsers() {
|
||||
>
|
||||
{newApiKey ? (
|
||||
<div className="space-y-4">
|
||||
<div className="bg-amber-50 border border-amber-200 rounded-lg p-4">
|
||||
<p className="text-sm font-medium text-amber-800 mb-2">
|
||||
<div className="bg-amber-500/10 border border-amber-500/30 rounded-lg p-4">
|
||||
<p className="text-sm font-medium text-amber-400 mb-2">
|
||||
API Key — copy it now, it won't be shown again
|
||||
</p>
|
||||
<div className="flex items-center gap-2">
|
||||
<code className="flex-1 text-xs bg-white border border-amber-200 rounded px-3 py-2 font-mono break-all">
|
||||
<code className="flex-1 text-xs bg-gray-800 border border-gray-700 text-gray-300 rounded px-3 py-2 font-mono break-all">
|
||||
{newApiKey}
|
||||
</code>
|
||||
<button
|
||||
onClick={() => copyToClipboard(newApiKey)}
|
||||
className="flex-shrink-0 text-amber-700 hover:text-amber-900 transition-colors"
|
||||
className="flex-shrink-0 text-amber-400 hover:text-amber-300 transition-colors"
|
||||
>
|
||||
{copiedKey === newApiKey ? <Check size={16} /> : <Copy size={16} />}
|
||||
</button>
|
||||
@@ -254,7 +261,7 @@ export default function AdminUsers() {
|
||||
) : (
|
||||
<form onSubmit={modal === 'add' ? handleAdd : handleEdit} className="space-y-4">
|
||||
{error && (
|
||||
<div className="bg-red-50 border border-red-200 text-red-700 text-sm px-3 py-2 rounded-lg">
|
||||
<div className="bg-red-500/10 border border-red-500/30 text-red-400 text-sm px-3 py-2 rounded-lg">
|
||||
{error}
|
||||
</div>
|
||||
)}
|
||||
@@ -296,7 +303,10 @@ export default function AdminUsers() {
|
||||
|
||||
<div>
|
||||
<label className={labelClass}>
|
||||
Password {modal === 'edit' && <span className="text-gray-400 font-normal">(leave blank to keep current)</span>}
|
||||
Password{' '}
|
||||
{modal === 'edit' && (
|
||||
<span className="text-gray-500 font-normal">(leave blank to keep current)</span>
|
||||
)}
|
||||
</label>
|
||||
<input
|
||||
type="password"
|
||||
@@ -316,16 +326,18 @@ export default function AdminUsers() {
|
||||
className={inputClass}
|
||||
>
|
||||
<option value="AGENT">Agent</option>
|
||||
<option value="USER">User</option>
|
||||
<option value="ADMIN">Admin</option>
|
||||
<option value="SERVICE">Service (API key auth)</option>
|
||||
<option value="SERVICE">Service</option>
|
||||
</select>
|
||||
<p className="mt-1.5 text-xs text-gray-500">{ROLE_DESCRIPTIONS[form.role]}</p>
|
||||
</div>
|
||||
|
||||
<div className="flex justify-end gap-3 pt-1">
|
||||
<button
|
||||
type="button"
|
||||
onClick={closeModal}
|
||||
className="px-4 py-2 text-sm text-gray-600 border border-gray-300 rounded-lg hover:bg-gray-50 transition-colors"
|
||||
className="px-4 py-2 text-sm text-gray-400 border border-gray-700 rounded-lg hover:bg-gray-800 transition-colors"
|
||||
>
|
||||
Cancel
|
||||
</button>
|
||||
|
||||
Reference in New Issue
Block a user