Delivery History Table

The webhook delivery history: one virtualized row per attempt with time, event type, endpoint, status and latency, plus an optional live tail.

Time
Event
Endpoint
Status
Latency
Attempt
Live tailingCtrl+Space
"use client"

import { useEffect, useState } from "react"

Built on the Virtual Data Table, so the table scrolls internally and rows render through the virtualizer. By default it is inverted like the portal: newest deliveries at the visual bottom, infinite scrollback above, and the live-tail strip underneath when onToggleStream is wired.

Installation

pnpm dlx shadcn@latest add @repost/delivery-history-table

Installs this item, every component it depends on, and the npm packages listed below.

Usage

This component is presentational: it renders what you pass it and calls back on interaction. In a live dashboard the data comes from useLogsFeed() or useLogs() (@repost/portal-react), but any data of the same shape works.

components/delivery-history-table-demo.tsx
"use client"

import { useEffect, useState } from "react"
import { DeliveryHistoryTable, deliveryHistoryRowId } from "@/components/delivery-history-table"
import { fixtureLogRows } from "@/fixtures/webhook-fixtures"
import type { WebhookLogRow } from "@/lib/webhook-types"

const EVENT_TYPES = ["user.created", "user.updated", "invoice.paid", "subscription.renewed"]

/** Fabricate a live row on top of the newest one. */
const nextLiveRow = (index: number): WebhookLogRow => ({
  deliveryId: `del_live_${index}`,
  messageId: `msg_live_${index}`,
  endpointId: "ep_primary",
  type: EVENT_TYPES[index % EVENT_TYPES.length]!,
  generation: 0,
  attempt: 1,
  url: "https://api.acme.dev/webhooks/repost",
  responseStatus: index % 9 === 4 ? 500 : 200,
  latencyMs: 40 + ((index * 53) % 300),
  errorClass: null,
  timestamp: new Date().toISOString(),
})

export function DeliveryHistoryTableDemo() {
  const [selected, setSelected] = useState<WebhookLogRow | null>(null)
  const [live, setLive] = useState(true)
  const [rows, setRows] = useState<WebhookLogRow[]>(() => fixtureLogRows(60))

  // The table is presentational — this stands in for `useLogsFeed`.
  useEffect(() => {
    if (!live) return
    let index = 0
    const timer = setInterval(() => {
      index += 1
      setRows((current) => [nextLiveRow(index), ...current])
    }, 3000)
    return () => clearInterval(timer)
  }, [live])

  return (
    <DeliveryHistoryTable
      className="h-[420px] w-full"
      rows={rows}
      onRowClick={(row) => setSelected((current) => (current && deliveryHistoryRowId(current) === deliveryHistoryRowId(row) ? null : row))}
      selectedRowId={selected ? deliveryHistoryRowId(selected) : null}
      streaming={live}
      streamStatus={live ? "live" : "off"}
      onToggleStream={() => setLive((value) => !value)}
    />
  )
}

Props

PropTypeDescription
rowsWebhookLogRow[]Attempt rows, newest first.
classNamestringSize the frame here: the table scrolls internally (e.g. h-[420px], or min-h-0 flex-1).
isLoadingbooleanRenders skeleton rows.
onRowClick(row) => voidOpen your detail view.
selectedRowIdstring | nullHighlights the active row.
invertedbooleanLog-style: newest at the visual bottom. Default true, like the portal.
onLoadOlder / isLoadingOlderInfinite scrollback; wire to loadOlder from useLogsFeed.
streaming / streamStatus / onToggleStreamThe live tail: auto-stick, the "Streaming · N new" pill, and the tail strip.
streamDroppedCount / onClearStreamDroppedSurface and clear rows the stream dropped.
hiddenColumnsstring[]Hide columns by id: timestamp, type, url, status, latency, attempt.
emptyMessageReactNodeShown when there are no rows.