Skip to content

Commit 47ebcaa

Browse files
committed
feat: add Connections workspace and navigation for provider management
1 parent c5f15d2 commit 47ebcaa

9 files changed

Lines changed: 630 additions & 0 deletions

File tree

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
import { ConnectionsWorkspace } from "@/components/connections/connections-workspace"
2+
3+
export default function ConnectionsPage() {
4+
return <ConnectionsWorkspace />
5+
}
Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
"use client"
2+
3+
import {
4+
CheckCircle2,
5+
AlertTriangle,
6+
ExternalLink,
7+
} from "lucide-react"
8+
9+
import { cn } from "@/lib/utils"
10+
import type { Connection } from "@/types/connection"
11+
12+
type Props = {
13+
connection: Connection
14+
selected?: boolean
15+
onClick?: () => void
16+
}
17+
18+
export function ConnectionCard({
19+
connection,
20+
selected,
21+
onClick,
22+
}: Props) {
23+
return (
24+
<button
25+
onClick={onClick}
26+
className={cn(
27+
`
28+
rounded-2xl
29+
border border-border
30+
bg-background/40
31+
p-5
32+
text-left
33+
transition-all
34+
hover:border-orange-500/30
35+
`,
36+
selected &&
37+
"border-orange-500/50 bg-orange-500/[0.03]"
38+
)}
39+
>
40+
<div className="mb-5 flex items-center justify-between">
41+
42+
<h3 className="text-lg font-semibold">
43+
{connection.provider}
44+
</h3>
45+
46+
{connection.status ===
47+
"healthy" ? (
48+
<CheckCircle2 className="h-5 w-5 text-emerald-400" />
49+
) : (
50+
<AlertTriangle className="h-5 w-5 text-rose-400" />
51+
)}
52+
53+
</div>
54+
55+
<div className="space-y-3 text-sm">
56+
57+
<Info
58+
label="Account"
59+
value={
60+
connection.accountName
61+
}
62+
/>
63+
64+
<Info
65+
label="Last Sync"
66+
value={
67+
connection.lastSync
68+
}
69+
/>
70+
71+
<Info
72+
label="API Version"
73+
value={
74+
connection.apiVersion
75+
}
76+
/>
77+
78+
</div>
79+
80+
<div className="mt-5 flex items-center justify-between border-t border-border pt-4">
81+
82+
<span className="text-xs text-muted-foreground">
83+
Connected
84+
{" "}
85+
{connection.connectedAt}
86+
</span>
87+
88+
<ExternalLink className="h-4 w-4 text-muted-foreground" />
89+
90+
</div>
91+
</button>
92+
)
93+
}
94+
95+
function Info({
96+
label,
97+
value,
98+
}: {
99+
label: string
100+
value: string
101+
}) {
102+
return (
103+
<div className="flex justify-between">
104+
<span className="text-muted-foreground">
105+
{label}
106+
</span>
107+
108+
<span>{value}</span>
109+
</div>
110+
)
111+
}
Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
"use client"
2+
3+
import { PlugZap } from "lucide-react"
4+
5+
import type { Connection } from "@/types/connection"
6+
7+
type Props = {
8+
connection: Connection | null
9+
}
10+
11+
export function ConnectionInspector({
12+
connection,
13+
}: Props) {
14+
if (!connection) {
15+
return (
16+
<div className="flex h-full items-center justify-center text-muted-foreground">
17+
Select a provider
18+
</div>
19+
)
20+
}
21+
22+
return (
23+
<div className="flex h-full flex-col">
24+
25+
<div className="border-b border-border p-5">
26+
27+
<div className="flex items-center gap-3">
28+
29+
<PlugZap className="h-5 w-5 text-orange-400" />
30+
31+
<div>
32+
33+
<h2 className="font-semibold">
34+
Connection Inspector
35+
</h2>
36+
37+
<p className="text-sm text-muted-foreground">
38+
provider details
39+
</p>
40+
41+
</div>
42+
43+
</div>
44+
45+
</div>
46+
47+
<div className="space-y-4 p-5 text-sm">
48+
49+
<Info
50+
label="Provider"
51+
value={
52+
connection.provider
53+
}
54+
/>
55+
56+
<Info
57+
label="Status"
58+
value={
59+
connection.status
60+
}
61+
/>
62+
63+
<Info
64+
label="Events"
65+
value={
66+
connection.eventsReceived
67+
}
68+
/>
69+
70+
<Info
71+
label="Rate Limit"
72+
value={
73+
connection.rateLimitRemaining
74+
}
75+
/>
76+
77+
<Info
78+
label="Last Event"
79+
value={
80+
connection.lastSync
81+
}
82+
/>
83+
84+
</div>
85+
86+
<div className="border-t border-border p-5">
87+
88+
<h3 className="mb-3 font-medium">
89+
Webhook URL
90+
</h3>
91+
92+
<div className="rounded-xl border border-border p-3 text-xs">
93+
{connection.webhookUrl}
94+
</div>
95+
96+
</div>
97+
98+
<div className="border-t border-border p-5">
99+
100+
<h3 className="mb-3 font-medium">
101+
Secret
102+
</h3>
103+
104+
<div className="rounded-xl border border-border p-3 text-xs">
105+
{connection.secret}
106+
</div>
107+
108+
</div>
109+
110+
</div>
111+
)
112+
}
113+
114+
function Info({
115+
label,
116+
value,
117+
}: {
118+
label: string
119+
value: React.ReactNode
120+
}) {
121+
return (
122+
<div className="flex justify-between">
123+
<span>{label}</span>
124+
<span>{value}</span>
125+
</div>
126+
)
127+
}
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
"use client"
2+
3+
import type { Connection } from "@/types/connection"
4+
5+
import { ConnectionCard } from "./connection-card"
6+
7+
type Props = {
8+
connections: Connection[]
9+
selected: Connection | null
10+
onSelect: (
11+
connection: Connection
12+
) => void
13+
}
14+
15+
export function ConnectionsGrid({
16+
connections,
17+
selected,
18+
onSelect,
19+
}: Props) {
20+
return (
21+
<div
22+
className="
23+
grid gap-5 p-5
24+
md:grid-cols-2
25+
xl:grid-cols-3
26+
"
27+
>
28+
{connections.map(
29+
(connection) => (
30+
<ConnectionCard
31+
key={connection.id}
32+
connection={connection}
33+
selected={
34+
selected?.id ===
35+
connection.id
36+
}
37+
onClick={() =>
38+
onSelect(connection)
39+
}
40+
/>
41+
)
42+
)}
43+
</div>
44+
)
45+
}
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
"use client"
2+
3+
import type { Connection } from "@/types/connection"
4+
5+
type Props = {
6+
connections: Connection[]
7+
}
8+
9+
export function ConnectionsStats({
10+
connections,
11+
}: Props) {
12+
const healthy =
13+
connections.filter(
14+
(c) =>
15+
c.status ===
16+
"healthy"
17+
).length
18+
19+
const errors =
20+
connections.filter(
21+
(c) =>
22+
c.status ===
23+
"error"
24+
).length
25+
26+
return (
27+
<div className="grid grid-cols-4 border-b border-border">
28+
29+
<Stat
30+
label="Providers"
31+
value={connections.length}
32+
/>
33+
34+
<Stat
35+
label="Healthy"
36+
value={healthy}
37+
/>
38+
39+
<Stat
40+
label="Errors"
41+
value={errors}
42+
/>
43+
44+
<Stat
45+
label="Events Today"
46+
value="128k"
47+
/>
48+
49+
</div>
50+
)
51+
}
52+
53+
function Stat({
54+
label,
55+
value,
56+
}: {
57+
label: string
58+
value: string | number
59+
}) {
60+
return (
61+
<div className="border-r border-border p-5 last:border-r-0">
62+
63+
<p className="text-sm text-muted-foreground">
64+
{label}
65+
</p>
66+
67+
<h3 className="mt-2 text-4xl font-bold">
68+
{value}
69+
</h3>
70+
71+
</div>
72+
)
73+
}

0 commit comments

Comments
 (0)