Skip to content

Commit c588e5a

Browse files
committed
Dashboard: Migrate engine/webhooks page from chakra to tailwind (#7717)
<!-- ## title your PR with this format: "[SDK/Dashboard/Portal] Feature/Fix: Concise title for the changes" If you did not copy the branch name from Linear, paste the issue tag here (format is TEAM-0000): ## Notes for the reviewer Anything important to call out? Be sure to also clarify these in your comments. ## How to test Unit tests, playground, etc. --> <!-- start pr-codex --> --- ## PR-Codex overview This PR focuses on refactoring the webhook components in the dashboard application. It improves the code structure, updates UI components, and enhances the form handling for creating and managing webhooks. ### Detailed summary - Changed `CreateWebhookInput` type definition. - Refactored `EngineWebhooks` component to simplify props handling. - Updated UI components in `EngineWebhooks` for better styling. - Replaced Chakra UI modal with custom dialog components in `AddWebhookButton`. - Enhanced form validation using `zod` in `AddWebhookButton`. - Refactored `WebhooksTable` component for improved UI and functionality. - Replaced modals with dialogs for delete and test webhook actions. - Improved error handling and user notifications with `toast`. - Updated event type selection and input handling in forms. > ✨ Ask PR-Codex anything about this PR by commenting with `/codex {your question}` <!-- end pr-codex --> <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **Refactor** * Updated webhooks-related components to use a custom UI library instead of Chakra UI, resulting in a more consistent interface. * Improved form validation and inline error messages when adding webhooks. * Enhanced dialog and modal interactions for adding, deleting, and testing webhooks with better state management. * Updated table and tooltip styling for better readability and user experience. * Adjusted button states and feedback to clearly indicate loading and error conditions. * Improved external links with enhanced security attributes and streamlined layout for webhook management. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
1 parent 2996121 commit c588e5a

File tree

4 files changed

+386
-275
lines changed

4 files changed

+386
-275
lines changed

apps/dashboard/src/@/hooks/useEngine.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1223,7 +1223,7 @@ export function useEngineUpdateAccessToken(params: {
12231223
});
12241224
}
12251225

1226-
export type CreateWebhookInput = {
1226+
type CreateWebhookInput = {
12271227
url: string;
12281228
name: string;
12291229
eventType: string;
Original file line numberDiff line numberDiff line change
@@ -1,33 +1,38 @@
1+
import { zodResolver } from "@hookform/resolvers/zod";
2+
import { PlusIcon } from "lucide-react";
3+
import { useState } from "react";
4+
import { useForm } from "react-hook-form";
5+
import { toast } from "sonner";
6+
import { z } from "zod";
7+
import { Button } from "@/components/ui/button";
8+
import {
9+
Dialog,
10+
DialogContent,
11+
DialogDescription,
12+
DialogHeader,
13+
DialogTitle,
14+
DialogTrigger,
15+
} from "@/components/ui/dialog";
116
import {
2-
Flex,
17+
Form,
318
FormControl,
4-
Input,
5-
Modal,
6-
ModalBody,
7-
ModalCloseButton,
8-
ModalContent,
9-
ModalFooter,
10-
ModalHeader,
11-
ModalOverlay,
12-
Select,
13-
useDisclosure,
14-
} from "@chakra-ui/react";
15-
import { Button } from "chakra/button";
16-
import { FormLabel } from "chakra/form";
17-
import { CirclePlusIcon } from "lucide-react";
18-
import { useForm } from "react-hook-form";
19+
FormField,
20+
FormItem,
21+
FormLabel,
22+
FormMessage,
23+
} from "@/components/ui/form";
24+
import { Input } from "@/components/ui/input";
25+
import { Spinner } from "@/components/ui/Spinner/Spinner";
1926
import {
20-
type CreateWebhookInput,
21-
useEngineCreateWebhook,
22-
} from "@/hooks/useEngine";
23-
import { useTxNotifications } from "@/hooks/useTxNotifications";
27+
Select,
28+
SelectContent,
29+
SelectItem,
30+
SelectTrigger,
31+
SelectValue,
32+
} from "@/components/ui/select";
33+
import { useEngineCreateWebhook } from "@/hooks/useEngine";
2434
import { beautifyString } from "./webhooks-table";
2535

26-
interface AddWebhookButtonProps {
27-
instanceUrl: string;
28-
authToken: string;
29-
}
30-
3136
const WEBHOOK_EVENT_TYPES = [
3237
"all_transactions",
3338
"sent_transaction",
@@ -38,97 +43,156 @@ const WEBHOOK_EVENT_TYPES = [
3843
"auth",
3944
];
4045

41-
export const AddWebhookButton: React.FC<AddWebhookButtonProps> = ({
46+
const webhookFormSchema = z.object({
47+
eventType: z.string().min(1, "Event type is required"),
48+
name: z.string().min(1, "Name is required"),
49+
url: z.string().url("Please enter a valid URL"),
50+
});
51+
52+
type WebhookFormValues = z.infer<typeof webhookFormSchema>;
53+
54+
export function AddWebhookButton({
4255
instanceUrl,
4356
authToken,
44-
}) => {
45-
const { isOpen, onOpen, onClose } = useDisclosure();
46-
const { mutate: createWebhook } = useEngineCreateWebhook({
57+
}: {
58+
instanceUrl: string;
59+
authToken: string;
60+
}) {
61+
const [open, setOpen] = useState(false);
62+
const createWebhook = useEngineCreateWebhook({
4763
authToken,
4864
instanceUrl,
4965
});
5066

51-
const form = useForm<CreateWebhookInput>();
67+
const form = useForm<WebhookFormValues>({
68+
resolver: zodResolver(webhookFormSchema),
69+
defaultValues: {
70+
eventType: "",
71+
name: "",
72+
url: "",
73+
},
74+
mode: "onChange",
75+
});
5276

53-
const { onSuccess, onError } = useTxNotifications(
54-
"Webhook created successfully.",
55-
"Failed to create webhook.",
56-
);
77+
const onSubmit = (data: WebhookFormValues) => {
78+
createWebhook.mutate(data, {
79+
onError: (error) => {
80+
toast.error("Failed to create webhook", {
81+
description: error.message,
82+
});
83+
console.error(error);
84+
},
85+
onSuccess: () => {
86+
toast.success("Webhook created successfully");
87+
setOpen(false);
88+
form.reset();
89+
},
90+
});
91+
};
5792

5893
return (
59-
<>
60-
<Button
61-
colorScheme="primary"
62-
leftIcon={<CirclePlusIcon className="size-6" />}
63-
onClick={onOpen}
64-
size="sm"
65-
variant="ghost"
66-
w="fit-content"
67-
>
68-
Create Webhook
69-
</Button>
94+
<Dialog open={open} onOpenChange={setOpen}>
95+
<DialogTrigger asChild>
96+
<Button className="w-fit gap-2" onClick={() => setOpen(true)}>
97+
<PlusIcon className="size-4" />
98+
Create Webhook
99+
</Button>
100+
</DialogTrigger>
70101

71-
<Modal isCentered isOpen={isOpen} onClose={onClose}>
72-
<ModalOverlay />
73-
<ModalContent
74-
as="form"
75-
className="!bg-background rounded-lg border border-border"
76-
onSubmit={form.handleSubmit((data) => {
77-
createWebhook(data, {
78-
onError: (error) => {
79-
onError(error);
80-
console.error(error);
81-
},
82-
onSuccess: () => {
83-
onSuccess();
84-
onClose();
85-
},
86-
});
87-
})}
88-
>
89-
<ModalHeader>Create Webhook</ModalHeader>
90-
<ModalCloseButton />
91-
<ModalBody>
92-
<Flex flexDir="column" gap={4}>
93-
<FormControl isRequired>
94-
<FormLabel>Event Type</FormLabel>
95-
<Select {...form.register("eventType", { required: true })}>
96-
{WEBHOOK_EVENT_TYPES.map((eventType) => (
97-
<option key={eventType} value={eventType}>
98-
{beautifyString(eventType)}
99-
</option>
100-
))}
101-
</Select>
102-
</FormControl>
103-
<FormControl isRequired>
104-
<FormLabel>Name</FormLabel>
105-
<Input
106-
placeholder="My webhook"
107-
type="text"
108-
{...form.register("name", { required: true })}
109-
/>
110-
</FormControl>
111-
<FormControl isRequired>
112-
<FormLabel>URL</FormLabel>
113-
<Input
114-
placeholder="https://"
115-
type="url"
116-
{...form.register("url", { required: true })}
117-
/>
118-
</FormControl>
119-
</Flex>
120-
</ModalBody>
102+
<DialogContent className="p-0 gap-0 overflow-hidden">
103+
<DialogHeader className="p-4 lg:p-6">
104+
<DialogTitle>Create Webhook</DialogTitle>
105+
<DialogDescription>
106+
Create a new webhook to receive notifications for engine events.
107+
</DialogDescription>
108+
</DialogHeader>
121109

122-
<ModalFooter as={Flex} gap={3}>
123-
<Button onClick={onClose} type="button" variant="ghost">
124-
Cancel
125-
</Button>
126-
<Button colorScheme="primary" type="submit">
127-
Create
128-
</Button>
129-
</ModalFooter>
130-
</ModalContent>
131-
</Modal>
132-
</>
110+
<Form {...form}>
111+
<form onSubmit={form.handleSubmit(onSubmit)} className="space-y-4">
112+
<div className="space-y-4 px-4 lg:px-6 pb-4">
113+
<FormField
114+
control={form.control}
115+
name="eventType"
116+
render={({ field }) => (
117+
<FormItem>
118+
<FormLabel>Event Type</FormLabel>
119+
<Select onValueChange={field.onChange} value={field.value}>
120+
<FormControl>
121+
<SelectTrigger className="bg-card">
122+
<SelectValue placeholder="Select event type" />
123+
</SelectTrigger>
124+
</FormControl>
125+
<SelectContent>
126+
{WEBHOOK_EVENT_TYPES.map((eventType) => (
127+
<SelectItem key={eventType} value={eventType}>
128+
{beautifyString(eventType)}
129+
</SelectItem>
130+
))}
131+
</SelectContent>
132+
</Select>
133+
<FormMessage />
134+
</FormItem>
135+
)}
136+
/>
137+
138+
<FormField
139+
control={form.control}
140+
name="name"
141+
render={({ field }) => (
142+
<FormItem>
143+
<FormLabel>Name</FormLabel>
144+
<FormControl>
145+
<Input
146+
className="bg-card"
147+
placeholder="My webhook"
148+
{...field}
149+
/>
150+
</FormControl>
151+
<FormMessage />
152+
</FormItem>
153+
)}
154+
/>
155+
156+
<FormField
157+
control={form.control}
158+
name="url"
159+
render={({ field }) => (
160+
<FormItem>
161+
<FormLabel>URL</FormLabel>
162+
<FormControl>
163+
<Input
164+
className="bg-card"
165+
placeholder="https://"
166+
type="url"
167+
{...field}
168+
/>
169+
</FormControl>
170+
<FormMessage />
171+
</FormItem>
172+
)}
173+
/>
174+
</div>
175+
176+
<div className="flex justify-end gap-3 p-4 lg:p-6 bg-card border-t border-border">
177+
<Button
178+
type="button"
179+
variant="outline"
180+
onClick={() => setOpen(false)}
181+
>
182+
Cancel
183+
</Button>
184+
<Button
185+
type="submit"
186+
disabled={createWebhook.isPending}
187+
className="gap-2"
188+
>
189+
{createWebhook.isPending && <Spinner className="size-4" />}
190+
Create
191+
</Button>
192+
</div>
193+
</form>
194+
</Form>
195+
</DialogContent>
196+
</Dialog>
133197
);
134-
};
198+
}
Original file line numberDiff line numberDiff line change
@@ -1,51 +1,49 @@
11
"use client";
22

3-
import { Heading } from "chakra/heading";
4-
import { Link } from "chakra/link";
5-
import { Text } from "chakra/text";
3+
import { UnderlineLink } from "@/components/ui/UnderlineLink";
64
import { useEngineWebhooks } from "@/hooks/useEngine";
75
import { AddWebhookButton } from "./add-webhook-button";
86
import { WebhooksTable } from "./webhooks-table";
97

10-
interface EngineWebhooksProps {
11-
instanceUrl: string;
12-
authToken: string;
13-
}
14-
15-
export const EngineWebhooks: React.FC<EngineWebhooksProps> = ({
8+
export function EngineWebhooks({
169
instanceUrl,
1710
authToken,
18-
}) => {
11+
}: {
12+
instanceUrl: string;
13+
authToken: string;
14+
}) {
1915
const webhooks = useEngineWebhooks({
2016
authToken,
2117
instanceUrl,
2218
});
2319

2420
return (
25-
<div className="flex flex-col gap-4">
26-
<div className="flex flex-col gap-2">
27-
<Heading size="title.md">Webhooks</Heading>
28-
<Text>
29-
Notify your app backend when transaction and backend wallet events
30-
occur.{" "}
31-
<Link
32-
color="primary.500"
33-
href="https://portal.thirdweb.com/infrastructure/engine/features/webhooks"
34-
isExternal
35-
>
36-
Learn more about webhooks
37-
</Link>
38-
.
39-
</Text>
40-
</div>
21+
<div>
22+
<h2 className="text-2xl tracking-tight font-semibold mb-1">Webhooks</h2>
23+
<p className="text-muted-foreground mb-4 text-sm">
24+
Notify your app backend when transaction and backend wallet events
25+
occur.{" "}
26+
<UnderlineLink
27+
href="https://portal.thirdweb.com/infrastructure/engine/features/webhooks"
28+
rel="noopener noreferrer"
29+
target="_blank"
30+
>
31+
Learn more about webhooks
32+
</UnderlineLink>
33+
.
34+
</p>
35+
4136
<WebhooksTable
4237
authToken={authToken}
4338
instanceUrl={instanceUrl}
4439
isFetched={webhooks.isFetched}
4540
isPending={webhooks.isPending}
4641
webhooks={webhooks.data || []}
4742
/>
48-
<AddWebhookButton authToken={authToken} instanceUrl={instanceUrl} />
43+
44+
<div className="mt-4 flex justify-end">
45+
<AddWebhookButton authToken={authToken} instanceUrl={instanceUrl} />
46+
</div>
4947
</div>
5048
);
51-
};
49+
}

0 commit comments

Comments
 (0)