'use client'; import { useState, useEffect, useCallback } from 'react'; import { auditLogService, type AuditLogEntry, type AuditLogQuery, } from '@/services'; const ACTION_OPTIONS: { value: string; label: string; group: string }[] = [ { value: '', label: 'All actions', group: '' }, // Auth { value: 'AUTH_SIGNUP', label: 'Auth: Signup', group: 'Auth' }, { value: 'AUTH_LOGIN_SUCCESS', label: 'Auth: Login Success', group: 'Auth' }, { value: 'AUTH_LOGIN_FAIL', label: 'Auth: Login Fail', group: 'Auth' }, { value: 'AUTH_LOGOUT', label: 'Auth: Logout', group: 'Auth' }, { value: 'AUTH_PASSWORD_RESET_REQUEST', label: 'Auth: Password Reset Requested', group: 'Auth' }, { value: 'AUTH_PASSWORD_RESET_CONFIRM', label: 'Auth: Password Reset Confirmed', group: 'Auth' }, { value: 'AUTH_PASSWORD_CHANGE', label: 'Auth: Password Change', group: 'Auth' }, { value: 'AUTH_EMAIL_CHANGE', label: 'Auth: Email Change', group: 'Auth' }, { value: 'AUTH_EMAIL_VERIFY', label: 'Auth: Email Verify', group: 'Auth' }, // User / role { value: 'USER_UPDATE', label: 'User: Update', group: 'User' }, { value: 'USER_DELETE', label: 'User: Delete', group: 'User' }, { value: 'USER_SUSPEND', label: 'User: Suspend', group: 'User' }, { value: 'USER_UNSUSPEND', label: 'User: Unsuspend', group: 'User' }, { value: 'USER_ROLE_CHANGE', label: 'User: Role Change', group: 'User' }, // Agent { value: 'AGENT_PROFILE_UPDATE', label: 'Agent: Profile Update', group: 'Agent' }, { value: 'AGENT_VERIFICATION_SUBMIT', label: 'Agent: Verification Submit', group: 'Agent' }, { value: 'AGENT_VERIFICATION_APPROVE', label: 'Agent: Verification Approve', group: 'Agent' }, { value: 'AGENT_VERIFICATION_REJECT', label: 'Agent: Verification Reject', group: 'Agent' }, // Billing { value: 'SUBSCRIPTION_CREATE', label: 'Billing: Subscription Create', group: 'Billing' }, { value: 'SUBSCRIPTION_CANCEL', label: 'Billing: Subscription Cancel', group: 'Billing' }, { value: 'PAYMENT_SUCCESS', label: 'Billing: Payment Success', group: 'Billing' }, { value: 'PAYMENT_FAILURE', label: 'Billing: Payment Failure', group: 'Billing' }, // Moderation { value: 'REPORT_STATUS_CHANGE', label: 'Moderation: Report Status Change', group: 'Moderation' }, { value: 'CONTACT_MESSAGE_RESPOND', label: 'Moderation: Contact Message', group: 'Moderation' }, { value: 'SUPPORT_TICKET_CLOSE', label: 'Moderation: Support Ticket Close', group: 'Moderation' }, // Schema { value: 'PROFILE_FIELD_UPDATE', label: 'Schema: Profile Field Update', group: 'Schema' }, { value: 'PROFILE_SECTION_UPDATE', label: 'Schema: Profile Section Update', group: 'Schema' }, { value: 'AGENT_TYPE_UPDATE', label: 'Schema: Agent Type Update', group: 'Schema' }, // Connections { value: 'CONNECTION_REQUEST_ACCEPT', label: 'Connection: Accept', group: 'Connection' }, { value: 'CONNECTION_REQUEST_REJECT', label: 'Connection: Reject', group: 'Connection' }, { value: 'CONNECTION_REQUEST_CANCEL', label: 'Connection: Cancel', group: 'Connection' }, // CMS { value: 'CMS_BLOG_PUBLISH', label: 'CMS: Publish', group: 'CMS' }, { value: 'CMS_BLOG_UNPUBLISH', label: 'CMS: Unpublish', group: 'CMS' }, { value: 'CMS_BLOG_DELETE', label: 'CMS: Delete', group: 'CMS' }, ]; const RESOURCE_OPTIONS = [ '', 'User', 'AgentProfile', 'AgentSubscription', 'Payment', 'UserReport', 'ContactMessage', 'SupportChat', 'CmsContent', 'ProfileField', 'ProfileSection', 'AgentType', 'ConnectionRequest', ]; function formatActor(entry: AuditLogEntry): string { if (!entry.actor) { if (entry.actorRole === 'SYSTEM') return 'SYSTEM'; return entry.actorId ? `${entry.actorId.slice(0, 8)}…` : 'anonymous'; } const profile = entry.actor.agentProfile ?? entry.actor.userProfile; const name = [profile?.firstName, profile?.lastName].filter(Boolean).join(' '); return name || entry.actor.email; } function formatRelative(iso: string): string { const d = new Date(iso); const diff = Date.now() - d.getTime(); const sec = Math.floor(diff / 1000); if (sec < 60) return `${sec}s ago`; const min = Math.floor(sec / 60); if (min < 60) return `${min}m ago`; const hr = Math.floor(min / 60); if (hr < 24) return `${hr}h ago`; const day = Math.floor(hr / 24); if (day < 30) return `${day}d ago`; return d.toLocaleDateString(); } export default function AuditLogPage() { const [items, setItems] = useState([]); const [total, setTotal] = useState(0); const [page, setPage] = useState(1); const limit = 25; const [loading, setLoading] = useState(true); const [error, setError] = useState(''); const [expandedId, setExpandedId] = useState(null); // Filters const [actionFilter, setActionFilter] = useState(''); const [resourceFilter, setResourceFilter] = useState(''); const [actorIdFilter, setActorIdFilter] = useState(''); const [fromDate, setFromDate] = useState(''); const [toDate, setToDate] = useState(''); const fetchData = useCallback(async () => { setLoading(true); setError(''); try { const query: AuditLogQuery = { page, limit, action: actionFilter || undefined, resourceType: resourceFilter || undefined, actorId: actorIdFilter || undefined, from: fromDate ? new Date(fromDate).toISOString() : undefined, to: toDate ? new Date(toDate + 'T23:59:59').toISOString() : undefined, }; const res = await auditLogService.list(query); setItems(res.items); setTotal(res.total); } catch (err) { console.error('Failed to fetch audit log:', err); setError('Failed to load audit log'); } finally { setLoading(false); } }, [page, actionFilter, resourceFilter, actorIdFilter, fromDate, toDate]); useEffect(() => { fetchData(); }, [fetchData]); const totalPages = Math.max(1, Math.ceil(total / limit)); const resetFilters = () => { setActionFilter(''); setResourceFilter(''); setActorIdFilter(''); setFromDate(''); setToDate(''); setPage(1); }; return (

Audit Log

Who did what, when, and from where. Filter to narrow the trail.

setActorIdFilter(e.target.value)} onKeyDown={(e) => e.key === 'Enter' && setPage(1)} placeholder="UUID" className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500 text-sm text-gray-900 bg-white placeholder:text-gray-500" />
{ setFromDate(e.target.value); setPage(1); }} className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500 text-sm text-gray-900 bg-white" />
{ setToDate(e.target.value); setPage(1); }} className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500 text-sm text-gray-900 bg-white" />
{error && (

{error}

)}
{loading ? (
) : items.length === 0 ? (
No audit entries match your filters.
) : ( {items.map((entry) => ( <> {expandedId === entry.id && ( )} ))}
When Actor Action Resource IP
{formatRelative(entry.createdAt)}
{formatActor(entry)}
{entry.actorRole ?? 'SYSTEM'}
{entry.action} {entry.resourceType ? ( <>
{entry.resourceType}
{entry.resourceId && (
{entry.resourceId.slice(0, 8)}…
)} ) : ( )}
{entry.ipAddress ?? '—'}
Full timestamp: {new Date(entry.createdAt).toLocaleString()}
Actor ID: {entry.actorId ?? '(none)'}
{entry.actor?.email && (
Actor email: {entry.actor.email}
)} {entry.userAgent && (
User Agent: {entry.userAgent}
)} {entry.metadata && (
Metadata:
{JSON.stringify(entry.metadata, null, 2)}
                              
)}
)} {!loading && items.length > 0 && (
Showing {(page - 1) * limit + 1}–{Math.min(page * limit, total)} of {total}
Page {page} of {totalPages}
)}
); }