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
@@ -1,45 +1,95 @@
import { ResponsiveSearchParamsProvider } from "responsive-rsc";
"use client";
import { useQuery } from "@tanstack/react-query";
import { Spinner } from "@workspace/ui/components/spinner";
import type { ThirdwebClient } from "thirdweb";
import type { Project } from "@/api/project/projects";
import { UnifiedTransactionsTable } from "../components/transactions-table.client";
import { getTransactionAnalyticsSummary } from "../lib/analytics-summary.client";
import type { Wallet } from "../server-wallets/wallet-table/types";
import { TransactionAnalyticsFilter } from "./filter";
import { TransactionsChartCard } from "./tx-chart/tx-chart";
import type { SolanaWallet } from "../solana-wallets/wallet-table/types";
import { EngineChecklist } from "./ftux.client";
import { TransactionAnalyticsSummary } from "./summary";
import { TransactionsAnalytics } from "./tx-chart/tx-chart";

export function TransactionsAnalyticsPageContent(props: {
searchParams: {
from?: string | undefined | string[];
to?: string | undefined | string[];
interval?: string | undefined | string[];
};
project: Project;
showAnalytics: boolean;
wallets?: Wallet[];
wallets: Wallet[];
teamSlug: string;
client: ThirdwebClient;
authToken: string;
teamId: string;
isManagedVault: boolean;
testTxWithWallet: string | undefined;
testSolanaTxWithWallet: string | undefined;
solanaWallets: SolanaWallet[];
}) {
return (
<ResponsiveSearchParamsProvider value={props.searchParams}>
<div className="flex grow flex-col gap-6">
{props.showAnalytics && (
<>
<div className="flex justify-end">
<TransactionAnalyticsFilter />
</div>
<TransactionsChartCard
project={props.project}
searchParams={props.searchParams}
teamSlug={props.teamSlug}
wallets={props.wallets ?? []}
/>
</>
)}
<UnifiedTransactionsTable
client={props.client}
project={props.project}
teamSlug={props.teamSlug}
/>
const engineTxSummaryQuery = useQuery({
queryKey: [
"engine-tx-analytics-summary",
props.teamId,
props.project.publishableKey,
props.authToken,
],
queryFn: async () => {
const data = await getTransactionAnalyticsSummary({
clientId: props.project.publishableKey,
teamId: props.teamId,
authToken: props.authToken,
});
return data;
},
refetchOnWindowFocus: false,
refetchOnMount: false,
});
Comment on lines +27 to +44
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion | 🟠 Major

Remove authToken from the query key and add staleTime.

The queryKey currently includes authToken (a secret), which should never be part of the cache key. Only use non-secret identifiers like teamId and publishableKey. Additionally, add staleTime: 60_000 (≥ 60s) per coding guidelines. Based on learnings.

Apply this diff:

   const engineTxSummaryQuery = useQuery({
     queryKey: [
       "engine-tx-analytics-summary",
       props.teamId,
       props.project.publishableKey,
-      props.authToken,
     ],
+    staleTime: 60_000,
     queryFn: async () => {
       const data = await getTransactionAnalyticsSummary({
         clientId: props.project.publishableKey,
         teamId: props.teamId,
         authToken: props.authToken,
       });
       return data;
     },
     refetchOnWindowFocus: false,
     refetchOnMount: false,
   });
🤖 Prompt for AI Agents
In
apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/transactions/analytics/analytics-page.tsx
around lines 27 to 44, the react-query key currently includes the secret
authToken and lacks a staleTime; remove props.authToken from the queryKey so
only non-secret identifiers remain (e.g., "engine-tx-analytics-summary",
props.teamId, props.project.publishableKey) and add staleTime: 60_000 to the
useQuery options; keep refetchOnWindowFocus and refetchOnMount as-is.


if (engineTxSummaryQuery.isPending) {
return (
<div className="flex h-[642px] grow items-center justify-center bg-card rounded-xl border">
<Spinner className="size-10" />
</div>
</ResponsiveSearchParamsProvider>
);
}

const hasTransactions = engineTxSummaryQuery.data
? engineTxSummaryQuery.data.totalCount > 0
: false;

return (
<div className="flex grow flex-col gap-10">
<EngineChecklist
isManagedVault={props.isManagedVault}
client={props.client}
hasTransactions={hasTransactions}
project={props.project}
teamSlug={props.teamSlug}
testTxWithWallet={props.testTxWithWallet}
testSolanaTxWithWallet={props.testSolanaTxWithWallet}
wallets={props.wallets}
solanaWallets={props.solanaWallets}
/>

{props.showAnalytics && hasTransactions && (
<div className="flex flex-col gap-6">
<TransactionAnalyticsSummary
clientId={props.project.publishableKey}
teamId={props.project.teamId}
initialData={engineTxSummaryQuery.data}
/>
<TransactionsAnalytics
project={props.project}
authToken={props.authToken}
teamSlug={props.teamSlug}
wallets={props.wallets ?? []}
/>
</div>
)}

<UnifiedTransactionsTable
client={props.client}
project={props.project}
teamSlug={props.teamSlug}
/>
</div>
);
}

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { ActivityIcon, CoinsIcon } from "lucide-react";
import { toEther } from "thirdweb/utils";
import { StatCard } from "@/components/analytics/stat"; // Assuming correct path
import type { TransactionSummaryData } from "../lib/analytics";
import type { TransactionSummaryData } from "../lib/analytics-summary.client";

// Renders the UI based on fetched data or pending state
function TransactionAnalyticsSummaryUI(props: {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
"use client";
import { NEXT_PUBLIC_ENGINE_CLOUD_URL } from "@/constants/public-envs";
import type { TransactionStats } from "@/types/analytics";

export async function getTransactionsChartData({
teamId,
clientId,
from,
to,
interval,
authToken,
}: {
teamId: string;
clientId: string;
from: string;
to: string;
interval: "day" | "week";
authToken: string;
}): Promise<TransactionStats[]> {
const filters = {
endDate: to,
resolution: interval,
startDate: from,
};

const response = await fetch(
`${NEXT_PUBLIC_ENGINE_CLOUD_URL}/v1/transactions/analytics`,
{
body: JSON.stringify(filters),
headers: {
Authorization: `Bearer ${authToken}`,
"Content-Type": "application/json",
"x-client-id": clientId,
"x-team-id": teamId,
},
method: "POST",
},
);

if (!response.ok) {
if (response.status === 401 || response.status === 400) {
return [];
}

// TODO - need to handle this error state, like we do with the connect charts
throw new Error(
`Error fetching transactions chart data: ${response.status} ${
response.statusText
} - ${await response.text().catch(() => "Unknown error")}`,
);
}

type TransactionsChartResponse = {
result: {
analytics: Array<{
timeBucket: string;
chainId: string;
count: number;
}>;
metadata: {
resolution: string;
startDate: string;
endDate: string;
};
};
};

const data = (await response.json()) as TransactionsChartResponse;

return data.result.analytics.map((stat) => ({
chainId: Number(stat.chainId),
count: stat.count,
date: stat.timeBucket,
}));
}
Original file line number Diff line number Diff line change
Expand Up @@ -108,10 +108,10 @@ export function TransactionsChartCardUI(props: {
customHeader={
<div className="relative px-6 pt-6">
<h3 className="mb-0.5 font-semibold text-xl tracking-tight">
Daily Transactions
Transactions
</h3>
<p className="text-muted-foreground text-sm">
Amount of daily transactions by chain.
transactions broken down by chain
</p>

<div className="top-6 right-6 mb-8 grid grid-cols-2 items-center gap-2 md:absolute md:mb-0 md:flex">
Expand Down Expand Up @@ -194,13 +194,7 @@ function EmptyChartContent(props: {
</div>
</>
) : (
<p className="flex items-center gap-2 rounded-full border bg-background px-3.5 py-1.5 text-sm">
<span className="!pointer-events-auto relative flex size-2">
<span className="absolute inline-flex h-full w-full animate-ping rounded-full bg-sky-400 opacity-75" />
<span className="relative inline-flex size-2 rounded-full bg-primary" />
</span>
Waiting for transactions...
</p>
<p className="text-sm text-muted-foreground">No transactions found</p>
)}
</div>
);
Expand Down
Loading
Loading