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..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
@@ -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}
/>
@@ -143,7 +156,6 @@ export function DedicatedRelayerActiveState(
Transaction Hash
Chain
Wallet
- Executor
Timestamp
Fee
@@ -244,9 +256,6 @@ function TransactionRow(props: {
-
-
-
@@ -275,9 +284,6 @@ function SkeletonRow() {
-
-
-
@@ -350,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)}
@@ -362,6 +371,7 @@ function ChainFilter(props: {
chainId: number | undefined;
setChainId: (chainId: number | undefined) => void;
client: ThirdwebClient;
+ chainIds: number[];
}) {
return (
@@ -373,6 +383,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..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,9 @@
"use client";
-import { useEffect, useState } from "react";
+import { subDays } from "date-fns";
+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";
import { getAbsoluteUrl } from "@/utils/vercel";
import { useFleetStatus, useFleetTransactionsSummary } from "../lib/hooks";
@@ -19,6 +21,7 @@ type DedicatedRelayerPageClientProps = {
fleetId: string;
from: string;
to: string;
+ rangeType: Range["type"];
initialFleet: Fleet | null;
};
@@ -30,6 +33,12 @@ export function DedicatedRelayerPageClient(
getInitialStatus(props.initialFleet),
);
+ const [dateRange, setDateRange] = useState({
+ from: new Date(props.from),
+ to: new Date(props.to),
+ type: props.rangeType,
+ });
+
// Poll for fleet status when not purchased or pending setup
const fleetStatusQuery = useFleetStatus(
props.teamSlug,
@@ -45,17 +54,26 @@ export function DedicatedRelayerPageClient(
}
}, [fleetStatusQuery.data]);
- // Only fetch transactions summary when we have an active fleet with executors
- const summaryQuery = useFleetTransactionsSummary({
+ 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: props.from,
- to: props.to,
+ from: activityWindowDates.from,
+ to: activityWindowDates.to,
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 +130,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..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
@@ -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");
@@ -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}
/>
);
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;
};
/**