Skip to content

Commit 586297d

Browse files
committed
feat: webhooks list in dashboard
1 parent c356813 commit 586297d

File tree

16 files changed

+1559
-20
lines changed

16 files changed

+1559
-20
lines changed

apps/dashboard/src/@/api/analytics.ts

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import type {
1313
UserOpStats,
1414
WalletStats,
1515
WalletUserStats,
16+
WebhookSummaryStats,
1617
} from "@/types/analytics";
1718
import { getAuthToken } from "./auth-token";
1819
import { getChains } from "./chain";
@@ -424,3 +425,48 @@ export async function getEngineCloudMethodUsage(
424425
const json = await res.json();
425426
return json.data as EngineCloudStats[];
426427
}
428+
429+
export async function getWebhookMetrics(params: {
430+
teamId: string;
431+
projectId: string;
432+
webhookId: string;
433+
period?: "day" | "week" | "month" | "year" | "all";
434+
from?: Date;
435+
to?: Date;
436+
}): Promise<WebhookSummaryStats[]> {
437+
const searchParams = new URLSearchParams();
438+
439+
// Required params
440+
searchParams.append("teamId", params.teamId);
441+
searchParams.append("projectId", params.projectId);
442+
searchParams.append("webhookId", params.webhookId);
443+
444+
// Optional params
445+
if (params.period) {
446+
searchParams.append("period", params.period);
447+
}
448+
if (params.from) {
449+
searchParams.append("from", params.from.toISOString());
450+
}
451+
if (params.to) {
452+
searchParams.append("to", params.to.toISOString());
453+
}
454+
455+
const res = await fetchAnalytics(
456+
`v2/webhook/summary?${searchParams.toString()}`,
457+
{
458+
method: "GET",
459+
},
460+
);
461+
462+
if (res?.status !== 200) {
463+
const reason = await res?.text();
464+
console.error(
465+
`Failed to fetch webhook metrics: ${res?.status} - ${res.statusText} - ${reason}`,
466+
);
467+
return [];
468+
}
469+
470+
const json = await res.json();
471+
return json.data as WebhookSummaryStats[];
472+
}
Lines changed: 271 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,271 @@
1+
"use server";
2+
3+
import { getAuthToken } from "@/api/auth-token";
4+
import { NEXT_PUBLIC_THIRDWEB_API_HOST } from "@/constants/public-envs";
5+
6+
export interface WebhookConfig {
7+
id: string;
8+
teamId: string;
9+
projectId: string;
10+
destinationUrl: string;
11+
description: string;
12+
pausedAt: string | null;
13+
webhookSecret: string;
14+
createdAt: string;
15+
updatedAt: string;
16+
deletedAt: string | null;
17+
topics: {
18+
id: string;
19+
serviceName: string;
20+
description: string;
21+
createdAt: string;
22+
updatedAt: string;
23+
deletedAt: string | null;
24+
}[];
25+
}
26+
27+
interface WebhookConfigsResponse {
28+
data: WebhookConfig[];
29+
error?: string;
30+
}
31+
32+
interface CreateWebhookConfigRequest {
33+
topicIds: string[];
34+
destinationUrl: string;
35+
description: string;
36+
isPaused?: boolean;
37+
}
38+
39+
interface CreateWebhookConfigResponse {
40+
data: WebhookConfig;
41+
error?: string;
42+
}
43+
44+
export interface Topic {
45+
id: string;
46+
serviceName: string;
47+
description: string;
48+
createdAt: string;
49+
updatedAt: string;
50+
deletedAt: string | null;
51+
}
52+
53+
interface TopicsResponse {
54+
data: Topic[];
55+
error?: string;
56+
}
57+
58+
interface UpdateWebhookConfigRequest {
59+
destinationUrl?: string;
60+
topicIds?: string[];
61+
description?: string;
62+
isPaused?: boolean;
63+
}
64+
65+
interface UpdateWebhookConfigResponse {
66+
data: WebhookConfig;
67+
error?: string;
68+
}
69+
70+
interface DeleteWebhookConfigResponse {
71+
data: WebhookConfig;
72+
error?: string;
73+
}
74+
75+
export async function getWebhookConfigs(props: {
76+
teamIdOrSlug: string;
77+
projectIdOrSlug: string;
78+
}): Promise<WebhookConfigsResponse> {
79+
const authToken = await getAuthToken();
80+
81+
if (!authToken) {
82+
return {
83+
data: [],
84+
error: "Authentication required",
85+
};
86+
}
87+
88+
const response = await fetch(
89+
`${NEXT_PUBLIC_THIRDWEB_API_HOST}/v1/teams/${props.teamIdOrSlug}/projects/${props.projectIdOrSlug}/webhook-configs`,
90+
{
91+
headers: {
92+
Authorization: `Bearer ${authToken}`,
93+
"Content-Type": "application/json",
94+
},
95+
method: "GET",
96+
},
97+
);
98+
99+
if (!response.ok) {
100+
const errorText = await response.text();
101+
return {
102+
data: [],
103+
error: `Failed to fetch webhook configs: ${errorText}`,
104+
};
105+
}
106+
107+
const result = await response.json();
108+
return {
109+
data: result.data,
110+
error: undefined,
111+
};
112+
}
113+
114+
export async function createWebhookConfig(props: {
115+
teamIdOrSlug: string;
116+
projectIdOrSlug: string;
117+
config: CreateWebhookConfigRequest;
118+
}): Promise<CreateWebhookConfigResponse> {
119+
const authToken = await getAuthToken();
120+
121+
if (!authToken) {
122+
return {
123+
data: {} as WebhookConfig,
124+
error: "Authentication required",
125+
};
126+
}
127+
128+
const response = await fetch(
129+
`${NEXT_PUBLIC_THIRDWEB_API_HOST}/v1/teams/${props.teamIdOrSlug}/projects/${props.projectIdOrSlug}/webhook-configs`,
130+
{
131+
body: JSON.stringify(props.config),
132+
headers: {
133+
Authorization: `Bearer ${authToken}`,
134+
"Content-Type": "application/json",
135+
},
136+
method: "POST",
137+
},
138+
);
139+
140+
if (!response.ok) {
141+
const errorText = await response.text();
142+
return {
143+
data: {} as WebhookConfig,
144+
error: `Failed to create webhook config: ${errorText}`,
145+
};
146+
}
147+
148+
const result = await response.json();
149+
return {
150+
data: result.data,
151+
error: undefined,
152+
};
153+
}
154+
155+
export async function getAvailableTopics(): Promise<TopicsResponse> {
156+
const authToken = await getAuthToken();
157+
158+
if (!authToken) {
159+
return {
160+
data: [],
161+
error: "Authentication required",
162+
};
163+
}
164+
165+
const response = await fetch(
166+
`${NEXT_PUBLIC_THIRDWEB_API_HOST}/v1/webhook-topics`,
167+
{
168+
headers: {
169+
Authorization: `Bearer ${authToken}`,
170+
"Content-Type": "application/json",
171+
},
172+
method: "GET",
173+
},
174+
);
175+
176+
if (!response.ok) {
177+
const errorText = await response.text();
178+
return {
179+
data: [],
180+
error: `Failed to fetch topics: ${errorText}`,
181+
};
182+
}
183+
184+
const result = await response.json();
185+
return {
186+
data: result.data,
187+
error: undefined,
188+
};
189+
}
190+
191+
export async function updateWebhookConfig(props: {
192+
teamIdOrSlug: string;
193+
projectIdOrSlug: string;
194+
webhookConfigId: string;
195+
config: UpdateWebhookConfigRequest;
196+
}): Promise<UpdateWebhookConfigResponse> {
197+
const authToken = await getAuthToken();
198+
199+
if (!authToken) {
200+
return {
201+
data: {} as WebhookConfig,
202+
error: "Authentication required",
203+
};
204+
}
205+
206+
const response = await fetch(
207+
`${NEXT_PUBLIC_THIRDWEB_API_HOST}/v1/teams/${props.teamIdOrSlug}/projects/${props.projectIdOrSlug}/webhook-configs/${props.webhookConfigId}`,
208+
{
209+
body: JSON.stringify(props.config),
210+
headers: {
211+
Authorization: `Bearer ${authToken}`,
212+
"Content-Type": "application/json",
213+
},
214+
method: "PATCH",
215+
},
216+
);
217+
218+
if (!response.ok) {
219+
const errorText = await response.text();
220+
return {
221+
data: {} as WebhookConfig,
222+
error: `Failed to update webhook config: ${errorText}`,
223+
};
224+
}
225+
226+
const result = await response.json();
227+
return {
228+
data: result.data,
229+
error: undefined,
230+
};
231+
}
232+
233+
export async function deleteWebhookConfig(props: {
234+
teamIdOrSlug: string;
235+
projectIdOrSlug: string;
236+
webhookConfigId: string;
237+
}): Promise<DeleteWebhookConfigResponse> {
238+
const authToken = await getAuthToken();
239+
240+
if (!authToken) {
241+
return {
242+
data: {} as WebhookConfig,
243+
error: "Authentication required",
244+
};
245+
}
246+
247+
const response = await fetch(
248+
`${NEXT_PUBLIC_THIRDWEB_API_HOST}/v1/teams/${props.teamIdOrSlug}/projects/${props.projectIdOrSlug}/webhook-configs/${props.webhookConfigId}`,
249+
{
250+
headers: {
251+
Authorization: `Bearer ${authToken}`,
252+
"Content-Type": "application/json",
253+
},
254+
method: "DELETE",
255+
},
256+
);
257+
258+
if (!response.ok) {
259+
const errorText = await response.text();
260+
return {
261+
data: {} as WebhookConfig,
262+
error: `Failed to delete webhook config: ${errorText}`,
263+
};
264+
}
265+
266+
const result = await response.json();
267+
return {
268+
data: result.data,
269+
error: undefined,
270+
};
271+
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
"use server";
2+
3+
import { getWebhookMetrics } from "@/api/analytics";
4+
import type { WebhookSummaryStats } from "@/types/analytics";
5+
6+
export async function getWebhookMetricsAction(params: {
7+
teamId: string;
8+
projectId: string;
9+
webhookId: string;
10+
period?: "day" | "week" | "month" | "year" | "all";
11+
from?: Date;
12+
to?: Date;
13+
}): Promise<WebhookSummaryStats | null> {
14+
const metrics = await getWebhookMetrics(params);
15+
return metrics[0] || null;
16+
}

apps/dashboard/src/@/types/analytics.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,16 @@ export interface UniversalBridgeWalletStats {
7272
developerFeeUsdCents: number;
7373
}
7474

75+
export interface WebhookSummaryStats {
76+
webhookId: string;
77+
totalRequests: number;
78+
successRequests: number;
79+
errorRequests: number;
80+
successRate: number;
81+
avgLatencyMs: number;
82+
errorBreakdown: Record<string, unknown>;
83+
}
84+
7585
export interface AnalyticsQueryParams {
7686
teamId: string;
7787
projectId?: string;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import type { Topic } from "@/api/webhook-configs";
2+
import { WebhookConfigModal } from "./webhook-config-modal";
3+
4+
interface CreateWebhookConfigModalProps {
5+
open: boolean;
6+
onOpenChange: (open: boolean) => void;
7+
teamSlug: string;
8+
projectSlug: string;
9+
topics: Topic[];
10+
}
11+
12+
export function CreateWebhookConfigModal(props: CreateWebhookConfigModalProps) {
13+
return (
14+
<WebhookConfigModal
15+
mode="create"
16+
onOpenChange={props.onOpenChange}
17+
open={props.open}
18+
projectSlug={props.projectSlug}
19+
teamSlug={props.teamSlug}
20+
topics={props.topics}
21+
/>
22+
);
23+
}

0 commit comments

Comments
 (0)