From 00d535258f4983f44c48dbaf0c0170df3d9f2428 Mon Sep 17 00:00:00 2001 From: 0xFirekeeper <0xFirekeeper@gmail.com> Date: Thu, 11 Dec 2025 06:08:37 +0700 Subject: [PATCH 1/4] Improve relayer analytics Introduces a date range selector to the dedicated relayer dashboard, allowing users to filter transaction data by date. Updates state management to use a Range object instead of separate from/to strings, and changes the default date range to the last 7 days. Also ensures chain filters and summary stats reflect the selected date range and only include relevant chains. --- .../components/active-state.tsx | 35 +++++++++++++------ .../components/page-client.tsx | 23 ++++++++---- .../wallets/dedicated-relayer/page.tsx | 4 +-- 3 files changed, 43 insertions(+), 19 deletions(-) diff --git a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/components/active-state.tsx b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/components/active-state.tsx index 04b52f5517a..52740cd5aa2 100644 --- a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/components/active-state.tsx +++ b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/components/active-state.tsx @@ -5,6 +5,10 @@ import { ExternalLinkIcon, XIcon } from "lucide-react"; import Link from "next/link"; import { useState } from "react"; import type { ThirdwebClient } from "thirdweb"; +import { + DateRangeSelector, + type Range, +} from "@/components/analytics/date-range-selector"; import { SingleNetworkSelector } from "@/components/blocks/NetworkSelectors"; import { PaginationButtons } from "@/components/blocks/pagination-buttons"; import { WalletAddress } from "@/components/blocks/wallet-address"; @@ -35,15 +39,15 @@ type DedicatedRelayerActiveStateProps = { teamId: string; fleetId: string; client: ThirdwebClient; - from: string; - to: string; + range: Range; + setRange: (range: Range) => void; className?: string; }; export function DedicatedRelayerActiveState( props: DedicatedRelayerActiveStateProps, ) { - const { fleet, teamId, fleetId, client, from, to } = props; + const { fleet, teamId, fleetId, client, range, setRange } = props; const pageSize = 10; const [page, setPage] = useState(1); @@ -52,15 +56,15 @@ export function DedicatedRelayerActiveState( const summaryQuery = useFleetTransactionsSummary({ teamId, fleetId, - from, - to, + from: range.from.toISOString(), + to: range.to.toISOString(), }); const transactionsQuery = useFleetTransactions({ teamId, fleetId, - from, - to, + from: range.from.toISOString(), + to: range.to.toISOString(), limit: pageSize, offset: (page - 1) * pageSize, chainId: chainIdFilter, @@ -70,8 +74,18 @@ export function DedicatedRelayerActiveState( ? Math.ceil(transactionsQuery.data.meta.total / pageSize) : 0; + // Filter active chains to only include those in the fleet + const activeChainsCount = + summaryQuery.data?.data.transactionsByChain.filter((c) => + fleet.chainIds.includes(Number(c.chainId)), + ).length ?? 0; + return (
+
+ +
+ {/* Summary Stats */}
@@ -132,6 +144,7 @@ export function DedicatedRelayerActiveState( setPage(1); }} client={client} + chainIds={fleet.chainIds} />
@@ -362,6 +375,7 @@ function ChainFilter(props: { chainId: number | undefined; setChainId: (chainId: number | undefined) => void; client: ThirdwebClient; + chainIds: number[]; }) { return (
@@ -373,6 +387,7 @@ function ChainFilter(props: { align="end" placeholder="All Chains" className="min-w-[150px]" + chainIds={props.chainIds} />
); diff --git a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/components/page-client.tsx b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/components/page-client.tsx index 4e90c38aa65..02ebd561e39 100644 --- a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/components/page-client.tsx +++ b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/components/page-client.tsx @@ -1,7 +1,9 @@ "use client"; +import { subDays } from "date-fns"; import { useEffect, useState } from "react"; import type { ThirdwebClient } from "thirdweb"; +import type { Range } from "@/components/analytics/date-range-selector"; import type { DedicatedRelayerSKU } from "@/types/billing"; import { getAbsoluteUrl } from "@/utils/vercel"; import { useFleetStatus, useFleetTransactionsSummary } from "../lib/hooks"; @@ -30,6 +32,12 @@ export function DedicatedRelayerPageClient( getInitialStatus(props.initialFleet), ); + const [dateRange, setDateRange] = useState({ + from: new Date(props.from), + to: new Date(props.to), + type: "last-7", + }); + // Poll for fleet status when not purchased or pending setup const fleetStatusQuery = useFleetStatus( props.teamSlug, @@ -45,17 +53,18 @@ export function DedicatedRelayerPageClient( } }, [fleetStatusQuery.data]); - // Only fetch transactions summary when we have an active fleet with executors - const summaryQuery = useFleetTransactionsSummary({ + // Check for any activity in the last 120 days to determine if we should show the dashboard + // This prevents switching back to "pending" state if the user selects a date range with no transactions + const hasActivityQuery = useFleetTransactionsSummary({ teamId: props.teamId, fleetId: props.fleetId, - from: props.from, - to: props.to, + from: subDays(new Date(), 120).toISOString(), + to: new Date().toISOString(), enabled: fleetStatus === "active", refetchInterval: 5000, }); - const totalTransactions = summaryQuery.data?.data.totalTransactions ?? 0; + const totalTransactions = hasActivityQuery.data?.data.totalTransactions ?? 0; const hasTransactions = totalTransactions > 0; // TODO-FLEET: Implement purchase flow @@ -112,8 +121,8 @@ export function DedicatedRelayerPageClient( teamId={props.teamId} fleetId={props.fleetId} client={props.client} - from={props.from} - to={props.to} + range={dateRange} + setRange={setDateRange} /> )} diff --git a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/page.tsx b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/page.tsx index 54ba757b3fc..850ac929e90 100644 --- a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/page.tsx +++ b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/page.tsx @@ -42,8 +42,8 @@ export default async function DedicatedRelayerPage(props: { // Build fleet ID from team and project const fleetId = buildFleetId(team.id, project.id); - // Default date range: last 30 days - const range = getLastNDaysRange("last-30"); + // Default date range: last 7 days + const range = getLastNDaysRange("last-7"); // Extract fleet configuration from bundler service const bundlerService = project.services.find((s) => s.name === "bundler"); From 139e541f71a79ec0413c67d1d02014db9f7ffb5b Mon Sep 17 00:00:00 2001 From: 0xFirekeeper <0xFirekeeper@gmail.com> Date: Thu, 11 Dec 2025 06:35:25 +0700 Subject: [PATCH 2/4] Remove executor column and handle null transaction fee Eliminated the 'Executor' column from the dedicated relayer transaction table and related components. Updated the FleetTransaction type to allow transactionFeeUsd to be null and adjusted the TransactionFeeCell to display a placeholder when the value is null. --- .../dedicated-relayer/components/active-state.tsx | 12 ++++-------- .../(sidebar)/wallets/dedicated-relayer/types.ts | 4 +--- 2 files changed, 5 insertions(+), 11 deletions(-) diff --git a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/components/active-state.tsx b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/components/active-state.tsx index 52740cd5aa2..9d2ac72f6fa 100644 --- a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/components/active-state.tsx +++ b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/components/active-state.tsx @@ -156,7 +156,6 @@ export function DedicatedRelayerActiveState( Transaction Hash Chain Wallet - Executor Timestamp Fee @@ -257,9 +256,6 @@ function TransactionRow(props: { - - - @@ -288,9 +284,6 @@ function SkeletonRow() { - - - @@ -363,7 +356,10 @@ function ChainCell(props: { chainId: string; client: ThirdwebClient }) { ); } -function TransactionFeeCell(props: { usdValue: number }) { +function TransactionFeeCell(props: { usdValue: number | null }) { + if (props.usdValue === null) { + return ; + } return ( ${props.usdValue < 0.01 ? "<0.01" : props.usdValue.toFixed(2)} diff --git a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/types.ts b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/types.ts index 3430e8608ca..727df66fade 100644 --- a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/types.ts +++ b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/types.ts @@ -33,11 +33,9 @@ export type FleetTransaction = { timestamp: string; chainId: string; transactionFee: number; - transactionFeeUsd: number; + transactionFeeUsd: number | null; walletAddress: string; transactionHash: string; - userOpHash: string; - executorAddress: string; }; /** From fc844467d103124058ca731538bb0185b08a80e4 Mon Sep 17 00:00:00 2001 From: 0xFirekeeper <0xFirekeeper@gmail.com> Date: Thu, 11 Dec 2025 06:39:24 +0700 Subject: [PATCH 3/4] Pass rangeType prop to DedicatedRelayerPageClient Added the rangeType prop to DedicatedRelayerPageClient and updated its initialization to use the provided range type. This ensures the date range type is correctly set based on the parent component's state. --- .../wallets/dedicated-relayer/components/page-client.tsx | 3 ++- .../(sidebar)/wallets/dedicated-relayer/page.tsx | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/components/page-client.tsx b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/components/page-client.tsx index 02ebd561e39..01574e4acd1 100644 --- a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/components/page-client.tsx +++ b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/components/page-client.tsx @@ -21,6 +21,7 @@ type DedicatedRelayerPageClientProps = { fleetId: string; from: string; to: string; + rangeType: Range["type"]; initialFleet: Fleet | null; }; @@ -35,7 +36,7 @@ export function DedicatedRelayerPageClient( const [dateRange, setDateRange] = useState({ from: new Date(props.from), to: new Date(props.to), - type: "last-7", + type: props.rangeType, }); // Poll for fleet status when not purchased or pending setup diff --git a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/page.tsx b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/page.tsx index 850ac929e90..965edf7695f 100644 --- a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/page.tsx +++ b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/page.tsx @@ -79,6 +79,7 @@ export default async function DedicatedRelayerPage(props: { fleetId={fleetId} from={range.from.toISOString()} to={range.to.toISOString()} + rangeType={range.type} initialFleet={initialFleet} /> ); From 3355d9e6fe9c802f9035a9e4391843337f0cbcdb Mon Sep 17 00:00:00 2001 From: 0xFirekeeper <0xFirekeeper@gmail.com> Date: Thu, 11 Dec 2025 06:46:47 +0700 Subject: [PATCH 4/4] Refactor activity date range with useMemo Replaces repeated date calculations for the activity window with a useMemo hook to optimize performance and ensure consistent date values for fleet transaction queries. --- .../dedicated-relayer/components/page-client.tsx | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/components/page-client.tsx b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/components/page-client.tsx index 01574e4acd1..29c97d80b8b 100644 --- a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/components/page-client.tsx +++ b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/dedicated-relayer/components/page-client.tsx @@ -1,7 +1,7 @@ "use client"; import { subDays } from "date-fns"; -import { useEffect, useState } from "react"; +import { useEffect, useMemo, useState } from "react"; import type { ThirdwebClient } from "thirdweb"; import type { Range } from "@/components/analytics/date-range-selector"; import type { DedicatedRelayerSKU } from "@/types/billing"; @@ -54,13 +54,21 @@ export function DedicatedRelayerPageClient( } }, [fleetStatusQuery.data]); + const activityWindowDates = useMemo(() => { + const now = new Date(); + return { + from: subDays(now, 120).toISOString(), + to: now.toISOString(), + }; + }, []); + // Check for any activity in the last 120 days to determine if we should show the dashboard // This prevents switching back to "pending" state if the user selects a date range with no transactions const hasActivityQuery = useFleetTransactionsSummary({ teamId: props.teamId, fleetId: props.fleetId, - from: subDays(new Date(), 120).toISOString(), - to: new Date().toISOString(), + from: activityWindowDates.from, + to: activityWindowDates.to, enabled: fleetStatus === "active", refetchInterval: 5000, });