Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down Expand Up @@ -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);
Expand All @@ -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,
Expand All @@ -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 (
<div className={cn("flex flex-col gap-6", props.className)}>
<div className="flex justify-end">
<DateRangeSelector range={range} setRange={setRange} />
</div>

{/* Summary Stats */}
<div className="grid gap-4 md:grid-cols-3">
<StatCard
Expand All @@ -92,9 +106,7 @@ export function DedicatedRelayerActiveState(
/>
<StatCard
title="Active Chains"
value={
summaryQuery.data?.data.transactionsByChain.length.toString() ?? "—"
}
value={summaryQuery.data ? activeChainsCount.toString() : "—"}
isLoading={summaryQuery.isPending}
/>
</div>
Expand Down Expand Up @@ -132,6 +144,7 @@ export function DedicatedRelayerActiveState(
setPage(1);
}}
client={client}
chainIds={fleet.chainIds}
/>
</div>
</div>
Expand All @@ -143,7 +156,6 @@ export function DedicatedRelayerActiveState(
<TableHead>Transaction Hash</TableHead>
<TableHead>Chain</TableHead>
<TableHead>Wallet</TableHead>
<TableHead>Executor</TableHead>
<TableHead>Timestamp</TableHead>
<TableHead>Fee</TableHead>
</TableRow>
Expand Down Expand Up @@ -244,9 +256,6 @@ function TransactionRow(props: {
<TableCell>
<WalletAddress address={transaction.walletAddress} client={client} />
</TableCell>
<TableCell>
<WalletAddress address={transaction.executorAddress} client={client} />
</TableCell>
<TableCell>
<ToolTipLabel hoverable label={format(new Date(utcTimestamp), "PPpp")}>
<span>
Expand Down Expand Up @@ -275,9 +284,6 @@ function SkeletonRow() {
<TableCell>
<Skeleton className="h-7 w-[130px]" />
</TableCell>
<TableCell>
<Skeleton className="h-7 w-[130px]" />
</TableCell>
<TableCell>
<Skeleton className="h-7 w-[80px]" />
</TableCell>
Expand Down Expand Up @@ -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 <span className="font-mono text-sm text-muted-foreground">—</span>;
}
return (
<span className="font-mono text-sm">
${props.usdValue < 0.01 ? "<0.01" : props.usdValue.toFixed(2)}
Expand All @@ -362,6 +371,7 @@ function ChainFilter(props: {
chainId: number | undefined;
setChainId: (chainId: number | undefined) => void;
client: ThirdwebClient;
chainIds: number[];
}) {
return (
<div className="flex bg-background">
Expand All @@ -373,6 +383,7 @@ function ChainFilter(props: {
align="end"
placeholder="All Chains"
className="min-w-[150px]"
chainIds={props.chainIds}
/>
</div>
);
Expand Down
Original file line number Diff line number Diff line change
@@ -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";
Expand All @@ -19,6 +21,7 @@ type DedicatedRelayerPageClientProps = {
fleetId: string;
from: string;
to: string;
rangeType: Range["type"];
initialFleet: Fleet | null;
};

Expand All @@ -30,6 +33,12 @@ export function DedicatedRelayerPageClient(
getInitialStatus(props.initialFleet),
);

const [dateRange, setDateRange] = useState<Range>({
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,
Expand All @@ -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
Expand Down Expand Up @@ -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}
/>
)}
</div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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");
Expand Down Expand Up @@ -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}
/>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
};

/**
Expand Down
Loading