diff --git a/bun.lock b/bun.lock index b1b5436..2de0fbb 100644 --- a/bun.lock +++ b/bun.lock @@ -34,6 +34,7 @@ "@eslint/eslintrc": "^3", "@next/eslint-plugin-next": "15.5.2", "@tailwindcss/postcss": "^4", + "@tanstack/react-query-devtools": "^5.90.2", "@types/js-cookie": "^3.0.6", "@types/node": "^20", "@types/react": "19.1.12", @@ -287,8 +288,12 @@ "@tanstack/query-core": ["@tanstack/query-core@5.90.2", "", {}, "sha512-k/TcR3YalnzibscALLwxeiLUub6jN5EDLwKDiO7q5f4ICEoptJ+n9+7vcEFy5/x/i6Q+Lb/tXrsKCggf5uQJXQ=="], + "@tanstack/query-devtools": ["@tanstack/query-devtools@5.90.1", "", {}, "sha512-GtINOPjPUH0OegJExZ70UahT9ykmAhmtNVcmtdnOZbxLwT7R5OmRztR5Ahe3/Cu7LArEmR6/588tAycuaWb1xQ=="], + "@tanstack/react-query": ["@tanstack/react-query@5.90.2", "", { "dependencies": { "@tanstack/query-core": "5.90.2" }, "peerDependencies": { "react": "^18 || ^19" } }, "sha512-CLABiR+h5PYfOWr/z+vWFt5VsOA2ekQeRQBFSKlcoW6Ndx/f8rfyVmq4LbgOM4GG2qtxAxjLYLOpCNTYm4uKzw=="], + "@tanstack/react-query-devtools": ["@tanstack/react-query-devtools@5.90.2", "", { "dependencies": { "@tanstack/query-devtools": "5.90.1" }, "peerDependencies": { "@tanstack/react-query": "^5.90.2", "react": "^18 || ^19" } }, "sha512-vAXJzZuBXtCQtrY3F/yUNJCV4obT/A/n81kb3+YqLbro5Z2+phdAbceO+deU3ywPw8B42oyJlp4FhO0SoivDFQ=="], + "@tybys/wasm-util": ["@tybys/wasm-util@0.10.0", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-VyyPYFlOMNylG45GoAe0xDoLwWuowvf92F9kySqzYh8vmYm7D2u4iUJKa1tOUpS70Ku13ASrOkS4ScXFsTaCNQ=="], "@types/estree": ["@types/estree@1.0.8", "", {}, "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w=="], diff --git a/package.json b/package.json index f07f8be..e5786b3 100644 --- a/package.json +++ b/package.json @@ -43,6 +43,7 @@ "@eslint/eslintrc": "^3", "@next/eslint-plugin-next": "15.5.2", "@tailwindcss/postcss": "^4", + "@tanstack/react-query-devtools": "^5.90.2", "@types/js-cookie": "^3.0.6", "@types/node": "^20", "@types/react": "19.1.12", diff --git a/src/app/(stats)/guild/[value]/_components/members.tsx b/src/app/(stats)/guild/[value]/_components/members.tsx index 52ac54d..4a6a288 100644 --- a/src/app/(stats)/guild/[value]/_components/members.tsx +++ b/src/app/(stats)/guild/[value]/_components/members.tsx @@ -7,10 +7,11 @@ import { formatDate, formatNumber } from "@/lib/formatters" import { head } from "@/lib/hypixel/general" import { Guild } from "@/lib/schema/guild" import { playerForGuildSchema } from "@/lib/schema/player" -import { QueryClient, QueryClientProvider, useQuery } from "@tanstack/react-query" +import { ApiResponse } from "@/types" +import { useQueries } from "@tanstack/react-query" import Image from "next/image" import Link from "next/link" -import { useCallback, useEffect, useRef, useState } from "react" +import { useEffect, useRef } from "react" import { toast } from "sonner" import z from "zod" @@ -21,68 +22,27 @@ type MemberWithPlayer = Guild["guild"]["members"][number] & { error?: boolean } -const queryClient = new QueryClient() - export function GuildMembers({ members, ranks }: { members: Guild["guild"]["members"], ranks: Guild["guild"]["ranks"] }) { - return ( - - - - ) -} - -function useMemberData(uuid: string) { - return useQuery({ - queryKey: ["guildMember", uuid], - queryFn: async () => { - const response = await fetch(`/api/guildmembers?uuid=${uuid}`) - const data = await response.json() - - if (data.error) { - throw new Error(data.message || "Failed to fetch member data") - } - - return data.player as PlayerForGuild["player"] - }, - staleTime: 1000 * 60 * 60 * 24, - gcTime: 1000 * 60 * 60, - retry: 3, - retryDelay: attemptIndex => Math.min(1000 * 2 ** attemptIndex, 30000) - }) -} - -function MemberRow({ member, onLoad }: { member: Guild["guild"]["members"][number], onLoad: () => void }) { - const { data: player, isLoading, isError } = useMemberData(member.uuid) - - useEffect(() => { - if (!isLoading && (player || isError)) { - onLoad() - } - }, [isLoading, player, isError, onLoad]) - - if (isLoading || (!player && !isError)) return null - if (isError || !player) return null - - const memberWithPlayer: MemberWithPlayer = { - ...member, - player, - loading: isLoading, - error: isError - } - - return -} - -function GuildMembersInternal({ members, ranks }: { members: Guild["guild"]["members"], ranks: Guild["guild"]["ranks"] }) { - const [loadedCount, setLoadedCount] = useState(0) const hasShownToast = useRef(false) - const totalMembers = members.length const TOAST_ID = "guild.members.progress" - const handleMemberLoad = useCallback(() => { - setLoadedCount(prev => prev + 1) - }, []) + const memberQueries = useQueries({ + queries: members.map(member => ({ + queryKey: ["guildMember", member.uuid], + queryFn: async () => { + const response = await fetch(`/api/guildmembers?uuid=${member.uuid}`) + const data = await response.json() as ApiResponse + if (data.error) { + throw new Error(data.message) + } + return data.player + }, + staleTime: 1000 * 60 * 60 * 24 + })) + }) + + const loadedCount = memberQueries.filter(query => !query.isLoading && (query.data || query.isError)).length const sortedMembers = [...members].sort((a, b) => { if (a.rank === "Guild Master" && b.rank !== "Guild Master") return -1 @@ -139,13 +99,21 @@ function GuildMembersInternal({ members, ranks }: { members: Guild["guild"]["mem - {sortedMembers.map(member => ( - - ))} + {sortedMembers.map((member) => { + const query = memberQueries[members.findIndex(m => m.uuid === member.uuid)] + + if (query.isLoading || (!query.data && !query.isError)) return null + if (query.isError || !query.data) return null + + const memberWithPlayer: MemberWithPlayer = { + ...member, + player: query.data, + loading: query.isLoading, + error: query.isError + } + + return + })} diff --git a/src/app/layout.tsx b/src/app/layout.tsx index 220f6dc..650428a 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -1,10 +1,11 @@ import "./globals.css" import { GeistSans as geist } from "geist/font/sans" +import type { Metadata } from "next" +import { QueryClientProvider, ReactQueryDevtools } from "@/components/query-provider" import ThemeProvider from "@/components/theme-provider" import { Toaster } from "@/components/ui/sonner" -import type { Metadata } from "next" export const metadata: Metadata = { title: { @@ -21,10 +22,13 @@ export default function RootLayout({ children }: LayoutProps<"/">) { {/* {process.env.NODE_ENV === "development" &&