Skip to content

Commit 9eced22

Browse files
d4mrjoaquim-verges
authored andcommitted
add summary analytics
1 parent 260211a commit 9eced22

File tree

2 files changed

+147
-36
lines changed
  • apps/dashboard/src/app/team/[team_slug]/[project_slug]/transactions

2 files changed

+147
-36
lines changed

apps/dashboard/src/app/team/[team_slug]/[project_slug]/transactions/analytics/summary.tsx

Lines changed: 146 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -1,72 +1,183 @@
1-
import { StatCard } from "components/analytics/stat";
1+
import { THIRDWEB_ENGINE_CLOUD_URL } from "@/constants/env";
2+
import { getAuthToken } from "app/api/lib/getAuthToken";
3+
import { StatCard } from "components/analytics/stat"; // Assuming correct path
24
import { ActivityIcon, CoinsIcon } from "lucide-react";
35
import { Suspense } from "react";
6+
// Import the specific utility function needed
7+
import { toEther } from "thirdweb/utils";
48

5-
// TODO: implement this
9+
// Define the structure of the data we expect back from our fetch function
10+
type TransactionSummaryData = {
11+
totalCount: number;
12+
totalGasCostWei: string;
13+
totalGasUnitsUsed: string; // Keep fetched data structure
14+
};
15+
16+
// Define the structure of the API response
17+
type AnalyticsSummaryApiResponse = {
18+
result: {
19+
summary: {
20+
totalCount: number;
21+
totalGasCostWei: string;
22+
totalGasUnitsUsed: string;
23+
};
24+
metadata: {
25+
startDate?: string;
26+
endDate?: string;
27+
};
28+
};
29+
};
30+
31+
// Fetches data from the /analytics-summary endpoint
632
async function getTransactionAnalyticsSummary(props: {
733
teamId: string;
8-
projectId: string;
9-
}) {
10-
console.log("getTransactionAnalyticsSummary called with", props);
11-
await new Promise((resolve) => setTimeout(resolve, 1000));
12-
13-
return {
14-
foo: 100,
15-
bar: 200,
34+
clientId: string;
35+
}): Promise<TransactionSummaryData> {
36+
const authToken = await getAuthToken();
37+
const body = {};
38+
const defaultData: TransactionSummaryData = {
39+
totalCount: 0,
40+
totalGasCostWei: "0",
41+
totalGasUnitsUsed: "0",
1642
};
43+
44+
try {
45+
const response = await fetch(
46+
`${THIRDWEB_ENGINE_CLOUD_URL}/project/transactions/analytics-summary`,
47+
{
48+
method: "POST",
49+
headers: {
50+
"Content-Type": "application/json",
51+
"x-team-id": props.teamId,
52+
"x-client-id": props.clientId,
53+
Authorization: `Bearer ${authToken}`,
54+
},
55+
body: JSON.stringify(body),
56+
},
57+
);
58+
59+
if (!response.ok) {
60+
if (response.status === 401) {
61+
console.error("Unauthorized fetching transaction summary");
62+
return defaultData;
63+
}
64+
const errorText = await response.text();
65+
throw new Error(
66+
`Error fetching transaction summary: ${response.status} ${response.statusText} - ${errorText}`,
67+
);
68+
}
69+
70+
const data = (await response.json()) as AnalyticsSummaryApiResponse;
71+
72+
return {
73+
totalCount: data.result.summary.totalCount ?? 0,
74+
totalGasCostWei: data.result.summary.totalGasCostWei ?? "0",
75+
totalGasUnitsUsed: data.result.summary.totalGasUnitsUsed ?? "0",
76+
};
77+
} catch (error) {
78+
console.error("Failed to fetch transaction summary:", error);
79+
return defaultData;
80+
}
1781
}
1882

19-
// TODO: rename props, change labels and icons
83+
// Renders the UI based on fetched data or pending state
2084
function TransactionAnalyticsSummaryUI(props: {
21-
data:
22-
| {
23-
foo: number;
24-
bar: number;
25-
}
26-
| undefined;
85+
data: TransactionSummaryData | undefined;
2786
isPending: boolean;
2887
}) {
88+
// Formatter function specifically for the StatCard prop
89+
// Typed to accept number for StatCard's prop type, but receives the string via `as any`
90+
const parseTotalGasCost = (valueFromCard: string): number => {
91+
// At runtime, valueFromCard is the string passed via `as any` if data exists,
92+
// or potentially the fallback number (like 0) if data doesn't exist.
93+
// We prioritize the actual string from props.data if available.
94+
const weiString = props.data?.totalGasCostWei ?? "0";
95+
96+
// Check if the effective value is zero
97+
if (weiString === "0") {
98+
return 0;
99+
}
100+
101+
try {
102+
// Convert the definitive wei string to BigInt
103+
const weiBigInt = BigInt(weiString);
104+
// Use the imported toEther function
105+
return Number.parseFloat(toEther(weiBigInt));
106+
} catch (e) {
107+
// Catch potential errors during BigInt conversion or formatting
108+
console.error("Error formatting wei value:", weiString, e);
109+
// Check if the value passed from card was actually the fallback number
110+
if (typeof valueFromCard === "number" && valueFromCard === 0) {
111+
return 0; // If fallback 0 was passed, display 0
112+
}
113+
}
114+
return 0;
115+
};
116+
117+
// NOTE: props.data?.totalGasUnitsUsed is fetched but not currently displayed.
118+
29119
return (
30-
<div className="grid grid-cols-2 gap-4">
120+
<div className="grid grid-cols-1 gap-4 md:grid-cols-2">
31121
<StatCard
32-
label="Foo"
33-
value={props.data?.foo}
122+
label="Total Transactions"
123+
// Value is a number, standard formatter works
124+
value={props.data?.totalCount}
34125
icon={ActivityIcon}
35126
isPending={props.isPending}
36127
/>
37128
<StatCard
38-
label="Bar"
39-
value={props.data?.bar}
40-
icon={CoinsIcon}
41-
formatter={(value: number) =>
42-
new Intl.NumberFormat("en-US", {
43-
style: "currency",
44-
currency: "USD",
45-
}).format(value)
129+
label="Total Gas Cost (ETH)"
130+
// If pending, value doesn't matter much.
131+
// If not pending, pass the wei string `as any` if data exists, otherwise pass 0.
132+
// Passing 0 ensures StatCard receives a number if data is missing post-loading.
133+
value={
134+
props.isPending
135+
? undefined
136+
: parseTotalGasCost(props.data?.totalGasCostWei ?? "0")
46137
}
138+
formatter={(v: number) => v.toFixed(12)}
139+
icon={CoinsIcon}
140+
// Pass the formatter that handles the type juggling
47141
isPending={props.isPending}
48142
/>
143+
{/*
144+
// Example of how totalGasUnitsUsed could be added later:
145+
<StatCard
146+
label="Total Gas Units Used"
147+
value={(props.isPending ? undefined : (props.data?.totalGasUnitsUsed ?? 0)) as any} // Pass string as any
148+
icon={SomeOtherIcon}
149+
isPending={props.isPending}
150+
formatter={(value: number) => { // Formatter receives string via `as any`
151+
const unitString = props.data?.totalGasUnitsUsed ?? '0';
152+
if (unitString === '0') return '0';
153+
try {
154+
return new Intl.NumberFormat("en-US").format(BigInt(unitString));
155+
} catch {
156+
return "Error";
157+
}
158+
}}
159+
/>
160+
*/}
49161
</div>
50162
);
51163
}
52164

53-
// fetches data and renders the UI
165+
// Fetches data and renders the UI component
54166
async function AsyncTransactionsAnalyticsSummary(props: {
55167
teamId: string;
56-
projectId: string;
168+
clientId: string;
57169
}) {
58170
const data = await getTransactionAnalyticsSummary({
59171
teamId: props.teamId,
60-
projectId: props.projectId,
172+
clientId: props.clientId,
61173
});
62-
63174
return <TransactionAnalyticsSummaryUI data={data} isPending={false} />;
64175
}
65176

66-
// shows loading state while fetching data
177+
// Main component: Shows loading state (Suspense fallback) while fetching data
67178
export function TransactionAnalyticsSummary(props: {
68179
teamId: string;
69-
projectId: string;
180+
clientId: string;
70181
}) {
71182
return (
72183
<Suspense
@@ -76,7 +187,7 @@ export function TransactionAnalyticsSummary(props: {
76187
>
77188
<AsyncTransactionsAnalyticsSummary
78189
teamId={props.teamId}
79-
projectId={props.projectId}
190+
clientId={props.clientId}
80191
/>
81192
</Suspense>
82193
);

apps/dashboard/src/app/team/[team_slug]/[project_slug]/transactions/page.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ export default async function TransactionsAnalyticsPage(props: {
4040
<div className="flex grow flex-col">
4141
<TransactionAnalyticsSummary
4242
teamId={project.teamId}
43-
projectId={project.id}
43+
clientId={project.publishableKey}
4444
/>
4545
<div className="h-10" />
4646
<TransactionsAnalyticsPageContent

0 commit comments

Comments
 (0)