Skip to content

Commit f1bfa1d

Browse files
committed
feat: build realtime event stream UI
1 parent f6f6bde commit f1bfa1d

10 files changed

Lines changed: 745 additions & 0 deletions

File tree

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import { StreamsWorkspace } from "@/components/streams/streams-workspace"
2+
3+
export default function StreamsPage() {
4+
return (
5+
<StreamsWorkspace />
6+
)
7+
}
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
"use client"
2+
3+
import { StreamRow } from "./stream-row"
4+
5+
type Row = {
6+
provider: string
7+
route: string
8+
status: string
9+
latency: string
10+
timestamp: string
11+
eventType: string
12+
}
13+
14+
type Props = {
15+
rows: Row[]
16+
selected: Row | null
17+
onSelect: (row: Row) => void
18+
}
19+
20+
export function LiveStreamTable({
21+
rows,
22+
selected,
23+
onSelect,
24+
}: Props) {
25+
return (
26+
<div className="h-full overflow-auto">
27+
28+
<div
29+
className="
30+
sticky top-0 z-10
31+
grid
32+
grid-cols-[120px_1fr_180px_120px_120px_140px]
33+
border-b border-border
34+
bg-background/95
35+
px-5 py-3
36+
text-xs uppercase tracking-wide
37+
text-muted-foreground
38+
backdrop-blur
39+
"
40+
>
41+
42+
<div>Provider</div>
43+
<div>Route</div>
44+
<div>Event</div>
45+
<div>Status</div>
46+
<div>Latency</div>
47+
<div>Time</div>
48+
49+
</div>
50+
51+
{rows.map((row, i) => (
52+
<StreamRow
53+
key={i}
54+
{...row}
55+
selected={
56+
selected === row
57+
}
58+
onClick={() =>
59+
onSelect(row)
60+
}
61+
/>
62+
))}
63+
64+
</div>
65+
)
66+
}
Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
"use client"
2+
3+
import { useMemo, useState } from "react"
4+
5+
import {
6+
Panel,
7+
PanelGroup,
8+
PanelResizeHandle,
9+
} from "react-resizable-panels"
10+
11+
import { LiveStreamTable } from "./live-stream-table"
12+
13+
import { StreamInspector } from "./stream-inspector"
14+
15+
const rows = Array.from({
16+
length: 30,
17+
}).map((_, i) => ({
18+
provider:
19+
i % 2 === 0
20+
? "stripe"
21+
: "github",
22+
23+
route:
24+
i % 2 === 0
25+
? "/webhooks/stripe"
26+
: "/webhooks/github",
27+
28+
eventType:
29+
i % 2 === 0
30+
? "payment.succeeded"
31+
: "repo.push",
32+
33+
status:
34+
i % 4 === 0
35+
? "failed"
36+
: "success",
37+
38+
latency: `${40 + i}ms`,
39+
40+
timestamp: `${i + 1}s ago`,
41+
}))
42+
43+
type Props = {
44+
query: string
45+
}
46+
47+
export function LiveStream({
48+
query,
49+
}: Props) {
50+
51+
const [selected, setSelected] =
52+
useState<
53+
(typeof rows)[number] | null
54+
>(null)
55+
56+
const filtered =
57+
useMemo(() => {
58+
59+
return rows.filter((row) =>
60+
`
61+
${row.provider}
62+
${row.route}
63+
${row.eventType}
64+
`
65+
.toLowerCase()
66+
.includes(query.toLowerCase())
67+
)
68+
69+
}, [query])
70+
71+
return (
72+
<PanelGroup direction="horizontal">
73+
74+
<Panel
75+
defaultSize={72}
76+
minSize={45}
77+
>
78+
79+
<LiveStreamTable
80+
rows={filtered}
81+
selected={selected}
82+
onSelect={setSelected}
83+
/>
84+
85+
</Panel>
86+
87+
<PanelResizeHandle className="w-2 bg-border/40" />
88+
89+
<Panel
90+
defaultSize={28}
91+
minSize={20}
92+
>
93+
94+
<div className="h-full border-l border-border bg-background/20">
95+
96+
<StreamInspector
97+
event={selected}
98+
/>
99+
100+
</div>
101+
102+
</Panel>
103+
104+
</PanelGroup>
105+
)
106+
}
Lines changed: 152 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,152 @@
1+
"use client"
2+
3+
import {
4+
Clock3,
5+
FileJson,
6+
Globe,
7+
Radio,
8+
} from "lucide-react"
9+
10+
import { StreamJson } from "./stream-json"
11+
12+
type StreamEvent = {
13+
provider: string
14+
route: string
15+
status: string
16+
latency: string
17+
timestamp: string
18+
eventType: string
19+
}
20+
21+
type Props = {
22+
event: StreamEvent | null
23+
}
24+
25+
export function StreamInspector({
26+
event,
27+
}: Props) {
28+
29+
if (!event) {
30+
return (
31+
<div className="flex h-full items-center justify-center text-sm text-muted-foreground">
32+
Select a stream event
33+
</div>
34+
)
35+
}
36+
37+
return (
38+
<div className="flex h-full flex-col">
39+
40+
{/* Header */}
41+
<div className="border-b border-border p-5">
42+
43+
<div className="flex items-center gap-3">
44+
45+
<div
46+
className="
47+
flex h-11 w-11 items-center justify-center
48+
rounded-xl border border-border
49+
bg-background/40
50+
"
51+
>
52+
<Radio className="h-5 w-5 text-orange-400" />
53+
</div>
54+
55+
<div>
56+
57+
<h2 className="text-lg font-semibold">
58+
Stream Inspector
59+
</h2>
60+
61+
<p className="mt-1 text-sm text-muted-foreground">
62+
live webhook payload
63+
</p>
64+
65+
</div>
66+
67+
</div>
68+
69+
</div>
70+
71+
{/* Meta */}
72+
<div className="space-y-3 border-b border-border p-5">
73+
74+
<MetaRow
75+
icon={Globe}
76+
label="Provider"
77+
value={event.provider}
78+
/>
79+
80+
<MetaRow
81+
icon={FileJson}
82+
label="Event Type"
83+
value={event.eventType}
84+
/>
85+
86+
<MetaRow
87+
icon={Clock3}
88+
label="Latency"
89+
value={event.latency}
90+
/>
91+
92+
</div>
93+
94+
{/* Payload */}
95+
<div className="flex-1 overflow-auto p-5">
96+
97+
<div className="mb-4">
98+
99+
<h3 className="text-sm font-semibold">
100+
Payload
101+
</h3>
102+
103+
</div>
104+
105+
<StreamJson
106+
payload={{
107+
id: "evt_12345",
108+
route: event.route,
109+
provider: event.provider,
110+
type: event.eventType,
111+
latency: event.latency,
112+
metadata: {
113+
retries: 1,
114+
region: "eu-west",
115+
worker: "worker-02",
116+
},
117+
}}
118+
/>
119+
120+
</div>
121+
122+
</div>
123+
)
124+
}
125+
126+
function MetaRow({
127+
icon: Icon,
128+
label,
129+
value,
130+
}: {
131+
icon: React.ElementType
132+
label: string
133+
value: string
134+
}) {
135+
return (
136+
<div className="flex items-center justify-between">
137+
138+
<div className="flex items-center gap-2 text-sm text-muted-foreground">
139+
140+
<Icon className="h-4 w-4" />
141+
142+
{label}
143+
144+
</div>
145+
146+
<div className="text-sm font-medium">
147+
{value}
148+
</div>
149+
150+
</div>
151+
)
152+
}
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
"use client"
2+
3+
import JsonView from "@uiw/react-json-view"
4+
5+
type Props = {
6+
payload: Record<string, unknown> | undefined
7+
}
8+
9+
export function StreamJson({
10+
payload,
11+
}: Props) {
12+
return (
13+
<div
14+
className="
15+
overflow-hidden rounded-xl
16+
border border-border
17+
bg-background/40
18+
p-4
19+
"
20+
>
21+
22+
<JsonView
23+
value={payload}
24+
displayDataTypes={false}
25+
displayObjectSize={false}
26+
enableClipboard
27+
/>
28+
29+
</div>
30+
)
31+
}

0 commit comments

Comments
 (0)