import { useState, useEffect } from "react"; import { X, Filter, Loader } from "lucide-react"; import type { ImpersonationAuditLog } from "@groombook/types"; interface Props { sessionId: string; onClose: () => void; } export function AuditLogViewer({ sessionId, onClose }: Props) { const [auditLog, setAuditLog] = useState([]); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); const [filterAction, setFilterAction] = useState("all"); useEffect(() => { setLoading(true); setError(null); fetch(`/api/impersonation/sessions/${sessionId}/audit-log`) .then((r) => { if (!r.ok) throw new Error(`Failed to load audit log (${r.status})`); return r.json() as Promise; }) .then((logs) => { // API returns newest-first; reverse for chronological display setAuditLog([...logs].reverse()); setLoading(false); }) .catch((err: unknown) => { setError(err instanceof Error ? err.message : "Failed to load audit log"); setLoading(false); }); }, [sessionId]); const actionTypes = ["all", ...new Set(auditLog.map((e) => e.action))]; const filtered = filterAction === "all" ? auditLog : auditLog.filter((e) => e.action === filterAction); return (

Impersonation Audit Log

{!loading && !error && (
{filtered.length} entries
)}
{loading && (
Loading audit log…
)} {error && (

{error}

)} {!loading && !error && filtered.length === 0 && (

No audit entries

)} {!loading && !error && filtered.length > 0 && (
{filtered.map((entry) => (
{new Date(entry.createdAt).toLocaleTimeString()}
{entry.action.replace(/_/g, " ")} {entry.pageVisited && (

{entry.pageVisited}

)} {entry.metadata && Object.keys(entry.metadata).length > 0 && (

{JSON.stringify(entry.metadata)}

)}
))}
)}
); }