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; }; /**