"use client" import { PlayerIGN, PlayerRank } from "@/app/(stats)/_components/displayname" import { Card, CardContent } from "@/components/ui/card" import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table" 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 { ApiResponse } from "@/types" import { useQueries } from "@tanstack/react-query" import Image from "next/image" import Link from "next/link" import { useEffect, useRef } from "react" import { toast } from "sonner" import z from "zod" type PlayerForGuild = z.infer type MemberWithPlayer = Guild["guild"]["members"][number] & { player?: PlayerForGuild["player"] loading?: boolean error?: boolean } export function GuildMembers({ members, ranks }: { members: Guild["guild"]["members"], ranks: Guild["guild"]["ranks"] }) { const hasShownToast = useRef(false) const totalMembers = members.length const TOAST_ID = "guild.members.progress" 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 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) }) useEffect(() => { if (!hasShownToast.current && totalMembers > 0) { toast.loading(`Loading guild members... (0/${totalMembers})`, { id: TOAST_ID, duration: 1000 }) hasShownToast.current = true } }, [totalMembers, TOAST_ID]) useEffect(() => { if (loadedCount > 0) { if (loadedCount >= totalMembers) { toast.success(`Loaded all ${totalMembers} guild members!`, { id: TOAST_ID }) } else { toast.loading(`Loading guild members... (${loadedCount}/${totalMembers})`, { id: TOAST_ID }) } } }, [loadedCount, totalMembers, TOAST_ID]) return ( Name Rank Weekly GEXP Joined Since {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 })}
) } function MemberCard({ member: m }: { member: MemberWithPlayer }) { return ( {`${m.player?.displayname}'s {" "} {m.rank} {formatNumber(Object.values(m.expHistory).reduce((a, b) => a + b))} {formatDate(m.joined)} ) }