From 6c01be3a40b6e95468894e9c41d13c90a7463b6f Mon Sep 17 00:00:00 2001 From: Jack-Khuu Date: Tue, 10 Feb 2026 17:00:26 -0800 Subject: [PATCH 1/3] Update UserTrendChart to user if logged in --- .../leaderboard/components/UserTrendChart.tsx | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/frontend/src/pages/leaderboard/components/UserTrendChart.tsx b/frontend/src/pages/leaderboard/components/UserTrendChart.tsx index 94c9efd..a24dcd8 100644 --- a/frontend/src/pages/leaderboard/components/UserTrendChart.tsx +++ b/frontend/src/pages/leaderboard/components/UserTrendChart.tsx @@ -23,6 +23,7 @@ import { formatMicroseconds, } from "../../../lib/utils/ranking"; import { useThemeStore } from "../../../lib/store/themeStore"; +import { useAuthStore } from "../../../lib/store/authStore"; interface UserTrendChartProps { leaderboardId: string; @@ -50,11 +51,13 @@ export default function UserTrendChart({ leaderboardId }: UserTrendChartProps) { const resolvedMode = useThemeStore((state) => state.resolvedMode); const isDark = resolvedMode === "dark"; const textColor = isDark ? "#e0e0e0" : "#333"; + const me = useAuthStore((s) => s.me); const [selectedUsers, setSelectedUsers] = useState([]); const [userOptions, setUserOptions] = useState([]); const [searchLoading, setSearchLoading] = useState(false); const [inputValue, setInputValue] = useState(""); + const [initializedWithMe, setInitializedWithMe] = useState(false); const loadData = useCallback( async (userIds: string[]) => { @@ -98,6 +101,21 @@ export default function UserTrendChart({ leaderboardId }: UserTrendChartProps) { loadInitialUsers(); }, [leaderboardId]); + // Pre-select the logged-in user if authenticated + useEffect(() => { + if (initializedWithMe) return; + if (!me?.authenticated || !me?.user?.id) return; + + const meAsUser: UserSearchResult = { + user_id: me.user.id, + username: me.user.display_name || me.user.id, + }; + + setSelectedUsers([meAsUser]); + loadData([me.user.id]); + setInitializedWithMe(true); + }, [me, initializedWithMe, loadData]); + // Search users when input changes useEffect(() => { const searchTimeout = setTimeout(async () => { From 1885b62f2e4d658322032ee96b049f46220c044b Mon Sep 17 00:00:00 2001 From: Jack-Khuu Date: Wed, 11 Feb 2026 12:51:59 -0800 Subject: [PATCH 2/3] Default to the strongest submission from the most popular GPU --- .../src/pages/leaderboard/Leaderboard.tsx | 60 ++++++++++++++++++- .../leaderboard/components/UserTrendChart.tsx | 45 ++++++++------ 2 files changed, 86 insertions(+), 19 deletions(-) diff --git a/frontend/src/pages/leaderboard/Leaderboard.tsx b/frontend/src/pages/leaderboard/Leaderboard.tsx index 7e55744..d4be09a 100644 --- a/frontend/src/pages/leaderboard/Leaderboard.tsx +++ b/frontend/src/pages/leaderboard/Leaderboard.tsx @@ -10,7 +10,7 @@ import { } from "@mui/material"; import Grid from "@mui/material/Grid"; import { useEffect, useState, useMemo } from "react"; -import { fetchLeaderBoard } from "../../api/api"; +import { fetchLeaderBoard, searchUsers } from "../../api/api"; import { fetcherApiCallback } from "../../lib/hooks/useApi"; import { isExpired, toDateUtc } from "../../lib/date/utils"; import RankingsList from "./components/RankingLists"; @@ -70,6 +70,13 @@ export default function Leaderboard() { const isAuthed = !!(me && me.authenticated); const userId = me?.user?.identity ?? null; + // State for top user (strongest submission) and default GPU type + const [topUser, setTopUser] = useState<{ + userId: string; + username: string; + } | null>(null); + const [defaultGpuType, setDefaultGpuType] = useState(null); + // Sync tab state with query parameter const [searchParams, setSearchParams] = useSearchParams(); @@ -109,6 +116,55 @@ export default function Leaderboard() { if (id) call(id); }, [id]); + // Fetch top user (strongest submission) when rankings are available + // Select from the GPU with the most unique users + useEffect(() => { + const findTopUser = async () => { + if (!id || !data?.rankings) return; + + const gpuTypes = Object.keys(data.rankings); + if (gpuTypes.length === 0) return; + + // Find the GPU type with the most unique users + let maxUsers = 0; + let bestGpu = gpuTypes[0]; + for (const gpuType of gpuTypes) { + const rankings = data.rankings[gpuType]; + const userCount = rankings ? rankings.length : 0; + if (userCount > maxUsers) { + maxUsers = userCount; + bestGpu = gpuType; + } + } + + // Set the default GPU type to the one with most users + setDefaultGpuType(bestGpu); + + const bestGpuRankings = data.rankings[bestGpu]; + if (!bestGpuRankings || bestGpuRankings.length === 0) return; + + // The first item is the top user (sorted by score ascending) + const topUserName = bestGpuRankings[0].user_name; + if (!topUserName) return; + + try { + // Search for the user by username to get their user_id + const result = await searchUsers(id, topUserName, 1); + if (result.users && result.users.length > 0) { + const foundUser = result.users[0]; + setTopUser({ + userId: foundUser.user_id, + username: foundUser.username, + }); + } + } catch (err) { + console.error("Failed to fetch top user:", err); + } + }; + + findTopUser(); + }, [id, data?.rankings]); + if (loading) return ; if (error) return ; @@ -267,7 +323,7 @@ export default function Leaderboard() { User Performance Trend - + diff --git a/frontend/src/pages/leaderboard/components/UserTrendChart.tsx b/frontend/src/pages/leaderboard/components/UserTrendChart.tsx index a24dcd8..a395d35 100644 --- a/frontend/src/pages/leaderboard/components/UserTrendChart.tsx +++ b/frontend/src/pages/leaderboard/components/UserTrendChart.tsx @@ -23,10 +23,11 @@ import { formatMicroseconds, } from "../../../lib/utils/ranking"; import { useThemeStore } from "../../../lib/store/themeStore"; -import { useAuthStore } from "../../../lib/store/authStore"; interface UserTrendChartProps { leaderboardId: string; + topUser?: { userId: string; username: string } | null; + defaultGpuType?: string | null; } function hashStringToColor(str: string): string { @@ -43,21 +44,20 @@ function hashStringToColor(str: string): string { return `hsl(${hue}, ${saturation}%, ${lightness}%)`; } -export default function UserTrendChart({ leaderboardId }: UserTrendChartProps) { +export default function UserTrendChart({ leaderboardId, topUser, defaultGpuType }: UserTrendChartProps) { const [data, setData] = useState(null); const [loading, setLoading] = useState(false); const [error, setError] = useState(null); - const [selectedGpuType, setSelectedGpuType] = useState(""); + const [selectedGpuType, setSelectedGpuType] = useState(defaultGpuType || ""); const resolvedMode = useThemeStore((state) => state.resolvedMode); const isDark = resolvedMode === "dark"; const textColor = isDark ? "#e0e0e0" : "#333"; - const me = useAuthStore((s) => s.me); const [selectedUsers, setSelectedUsers] = useState([]); const [userOptions, setUserOptions] = useState([]); const [searchLoading, setSearchLoading] = useState(false); const [inputValue, setInputValue] = useState(""); - const [initializedWithMe, setInitializedWithMe] = useState(false); + const [initializedWithTopUser, setInitializedWithTopUser] = useState(false); const loadData = useCallback( async (userIds: string[]) => { @@ -71,10 +71,21 @@ export default function UserTrendChart({ leaderboardId }: UserTrendChartProps) { try { const result = await fetchUserTrend(leaderboardId, userIds); setData(result); - // Set default GPU type to the first available + // Set default GPU type to the one with the most unique users const gpuTypes = Object.keys(result.time_series || {}); if (gpuTypes.length > 0 && !gpuTypes.includes(selectedGpuType)) { - setSelectedGpuType(gpuTypes[0]); + // Count unique users per GPU type + let maxUsers = 0; + let defaultGpu = gpuTypes[0]; + for (const gpuType of gpuTypes) { + const gpuData = result.time_series[gpuType]; + const userCount = gpuData ? Object.keys(gpuData).length : 0; + if (userCount > maxUsers) { + maxUsers = userCount; + defaultGpu = gpuType; + } + } + setSelectedGpuType(defaultGpu); } } catch (err: any) { setError(err.message || "Failed to load data"); @@ -101,20 +112,20 @@ export default function UserTrendChart({ leaderboardId }: UserTrendChartProps) { loadInitialUsers(); }, [leaderboardId]); - // Pre-select the logged-in user if authenticated + // Pre-select the top user if provided useEffect(() => { - if (initializedWithMe) return; - if (!me?.authenticated || !me?.user?.id) return; + if (initializedWithTopUser) return; + if (!topUser?.userId) return; - const meAsUser: UserSearchResult = { - user_id: me.user.id, - username: me.user.display_name || me.user.id, + const topUserAsSearchResult: UserSearchResult = { + user_id: topUser.userId, + username: topUser.username, }; - setSelectedUsers([meAsUser]); - loadData([me.user.id]); - setInitializedWithMe(true); - }, [me, initializedWithMe, loadData]); + setSelectedUsers([topUserAsSearchResult]); + loadData([topUser.userId]); + setInitializedWithTopUser(true); + }, [topUser, initializedWithTopUser, loadData]); // Search users when input changes useEffect(() => { From 0db2d4c09c19c8fb2380579c61e42574fda5caa2 Mon Sep 17 00:00:00 2001 From: Jack-Khuu Date: Wed, 11 Feb 2026 15:11:55 -0800 Subject: [PATCH 3/3] Address comments --- .../src/pages/leaderboard/Leaderboard.tsx | 26 ++++++++----------- .../leaderboard/components/UserTrendChart.tsx | 23 +++++++--------- 2 files changed, 21 insertions(+), 28 deletions(-) diff --git a/frontend/src/pages/leaderboard/Leaderboard.tsx b/frontend/src/pages/leaderboard/Leaderboard.tsx index d4be09a..2733713 100644 --- a/frontend/src/pages/leaderboard/Leaderboard.tsx +++ b/frontend/src/pages/leaderboard/Leaderboard.tsx @@ -71,7 +71,7 @@ export default function Leaderboard() { const userId = me?.user?.identity ?? null; // State for top user (strongest submission) and default GPU type - const [topUser, setTopUser] = useState<{ + const [defaultUser, setDefaultUser] = useState<{ userId: string; username: string; } | null>(null); @@ -126,25 +126,21 @@ export default function Leaderboard() { if (gpuTypes.length === 0) return; // Find the GPU type with the most unique users - let maxUsers = 0; - let bestGpu = gpuTypes[0]; - for (const gpuType of gpuTypes) { + const mostActiveGpu = gpuTypes.reduce((currentMax, gpuType) => { const rankings = data.rankings[gpuType]; const userCount = rankings ? rankings.length : 0; - if (userCount > maxUsers) { - maxUsers = userCount; - bestGpu = gpuType; - } - } + const maxCount = data.rankings[currentMax]?.length ?? 0; + return userCount > maxCount ? gpuType : currentMax; + }, gpuTypes[0]); // Set the default GPU type to the one with most users - setDefaultGpuType(bestGpu); + setDefaultGpuType(mostActiveGpu); - const bestGpuRankings = data.rankings[bestGpu]; - if (!bestGpuRankings || bestGpuRankings.length === 0) return; + const mostActiveGpuRankings = data.rankings[mostActiveGpu]; + if (!mostActiveGpuRankings || mostActiveGpuRankings.length === 0) return; // The first item is the top user (sorted by score ascending) - const topUserName = bestGpuRankings[0].user_name; + const topUserName = mostActiveGpuRankings[0].user_name; if (!topUserName) return; try { @@ -152,7 +148,7 @@ export default function Leaderboard() { const result = await searchUsers(id, topUserName, 1); if (result.users && result.users.length > 0) { const foundUser = result.users[0]; - setTopUser({ + setDefaultUser({ userId: foundUser.user_id, username: foundUser.username, }); @@ -323,7 +319,7 @@ export default function Leaderboard() { User Performance Trend - + diff --git a/frontend/src/pages/leaderboard/components/UserTrendChart.tsx b/frontend/src/pages/leaderboard/components/UserTrendChart.tsx index a395d35..e552feb 100644 --- a/frontend/src/pages/leaderboard/components/UserTrendChart.tsx +++ b/frontend/src/pages/leaderboard/components/UserTrendChart.tsx @@ -26,7 +26,7 @@ import { useThemeStore } from "../../../lib/store/themeStore"; interface UserTrendChartProps { leaderboardId: string; - topUser?: { userId: string; username: string } | null; + defaultUser?: { userId: string; username: string } | null; defaultGpuType?: string | null; } @@ -44,7 +44,7 @@ function hashStringToColor(str: string): string { return `hsl(${hue}, ${saturation}%, ${lightness}%)`; } -export default function UserTrendChart({ leaderboardId, topUser, defaultGpuType }: UserTrendChartProps) { +export default function UserTrendChart({ leaderboardId, defaultUser, defaultGpuType }: UserTrendChartProps) { const [data, setData] = useState(null); const [loading, setLoading] = useState(false); const [error, setError] = useState(null); @@ -57,7 +57,6 @@ export default function UserTrendChart({ leaderboardId, topUser, defaultGpuType const [userOptions, setUserOptions] = useState([]); const [searchLoading, setSearchLoading] = useState(false); const [inputValue, setInputValue] = useState(""); - const [initializedWithTopUser, setInitializedWithTopUser] = useState(false); const loadData = useCallback( async (userIds: string[]) => { @@ -112,20 +111,18 @@ export default function UserTrendChart({ leaderboardId, topUser, defaultGpuType loadInitialUsers(); }, [leaderboardId]); - // Pre-select the top user if provided + // Pre-select the default user if provided useEffect(() => { - if (initializedWithTopUser) return; - if (!topUser?.userId) return; + if (!defaultUser?.userId) return; - const topUserAsSearchResult: UserSearchResult = { - user_id: topUser.userId, - username: topUser.username, + const defaultUserAsSearchResult: UserSearchResult = { + user_id: defaultUser.userId, + username: defaultUser.username, }; - setSelectedUsers([topUserAsSearchResult]); - loadData([topUser.userId]); - setInitializedWithTopUser(true); - }, [topUser, initializedWithTopUser, loadData]); + setSelectedUsers([defaultUserAsSearchResult]); + loadData([defaultUser.userId]); + }, [defaultUser, loadData]); // Search users when input changes useEffect(() => {