From 83d0444538176c627b5657b0ee283b554d254518 Mon Sep 17 00:00:00 2001 From: VarshiniGunti Date: Sat, 21 Mar 2026 21:20:22 +0530 Subject: [PATCH 1/4] feat: implement friends dashboard and wire friends button flow (#160) # Conflicts: # app/src/App.jsx # app/src/components/CodeforcesTable.jsx --- app/src/App.jsx | 17 ++ app/src/components/CodeforcesTable.jsx | 126 ++++++-- app/src/components/FriendsPage.jsx | 384 +++++++++++++++++++++++++ app/src/components/Navbar.jsx | 4 +- 4 files changed, 503 insertions(+), 28 deletions(-) create mode 100644 app/src/components/FriendsPage.jsx diff --git a/app/src/App.jsx b/app/src/App.jsx index 16f47ad..bc5abf0 100644 --- a/app/src/App.jsx +++ b/app/src/App.jsx @@ -18,6 +18,7 @@ import Footer from "./components/Footer"; import LeetcodeRankings from "./components/LeetcodeRankings"; import LeetcodeRankingsCCPS from "./components/LeetcodeRankingsCCPS"; import LeetcodeGraphs from "./components/LeetcodeGraphs"; +import FriendsPage from "./components/FriendsPage.jsx"; import { AuthProvider } from "./Context/AuthContext.jsx"; import Dashboard from "./components/discussion-forum/dashboard.jsx"; import { SidebarProvider } from "./components/ui/sidebar.jsx"; @@ -186,6 +187,22 @@ function App() { } /> + + + + } + /> + {/* } /> */} } /> diff --git a/app/src/components/CodeforcesTable.jsx b/app/src/components/CodeforcesTable.jsx index 62d484b..103e744 100644 --- a/app/src/components/CodeforcesTable.jsx +++ b/app/src/components/CodeforcesTable.jsx @@ -13,6 +13,25 @@ import { User, Trophy, Users, Loader2, Search,Crown } from "lucide-react"; const BACKEND = import.meta.env.VITE_BACKEND; +const readJsonIfAvailable = async (response) => { + const contentType = response.headers.get("content-type") || ""; + if (!contentType.toLowerCase().includes("application/json")) { + const fallbackText = await response.text(); + return { + isJson: false, + data: null, + message: fallbackText || `Unexpected response (${response.status})`, + }; + } + + try { + const data = await response.json(); + return { isJson: true, data, message: null }; + } catch { + return { isJson: false, data: null, message: "Invalid JSON response" }; + } +}; + export function CFTable({ codeforcesUsers }) { let accessToken = null; try { @@ -219,51 +238,110 @@ useEffect(() => { } }; - // Get friends list (TODO: Implement backend integration) + // Get friends list from backend const getcffriends = async () => { + if (!accessToken) { + setCodeforcesfriends([]); + return; + } + try { - // TODO: Implement actual friends list from backend - // For now, using local storage - const savedFriends = localStorage.getItem('codeforces_friends'); - if (savedFriends) { - setCodeforcesfriends(JSON.parse(savedFriends)); - } else { + const response = await fetch(BACKEND + "/codeforcesFL/", { + method: "GET", + headers: { + "Content-Type": "application/json", + Authorization: "Bearer " + accessToken, + }, + }); + const parsed = await readJsonIfAvailable(response); + if (!response.ok || !parsed.isJson) { + console.error("Failed to fetch Codeforces friends:", parsed.message); setCodeforcesfriends([]); + return; } + const newData = parsed.data; + setCodeforcesfriends(Array.isArray(newData) ? newData : []); } catch (error) { - console.error("Error fetching friends:", error); + console.error("Failed to fetch Codeforces friends:", error); setCodeforcesfriends([]); } }; // Friend functions async function addfriend(username) { - if (!isAuthenticated) { + if (!accessToken) { alert("Please login to add friends."); return; } - const currentFriends = Array.isArray(codeforcesfriends) ? codeforcesfriends : []; - if (!currentFriends.includes(username)) { - const updatedFriends = [...currentFriends, username]; - setCodeforcesfriends(updatedFriends); - localStorage.setItem('codeforces_friends', JSON.stringify(updatedFriends)); + + if (codeforcesfriends.includes(username)) { + return; + } + + try { + const response = await fetch(BACKEND + "/codeforcesFA/", { + method: "POST", + headers: { + "Content-Type": "application/json", + Authorization: "Bearer " + accessToken, + }, + body: JSON.stringify({ + friendName: username, + }), + }); + const parsed = await readJsonIfAvailable(response); + if (!response.ok) { + console.error("Failed to add Codeforces friend:", parsed.message); + alert("ERROR!!!!"); + return; + } + setCodeforcesfriends((current) => [...current, username]); + } catch (error) { + console.error("Failed to add Codeforces friend:", error); + alert("ERROR!!!!"); } } async function dropfriend(username) { - if (!isAuthenticated) { + if (!accessToken) { alert("Please login to remove friends."); return; } - const currentFriends = Array.isArray(codeforcesfriends) ? codeforcesfriends : []; - const updatedFriends = currentFriends.filter((friend) => friend !== username); - setCodeforcesfriends(updatedFriends); - localStorage.setItem('codeforces_friends', JSON.stringify(updatedFriends)); + + try { + const response = await fetch(BACKEND + "/codeforcesFD/", { + method: "POST", + headers: { + "Content-Type": "application/json", + Authorization: "Bearer " + accessToken, + }, + body: JSON.stringify({ + friendName: username, + }), + }); + const parsed = await readJsonIfAvailable(response); + if (!response.ok) { + console.error("Failed to remove Codeforces friend:", parsed.message); + alert("ERROR!!!!"); + return; + } + setCodeforcesfriends((current) => + current.filter((friendName) => friendName !== username), + ); + } catch (error) { + console.error("Failed to remove Codeforces friend:", error); + alert("ERROR!!!!"); + } } useEffect(() => { - getcffriends(); - }, []); + if (isAuthenticated) { + getcffriends(); + } else { + setCodeforcesfriends([]); + setShowFriendsInContest(false); + } + }, [isAuthenticated]); // Filter logic for friends tab useEffect(() => { @@ -289,7 +367,6 @@ useEffect(() => { } } - // TODO: Sort by rating (implement when backend is ready) usersToDisplay.sort((a, b) => b.rating - a.rating); setFilteredusers(usersToDisplay); @@ -646,7 +723,6 @@ useEffect(() => {

Friends Leaderboard

- {/* TODO: Sort by rating when backend is implemented */} Sorted by rating @@ -678,9 +754,7 @@ useEffect(() => { {loggedInUser && " (including you)"} -
- TODO: Implement rating-based sorting from backend -
+
Sorted by rating
`https://codeforces.com/profile/${username}`, + rankLabel: "Rating Rank", + metricLabel: "Rating", + metricValue: (user) => user.rating ?? 0, + details: (user) => `Max ${user.max_rating ?? 0} | Solved ${user.total_solved ?? 0}`, + sortUsers: (users) => + [...users].sort((a, b) => (b.rating ?? 0) - (a.rating ?? 0)), + }, + { + key: "codechef", + title: "CodeChef", + endpoint: "/codechefFL/", + removeEndpoint: "/codechefFD/", + profile: (username) => `https://www.codechef.com/users/${username}`, + rankLabel: "Rating Rank", + metricLabel: "Rating", + metricValue: (user) => user.rating ?? 0, + details: (user) => + `Global ${user.Global_rank ?? "N/A"} | Country ${user.Country_rank ?? "N/A"}`, + sortUsers: (users) => + [...users].sort((a, b) => (b.rating ?? 0) - (a.rating ?? 0)), + }, + { + key: "leetcode", + title: "LeetCode", + endpoint: "/leetcodeFL/", + removeEndpoint: "/leetcodeFD/", + profile: (username) => `https://leetcode.com/u/${username}`, + rankLabel: "Global Rank", + metricLabel: "Solved", + metricValue: (user) => user.total_solved ?? 0, + details: (user) => `Platform rank ${user.ranking ?? "N/A"}`, + sortUsers: (users) => + [...users].sort((a, b) => (a.ranking ?? Infinity) - (b.ranking ?? Infinity)), + }, + { + key: "github", + title: "GitHub", + endpoint: "/githubFL/", + removeEndpoint: "/githubFD/", + profile: (username) => `https://github.com/${username}`, + rankLabel: "Contribution Rank", + metricLabel: "Contributions", + metricValue: (user) => user.contributions ?? 0, + details: (user) => `Repos ${user.repositories ?? 0} | Stars ${user.stars ?? 0}`, + sortUsers: (users) => + [...users].sort((a, b) => (b.contributions ?? 0) - (a.contributions ?? 0)), + }, + { + key: "openlake", + title: "OpenLake", + endpoint: "/openlakeFL/", + removeEndpoint: "/openlakeFD/", + profile: (username) => `https://github.com/${username}`, + rankLabel: "Contribution Rank", + metricLabel: "Contributions", + metricValue: (user) => user.contributions ?? 0, + details: () => "OpenLake contributors leaderboard", + sortUsers: (users) => + [...users].sort((a, b) => (b.contributions ?? 0) - (a.contributions ?? 0)), + }, +]; + +const readJsonIfAvailable = async (response) => { + const contentType = response.headers.get("content-type") || ""; + if (!contentType.toLowerCase().includes("application/json")) { + const fallbackText = await response.text(); + return { + isJson: false, + data: null, + message: fallbackText || `Unexpected response (${response.status})`, + }; + } + + try { + const data = await response.json(); + return { isJson: true, data, message: null }; + } catch { + return { isJson: false, data: null, message: "Invalid JSON response" }; + } +}; + +const getAccessToken = () => { + try { + return JSON.parse(localStorage.getItem("authTokens"))?.access || null; + } catch { + return null; + } +}; + +const mapFromUsers = (users) => + new Map((Array.isArray(users) ? users : []).map((user) => [user.username, user])); + +const findPlatformFriends = (friendNames, users, sortUsers) => { + const usersMap = mapFromUsers(users); + const sorted = sortUsers(Array.isArray(users) ? users : []); + const rankingMap = new Map(); + sorted.forEach((user, idx) => { + rankingMap.set(user.username, idx + 1); + }); + + return friendNames + .map((name) => usersMap.get(name)) + .filter(Boolean) + .map((user) => ({ + ...user, + computedRank: rankingMap.get(user.username) ?? null, + })); +}; + +export default function FriendsPage({ + codeforcesUsers, + codechefUsers, + leetcodeUsers, + githubUsers, + openlakeUsers, +}) { + const { open, isMobile } = useSidebar(); + const { userNames } = useAuth(); + const [friendsByPlatform, setFriendsByPlatform] = useState({ + codeforces: [], + codechef: [], + leetcode: [], + github: [], + openlake: [], + }); + const [loading, setLoading] = useState(false); + const [error, setError] = useState(""); + + const accessToken = getAccessToken(); + const isAuthenticated = Boolean(accessToken); + + const refreshFriends = async () => { + if (!accessToken) { + setFriendsByPlatform({ + codeforces: [], + codechef: [], + leetcode: [], + github: [], + openlake: [], + }); + return; + } + + setLoading(true); + setError(""); + try { + const responses = await Promise.all( + PLATFORM_CONFIG.map((platform) => + fetch(BACKEND + platform.endpoint, { + method: "GET", + headers: { + "Content-Type": "application/json", + Authorization: "Bearer " + accessToken, + }, + }), + ), + ); + + const parsed = await Promise.all( + responses.map((response) => readJsonIfAvailable(response)), + ); + + const nextState = { + codeforces: [], + codechef: [], + leetcode: [], + github: [], + openlake: [], + }; + + PLATFORM_CONFIG.forEach((platform, idx) => { + const response = responses[idx]; + const result = parsed[idx]; + nextState[platform.key] = + response.ok && result.isJson && Array.isArray(result.data) + ? result.data + : []; + }); + + setFriendsByPlatform(nextState); + } catch { + setError("Unable to load friends right now. Please try again."); + } finally { + setLoading(false); + } + }; + + useEffect(() => { + refreshFriends(); + }, [isAuthenticated]); + + const platformFriendRows = useMemo( + () => ({ + codeforces: findPlatformFriends( + friendsByPlatform.codeforces, + codeforcesUsers, + PLATFORM_CONFIG[0].sortUsers, + ), + codechef: findPlatformFriends( + friendsByPlatform.codechef, + codechefUsers, + PLATFORM_CONFIG[1].sortUsers, + ), + leetcode: findPlatformFriends( + friendsByPlatform.leetcode, + leetcodeUsers, + PLATFORM_CONFIG[2].sortUsers, + ), + github: findPlatformFriends( + friendsByPlatform.github, + githubUsers, + PLATFORM_CONFIG[3].sortUsers, + ), + openlake: findPlatformFriends( + friendsByPlatform.openlake, + openlakeUsers, + PLATFORM_CONFIG[4].sortUsers, + ), + }), + [friendsByPlatform, codeforcesUsers, codechefUsers, leetcodeUsers, githubUsers, openlakeUsers], + ); + + const removeFriend = async (platformKey, username) => { + if (!accessToken) { + return; + } + const platform = PLATFORM_CONFIG.find((item) => item.key === platformKey); + if (!platform) { + return; + } + + const response = await fetch(BACKEND + platform.removeEndpoint, { + method: "POST", + headers: { + "Content-Type": "application/json", + Authorization: "Bearer " + accessToken, + }, + body: JSON.stringify({ friendName: username }), + }); + if (!response.ok) { + alert("Could not remove friend. Please try again."); + return; + } + + setFriendsByPlatform((current) => ({ + ...current, + [platformKey]: current[platformKey].filter((friend) => friend !== username), + })); + }; + + if (!isAuthenticated) { + return ( +
+
Friends
+
+ +

Login required

+

+ Please sign in to view and manage your friends across leaderboards. +

+
+
+ ); + } + + return ( +
+
+
+
Friends
+

+ {userNames?.username + ? `${userNames.username}, manage your friends across all leaderboards.` + : "Manage your friends across all leaderboards."} +

+
+ +
+ + {error ? ( +
+ {error} +
+ ) : null} + + {PLATFORM_CONFIG.map((platform) => { + const rows = platformFriendRows[platform.key] || []; + return ( +
+
+

{platform.title}

+ + {rows.length} friend{rows.length === 1 ? "" : "s"} + +
+ + {rows.length === 0 ? ( +

+ No friends added on {platform.title} yet. +

+ ) : ( +
+ + + + + + + + + + + + {rows.map((user) => ( + + + + + + + + ))} + +
{platform.rankLabel}Username{platform.metricLabel}DetailsAction
#{user.computedRank ?? "N/A"} + + {user.username} + + {platform.metricValue(user)} + {platform.details(user)} + + +
+
+ )} +
+ ); + })} +
+ ); +} diff --git a/app/src/components/Navbar.jsx b/app/src/components/Navbar.jsx index d6986b1..3462b7f 100644 --- a/app/src/components/Navbar.jsx +++ b/app/src/components/Navbar.jsx @@ -49,7 +49,7 @@ const items = [ }, { title: "Friends", - url: "/", + url: "/friends", icon: Users, }, { @@ -148,4 +148,4 @@ export const Navbar = () => { ); -}; \ No newline at end of file +}; From 1561a56cefc3daf8f7d3ee5d11c3ae71da015105 Mon Sep 17 00:00:00 2001 From: VarshiniGunti Date: Sat, 21 Mar 2026 21:26:33 +0530 Subject: [PATCH 2/4] fix: address review feedback in friends flow and error handling --- app/src/components/CodeforcesTable.jsx | 8 ++-- app/src/components/FriendsPage.jsx | 56 ++++++++------------------ 2 files changed, 21 insertions(+), 43 deletions(-) diff --git a/app/src/components/CodeforcesTable.jsx b/app/src/components/CodeforcesTable.jsx index 103e744..4d7b1dc 100644 --- a/app/src/components/CodeforcesTable.jsx +++ b/app/src/components/CodeforcesTable.jsx @@ -292,13 +292,13 @@ useEffect(() => { const parsed = await readJsonIfAvailable(response); if (!response.ok) { console.error("Failed to add Codeforces friend:", parsed.message); - alert("ERROR!!!!"); + alert(parsed.message || "Failed to add friend. Please try again."); return; } setCodeforcesfriends((current) => [...current, username]); } catch (error) { console.error("Failed to add Codeforces friend:", error); - alert("ERROR!!!!"); + alert("Failed to add friend. Please check your connection and try again."); } } @@ -322,7 +322,7 @@ useEffect(() => { const parsed = await readJsonIfAvailable(response); if (!response.ok) { console.error("Failed to remove Codeforces friend:", parsed.message); - alert("ERROR!!!!"); + alert(parsed.message || "Failed to remove friend. Please try again."); return; } setCodeforcesfriends((current) => @@ -330,7 +330,7 @@ useEffect(() => { ); } catch (error) { console.error("Failed to remove Codeforces friend:", error); - alert("ERROR!!!!"); + alert("Failed to remove friend. Please check your connection and try again."); } } diff --git a/app/src/components/FriendsPage.jsx b/app/src/components/FriendsPage.jsx index 33b00c4..8776746 100644 --- a/app/src/components/FriendsPage.jsx +++ b/app/src/components/FriendsPage.jsx @@ -2,7 +2,6 @@ import { useEffect, useMemo, useState } from "react"; import { useSidebar } from "@/components/ui/sidebar"; import { Button } from "@/components/ui/button"; import { useAuth } from "@/Context/AuthContext"; -import { Users } from "lucide-react"; const BACKEND = import.meta.env.VITE_BACKEND; @@ -94,14 +93,6 @@ const readJsonIfAvailable = async (response) => { } }; -const getAccessToken = () => { - try { - return JSON.parse(localStorage.getItem("authTokens"))?.access || null; - } catch { - return null; - } -}; - const mapFromUsers = (users) => new Map((Array.isArray(users) ? users : []).map((user) => [user.username, user])); @@ -118,7 +109,7 @@ const findPlatformFriends = (friendNames, users, sortUsers) => { .filter(Boolean) .map((user) => ({ ...user, - computedRank: rankingMap.get(user.username) ?? null, + rank: rankingMap.get(user.username) ?? null, })); }; @@ -130,7 +121,7 @@ export default function FriendsPage({ openlakeUsers, }) { const { open, isMobile } = useSidebar(); - const { userNames } = useAuth(); + const { userNames, authTokens } = useAuth(); const [friendsByPlatform, setFriendsByPlatform] = useState({ codeforces: [], codechef: [], @@ -141,7 +132,7 @@ export default function FriendsPage({ const [loading, setLoading] = useState(false); const [error, setError] = useState(""); - const accessToken = getAccessToken(); + const accessToken = authTokens?.access || null; const isAuthenticated = Boolean(accessToken); const refreshFriends = async () => { @@ -159,7 +150,7 @@ export default function FriendsPage({ setLoading(true); setError(""); try { - const responses = await Promise.all( + const responses = await Promise.allSettled( PLATFORM_CONFIG.map((platform) => fetch(BACKEND + platform.endpoint, { method: "GET", @@ -172,7 +163,12 @@ export default function FriendsPage({ ); const parsed = await Promise.all( - responses.map((response) => readJsonIfAvailable(response)), + responses.map(async (responseResult) => { + if (responseResult.status !== "fulfilled") { + return null; + } + return readJsonIfAvailable(responseResult.value); + }), ); const nextState = { @@ -184,8 +180,13 @@ export default function FriendsPage({ }; PLATFORM_CONFIG.forEach((platform, idx) => { - const response = responses[idx]; + const responseResult = responses[idx]; const result = parsed[idx]; + if (responseResult.status !== "fulfilled" || !result) { + nextState[platform.key] = []; + return; + } + const response = responseResult.value; nextState[platform.key] = response.ok && result.isJson && Array.isArray(result.data) ? result.data @@ -263,29 +264,6 @@ export default function FriendsPage({ })); }; - if (!isAuthenticated) { - return ( -
-
Friends
-
- -

Login required

-

- Please sign in to view and manage your friends across leaderboards. -

-
-
- ); - } - return (
{rows.map((user) => ( - #{user.computedRank ?? "N/A"} + #{user.rank ?? "N/A"} Date: Sat, 21 Mar 2026 21:32:43 +0530 Subject: [PATCH 3/4] fix: harden friends page mapping and remove-friend error handling --- app/src/components/FriendsPage.jsx | 52 ++++++++++++++++++------------ 1 file changed, 31 insertions(+), 21 deletions(-) diff --git a/app/src/components/FriendsPage.jsx b/app/src/components/FriendsPage.jsx index 8776746..49b749d 100644 --- a/app/src/components/FriendsPage.jsx +++ b/app/src/components/FriendsPage.jsx @@ -74,6 +74,11 @@ const PLATFORM_CONFIG = [ }, ]; +const PLATFORM_CONFIG_BY_KEY = PLATFORM_CONFIG.reduce((acc, platform) => { + acc[platform.key] = platform; + return acc; +}, {}); + const readJsonIfAvailable = async (response) => { const contentType = response.headers.get("content-type") || ""; if (!contentType.toLowerCase().includes("application/json")) { @@ -210,27 +215,27 @@ export default function FriendsPage({ codeforces: findPlatformFriends( friendsByPlatform.codeforces, codeforcesUsers, - PLATFORM_CONFIG[0].sortUsers, + PLATFORM_CONFIG_BY_KEY.codeforces.sortUsers, ), codechef: findPlatformFriends( friendsByPlatform.codechef, codechefUsers, - PLATFORM_CONFIG[1].sortUsers, + PLATFORM_CONFIG_BY_KEY.codechef.sortUsers, ), leetcode: findPlatformFriends( friendsByPlatform.leetcode, leetcodeUsers, - PLATFORM_CONFIG[2].sortUsers, + PLATFORM_CONFIG_BY_KEY.leetcode.sortUsers, ), github: findPlatformFriends( friendsByPlatform.github, githubUsers, - PLATFORM_CONFIG[3].sortUsers, + PLATFORM_CONFIG_BY_KEY.github.sortUsers, ), openlake: findPlatformFriends( friendsByPlatform.openlake, openlakeUsers, - PLATFORM_CONFIG[4].sortUsers, + PLATFORM_CONFIG_BY_KEY.openlake.sortUsers, ), }), [friendsByPlatform, codeforcesUsers, codechefUsers, leetcodeUsers, githubUsers, openlakeUsers], @@ -245,23 +250,28 @@ export default function FriendsPage({ return; } - const response = await fetch(BACKEND + platform.removeEndpoint, { - method: "POST", - headers: { - "Content-Type": "application/json", - Authorization: "Bearer " + accessToken, - }, - body: JSON.stringify({ friendName: username }), - }); - if (!response.ok) { - alert("Could not remove friend. Please try again."); - return; - } + setError(""); + try { + const response = await fetch(BACKEND + platform.removeEndpoint, { + method: "POST", + headers: { + "Content-Type": "application/json", + Authorization: "Bearer " + accessToken, + }, + body: JSON.stringify({ friendName: username }), + }); + if (!response.ok) { + setError("Could not remove friend. Please try again."); + return; + } - setFriendsByPlatform((current) => ({ - ...current, - [platformKey]: current[platformKey].filter((friend) => friend !== username), - })); + setFriendsByPlatform((current) => ({ + ...current, + [platformKey]: current[platformKey].filter((friend) => friend !== username), + })); + } catch { + setError("Could not remove friend. Please check your connection and try again."); + } }; return ( From 8c7f36b8c59cf729eb28572af3fb70d09259348f Mon Sep 17 00:00:00 2001 From: VarshiniGunti Date: Sat, 21 Mar 2026 21:38:32 +0530 Subject: [PATCH 4/4] fix: preserve last-known friend data and sync contest friend flags --- app/src/components/CodeforcesTable.jsx | 46 ++++++++++++++++++++++---- app/src/components/FriendsPage.jsx | 43 ++++++++++++------------ 2 files changed, 61 insertions(+), 28 deletions(-) diff --git a/app/src/components/CodeforcesTable.jsx b/app/src/components/CodeforcesTable.jsx index 4d7b1dc..a8e0f82 100644 --- a/app/src/components/CodeforcesTable.jsx +++ b/app/src/components/CodeforcesTable.jsx @@ -52,6 +52,7 @@ export function CFTable({ codeforcesUsers }) { const [contestName, setContestName] = useState(""); const [contestLoaded, setContestLoaded] = useState(false); const [showFriendsInContest, setShowFriendsInContest] = useState(false); + const [friendsError, setFriendsError] = useState(""); // New states for logged in user const [loggedInUser, setLoggedInUser] = useState(null); @@ -238,10 +239,21 @@ useEffect(() => { } }; + const syncContestFriendFlags = (nextFriends) => { + setContestStandings((current) => + current.map((standing) => ({ + ...standing, + isFriend: nextFriends.includes(standing.username), + })), + ); + }; + // Get friends list from backend const getcffriends = async () => { if (!accessToken) { + setFriendsError(""); setCodeforcesfriends([]); + syncContestFriendFlags([]); return; } @@ -256,14 +268,17 @@ useEffect(() => { const parsed = await readJsonIfAvailable(response); if (!response.ok || !parsed.isJson) { console.error("Failed to fetch Codeforces friends:", parsed.message); - setCodeforcesfriends([]); + setFriendsError(parsed.message || "Could not refresh friends right now."); return; } const newData = parsed.data; - setCodeforcesfriends(Array.isArray(newData) ? newData : []); + const nextFriends = Array.isArray(newData) ? newData : []; + setFriendsError(""); + setCodeforcesfriends(nextFriends); + syncContestFriendFlags(nextFriends); } catch (error) { console.error("Failed to fetch Codeforces friends:", error); - setCodeforcesfriends([]); + setFriendsError("Could not refresh friends right now. Showing last known data."); } }; @@ -295,7 +310,14 @@ useEffect(() => { alert(parsed.message || "Failed to add friend. Please try again."); return; } - setCodeforcesfriends((current) => [...current, username]); + setFriendsError(""); + setCodeforcesfriends((current) => { + const nextFriends = current.includes(username) + ? current + : [...current, username]; + syncContestFriendFlags(nextFriends); + return nextFriends; + }); } catch (error) { console.error("Failed to add Codeforces friend:", error); alert("Failed to add friend. Please check your connection and try again."); @@ -325,9 +347,12 @@ useEffect(() => { alert(parsed.message || "Failed to remove friend. Please try again."); return; } - setCodeforcesfriends((current) => - current.filter((friendName) => friendName !== username), - ); + setFriendsError(""); + setCodeforcesfriends((current) => { + const nextFriends = current.filter((friendName) => friendName !== username); + syncContestFriendFlags(nextFriends); + return nextFriends; + }); } catch (error) { console.error("Failed to remove Codeforces friend:", error); alert("Failed to remove friend. Please check your connection and try again."); @@ -338,7 +363,9 @@ useEffect(() => { if (isAuthenticated) { getcffriends(); } else { + setFriendsError(""); setCodeforcesfriends([]); + syncContestFriendFlags([]); setShowFriendsInContest(false); } }, [isAuthenticated]); @@ -745,6 +772,11 @@ useEffect(() => {
) : filteredusers.length > 0 ? ( <> + {friendsError ? ( +
+ {friendsError} +
+ ) : null}
diff --git a/app/src/components/FriendsPage.jsx b/app/src/components/FriendsPage.jsx index 49b749d..e61510e 100644 --- a/app/src/components/FriendsPage.jsx +++ b/app/src/components/FriendsPage.jsx @@ -176,29 +176,30 @@ export default function FriendsPage({ }), ); - const nextState = { - codeforces: [], - codechef: [], - leetcode: [], - github: [], - openlake: [], - }; + let hadFailure = false; + setFriendsByPlatform((previous) => { + const nextState = { ...previous }; - PLATFORM_CONFIG.forEach((platform, idx) => { - const responseResult = responses[idx]; - const result = parsed[idx]; - if (responseResult.status !== "fulfilled" || !result) { - nextState[platform.key] = []; - return; - } - const response = responseResult.value; - nextState[platform.key] = - response.ok && result.isJson && Array.isArray(result.data) - ? result.data - : []; - }); + PLATFORM_CONFIG.forEach((platform, idx) => { + const responseResult = responses[idx]; + const result = parsed[idx]; + if (responseResult.status !== "fulfilled" || !result) { + hadFailure = true; + return; + } + const response = responseResult.value; + if (!response.ok || !result.isJson || !Array.isArray(result.data)) { + hadFailure = true; + return; + } + nextState[platform.key] = result.data; + }); - setFriendsByPlatform(nextState); + return nextState; + }); + if (hadFailure) { + setError("Some friends lists could not be loaded. Showing last successful data."); + } } catch { setError("Unable to load friends right now. Please try again."); } finally {