diff --git a/web/lib/firebase/users.ts b/web/lib/firebase/users.ts index e4a07406..b7cafad4 100644 --- a/web/lib/firebase/users.ts +++ b/web/lib/firebase/users.ts @@ -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,23 @@ export function listenForAllUsers(setUsers: (users: User[]) => void) { const q = query(userCollection) listenForValues(q, setUsers) } + +const topTradersQuery = query( + collection(db, 'users'), + orderBy('totalPnLCached', 'desc'), + limit(10) +) + +export function getTopTraders() { + return getValues(topTradersQuery) +} + +const topCreatorsQuery = query( + collection(db, 'users'), + orderBy('creatorVolumeCached', 'desc'), + limit(10) +) + +export function getTopCreators() { + return getValues(topCreatorsQuery) +} diff --git a/web/pages/leaderboards.tsx b/web/pages/leaderboards.tsx new file mode 100644 index 00000000..904c3f41 --- /dev/null +++ b/web/pages/leaderboards.tsx @@ -0,0 +1,108 @@ +import _ from 'lodash' +import { Row } from '../components/layout/row' +import { Spacer } from '../components/layout/spacer' +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 ( + + formatMoney(user.totalPnLCached), + }, + ]} + /> + + formatMoney(user.creatorVolumeCached), + }, + ]} + /> + + ) +} + +function Leaderboard(props: { + title: string + users: User[] + columns: { + header: string + renderCell: (user: User) => any + }[] +}) { + const { title, users, columns } = props + return ( +
+ + <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> + <td>{index + 1}</td> + <td> + <SiteLink className="relative" href={`/${user.username}`}> + <Row className="items-center gap-4"> + <img + className="h-8 w-8 rounded-full bg-gray-400 flex items-center justify-center ring-8 ring-gray-50" + src={user.avatarUrl} + alt="" + /> + <div>{user.name}</div> + </Row> + </SiteLink> + </td> + {columns.map((column) => ( + <td key={column.header}>{column.renderCell(user)}</td> + ))} + </tr> + ))} + </tbody> + </table> + </div> + </div> + ) +}