Audit Log Timeline

A vertical event timeline inspired by Vercel. Shows system and user actions with typed icons, actor avatars, and day grouping. Perfect for settings pages and admin dashboards.

Activity FeedsInspired by Vercel
Live PreviewInteractive
AuditLogTimeline.tsx
// Dependencies: react ^18, lucide-react
import React from 'react';
import type { LucideIcon } from 'lucide-react';
import {
  Rocket, KeyRound, UserPlus, Shield,
  Settings, Trash2, GitMerge, AlertTriangle,
} from 'lucide-react';

type AuditEvent = {
  id: string;
  icon: LucideIcon;
  iconColor: string;
  actor: string;
  actorColor: string;
  actorInitials: string;
  description: string;
  timestamp: Date;
};

function formatRelative(date: Date): string {
  const now = new Date();
  const diffMs = now.getTime() - date.getTime();
  const diffMins = Math.floor(diffMs / 60000);
  const diffHours = Math.floor(diffMins / 60);
  const diffDays = Math.floor(diffHours / 24);
  if (diffMins < 1) return 'Just now';
  if (diffMins < 60) return `${diffMins}m ago`;
  if (diffHours < 24) return `${diffHours}h ago`;
  if (diffDays === 1) return 'Yesterday';
  return `${diffDays}d ago`;
}

function groupByDay(events: AuditEvent[]): { label: string; events: AuditEvent[] }[] {
  const now = new Date();
  const todayStart = new Date(now.getFullYear(), now.getMonth(), now.getDate()).getTime();
  const yesterdayStart = todayStart - 86400000;
  const groups: Record<string, AuditEvent[]> = {};
  const order: string[] = [];
  for (const e of events) {
    const t = e.timestamp.getTime();
    let label: string;
    if (t >= todayStart) label = 'Today';
    else if (t >= yesterdayStart) label = 'Yesterday';
    else label = e.timestamp.toLocaleDateString('en-US', { month: 'short', day: 'numeric' });
    if (!groups[label]) { groups[label] = []; order.push(label); }
    groups[label].push(e);
  }
  return order.map((label) => ({ label, events: groups[label] }));
}

type Props = { events: AuditEvent[] };

export function AuditLogTimeline({ events }: Props) {
  const groups = groupByDay(events);
  return (
    <div className="flex flex-col h-full overflow-hidden bg-[#0a0a0a] border border-[#1a1a1a] rounded-xl">
      <div className="flex items-center justify-between px-4 py-3 border-b border-[#1a1a1a] shrink-0">
        <span className="text-sm font-semibold text-white">Audit Log</span>
        <span className="text-xs text-zinc-600">{events.length} events</span>
      </div>
      <div className="flex-1 overflow-y-auto px-4 py-3">
        {groups.map((group) => (
          <div key={group.label} className="mb-4">
            <div className="flex items-center gap-2 mb-3">
              <span className="text-[11px] font-medium text-zinc-500 uppercase tracking-wide">
                {group.label}
              </span>
              <div className="flex-1 h-px bg-[#1a1a1a]" />
            </div>
            <div className="relative">
              <div className="absolute left-[13px] top-0 bottom-0 w-px bg-[#1e1e1e]" />
              <div className="flex flex-col">
                {group.events.map((event) => (
                  <EventRow key={event.id} event={event} />
                ))}
              </div>
            </div>
          </div>
        ))}
      </div>
    </div>
  );
}

function EventRow({ event }: { event: AuditEvent }) {
  const Icon = event.icon;
  return (
    <div className="flex items-start gap-3 py-2">
      <div
        className="relative z-10 w-7 h-7 rounded-full border flex items-center justify-center shrink-0"
        style={{ backgroundColor: `${event.iconColor}18`, borderColor: `${event.iconColor}30` }}
      >
        <Icon className="w-3 h-3" style={{ color: event.iconColor }} />
      </div>
      <div className="flex-1 min-w-0 pt-1">
        <div className="flex items-center gap-2 flex-wrap">
          <div
            className="w-4 h-4 rounded-full flex items-center justify-center text-white text-[9px] font-bold shrink-0"
            style={{ backgroundColor: event.actorColor }}
          >
            {event.actorInitials}
          </div>
          <span className="text-sm font-medium text-white">{event.actor}</span>
          <span className="text-sm text-zinc-400">{event.description}</span>
        </div>
        <p className="text-xs text-zinc-600 mt-0.5">{formatRelative(event.timestamp)}</p>
      </div>
    </div>
  );
}

// --- Usage example ---
// const now = new Date();
// const mins = (n: number) => new Date(now.getTime() - n * 60 * 1000);
// const events: AuditEvent[] = [
//   { id: 'e1', icon: Rocket, iconColor: '#6366f1', actor: 'Sarah Chen', actorColor: '#6366f1', actorInitials: 'SC', description: 'deployed to production', timestamp: mins(8) },
//   { id: 'e2', icon: KeyRound, iconColor: '#f59e0b', actor: 'System', actorColor: '#6b7280', actorInitials: 'SY', description: 'rotated API key', timestamp: mins(45) },
// ];
// <AuditLogTimeline events={events} />

Unlock to copy

Free access to all patterns