Skip to content

Commit d874f90

Browse files
committed
feat: build issues/dlq UI
1 parent f1bfa1d commit d874f90

8 files changed

Lines changed: 742 additions & 0 deletions

File tree

Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
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 { IssuesToolbar } from "@/components/issues/issues-toolbar"
12+
13+
import { IssueStats } from "@/components/issues/issue-stats"
14+
15+
import { IssueStream } from "@/components/issues/issue-stream"
16+
17+
import { IssueInspector } from "@/components/issues/issue-inspector"
18+
19+
const issues = Array.from({
20+
length: 18,
21+
}).map((_, i) => ({
22+
provider:
23+
i % 2 === 0
24+
? "stripe"
25+
: "github",
26+
27+
route:
28+
i % 2 === 0
29+
? "/webhooks/stripe"
30+
: "/webhooks/github",
31+
32+
error:
33+
i % 2 === 0
34+
? "signature verification failed"
35+
: "timeout exceeded",
36+
37+
retries: i % 4,
38+
39+
severity:
40+
i % 3 === 0
41+
? "critical"
42+
: "warning",
43+
44+
timestamp: `${i + 1}m ago`,
45+
}))
46+
47+
export default function IssuesWorkspace() {
48+
49+
const [query, setQuery] =
50+
useState("")
51+
52+
const [selected, setSelected] =
53+
useState<
54+
(typeof issues)[number] | null
55+
>(null)
56+
57+
const filtered =
58+
useMemo(() => {
59+
60+
return issues.filter((issue) =>
61+
`
62+
${issue.provider}
63+
${issue.route}
64+
${issue.error}
65+
`
66+
.toLowerCase()
67+
.includes(query.toLowerCase())
68+
)
69+
70+
}, [query])
71+
72+
return (
73+
<div
74+
className="
75+
flex h-[calc(100vh-92px)]
76+
flex-col overflow-hidden
77+
rounded-2xl border border-border
78+
bg-surface-1
79+
"
80+
>
81+
82+
<IssuesToolbar
83+
query={query}
84+
setQuery={setQuery}
85+
/>
86+
87+
<IssueStats />
88+
89+
<PanelGroup direction="horizontal">
90+
91+
<Panel
92+
defaultSize={68}
93+
minSize={45}
94+
>
95+
96+
<IssueStream
97+
issues={filtered}
98+
selected={selected}
99+
onSelect={setSelected}
100+
/>
101+
102+
</Panel>
103+
104+
<PanelResizeHandle className="w-2 bg-border/40" />
105+
106+
<Panel
107+
defaultSize={32}
108+
minSize={24}
109+
>
110+
111+
<div className="h-full border-l border-border bg-background/20">
112+
113+
<IssueInspector
114+
issue={selected}
115+
/>
116+
117+
</div>
118+
119+
</Panel>
120+
121+
</PanelGroup>
122+
123+
</div>
124+
)
125+
}
Lines changed: 143 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,143 @@
1+
"use client"
2+
3+
import JsonView from "@uiw/react-json-view"
4+
5+
import {
6+
AlertTriangle,
7+
RotateCcw,
8+
} from "lucide-react"
9+
10+
import { ReplayPanel } from "./replay-panel"
11+
12+
type Issue = {
13+
provider: string
14+
route: string
15+
error: string
16+
retries: number
17+
severity: string
18+
timestamp: string
19+
}
20+
21+
type Props = {
22+
issue: Issue | null
23+
}
24+
25+
export function IssueInspector({
26+
issue,
27+
}: Props) {
28+
29+
if (!issue) {
30+
return (
31+
<div className="flex h-full items-center justify-center text-sm text-muted-foreground">
32+
Select an issue
33+
</div>
34+
)
35+
}
36+
37+
return (
38+
<div className="flex h-full flex-col">
39+
40+
<div className="border-b border-border p-5">
41+
42+
<div className="flex items-center gap-3">
43+
44+
<div
45+
className="
46+
flex h-11 w-11 items-center justify-center
47+
rounded-xl border border-rose-500/20
48+
bg-rose-500/10
49+
"
50+
>
51+
<AlertTriangle className="h-5 w-5 text-rose-400" />
52+
</div>
53+
54+
<div>
55+
56+
<h2 className="text-lg font-semibold">
57+
Issue Inspector
58+
</h2>
59+
60+
<p className="mt-1 text-sm text-muted-foreground">
61+
delivery failure diagnostics
62+
</p>
63+
64+
</div>
65+
66+
</div>
67+
68+
</div>
69+
70+
<div className="space-y-3 border-b border-border p-5 text-sm">
71+
72+
<div className="flex justify-between">
73+
<span className="text-muted-foreground">
74+
Provider
75+
</span>
76+
77+
<span>
78+
{issue.provider}
79+
</span>
80+
</div>
81+
82+
<div className="flex justify-between">
83+
<span className="text-muted-foreground">
84+
Retries
85+
</span>
86+
87+
<span>
88+
{issue.retries}
89+
</span>
90+
</div>
91+
92+
<div className="flex justify-between">
93+
<span className="text-muted-foreground">
94+
Severity
95+
</span>
96+
97+
<span className="text-rose-400">
98+
{issue.severity}
99+
</span>
100+
</div>
101+
102+
</div>
103+
104+
<div className="flex-1 overflow-auto p-5">
105+
106+
<div className="mb-4 flex items-center gap-2">
107+
108+
<RotateCcw className="h-4 w-4 text-orange-400" />
109+
110+
<h3 className="text-sm font-semibold">
111+
Failure Payload
112+
</h3>
113+
114+
</div>
115+
116+
<div
117+
className="
118+
overflow-hidden rounded-xl
119+
border border-border
120+
bg-background/40 p-4
121+
"
122+
>
123+
124+
<JsonView
125+
value={{
126+
provider: issue.provider,
127+
route: issue.route,
128+
error: issue.error,
129+
retries: issue.retries,
130+
}}
131+
displayDataTypes={false}
132+
displayObjectSize={false}
133+
/>
134+
135+
</div>
136+
137+
</div>
138+
139+
<ReplayPanel />
140+
141+
</div>
142+
)
143+
}
Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
"use client"
2+
3+
import { cn } from "@/lib/utils"
4+
5+
type Props = {
6+
issue: {
7+
provider: string
8+
route: string
9+
error: string
10+
retries: number
11+
severity: string
12+
timestamp: string
13+
}
14+
selected?: boolean
15+
onClick?: () => void
16+
}
17+
18+
export function IssueRow({
19+
issue,
20+
selected,
21+
onClick,
22+
}: Props) {
23+
return (
24+
<button
25+
onClick={onClick}
26+
className={cn(
27+
`
28+
grid
29+
grid-cols-[120px_1fr_1fr_120px_120px_140px]
30+
items-center
31+
border-b border-border
32+
px-5 py-3 text-left
33+
transition-colors
34+
hover:bg-white/[0.02]
35+
`,
36+
selected &&
37+
"bg-white/[0.03]"
38+
)}
39+
>
40+
41+
<div>
42+
43+
<span
44+
className="
45+
rounded-full border border-border
46+
bg-background/30
47+
px-2 py-1 text-xs uppercase
48+
"
49+
>
50+
{issue.provider}
51+
</span>
52+
53+
</div>
54+
55+
<div className="truncate font-medium">
56+
{issue.route}
57+
</div>
58+
59+
<div className="truncate text-rose-400">
60+
{issue.error}
61+
</div>
62+
63+
<div>
64+
{issue.retries}
65+
</div>
66+
67+
<div>
68+
69+
<span
70+
className="
71+
rounded-full
72+
bg-rose-500/10
73+
px-2 py-1 text-xs
74+
text-rose-400
75+
"
76+
>
77+
{issue.severity}
78+
</span>
79+
80+
</div>
81+
82+
<div className="text-muted-foreground">
83+
{issue.timestamp}
84+
</div>
85+
86+
</button>
87+
)
88+
}

0 commit comments

Comments
 (0)