Updated code
This commit is contained in:
@@ -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 (
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<GuildMembersInternal members={members} ranks={ranks} />
|
||||
</QueryClientProvider>
|
||||
)
|
||||
}
|
||||
|
||||
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 <MemberCard member={memberWithPlayer} />
|
||||
}
|
||||
|
||||
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<PlayerForGuild["player"]>
|
||||
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
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody className="space-y-4">
|
||||
{sortedMembers.map(member => (
|
||||
<MemberRow
|
||||
key={member.uuid}
|
||||
member={member}
|
||||
onLoad={handleMemberLoad}
|
||||
/>
|
||||
))}
|
||||
{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 <MemberCard key={member.uuid} member={memberWithPlayer} />
|
||||
})}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</CardContent>
|
||||
|
||||
@@ -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" && <script src="https://unpkg.com/react-scan/dist/auto.global.js" />} */}
|
||||
</head>
|
||||
<body className="antialiased">
|
||||
<ThemeProvider>
|
||||
{children}
|
||||
<Toaster />
|
||||
</ThemeProvider>
|
||||
<QueryClientProvider>
|
||||
<ThemeProvider>
|
||||
{children}
|
||||
<Toaster />
|
||||
</ThemeProvider>
|
||||
<ReactQueryDevtools />
|
||||
</QueryClientProvider>
|
||||
</body>
|
||||
</html>
|
||||
)
|
||||
|
||||
16
src/components/query-provider.tsx
Normal file
16
src/components/query-provider.tsx
Normal file
@@ -0,0 +1,16 @@
|
||||
"use client"
|
||||
|
||||
import { QueryClient, QueryClientProvider as OriginalQueryClientProvider } from "@tanstack/react-query"
|
||||
import { ReactQueryDevtools as OriginalReactQueryDevtools } from "@tanstack/react-query-devtools"
|
||||
import { ReactNode } from "react"
|
||||
|
||||
const queryClient = new QueryClient()
|
||||
export function QueryClientProvider({ children }: { children: ReactNode }) {
|
||||
return <OriginalQueryClientProvider client={queryClient}>{children}</OriginalQueryClientProvider>
|
||||
}
|
||||
|
||||
export function ReactQueryDevtools() {
|
||||
if (process.env.NODE_ENV !== "development") return null
|
||||
|
||||
return <OriginalReactQueryDevtools client={queryClient} />
|
||||
}
|
||||
@@ -1,6 +1,10 @@
|
||||
import { type ClassValue, clsx } from "clsx"
|
||||
import { twMerge } from "tailwind-merge"
|
||||
|
||||
export async function wait(ms: number) {
|
||||
return new Promise((res) => setTimeout(res, ms))
|
||||
}
|
||||
|
||||
export function cn(...inputs: ClassValue[]) {
|
||||
return twMerge(clsx(inputs))
|
||||
}
|
||||
|
||||
7
src/types.ts
Normal file
7
src/types.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
export type ApiResponse<T> = {
|
||||
error: true
|
||||
message: string
|
||||
} | {
|
||||
error: false
|
||||
player: T
|
||||
}
|
||||
Reference in New Issue
Block a user