Leaderboards 🏅 (#30)
* Simple leaderboards * Filter out SG from traders. Center leaderboard. Use Nextjs Image. * Add leaderboards menu option * Put leaderboards side-by-side on large screens * Add medal emoji
This commit is contained in:
parent
746a97c1df
commit
6625983bb7
|
@ -51,6 +51,10 @@ function getNavigationOptions(user: User, options: { mobile: boolean }) {
|
|||
name: 'Your markets',
|
||||
href: `/${user.username}`,
|
||||
},
|
||||
{
|
||||
name: 'Leaderboards',
|
||||
href: '/leaderboards',
|
||||
},
|
||||
{
|
||||
name: 'Discord',
|
||||
href: 'https://discord.gg/eHQBNBqXuh',
|
||||
|
|
|
@ -10,6 +10,7 @@ import {
|
|||
where,
|
||||
limit,
|
||||
getDocs,
|
||||
orderBy,
|
||||
} from 'firebase/firestore'
|
||||
import { getAuth } from 'firebase/auth'
|
||||
import { ref, getStorage, uploadBytes, getDownloadURL } from 'firebase/storage'
|
||||
|
@ -20,7 +21,7 @@ import {
|
|||
} from 'firebase/auth'
|
||||
|
||||
import { User } from '../../../common/user'
|
||||
import { listenForValues } from './utils'
|
||||
import { getValues, listenForValues } from './utils'
|
||||
export type { User }
|
||||
|
||||
export const STARTING_BALANCE = 1000
|
||||
|
@ -127,3 +128,24 @@ export function listenForAllUsers(setUsers: (users: User[]) => void) {
|
|||
const q = query(userCollection)
|
||||
listenForValues(q, setUsers)
|
||||
}
|
||||
|
||||
const topTradersQuery = query(
|
||||
collection(db, 'users'),
|
||||
orderBy('totalPnLCached', 'desc'),
|
||||
limit(21)
|
||||
)
|
||||
|
||||
export async function getTopTraders() {
|
||||
const users = await getValues<User>(topTradersQuery)
|
||||
return users.filter((user) => user.username !== 'SG').slice(0, 20)
|
||||
}
|
||||
|
||||
const topCreatorsQuery = query(
|
||||
collection(db, 'users'),
|
||||
orderBy('creatorVolumeCached', 'desc'),
|
||||
limit(20)
|
||||
)
|
||||
|
||||
export function getTopCreators() {
|
||||
return getValues<User>(topCreatorsQuery)
|
||||
}
|
||||
|
|
112
web/pages/leaderboards.tsx
Normal file
112
web/pages/leaderboards.tsx
Normal file
|
@ -0,0 +1,112 @@
|
|||
import _ from 'lodash'
|
||||
import Image from 'next/image'
|
||||
import { Col } from '../components/layout/col'
|
||||
import { Row } from '../components/layout/row'
|
||||
import { Page } from '../components/page'
|
||||
import { SiteLink } from '../components/site-link'
|
||||
import { Title } from '../components/title'
|
||||
import { getTopCreators, getTopTraders, User } from '../lib/firebase/users'
|
||||
import { formatMoney } from '../lib/util/format'
|
||||
|
||||
export async function getStaticProps() {
|
||||
const [topTraders, topCreators] = await Promise.all([
|
||||
getTopTraders().catch((_) => {}),
|
||||
getTopCreators().catch((_) => {}),
|
||||
])
|
||||
|
||||
return {
|
||||
props: {
|
||||
topTraders,
|
||||
topCreators,
|
||||
},
|
||||
|
||||
revalidate: 60, // regenerate after a minute
|
||||
}
|
||||
}
|
||||
|
||||
export default function Leaderboards(props: {
|
||||
topTraders: User[]
|
||||
topCreators: User[]
|
||||
}) {
|
||||
const { topTraders, topCreators } = props
|
||||
|
||||
return (
|
||||
<Page>
|
||||
<Col className="items-center lg:flex-row gap-10">
|
||||
<Leaderboard
|
||||
title="🏅 Top traders"
|
||||
users={topTraders}
|
||||
columns={[
|
||||
{
|
||||
header: 'Total profit',
|
||||
renderCell: (user) => formatMoney(user.totalPnLCached),
|
||||
},
|
||||
]}
|
||||
/>
|
||||
<Leaderboard
|
||||
title="🏅 Top creators"
|
||||
users={topCreators}
|
||||
columns={[
|
||||
{
|
||||
header: 'Market volume',
|
||||
renderCell: (user) => formatMoney(user.creatorVolumeCached),
|
||||
},
|
||||
]}
|
||||
/>
|
||||
</Col>
|
||||
</Page>
|
||||
)
|
||||
}
|
||||
|
||||
function Leaderboard(props: {
|
||||
title: string
|
||||
users: User[]
|
||||
columns: {
|
||||
header: string
|
||||
renderCell: (user: User) => any
|
||||
}[]
|
||||
}) {
|
||||
const { title, users, columns } = props
|
||||
return (
|
||||
<div className="max-w-xl w-full px-1">
|
||||
<Title text={title} />
|
||||
<div className="overflow-x-auto">
|
||||
<table className="table table-zebra table-compact text-gray-500 w-full">
|
||||
<thead>
|
||||
<tr className="p-2">
|
||||
<th>#</th>
|
||||
<th>Name</th>
|
||||
{columns.map((column) => (
|
||||
<th key={column.header}>{column.header}</th>
|
||||
))}
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{users.map((user, index) => (
|
||||
<tr key={user.id}>
|
||||
<td>{index + 1}</td>
|
||||
<td>
|
||||
<SiteLink className="relative" href={`/${user.username}`}>
|
||||
<Row className="items-center gap-4">
|
||||
<Image
|
||||
className="rounded-full bg-gray-400 flex-shrink-0 ring-8 ring-gray-50"
|
||||
src={user.avatarUrl}
|
||||
alt=""
|
||||
width={32}
|
||||
height={32}
|
||||
/>
|
||||
<div>{user.name}</div>
|
||||
</Row>
|
||||
</SiteLink>
|
||||
</td>
|
||||
{columns.map((column) => (
|
||||
<td key={column.header}>{column.renderCell(user)}</td>
|
||||
))}
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
Loading…
Reference in New Issue
Block a user