Skip to content

Commit 24f086f

Browse files
feat: inspect call to check jam counter, pagination in ui jamstats
1 parent a350a3a commit 24f086f

6 files changed

Lines changed: 158 additions & 33 deletions

File tree

.gitignore

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,4 +5,7 @@ dist
55
node_modules
66
# Local Netlify folder
77
.netlify
8-
netlify.toml
8+
netlify.toml
9+
10+
# Cron env
11+
cronjob.env

apps/jam-backend/README.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,3 +113,7 @@ jams/<input-jamID-here>
113113
// Get all jam statistics
114114

115115
> body -> jamstats
116+
117+
// Get the next jam ID (useful for knowing the current jam count)
118+
119+
> body -> nextjamid

apps/jam-backend/src/JamManager.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -220,4 +220,8 @@ export default class Jam {
220220

221221
return filteredJams.map(Jam.#mapToJamLite);
222222
};
223+
224+
static getNextJamId = () => {
225+
return Jam.jamIDCounter;
226+
};
223227
}

apps/jam-backend/src/index.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -343,6 +343,12 @@ app.addInspectHandler(async ({ payload }) => {
343343
payload: stringToHex(JSON.stringify(allJamStats)),
344344
});
345345
break;
346+
case "nextjamid":
347+
const nextJamId = Jam.getNextJamId();
348+
await app.createReport({
349+
payload: stringToHex(JSON.stringify({ nextJamId })),
350+
});
351+
break;
346352
default:
347353
throw new Error("Invalid inspect action");
348354
}

apps/jam-ui/src/components/jams/JamsStatsView.tsx

Lines changed: 38 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import {
99
getThemeColor,
1010
Group,
1111
NumberFormatter,
12+
Pagination,
1213
Table,
1314
Text,
1415
Title,
@@ -17,14 +18,16 @@ import {
1718
} from "@mantine/core";
1819

1920
import Link from "next/link";
20-
import { FC, useMemo } from "react";
21+
import { FC, useMemo, useState } from "react";
2122
import { FaEye } from "react-icons/fa";
2223
import { theme } from "../../providers/theme";
2324
import { CenteredLoaderBars } from "../CenteredLoaderBars";
2425
import { CometAlert } from "../CometAlert";
2526
import { useListJamsStats } from "./queries";
2627
import { JamStats } from "./types";
2728

29+
const LEADERBOARD_PAGE_SIZE = 25;
30+
2831
const getBackgroundColourByRank = (rank: number) => {
2932
switch (rank) {
3033
case 0:
@@ -49,42 +52,44 @@ const getTextColourByRank = (rank: number) => {
4952
}
5053
};
5154

52-
const Leaderboard: FC<{ jamStats: JamStats[] }> = ({ jamStats }) => {
55+
const Leaderboard: FC<{ jamStats: JamStats[]; rankOffset?: number }> = ({ jamStats, rankOffset = 0 }) => {
5356
const theme = useMantineTheme();
5457
const shadowConfig = {
5558
boxShadow: `0px 8px 5px -5px ${getThemeColor("grey", theme)}`,
5659
};
5760

5861
const rows = useMemo(
5962
() =>
60-
jamStats.map((stats, idx) => (
63+
jamStats.map((stats, idx) => {
64+
const globalRank = rankOffset + idx;
65+
return (
6166
<Table.Tr
6267
key={idx}
6368
style={{
6469
...shadowConfig,
6570
textAlign: "center",
6671
}}
67-
bg={getBackgroundColourByRank(idx)}
72+
bg={getBackgroundColourByRank(globalRank)}
6873
>
6974
<Table.Td>
7075
<Text
71-
c={getTextColourByRank(idx)}
76+
c={getTextColourByRank(globalRank)}
7277
size="1.5rem"
7378
fw="500"
7479
>
75-
{idx + 1}
80+
{globalRank + 1}
7681
</Text>
7782
</Table.Td>
7883
<Table.Td>
79-
<Text fw="bold" c={getTextColourByRank(idx)}>
84+
<Text fw="bold" c={getTextColourByRank(globalRank)}>
8085
{stats.name}
8186
</Text>
8287
</Table.Td>
8388
<Table.Td>
8489
<Text
8590
fw="bolder"
8691
size="xl"
87-
c={getTextColourByRank(idx)}
92+
c={getTextColourByRank(globalRank)}
8893
>
8994
{stats.totalContributors}
9095
</Text>
@@ -93,7 +98,7 @@ const Leaderboard: FC<{ jamStats: JamStats[] }> = ({ jamStats }) => {
9398
<Text
9499
fw="bolder"
95100
size="xl"
96-
c={getTextColourByRank(idx)}
101+
c={getTextColourByRank(globalRank)}
97102
>
98103
{stats.numTotalMints}
99104
</Text>{" "}
@@ -102,7 +107,7 @@ const Leaderboard: FC<{ jamStats: JamStats[] }> = ({ jamStats }) => {
102107
<Text
103108
fw="bolder"
104109
size="xl"
105-
c={getTextColourByRank(idx)}
110+
c={getTextColourByRank(globalRank)}
106111
>
107112
<NumberFormatter
108113
value={stats.totalMintAmount}
@@ -115,7 +120,7 @@ const Leaderboard: FC<{ jamStats: JamStats[] }> = ({ jamStats }) => {
115120
<Text
116121
fw="bolder"
117122
size="xl"
118-
c={getTextColourByRank(idx)}
123+
c={getTextColourByRank(globalRank)}
119124
>
120125
<NumberFormatter
121126
value={stats.score}
@@ -124,8 +129,9 @@ const Leaderboard: FC<{ jamStats: JamStats[] }> = ({ jamStats }) => {
124129
</Text>
125130
</Table.Td>
126131
</Table.Tr>
127-
)),
128-
[jamStats],
132+
);
133+
}),
134+
[jamStats, rankOffset],
129135
);
130136

131137
return (
@@ -164,14 +170,15 @@ const Leaderboard: FC<{ jamStats: JamStats[] }> = ({ jamStats }) => {
164170
};
165171

166172
const sortByScore = (stats: JamStats[]) =>
167-
stats.sort((a, b) => {
173+
[...stats].sort((a, b) => {
168174
const scoreA = Number(a.score);
169175
const scoreB = Number(b.score);
170176
return scoreA < scoreB ? 1 : scoreA > scoreB ? -1 : 0;
171177
});
172178

173179
export const JamsStatsView: FC = () => {
174180
const { data, isLoading, error } = useListJamsStats();
181+
const [page, setPage] = useState(1);
175182

176183
if (error) console.log(error.message);
177184

@@ -203,13 +210,29 @@ export const JamsStatsView: FC = () => {
203210
</Center>
204211
);
205212

213+
const sortedStats = sortByScore(data);
214+
const total = sortedStats.length;
215+
const totalPages = Math.ceil(total / LEADERBOARD_PAGE_SIZE);
216+
const pageStart = (page - 1) * LEADERBOARD_PAGE_SIZE;
217+
const statsOnPage = sortedStats.slice(pageStart, pageStart + LEADERBOARD_PAGE_SIZE);
218+
206219
return (
207220
<Container
208221
fluid
209222
w={{ base: "95%", sm: "89%", lg: "75%" }}
210223
px={{ base: 3, sm: 8 }}
211224
>
212-
<Leaderboard jamStats={sortByScore(data)} />
225+
<Leaderboard jamStats={statsOnPage} rankOffset={pageStart} />
226+
{totalPages > 1 && (
227+
<Center mt="lg">
228+
<Pagination
229+
total={totalPages}
230+
value={page}
231+
onChange={setPage}
232+
color="haiti"
233+
/>
234+
</Center>
235+
)}
213236
</Container>
214237
);
215238
};

apps/jam-ui/src/components/jams/fetchers.ts

Lines changed: 102 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,19 @@
1-
import { hexToString } from "viem";
1+
import { Hex, hexToString } from "viem";
22
import { inspectUrl } from "../../utils/rollups.inspect";
33
import { InspectResponseBody, Report } from "../../utils/rollups.types";
44
import { Jam, JamListFilter, JamLite, JamStats } from "./types";
55

6+
const safeHexToString = (hex: Hex | undefined): string => {
7+
if (!hex) return "Unknown error";
8+
try {
9+
return hexToString(hex);
10+
} catch {
11+
return "Failed to decode error message";
12+
}
13+
};
14+
615
const parseReport = <T>(report: Report, defaultValue: any): T => {
7-
if (report) {
16+
if (report && report.payload) {
817
try {
918
return JSON.parse(hexToString(report.payload)) as T;
1019
} catch (error: any) {
@@ -39,10 +48,26 @@ export const fetchJams = async (filter: JamListFilter = "all") => {
3948

4049
const data = (await response.json()) as InspectResponseBody;
4150

42-
if (data.status === "Exception")
43-
return createError(hexToString(data.exception_payload));
51+
if (data.status === "Exception") {
52+
let errorMsg = "Unknown exception";
53+
if (data.exception_payload) {
54+
errorMsg = safeHexToString(data.exception_payload);
55+
} else if (data.reports && data.reports.length > 0 && data.reports[0]?.payload) {
56+
errorMsg = safeHexToString(data.reports[0].payload);
57+
}
58+
return createError(errorMsg);
59+
}
60+
61+
if (!data.reports || data.reports.length === 0) {
62+
return createError("No reports returned from jams inspect");
63+
}
64+
65+
const report = data.reports[0];
66+
if (!report || !report.payload) {
67+
return createError("Invalid report structure from jams inspect");
68+
}
4469

45-
const result = parseReport<JamLite[]>(data.reports[0], []);
70+
const result = parseReport<JamLite[]>(report, []);
4671

4772
return result;
4873
};
@@ -65,31 +90,91 @@ export const fetchJamById = async (id: number) => {
6590

6691
const data = (await response.json()) as InspectResponseBody;
6792

68-
if (data.status === "Exception")
69-
return createError(hexToString(data.exception_payload));
93+
if (data.status === "Exception") {
94+
let errorMsg = "Unknown exception";
95+
if (data.exception_payload) {
96+
errorMsg = safeHexToString(data.exception_payload);
97+
} else if (data.reports && data.reports.length > 0 && data.reports[0]?.payload) {
98+
errorMsg = safeHexToString(data.reports[0].payload);
99+
}
100+
return createError(errorMsg);
101+
}
102+
103+
if (!data.reports || data.reports.length === 0) {
104+
return createError("No reports returned from jam detail inspect");
105+
}
70106

71-
const result = parseReport<Jam>(data.reports[0], null);
107+
const report = data.reports[0];
108+
if (!report || !report.payload) {
109+
return createError("Invalid report structure from jam detail inspect");
110+
}
111+
112+
const result = parseReport<Jam>(report, null);
72113
return result;
73114
};
74115

75116
export const fetchJamsStats = async () => {
76117
const payload = 'jamstats';
77118
const payloadBlob = new TextEncoder().encode(payload);
78-
const response = await fetch(inspectUrl, {
79-
method: 'POST',
80-
body: payloadBlob,
81-
});
119+
120+
console.log("Fetching jamstats from:", inspectUrl);
121+
122+
if (!inspectUrl || inspectUrl.includes("undefined")) {
123+
return createError("Invalid inspect URL - check environment variables");
124+
}
125+
126+
let response: Response;
127+
try {
128+
response = await fetch(inspectUrl, {
129+
method: 'POST',
130+
body: payloadBlob,
131+
});
132+
} catch (error: any) {
133+
console.error("Fetch error for jamstats:", error);
134+
return createError(`Network error: ${error.message || "Failed to connect"}`);
135+
}
82136

83137
if (!response.ok) {
84138
return createError(`Network response error - ${response.status}`);
85139
}
86140

87-
const data = (await response.json()) as InspectResponseBody;
141+
let data: InspectResponseBody;
142+
try {
143+
data = (await response.json()) as InspectResponseBody;
144+
} catch (error: any) {
145+
console.error("JSON parse error for jamstats:", error);
146+
return createError(`Failed to parse response: ${error.message}`);
147+
}
148+
149+
console.log("jamstats response status:", data.status, "reports count:", data.reports?.length);
88150

89-
if (data.status === "Exception")
90-
return createError(hexToString(data.exception_payload));
151+
// Try to parse report data first, even if status is Exception
152+
// (backend may return Exception status but still have valid data in reports)
153+
if (data.reports && data.reports.length > 0 && data.reports[0]?.payload) {
154+
const report = data.reports[0];
155+
try {
156+
const result = parseReport<JamStats[]>(report, []);
157+
// If we got a valid array, return it regardless of status
158+
if (Array.isArray(result)) {
159+
console.log("jamstats parsed successfully, count:", result.length);
160+
return result;
161+
}
162+
} catch (e) {
163+
console.warn("Failed to parse jamstats report as data, treating as error");
164+
}
165+
}
91166

92-
const result = parseReport<JamStats[]>(data.reports[0], []);
167+
// If we get here with Exception status, it's a real error
168+
if (data.status === "Exception") {
169+
let errorMsg = "Unknown exception";
170+
if (data.exception_payload) {
171+
errorMsg = safeHexToString(data.exception_payload);
172+
} else if (data.reports && data.reports.length > 0 && data.reports[0]?.payload) {
173+
errorMsg = safeHexToString(data.reports[0].payload);
174+
}
175+
console.error("jamstats exception:", errorMsg);
176+
return createError(errorMsg);
177+
}
93178

94-
return result;
179+
return createError("No valid data returned from jamstats inspect");
95180
};

0 commit comments

Comments
 (0)