[AB-xxx] initial experience leaderboard version

This commit is contained in:
Sheldan
2024-03-25 20:39:21 +01:00
parent 8f9b7eba16
commit bc0c3a18d7
46 changed files with 17561 additions and 1 deletions

View File

@@ -0,0 +1,8 @@
export const ErrorDisplay = () => {
return (
<>
<h1 className="mb-4 text-4xl font-extrabold leading-none tracking-tight md:text-5xl lg:text-6xl text-white">Failed to load leaderboard</h1>
</>
);
}

View File

@@ -0,0 +1,61 @@
import {ExperienceConfig, ExperienceRole} from "../data/leaderboard";
import {RoleDisplay} from "./RoleDisplay";
import {useEffect, useState} from "react";
export const ExperienceConfigDisplay = ({serverId}: { serverId: bigint }) => {
const [roles, setRoles] = useState<ExperienceRole[]>([])
const [hasError, setError] = useState(false)
async function loadConfig() {
try {
const configResponse = await fetch(`/experience/v1/leaderboards/${serverId}/config`)
let configObj: ExperienceConfig = await configResponse.json();
const roles = configObj.roles;
setRoles(roles)
} catch (error) {
console.log(error)
setError(true)
}
}
useEffect(()=> {
loadConfig()
// eslint-disable-next-line react-hooks/exhaustive-deps
},[])
return (
<>
{!hasError ?
<div className="py-10">
<h2 className="text-4xl font-extrabold leading-none tracking-tight text-white">Role
config</h2>
<table className="w-full text-gray-400">
<thead
className="text-xs uppercase bg-gray-700 text-gray-400">
<tr>
<th className="px-6 py-3 w-1/2">Role</th>
<th className="px-6 py-3 w-1/8">Level</th>
</tr>
</thead>
<tbody>
{roles.map(role =>
<tr key={role.role.id} className="border-b bg-gray-800 border-gray-700">
<td className="px-6 py-4">
<RoleDisplay role={role.role}/>
</td>
<td className="px-6 py-4 text-center">
{role.level}
</td>
</tr>)}
</tbody>
</table>
{roles.length === 0 ?
<div className="w-full flex items-center justify-center">
<span className="text-gray-400">No roles</span>
</div> : ''}
</div>
: ''}
</>
);
}

View File

@@ -0,0 +1,112 @@
import {LeaderboardEntry} from "./LeaderboardEntry";
import {useEffect, useState} from "react";
import {ExperienceMember, GuildInfo} from "../data/leaderboard";
import {ExperienceConfigDisplay} from "./ExperienceConfigDisplay";
import {ErrorDisplay} from "./ErrorDisplay";
export function Leaderboard({serverId}: { serverId: bigint }) {
const pageSize = 25;
const [members, setMembers] = useState<ExperienceMember[]>([])
const [memberCount, setMemberCount] = useState(0)
const [pageCount, setPageCount] = useState(0)
const [hasMore, setHasMore] = useState(true)
const [hasError, setError] = useState(false)
const [guildInfo, setGuildInfo] = useState<GuildInfo>({} as GuildInfo)
async function loadLeaderboard(page: number, size: number) {
try {
const leaderboardResponse = await fetch(`/experience/v1/leaderboards/${serverId}?page=${page}&size=${size}`)
const leaderboardJson = await leaderboardResponse.json()
const loadedMembers: Array<ExperienceMember> = leaderboardJson.content;
setMemberCount(memberCount + loadedMembers.length)
setHasMore(!leaderboardJson.last)
setPageCount(page)
setMembers(members.concat(loadedMembers))
} catch (error) {
console.log(error)
setError(true)
}
}
async function loadGuildInfo() {
try {
const guildInfoResponse = await fetch(`/servers/v1/${serverId}/info`)
const guildInfoJson: GuildInfo= await guildInfoResponse.json()
setGuildInfo(guildInfoJson)
} catch (error) {
console.log(error)
}
}
useEffect(()=> {
if(memberCount === 0) {
loadLeaderboard(0, pageSize)
}
loadGuildInfo()
// eslint-disable-next-line react-hooks/exhaustive-deps
},[])
function loadMore() {
loadLeaderboard(pageCount + 1, pageSize)
}
let loadMoreButton = <button className="w-full bg-gray-500 hover:bg-gray-700 text-white" onClick={loadMore}>Load more</button>;
return (
<>
{!hasError ?
<>
<div className="relative font-[sans-serif] before:absolute before:w-full before:h-full before:inset-0 before:bg-black before:opacity-50 before:z-10 h-48">
{guildInfo.bannerUrl !== null ? <img src={guildInfo.bannerUrl + "?size=4096"}
alt="Banner"
className="absolute inset-0 w-full h-full object-cover"/> : ''}
<div
className="min-h-[150px] relative z-50 h-full max-w-6xl mx-auto flex flex-row justify-center items-center text-center text-white p-6">
{guildInfo.iconUrl !== null ? <img
src={guildInfo.iconUrl + "?size=512"}
alt="Icon"
className="w-24"/>
: ''}
<h1 className="text-4xl font-extrabold leading-none tracking-tight md:text-5xl lg:text-6xl text-white">{'Leaderboard for ' + guildInfo.name}</h1>
</div>
</div>
<div className="flex">
<div className="text-sm text-left w-3/4 ">
<table className="w-full text-gray-400">
<thead
className="text-xs uppercase bg-gray-700 text-gray-400">
<tr>
<th scope="col" className="px-6 py-3 w-1/3">
Member
</th>
<th scope="col" className="px-6 py-3 w-1/6 text-center">
Experience
</th>
<th scope="col" className="px-6 py-3 w-1/6 text-center">
Messages
</th>
<th scope="col" className="px-6 py-3 w-1/6 text-center">
Level
</th>
<th scope="col" className="px-6 py-3 w-1/3 text-center">
Role
</th>
</tr>
</thead>
<tbody>
{members.map(member => <LeaderboardEntry key={member.id} member={member}/>)}
</tbody>
</table>
{hasMore ? loadMoreButton : ''}
</div>
<div className="w-1/4 px-3">
<ExperienceConfigDisplay serverId={serverId}/>
</div>
</div>
</>
: <ErrorDisplay/>}
</>
);
}

View File

@@ -0,0 +1,37 @@
import {ExperienceMember} from "../data/leaderboard";
import {RoleDisplay} from "./RoleDisplay";
import createStyle from "../utils/styleUtils";
export const LeaderboardEntry = ({member}: { member: ExperienceMember }) => {
const userHasRole = member.role !== null;
const memberExists = member.member !== null;
const nameColor = userHasRole ? createStyle(member.role!) : ''
let memberDisplay = memberExists ? <>
<img alt={member.member!.name} src={member.member!.avatarUrl}
className="object-contain h-16 w-16 rounded-full"/>
<span className="align-middle" style={{color: nameColor}}>{member.member!.name}</span>
</> : <>{member.id}</>;
return (
<>
<tr className="border-b bg-gray-800 border-gray-700">
<td
className="px-2 py-4 font-medium whitespace-nowrap text-white flex items-center gap-3">
{memberDisplay}
</td>
<td className="px-6 py-4 text-center">
{member.experience.toLocaleString()}
</td>
<td className="px-6 py-4 text-center">
{member.messages.toLocaleString()}
</td>
<td className="px-6 py-4 text-center">
{member.level.toString()}
</td>
<td className="px-6 py-4 text-center">
{userHasRole ? <RoleDisplay role={member.role!}/> : 'No role'}
</td>
</tr>
</>
);
}

View File

@@ -0,0 +1,12 @@
import {Role} from "../data/leaderboard";
import createStyle from "../utils/styleUtils";
export const RoleDisplay = ({role}: { role: Role | null }) => {
const roleColor = createStyle(role);
let roleDisplay = role !== null && role.name !== null ? <span style={{ color: roleColor}}>{role.name}</span> : <>Deleted role {role !== null ? role!.id : ''}</>
return (
<>
{roleDisplay}
</>
);
}