Skip to content

Commit cad2457

Browse files
[Dashboard] Add webhook version support to Universal Bridge
1 parent d7dc83f commit cad2457

File tree

3 files changed

+95
-61
lines changed

3 files changed

+95
-61
lines changed

apps/dashboard/src/app/team/[team_slug]/[project_slug]/connect/universal-bridge/webhooks/components/webhooks.client.tsx

Lines changed: 13 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ type Webhook = {
5757
createdAt: string;
5858
id: string;
5959
secret: string;
60+
version?: string; // TODO (UB) make this mandatory after migration
6061
};
6162

6263
type PayWebhooksPageProps = {
@@ -75,7 +76,7 @@ export function PayWebhooksPage(props: PayWebhooksPageProps) {
7576
queryFn: async () => {
7677
const res = await payServerProxy({
7778
method: "GET",
78-
pathname: "/webhooks/get-all",
79+
pathname: "/webhooks/get-all", // TODO (UB) switch to UB endpoint after migration
7980
searchParams: {
8081
/**
8182
* @deprecated - remove after migration
@@ -147,6 +148,7 @@ export function PayWebhooksPage(props: PayWebhooksPageProps) {
147148
<TableHead>Url</TableHead>
148149
<TableHead>Secret</TableHead>
149150
<TableHead>Created</TableHead>
151+
<TableHead>Webhook Version</TableHead>
150152
<TableHead>Delete</TableHead>
151153
</TableRow>
152154
</TableHeader>
@@ -166,6 +168,7 @@ export function PayWebhooksPage(props: PayWebhooksPageProps) {
166168
<TableCell>
167169
{formatDistanceToNow(webhook.createdAt, { addSuffix: true })}
168170
</TableCell>
171+
<TableCell>{webhook.version || "v1"}</TableCell>
169172
<TableCell className="text-right">
170173
<DeleteWebhookButton
171174
clientId={props.clientId}
@@ -263,7 +266,8 @@ function CreateWebhookButton(props: PropsWithChildren<PayWebhooksPageProps>) {
263266
<DialogHeader>
264267
<DialogTitle>Create Webhook</DialogTitle>
265268
<DialogDescription>
266-
Receive a webhook notification when a pay event occurs.
269+
Receive a webhook notification when a bridge, swap or onramp
270+
event occurs.
267271
</DialogDescription>
268272
</DialogHeader>
269273

@@ -294,21 +298,20 @@ function CreateWebhookButton(props: PropsWithChildren<PayWebhooksPageProps>) {
294298
)}
295299
/>
296300

297-
{/* Note: this is a "fake" form field since there is nothing to select yet */}
298301
<FormItem>
299-
<FormLabel>Event Type</FormLabel>
300-
<Select disabled defaultValue="purchase_complete">
302+
<FormLabel>Webhook Version</FormLabel>
303+
<Select defaultValue="v2">
301304
<SelectTrigger className="w-full">
302-
<SelectValue placeholder="Purchase Complete" />
305+
<SelectValue placeholder="v2" />
303306
</SelectTrigger>
304307
<SelectContent>
305-
<SelectItem value="purchase_complete">
306-
Purchase Complete
307-
</SelectItem>
308+
<SelectItem value="v2">v2</SelectItem>
309+
<SelectItem value="v1">v1</SelectItem>
308310
</SelectContent>
309311
</Select>
310312
<FormDescription>
311-
Which event should trigger this webhook?
313+
Select the data format of the webhook payload (v2 recommended,
314+
v1 for legacy users).
312315
</FormDescription>
313316
<FormMessage />
314317
</FormItem>

apps/dashboard/src/components/pay/PayConfig.tsx

Lines changed: 75 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ export const PayConfig: React.FC<PayConfigProps> = (props) => {
4242
resolver: zodResolver(apiKeyPayConfigValidationSchema),
4343
values: {
4444
payoutAddress: payService?.payoutAddress ?? "",
45+
developerFeeBPS: payService?.developerFeeBPS ?? 0,
4546
},
4647
});
4748

@@ -59,49 +60,55 @@ export const PayConfig: React.FC<PayConfigProps> = (props) => {
5960
},
6061
});
6162

62-
const handleSubmit = form.handleSubmit(({ payoutAddress }) => {
63-
const services = props.project.services;
63+
const handleSubmit = form.handleSubmit(
64+
({ payoutAddress, developerFeeBPS }) => {
65+
const services = props.project.services;
6466

65-
const newServices = services.map((service) => {
66-
if (service.name !== "pay") {
67-
return service;
68-
}
67+
const newServices = services.map((service) => {
68+
if (service.name !== "pay") {
69+
return service;
70+
}
6971

70-
return {
71-
...service,
72-
payoutAddress,
73-
};
74-
});
72+
return {
73+
...service,
74+
payoutAddress,
75+
developerFeeBPS: developerFeeBPS ? developerFeeBPS * 100 : 0,
76+
};
77+
});
7578

76-
updateProject.mutate(
77-
{
78-
services: newServices,
79-
},
80-
{
81-
onSuccess: () => {
82-
toast.success("Fee sharing updated");
83-
trackEvent({
84-
category: TRACKING_CATEGORY,
85-
action: "configuration-update",
86-
label: "success",
87-
data: {
88-
payoutAddress,
89-
},
90-
});
79+
updateProject.mutate(
80+
{
81+
services: newServices,
9182
},
92-
onError: (err) => {
93-
toast.error("Failed to update fee sharing");
94-
console.error(err);
95-
trackEvent({
96-
category: TRACKING_CATEGORY,
97-
action: "configuration-update",
98-
label: "error",
99-
error: err,
100-
});
83+
{
84+
onSuccess: () => {
85+
toast.success("Fee sharing updated");
86+
trackEvent({
87+
category: TRACKING_CATEGORY,
88+
action: "configuration-update",
89+
label: "success",
90+
data: {
91+
payoutAddress,
92+
},
93+
});
94+
},
95+
onError: (err) => {
96+
toast.error("Failed to update fee sharing");
97+
console.error(err);
98+
trackEvent({
99+
category: TRACKING_CATEGORY,
100+
action: "configuration-update",
101+
label: "error",
102+
error: err,
103+
});
104+
},
101105
},
102-
},
103-
);
104-
});
106+
);
107+
},
108+
(errors) => {
109+
console.log(errors);
110+
},
111+
);
105112

106113
if (!payService) {
107114
return (
@@ -140,7 +147,7 @@ export const PayConfig: React.FC<PayConfigProps> = (props) => {
140147
</h3>
141148
<p className="mt-1.5 mb-4 text-foreground text-sm">
142149
thirdweb collects a 0.3% protocol fee on swap transactions. You
143-
may set your own developer fee in addition to this fee.
150+
may set your own developer fee in addition to this fee.{" "}
144151
<Link
145152
href="https://portal.thirdweb.com/connect/pay/fee-sharing"
146153
target="_blank"
@@ -150,18 +157,35 @@ export const PayConfig: React.FC<PayConfigProps> = (props) => {
150157
</Link>
151158
</p>
152159

153-
<FormField
154-
control={form.control}
155-
name="payoutAddress"
156-
render={({ field }) => (
157-
<FormItem>
158-
<FormLabel>Recipient address</FormLabel>
159-
<FormControl>
160-
<Input {...field} placeholder="0x..." />
161-
</FormControl>
162-
</FormItem>
163-
)}
164-
/>
160+
<div className="grid grid-cols-1 gap-4 lg:grid-cols-2">
161+
<FormField
162+
control={form.control}
163+
name="payoutAddress"
164+
render={({ field }) => (
165+
<FormItem>
166+
<FormLabel>Recipient address</FormLabel>
167+
<FormControl>
168+
<Input {...field} placeholder="0x..." />
169+
</FormControl>
170+
</FormItem>
171+
)}
172+
/>
173+
<FormField
174+
control={form.control}
175+
name="developerFeeBPS"
176+
render={({ field }) => (
177+
<FormItem>
178+
<FormLabel>Fee amount</FormLabel>
179+
<FormControl>
180+
<div className="flex items-center gap-2">
181+
<Input {...field} type="number" placeholder="0.5" />
182+
<span className="text-muted-foreground text-sm">%</span>
183+
</div>
184+
</FormControl>
185+
</FormItem>
186+
)}
187+
/>
188+
</div>
165189
</div>
166190
</SettingsCard>
167191
</form>

apps/dashboard/src/components/settings/ApiKeys/validations.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,13 @@ export const apiKeyEmbeddedWalletsValidationSchema = z.object({
114114

115115
export const apiKeyPayConfigValidationSchema = z.object({
116116
payoutAddress: payoutAddressValidation,
117+
developerFeeBPS: z
118+
.string()
119+
.transform((val) => Number(val))
120+
.refine((val) => val >= 0 && val <= 100, {
121+
message: "Developer fee must be between 0 and 100",
122+
})
123+
.optional(),
117124
});
118125

119126
export type ApiKeyEmbeddedWalletsValidationSchema = z.infer<

0 commit comments

Comments
 (0)