From deeca886a43c766c910f027221a3ffea8aa6f01b Mon Sep 17 00:00:00 2001 From: AmineAfia Date: Thu, 29 May 2025 23:32:31 +0000 Subject: [PATCH] Add support for chain filtering in webhook creation (#7209) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## [Dashboard] Feature: Add Chain Filtering for Webhooks ## Notes for the reviewer This PR adds support for filtering available chains in the webhook creation flow. It fetches supported chains from the Insight API and restricts the chain selection in the UI to only those chains that are supported for webhooks. ## How to test 1. Navigate to the webhooks page for a project 2. Verify that the chain selector in the webhook creation modal only shows supported chains 3. Confirm that the API call to fetch supported chains works correctly ## Summary by CodeRabbit - **New Features** - Added support for dynamically fetching and displaying supported blockchain networks when creating or managing webhooks. - The network selector now only shows networks that are currently supported for webhooks. - Users are notified if no supported blockchain networks are available for webhooks. - **Bug Fixes** - Improved error handling and messaging when no supported blockchain networks are available. --- ## PR-Codex overview This PR focuses on enhancing the `NetworkSelectors` and webhook components by adding support for multiple chain IDs, improving the data handling for webhooks, and integrating the fetching of supported chains from an API. ### Detailed summary - Added `chainIds` prop to `NetworkSelectors` and filtered `allChains` based on it. - Updated `CreateWebhookModal` to accept `supportedChainIds`. - Modified `WebhooksTable` to include `supportedChainIds`. - Introduced `getSupportedWebhookChains` API call to fetch supported chains. - Updated `FilterDetailsStep` to utilize `supportedChainIds`. - Adjusted `WebhooksPage` to manage `supportedChainIds` and handle API responses. > ✨ Ask PR-Codex anything about this PR by commenting with `/codex {your question}` --- apps/dashboard/src/@/api/insight/webhooks.ts | 32 +++++++++++++++++++ .../@/components/blocks/NetworkSelectors.tsx | 9 +++++- .../components/CreateWebhookModal.tsx | 7 +++- .../webhooks/components/FilterDetailsStep.tsx | 5 ++- .../webhooks/components/WebhooksTable.tsx | 12 +++++-- .../(sidebar)/webhooks/page.tsx | 25 +++++++++++++-- 6 files changed, 82 insertions(+), 8 deletions(-) diff --git a/apps/dashboard/src/@/api/insight/webhooks.ts b/apps/dashboard/src/@/api/insight/webhooks.ts index 8ac3733f84c..1b14d0a790e 100644 --- a/apps/dashboard/src/@/api/insight/webhooks.ts +++ b/apps/dashboard/src/@/api/insight/webhooks.ts @@ -67,6 +67,10 @@ interface TestWebhookResponse { error?: string; } +type SupportedWebhookChainsResponse = + | { chains: Array } + | { error: string }; + export async function createWebhook( payload: CreateWebhookPayload, clientId: string, @@ -205,3 +209,31 @@ export async function testWebhook( }; } } + +export async function getSupportedWebhookChains(): Promise { + try { + const response = await fetch( + `https://${THIRDWEB_INSIGHT_API_DOMAIN}/service/chains`, + { + method: "GET", + headers: { + "x-service-api-key": process.env.INSIGHT_SERVICE_API_KEY || "", + }, + }, + ); + + if (!response.ok) { + const errorText = await response.json(); + return { error: `Failed to fetch supported chains: ${errorText.error}` }; + } + + const data = await response.json(); + if (Array.isArray(data.data)) { + return { chains: data.data }; + } + return { error: "Unexpected response format" }; + } catch (error) { + console.error("Error fetching supported chains:", error); + return { error: `Failed to fetch supported chains: ${error}` }; + } +} diff --git a/apps/dashboard/src/@/components/blocks/NetworkSelectors.tsx b/apps/dashboard/src/@/components/blocks/NetworkSelectors.tsx index a16b0b40548..9f0dde55afa 100644 --- a/apps/dashboard/src/@/components/blocks/NetworkSelectors.tsx +++ b/apps/dashboard/src/@/components/blocks/NetworkSelectors.tsx @@ -27,8 +27,15 @@ export function MultiNetworkSelector(props: { side?: "left" | "right" | "top" | "bottom"; showSelectedValuesInModal?: boolean; client: ThirdwebClient; + chainIds?: number[]; }) { - const { allChains, idToChain } = useAllChainsData(); + let { allChains, idToChain } = useAllChainsData(); + + if (props.chainIds && props.chainIds.length > 0) { + allChains = allChains.filter((chain) => + props.chainIds?.includes(chain.chainId), + ); + } const options = useMemo(() => { let sortedChains = allChains; diff --git a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/webhooks/components/CreateWebhookModal.tsx b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/webhooks/components/CreateWebhookModal.tsx index e9e33451966..35cdaf0387f 100644 --- a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/webhooks/components/CreateWebhookModal.tsx +++ b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/webhooks/components/CreateWebhookModal.tsx @@ -41,9 +41,13 @@ import { interface CreateWebhookModalProps { clientId: string; + supportedChainIds: Array; } -export function CreateWebhookModal({ clientId }: CreateWebhookModalProps) { +export function CreateWebhookModal({ + clientId, + supportedChainIds, +}: CreateWebhookModalProps) { const router = useDashboardRouter(); const thirdwebClient = useThirdwebClient(); const [isOpen, setIsOpen] = useState(false); @@ -270,6 +274,7 @@ export function CreateWebhookModal({ clientId }: CreateWebhookModalProps) { goToNextStep={goToNextStep} goToPreviousStep={goToPreviousStep} isLoading={isLoading} + supportedChainIds={supportedChainIds} /> )} diff --git a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/webhooks/components/FilterDetailsStep.tsx b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/webhooks/components/FilterDetailsStep.tsx index 1a1fbf01038..df8140ed8a8 100644 --- a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/webhooks/components/FilterDetailsStep.tsx +++ b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/webhooks/components/FilterDetailsStep.tsx @@ -1,6 +1,5 @@ "use client"; -import { MultiNetworkSelector } from "@/components/blocks/NetworkSelectors"; import { Spinner } from "@/components/ui/Spinner/Spinner"; import { Badge } from "@/components/ui/badge"; import { Button } from "@/components/ui/button"; @@ -24,6 +23,7 @@ import { useThirdwebClient } from "@/constants/thirdweb.client"; import { useQueryClient } from "@tanstack/react-query"; import type { UseFormReturn } from "react-hook-form"; +import { MultiNetworkSelector } from "@/components/blocks/NetworkSelectors"; import { truncateMiddle } from "../utils/abiUtils"; import type { AbiData, @@ -45,6 +45,7 @@ interface FilterDetailsStepProps { goToNextStep: () => void; goToPreviousStep: () => void; isLoading: boolean; + supportedChainIds: Array; } export function FilterDetailsStep({ @@ -60,6 +61,7 @@ export function FilterDetailsStep({ goToNextStep, goToPreviousStep, isLoading, + supportedChainIds, }: FilterDetailsStepProps) { const thirdwebClient = useThirdwebClient(); const queryClient = useQueryClient(); @@ -106,6 +108,7 @@ export function FilterDetailsStep({ } onChange={(chainIds) => field.onChange(chainIds.map(String))} client={thirdwebClient} + chainIds={supportedChainIds} /> diff --git a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/webhooks/components/WebhooksTable.tsx b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/webhooks/components/WebhooksTable.tsx index 93f133227a4..b7a89b3f20b 100644 --- a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/webhooks/components/WebhooksTable.tsx +++ b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/webhooks/components/WebhooksTable.tsx @@ -32,9 +32,14 @@ function getEventType(filters: WebhookFilters): string { interface WebhooksTableProps { webhooks: WebhookResponse[]; clientId: string; + supportedChainIds: number[]; } -export function WebhooksTable({ webhooks, clientId }: WebhooksTableProps) { +export function WebhooksTable({ + webhooks, + clientId, + supportedChainIds, +}: WebhooksTableProps) { const [isDeleting, setIsDeleting] = useState>({}); const { testWebhookEndpoint, isTestingMap } = useTestWebhook(clientId); const router = useDashboardRouter(); @@ -210,7 +215,10 @@ export function WebhooksTable({ webhooks, clientId }: WebhooksTableProps) { return (
- +
) : webhooks.length > 0 ? ( - + ) : (
@@ -80,7 +96,10 @@ export default async function WebhooksPage({ Create a webhook to get started.

- +
)}