Updated code
This commit is contained in:
5
bun.lock
5
bun.lock
@@ -34,6 +34,7 @@
|
|||||||
"@eslint/eslintrc": "^3",
|
"@eslint/eslintrc": "^3",
|
||||||
"@next/eslint-plugin-next": "15.5.2",
|
"@next/eslint-plugin-next": "15.5.2",
|
||||||
"@tailwindcss/postcss": "^4",
|
"@tailwindcss/postcss": "^4",
|
||||||
|
"@tanstack/react-query-devtools": "^5.90.2",
|
||||||
"@types/js-cookie": "^3.0.6",
|
"@types/js-cookie": "^3.0.6",
|
||||||
"@types/node": "^20",
|
"@types/node": "^20",
|
||||||
"@types/react": "19.1.12",
|
"@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-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": ["@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=="],
|
"@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=="],
|
"@types/estree": ["@types/estree@1.0.8", "", {}, "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w=="],
|
||||||
|
|||||||
@@ -43,6 +43,7 @@
|
|||||||
"@eslint/eslintrc": "^3",
|
"@eslint/eslintrc": "^3",
|
||||||
"@next/eslint-plugin-next": "15.5.2",
|
"@next/eslint-plugin-next": "15.5.2",
|
||||||
"@tailwindcss/postcss": "^4",
|
"@tailwindcss/postcss": "^4",
|
||||||
|
"@tanstack/react-query-devtools": "^5.90.2",
|
||||||
"@types/js-cookie": "^3.0.6",
|
"@types/js-cookie": "^3.0.6",
|
||||||
"@types/node": "^20",
|
"@types/node": "^20",
|
||||||
"@types/react": "19.1.12",
|
"@types/react": "19.1.12",
|
||||||
|
|||||||
@@ -7,10 +7,11 @@ import { formatDate, formatNumber } from "@/lib/formatters"
|
|||||||
import { head } from "@/lib/hypixel/general"
|
import { head } from "@/lib/hypixel/general"
|
||||||
import { Guild } from "@/lib/schema/guild"
|
import { Guild } from "@/lib/schema/guild"
|
||||||
import { playerForGuildSchema } from "@/lib/schema/player"
|
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 Image from "next/image"
|
||||||
import Link from "next/link"
|
import Link from "next/link"
|
||||||
import { useCallback, useEffect, useRef, useState } from "react"
|
import { useEffect, useRef } from "react"
|
||||||
import { toast } from "sonner"
|
import { toast } from "sonner"
|
||||||
import z from "zod"
|
import z from "zod"
|
||||||
|
|
||||||
@@ -21,68 +22,27 @@ type MemberWithPlayer = Guild["guild"]["members"][number] & {
|
|||||||
error?: boolean
|
error?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
const queryClient = new QueryClient()
|
|
||||||
|
|
||||||
export function GuildMembers({ members, ranks }: { members: Guild["guild"]["members"], ranks: Guild["guild"]["ranks"] }) {
|
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 hasShownToast = useRef(false)
|
||||||
|
|
||||||
const totalMembers = members.length
|
const totalMembers = members.length
|
||||||
const TOAST_ID = "guild.members.progress"
|
const TOAST_ID = "guild.members.progress"
|
||||||
|
|
||||||
const handleMemberLoad = useCallback(() => {
|
const memberQueries = useQueries({
|
||||||
setLoadedCount(prev => prev + 1)
|
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) => {
|
const sortedMembers = [...members].sort((a, b) => {
|
||||||
if (a.rank === "Guild Master" && b.rank !== "Guild Master") return -1
|
if (a.rank === "Guild Master" && b.rank !== "Guild Master") return -1
|
||||||
@@ -139,13 +99,21 @@ function GuildMembersInternal({ members, ranks }: { members: Guild["guild"]["mem
|
|||||||
</TableRow>
|
</TableRow>
|
||||||
</TableHeader>
|
</TableHeader>
|
||||||
<TableBody className="space-y-4">
|
<TableBody className="space-y-4">
|
||||||
{sortedMembers.map(member => (
|
{sortedMembers.map((member) => {
|
||||||
<MemberRow
|
const query = memberQueries[members.findIndex(m => m.uuid === member.uuid)]
|
||||||
key={member.uuid}
|
|
||||||
member={member}
|
if (query.isLoading || (!query.data && !query.isError)) return null
|
||||||
onLoad={handleMemberLoad}
|
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>
|
</TableBody>
|
||||||
</Table>
|
</Table>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
|
|||||||
@@ -1,10 +1,11 @@
|
|||||||
import "./globals.css"
|
import "./globals.css"
|
||||||
|
|
||||||
import { GeistSans as geist } from "geist/font/sans"
|
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 ThemeProvider from "@/components/theme-provider"
|
||||||
import { Toaster } from "@/components/ui/sonner"
|
import { Toaster } from "@/components/ui/sonner"
|
||||||
import type { Metadata } from "next"
|
|
||||||
|
|
||||||
export const metadata: Metadata = {
|
export const metadata: Metadata = {
|
||||||
title: {
|
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" />} */}
|
{/* {process.env.NODE_ENV === "development" && <script src="https://unpkg.com/react-scan/dist/auto.global.js" />} */}
|
||||||
</head>
|
</head>
|
||||||
<body className="antialiased">
|
<body className="antialiased">
|
||||||
<ThemeProvider>
|
<QueryClientProvider>
|
||||||
{children}
|
<ThemeProvider>
|
||||||
<Toaster />
|
{children}
|
||||||
</ThemeProvider>
|
<Toaster />
|
||||||
|
</ThemeProvider>
|
||||||
|
<ReactQueryDevtools />
|
||||||
|
</QueryClientProvider>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</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 { type ClassValue, clsx } from "clsx"
|
||||||
import { twMerge } from "tailwind-merge"
|
import { twMerge } from "tailwind-merge"
|
||||||
|
|
||||||
|
export async function wait(ms: number) {
|
||||||
|
return new Promise((res) => setTimeout(res, ms))
|
||||||
|
}
|
||||||
|
|
||||||
export function cn(...inputs: ClassValue[]) {
|
export function cn(...inputs: ClassValue[]) {
|
||||||
return twMerge(clsx(inputs))
|
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