Moved to react query
This commit is contained in:
5
bun.lock
5
bun.lock
@@ -12,6 +12,7 @@
|
|||||||
"@radix-ui/react-separator": "^1.1.7",
|
"@radix-ui/react-separator": "^1.1.7",
|
||||||
"@radix-ui/react-slot": "^1.2.3",
|
"@radix-ui/react-slot": "^1.2.3",
|
||||||
"@t3-oss/env-nextjs": "^0.13.8",
|
"@t3-oss/env-nextjs": "^0.13.8",
|
||||||
|
"@tanstack/react-query": "^5.90.2",
|
||||||
"babel-plugin-react-compiler": "^19.1.0-rc.3",
|
"babel-plugin-react-compiler": "^19.1.0-rc.3",
|
||||||
"class-variance-authority": "^0.7.1",
|
"class-variance-authority": "^0.7.1",
|
||||||
"clsx": "^2.1.1",
|
"clsx": "^2.1.1",
|
||||||
@@ -284,6 +285,10 @@
|
|||||||
|
|
||||||
"@tailwindcss/postcss": ["@tailwindcss/postcss@4.1.12", "", { "dependencies": { "@alloc/quick-lru": "^5.2.0", "@tailwindcss/node": "4.1.12", "@tailwindcss/oxide": "4.1.12", "postcss": "^8.4.41", "tailwindcss": "4.1.12" } }, "sha512-5PpLYhCAwf9SJEeIsSmCDLgyVfdBhdBpzX1OJ87anT9IVR0Z9pjM0FNixCAUAHGnMBGB8K99SwAheXrT0Kh6QQ=="],
|
"@tailwindcss/postcss": ["@tailwindcss/postcss@4.1.12", "", { "dependencies": { "@alloc/quick-lru": "^5.2.0", "@tailwindcss/node": "4.1.12", "@tailwindcss/oxide": "4.1.12", "postcss": "^8.4.41", "tailwindcss": "4.1.12" } }, "sha512-5PpLYhCAwf9SJEeIsSmCDLgyVfdBhdBpzX1OJ87anT9IVR0Z9pjM0FNixCAUAHGnMBGB8K99SwAheXrT0Kh6QQ=="],
|
||||||
|
|
||||||
|
"@tanstack/query-core": ["@tanstack/query-core@5.90.2", "", {}, "sha512-k/TcR3YalnzibscALLwxeiLUub6jN5EDLwKDiO7q5f4ICEoptJ+n9+7vcEFy5/x/i6Q+Lb/tXrsKCggf5uQJXQ=="],
|
||||||
|
|
||||||
|
"@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=="],
|
||||||
|
|
||||||
"@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=="],
|
||||||
|
|||||||
@@ -21,6 +21,7 @@
|
|||||||
"@radix-ui/react-separator": "^1.1.7",
|
"@radix-ui/react-separator": "^1.1.7",
|
||||||
"@radix-ui/react-slot": "^1.2.3",
|
"@radix-ui/react-slot": "^1.2.3",
|
||||||
"@t3-oss/env-nextjs": "^0.13.8",
|
"@t3-oss/env-nextjs": "^0.13.8",
|
||||||
|
"@tanstack/react-query": "^5.90.2",
|
||||||
"babel-plugin-react-compiler": "^19.1.0-rc.3",
|
"babel-plugin-react-compiler": "^19.1.0-rc.3",
|
||||||
"class-variance-authority": "^0.7.1",
|
"class-variance-authority": "^0.7.1",
|
||||||
"clsx": "^2.1.1",
|
"clsx": "^2.1.1",
|
||||||
|
|||||||
@@ -7,10 +7,9 @@ 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 Image from "next/image"
|
import Image from "next/image"
|
||||||
import Link from "next/link"
|
import Link from "next/link"
|
||||||
import { useEffect, useState } from "react"
|
|
||||||
import { toast } from "sonner"
|
|
||||||
import z from "zod"
|
import z from "zod"
|
||||||
|
|
||||||
type PlayerForGuild = z.infer<typeof playerForGuildSchema>
|
type PlayerForGuild = z.infer<typeof playerForGuildSchema>
|
||||||
@@ -20,48 +19,69 @@ type MemberWithPlayer = Guild["guild"]["members"][number] & {
|
|||||||
error?: boolean
|
error?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
export function GuildMembers({ members: mem, ranks }: { members: Guild["guild"]["members"], ranks: Guild["guild"]["ranks"] }) {
|
const queryClient = new QueryClient()
|
||||||
const [members, setMembers] = useState<MemberWithPlayer[]>(
|
|
||||||
mem.map(member => ({ ...member, loading: false, error: false }))
|
export function GuildMembers({ members, ranks }: { members: Guild["guild"]["members"], ranks: Guild["guild"]["ranks"] }) {
|
||||||
|
return (
|
||||||
|
<QueryClientProvider client={queryClient}>
|
||||||
|
<GuildMembersInternal members={members} ranks={ranks} />
|
||||||
|
</QueryClientProvider>
|
||||||
)
|
)
|
||||||
const [currentIndex, setCurrentIndex] = useState(0)
|
}
|
||||||
const [isLoading, setIsLoading] = useState(false)
|
|
||||||
|
|
||||||
const fetchMemberData = async (uuid: string, index: number) => {
|
function useMemberData(uuid: string) {
|
||||||
setMembers(prev => prev.map((member, i) => i === index ? { ...member, loading: true } : member))
|
return useQuery({
|
||||||
|
queryKey: ["guildMember", uuid],
|
||||||
try {
|
queryFn: async () => {
|
||||||
const response = await fetch(`/api/guildmembers?uuid=${uuid}`)
|
const response = await fetch(`/api/guildmembers?uuid=${uuid}`)
|
||||||
const data = await response.json()
|
const data = await response.json()
|
||||||
|
|
||||||
if (data.error) {
|
if (data.error) {
|
||||||
setMembers(prev => prev.map((member, i) => i === index ? { ...member, loading: false, error: true } : member))
|
throw new Error(data.message || "Failed to fetch member data")
|
||||||
} else {
|
|
||||||
setMembers(prev => prev.map((member, i) => i === index ? { ...member, loading: false, player: data.player } : member))
|
|
||||||
}
|
}
|
||||||
} catch {
|
|
||||||
setMembers(prev => prev.map((member, i) => i === index ? { ...member, loading: false, error: true } : member))
|
return data.player as PlayerForGuild["player"]
|
||||||
}
|
},
|
||||||
|
staleTime: 24 * 60 * 60 * 1000,
|
||||||
|
gcTime: 24 * 60 * 60 * 1000,
|
||||||
|
retry: 3,
|
||||||
|
retryDelay: attemptIndex => Math.min(1000 * 2 ** attemptIndex, 30000)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function MemberRow({ member }: { member: Guild["guild"]["members"][number] }) {
|
||||||
|
const { data: player, isLoading, isError } = useMemberData(member.uuid)
|
||||||
|
|
||||||
|
if (isLoading || (!player && !isError)) return null
|
||||||
|
if (isError || !player) return null
|
||||||
|
|
||||||
|
const memberWithPlayer: MemberWithPlayer = {
|
||||||
|
...member,
|
||||||
|
player,
|
||||||
|
loading: isLoading,
|
||||||
|
error: isError
|
||||||
}
|
}
|
||||||
|
|
||||||
useEffect(() => {
|
return <MemberCard member={memberWithPlayer} />
|
||||||
if (currentIndex < members.length && !isLoading) {
|
}
|
||||||
const timer = setTimeout(() => {
|
|
||||||
setIsLoading(true)
|
|
||||||
fetchMemberData(members[currentIndex].uuid, currentIndex).then(() => {
|
|
||||||
setCurrentIndex(prev => prev + 1)
|
|
||||||
setIsLoading(false)
|
|
||||||
})
|
|
||||||
}, 100)
|
|
||||||
|
|
||||||
toast.info(`Loaded ${members.filter(member => member.player).length} out of ${members.length} guild members`, {
|
function GuildMembersInternal({ members, ranks }: { members: Guild["guild"]["members"], ranks: Guild["guild"]["ranks"] }) {
|
||||||
id: "guild.members.loader",
|
const sortedMembers = [...members].sort((a, b) => {
|
||||||
duration: 1000
|
if (a.rank === "Guild Master" && b.rank !== "Guild Master") return -1
|
||||||
})
|
if (b.rank === "Guild Master" && a.rank !== "Guild Master") return 1
|
||||||
|
|
||||||
return () => clearTimeout(timer)
|
const aRank = ranks?.find(rank => rank.name === a.rank)
|
||||||
|
const bRank = ranks?.find(rank => rank.name === b.rank)
|
||||||
|
|
||||||
|
const aPriority = aRank?.priority ?? Number.MIN_SAFE_INTEGER
|
||||||
|
const bPriority = bRank?.priority ?? Number.MIN_SAFE_INTEGER
|
||||||
|
|
||||||
|
if (aPriority !== bPriority) {
|
||||||
|
return bPriority - aPriority
|
||||||
}
|
}
|
||||||
}, [currentIndex, members, isLoading])
|
|
||||||
|
return a.uuid.localeCompare(b.uuid)
|
||||||
|
})
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Card>
|
<Card>
|
||||||
@@ -77,22 +97,7 @@ export function GuildMembers({ members: mem, ranks }: { members: Guild["guild"][
|
|||||||
</TableRow>
|
</TableRow>
|
||||||
</TableHeader>
|
</TableHeader>
|
||||||
<TableBody className="space-y-4">
|
<TableBody className="space-y-4">
|
||||||
{members.filter(member => member.player).sort((a, b) => {
|
{sortedMembers.map(member => <MemberRow key={member.uuid} member={member} />)}
|
||||||
if (a.rank === "Guild Master" && b.rank !== "Guild Master") return -1
|
|
||||||
if (b.rank === "Guild Master" && a.rank !== "Guild Master") return 1
|
|
||||||
|
|
||||||
const aRank = ranks?.find(rank => rank.name === a.rank)
|
|
||||||
const bRank = ranks?.find(rank => rank.name === b.rank)
|
|
||||||
|
|
||||||
const aPriority = aRank?.priority ?? Number.MIN_SAFE_INTEGER
|
|
||||||
const bPriority = bRank?.priority ?? Number.MIN_SAFE_INTEGER
|
|
||||||
|
|
||||||
if (aPriority !== bPriority) {
|
|
||||||
return bPriority - aPriority
|
|
||||||
}
|
|
||||||
|
|
||||||
return a.uuid.localeCompare(b.uuid)
|
|
||||||
}).map((member, i) => <MemberCard key={i} member={member} />)}
|
|
||||||
</TableBody>
|
</TableBody>
|
||||||
</Table>
|
</Table>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
@@ -139,3 +144,4 @@ function MemberCard({ member: m }: { member: MemberWithPlayer }) {
|
|||||||
</TableRow>
|
</TableRow>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user