Skip to content

Commit b083bcc

Browse files
committed
Add webhooks feature to Insight dashboard
1 parent 1e82083 commit b083bcc

File tree

9 files changed

+1604
-77
lines changed

9 files changed

+1604
-77
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,245 @@
1+
"use server";
2+
import { getAuthToken } from "app/(app)/api/lib/getAuthToken";
3+
4+
const INSIGHT_API_BASE_URL = "https://insight.thirdweb-dev.com";
5+
6+
export interface WebhookResponse {
7+
id: string;
8+
team_id: string;
9+
project_id: string;
10+
webhook_url: string;
11+
webhook_secret: string;
12+
filters: WebhookFilters;
13+
suspended_at: string | null;
14+
suspended_reason: string | null;
15+
disabled: boolean;
16+
created_at: string;
17+
updated_at: string | null;
18+
}
19+
20+
export interface WebhookFilters {
21+
"v1.events"?: {
22+
chain_ids?: string[];
23+
addresses?: string[];
24+
signatures?: Array<{
25+
sig_hash: string;
26+
abi?: string;
27+
params?: Record<string, unknown>;
28+
}>;
29+
};
30+
"v1.transactions"?: {
31+
chain_ids?: string[];
32+
from_addresses?: string[];
33+
to_addresses?: string[];
34+
signatures?: Array<{
35+
sig_hash: string;
36+
abi?: string;
37+
params?: string[];
38+
}>;
39+
};
40+
}
41+
42+
export interface CreateWebhookPayload {
43+
webhook_url: string;
44+
filters: WebhookFilters;
45+
}
46+
47+
export interface WebhooksListResponse {
48+
data: WebhookResponse[];
49+
}
50+
51+
export interface WebhookSingleResponse {
52+
data: WebhookResponse;
53+
}
54+
55+
export interface TestWebhookPayload {
56+
webhook_url: string;
57+
type?: "event" | "transaction";
58+
}
59+
60+
export interface TestWebhookResponse {
61+
success: boolean;
62+
}
63+
64+
// Create a new webhook
65+
export async function createWebhook(
66+
payload: CreateWebhookPayload,
67+
clientId: string
68+
): Promise<WebhookSingleResponse> {
69+
const response = await fetch(
70+
`${INSIGHT_API_BASE_URL}/v1/webhooks`,
71+
{
72+
method: "POST",
73+
headers: {
74+
"Content-Type": "application/json",
75+
"x-client-id": clientId,
76+
},
77+
body: JSON.stringify(payload),
78+
}
79+
);
80+
81+
console.log({
82+
method: "POST",
83+
headers: {
84+
"Content-Type": "application/json",
85+
"x-client-id": clientId,
86+
},
87+
body: JSON.stringify(payload),
88+
});
89+
90+
if (!response.ok) {
91+
const errorText = await response.text();
92+
throw new Error(`Failed to create webhook: ${errorText}`);
93+
}
94+
95+
return await response.json();
96+
}
97+
98+
// Get all webhooks for a project
99+
export async function getWebhooks(
100+
clientId: string
101+
): Promise<WebhooksListResponse> {
102+
const token = await getAuthToken();
103+
104+
if (!token) {
105+
throw new Error("Authentication token not found");
106+
}
107+
108+
const response = await fetch(
109+
`${INSIGHT_API_BASE_URL}/v1/webhooks`,
110+
{
111+
method: "GET",
112+
headers: {
113+
"x-client-id": clientId,
114+
},
115+
}
116+
);
117+
118+
if (!response.ok) {
119+
const errorText = await response.text();
120+
throw new Error(`Failed to get webhooks: ${errorText}`);
121+
}
122+
123+
return await response.json();
124+
}
125+
126+
// Get a single webhook by ID
127+
export async function getWebhookById(
128+
webhookId: string,
129+
clientId: string
130+
): Promise<WebhookSingleResponse> {
131+
const token = await getAuthToken();
132+
133+
if (!token) {
134+
throw new Error("Authentication token not found");
135+
}
136+
137+
const response = await fetch(
138+
`${INSIGHT_API_BASE_URL}/v1/webhooks/${webhookId}`,
139+
{
140+
method: "GET",
141+
headers: {
142+
"x-client-id": clientId,
143+
},
144+
}
145+
);
146+
147+
if (!response.ok) {
148+
const errorText = await response.text();
149+
throw new Error(`Failed to get webhook: ${errorText}`);
150+
}
151+
152+
return await response.json();
153+
}
154+
155+
// Update a webhook by ID
156+
export async function updateWebhook(
157+
webhookId: string,
158+
payload: Partial<CreateWebhookPayload> & { disabled?: boolean },
159+
clientId: string
160+
): Promise<WebhookSingleResponse> {
161+
const token = await getAuthToken();
162+
163+
if (!token) {
164+
throw new Error("Authentication token not found");
165+
}
166+
167+
const response = await fetch(
168+
`${INSIGHT_API_BASE_URL}/v1/webhooks/${webhookId}`,
169+
{
170+
method: "PATCH",
171+
headers: {
172+
"Content-Type": "application/json",
173+
"x-client-id": clientId,
174+
},
175+
body: JSON.stringify(payload),
176+
}
177+
);
178+
179+
if (!response.ok) {
180+
const errorText = await response.text();
181+
throw new Error(`Failed to update webhook: ${errorText}`);
182+
}
183+
184+
return await response.json();
185+
}
186+
187+
// Delete a webhook by ID
188+
export async function deleteWebhook(
189+
webhookId: string,
190+
clientId: string
191+
): Promise<WebhookSingleResponse> {
192+
const token = await getAuthToken();
193+
194+
if (!token) {
195+
throw new Error("Authentication token not found");
196+
}
197+
198+
const response = await fetch(
199+
`${INSIGHT_API_BASE_URL}/v1/webhooks/${webhookId}`,
200+
{
201+
method: "DELETE",
202+
headers: {
203+
"x-client-id": clientId,
204+
},
205+
}
206+
);
207+
208+
if (!response.ok) {
209+
const errorText = await response.text();
210+
throw new Error(`Failed to delete webhook: ${errorText}`);
211+
}
212+
213+
return await response.json();
214+
}
215+
216+
// Test a webhook
217+
export async function testWebhook(
218+
payload: TestWebhookPayload,
219+
clientId: string
220+
): Promise<TestWebhookResponse> {
221+
const token = await getAuthToken();
222+
223+
if (!token) {
224+
throw new Error("Authentication token not found");
225+
}
226+
227+
const response = await fetch(
228+
`${INSIGHT_API_BASE_URL}/v1/webhooks/test`,
229+
{
230+
method: "POST",
231+
headers: {
232+
"Content-Type": "application/json",
233+
"x-client-id": clientId,
234+
},
235+
body: JSON.stringify(payload),
236+
}
237+
);
238+
239+
if (!response.ok) {
240+
const errorText = await response.text();
241+
throw new Error(`Failed to test webhook: ${errorText}`);
242+
}
243+
244+
return await response.json();
245+
}

apps/dashboard/src/@/components/blocks/SidebarLayout.tsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -137,7 +137,7 @@ function RenderSidebarGroup(props: {
137137
}
138138

139139
if ("separator" in link) {
140-
return <SidebarSeparator className="my-1" />;
140+
return <SidebarSeparator className="my-1" key="separator" />;
141141
}
142142

143143
return (
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
"use client";
2+
3+
import { TabPathLinks } from "@/components/ui/tabs";
4+
import { FooterLinksSection } from "../components/footer/FooterLinksSection";
5+
6+
const InsightLayout = (props: {
7+
projectSlug: string;
8+
projectId: string;
9+
teamSlug: string;
10+
children: React.ReactNode;
11+
}) => {
12+
const insightLayoutSlug = `/team/${props.teamSlug}/${props.projectSlug}/insight`;
13+
14+
return (
15+
<div className="flex grow flex-col">
16+
{/* header */}
17+
<div className="pt-4 lg:pt-6">
18+
<div className="container flex max-w-7xl flex-col gap-4">
19+
<div>
20+
<h1 className="mb-1 font-semibold text-2xl tracking-tight lg:text-3xl">
21+
Insight
22+
</h1>
23+
<p className="text-muted-foreground text-sm">
24+
APIs to retrieve blockchain data from any EVM chain, enrich it with
25+
metadata, and transform it using custom logic
26+
</p>
27+
</div>
28+
</div>
29+
30+
<div className="h-4" />
31+
32+
{/* Nav */}
33+
<TabPathLinks
34+
scrollableClassName="container max-w-7xl"
35+
links={[
36+
{
37+
name: "Overview",
38+
path: `${insightLayoutSlug}`,
39+
exactMatch: true,
40+
},
41+
{
42+
name: "Webhooks",
43+
path: `${insightLayoutSlug}/webhooks`,
44+
},
45+
]}
46+
/>
47+
</div>
48+
49+
{/* content */}
50+
<div className="h-6" />
51+
<div className="container flex max-w-7xl grow flex-col gap-6">
52+
<div>{props.children}</div>
53+
</div>
54+
<div className="h-20" />
55+
56+
{/* footer */}
57+
<div className="border-border border-t">
58+
<div className="container max-w-7xl">
59+
<InsightFooter />
60+
</div>
61+
</div>
62+
</div>
63+
);
64+
};
65+
66+
function InsightFooter() {
67+
return (
68+
<FooterLinksSection
69+
left={{
70+
title: "Documentation",
71+
links: [
72+
{
73+
label: "Overview",
74+
href: "https://portal.thirdweb.com/insight",
75+
},
76+
{
77+
label: "API Reference",
78+
href: "https://insight-api.thirdweb.com/reference",
79+
},
80+
],
81+
}}
82+
center={{
83+
title: "Tutorials",
84+
links: [
85+
{
86+
label:
87+
"Blockchain Data on Any EVM - Quick and Easy REST APIs for Onchain Data",
88+
href: "https://www.youtube.com/watch?v=U2aW7YIUJVw",
89+
},
90+
{
91+
label: "Build a Whale Alerts Telegram Bot with Insight",
92+
href: "https://www.youtube.com/watch?v=HvqewXLVRig",
93+
},
94+
],
95+
}}
96+
right={{
97+
title: "Demos",
98+
links: [
99+
{
100+
label: "API Playground",
101+
href: "https://playground.thirdweb.com/insight",
102+
},
103+
],
104+
}}
105+
trackingCategory="insight"
106+
/>
107+
);
108+
}
109+
110+
export default InsightLayout;

0 commit comments

Comments
 (0)