mirror of
https://github.com/Rain-kl/OpenFlare.git
synced 2026-10-11 17:56:37 +08:00
103 lines
3.1 KiB
TypeScript
103 lines
3.1 KiB
TypeScript
"use client"
|
|
|
|
import * as React from "react"
|
|
import { CircleHelp, RefreshCw } from "lucide-react"
|
|
import { Button } from "@/components/ui/button"
|
|
import {
|
|
Dialog,
|
|
DialogContent,
|
|
DialogHeader,
|
|
DialogTitle,
|
|
DialogTrigger,
|
|
} from "@/components/ui/dialog"
|
|
import { useUser } from "@/contexts/user-context"
|
|
import { useLeaderboard } from "@/hooks/use-leaderboard"
|
|
import { LeaderboardPodium } from "@/components/common/leaderboard/leaderboard-podium"
|
|
import { LeaderboardTable } from "@/components/common/leaderboard/leaderboard-table"
|
|
import { cn } from "@/lib/utils"
|
|
|
|
/**
|
|
* 排行榜主组件
|
|
*/
|
|
export function LeaderboardMain() {
|
|
const { user } = useUser()
|
|
const {
|
|
items,
|
|
myRank,
|
|
loading,
|
|
loadingMore,
|
|
myRankLoading,
|
|
hasMore,
|
|
loadNextPage,
|
|
refresh,
|
|
} = useLeaderboard()
|
|
const isInitialLoading = loading && items.length === 0
|
|
|
|
const currentUserEntry = React.useMemo(() => {
|
|
if (!myRank?.user) return undefined
|
|
|
|
const matchedEntry = items.find((item) => item.user_id === myRank.user.user_id)
|
|
if (matchedEntry) return matchedEntry
|
|
|
|
if (!user) return undefined
|
|
|
|
return {
|
|
user_id: myRank.user.user_id,
|
|
username: user.nickname || user.username,
|
|
avatar_url: user.avatar_url,
|
|
available_balance: myRank.user.available_balance,
|
|
}
|
|
}, [items, myRank, user])
|
|
|
|
return (
|
|
<div className="py-6 space-y-10">
|
|
<div className="flex items-center justify-between">
|
|
<h1 className="text-2xl font-semibold tracking-tight">全局排行榜</h1>
|
|
|
|
<div className="flex items-center gap-2">
|
|
<Dialog>
|
|
<DialogTrigger asChild>
|
|
<Button variant="ghost" size="sm" className="h-8 gap-1.5 text-muted-foreground">
|
|
<CircleHelp className="size-4" />
|
|
规则
|
|
</Button>
|
|
</DialogTrigger>
|
|
<DialogContent>
|
|
<DialogHeader>
|
|
<DialogTitle>排行榜如何运作</DialogTitle>
|
|
</DialogHeader>
|
|
<div className="space-y-3 text-sm text-muted-foreground">
|
|
<p>排行榜按可用积分实时排序,数据会定期刷新。</p>
|
|
<p>参与社区活动、进行积分流转,都会影响可用积分与当前排名。</p>
|
|
</div>
|
|
</DialogContent>
|
|
</Dialog>
|
|
|
|
<Button variant="ghost" size="icon" className="size-8 text-muted-foreground" onClick={refresh} aria-label="刷新排行榜">
|
|
<RefreshCw className={cn("size-4", (loading || loadingMore || myRankLoading) && "animate-spin")} />
|
|
</Button>
|
|
</div>
|
|
</div>
|
|
|
|
<LeaderboardPodium
|
|
items={items.slice(0, 3)}
|
|
loading={isInitialLoading}
|
|
/>
|
|
|
|
<section>
|
|
<h2 className="font-semibold mb-4">完整榜单</h2>
|
|
<LeaderboardTable
|
|
items={items}
|
|
loading={isInitialLoading}
|
|
currentUserId={myRank?.user.user_id}
|
|
currentUserEntry={currentUserEntry}
|
|
currentUserRank={myRank?.user.rank}
|
|
onLoadMore={loadNextPage}
|
|
hasMore={hasMore}
|
|
startRank={1}
|
|
/>
|
|
</section>
|
|
</div>
|
|
)
|
|
}
|