Show a top5 leaderboard on resolved markets
This commit is contained in:
parent
d6a751d9a2
commit
b8e391f0f0
|
@ -14,6 +14,7 @@ export function Leaderboard(props: {
|
||||||
}[]
|
}[]
|
||||||
className?: string
|
className?: string
|
||||||
}) {
|
}) {
|
||||||
|
// TODO: Ideally, highlight your own entry on the leaderboard
|
||||||
const { title, users, columns, className } = props
|
const { title, users, columns, className } = props
|
||||||
return (
|
return (
|
||||||
<div className={clsx('w-full px-1', className)}>
|
<div className={clsx('w-full px-1', className)}>
|
||||||
|
|
|
@ -126,6 +126,16 @@ export async function uploadData(
|
||||||
return await getDownloadURL(uploadRef)
|
return await getDownloadURL(uploadRef)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function listUsers(userIds: string[]) {
|
||||||
|
if (userIds.length > 10) {
|
||||||
|
throw new Error('Too many users requested at once; Firestore limits to 10')
|
||||||
|
}
|
||||||
|
const userCollection = collection(db, 'users')
|
||||||
|
const q = query(userCollection, where('id', 'in', userIds))
|
||||||
|
const docs = await getDocs(q)
|
||||||
|
return docs.docs.map((doc) => doc.data() as User)
|
||||||
|
}
|
||||||
|
|
||||||
export async function listAllUsers() {
|
export async function listAllUsers() {
|
||||||
const userCollection = collection(db, 'users')
|
const userCollection = collection(db, 'users')
|
||||||
const q = query(userCollection)
|
const q = query(userCollection)
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import React from 'react'
|
import React, { useEffect, useState } from 'react'
|
||||||
|
|
||||||
import { useContractWithPreload } from '../../hooks/use-contract'
|
import { useContractWithPreload } from '../../hooks/use-contract'
|
||||||
import { ContractOverview } from '../../components/contract-overview'
|
import { ContractOverview } from '../../components/contract-overview'
|
||||||
|
@ -10,7 +10,7 @@ import { ContractBetsTable, MyBetsSummary } from '../../components/bets-list'
|
||||||
import { useBets } from '../../hooks/use-bets'
|
import { useBets } from '../../hooks/use-bets'
|
||||||
import { Title } from '../../components/title'
|
import { Title } from '../../components/title'
|
||||||
import { Spacer } from '../../components/layout/spacer'
|
import { Spacer } from '../../components/layout/spacer'
|
||||||
import { User } from '../../lib/firebase/users'
|
import { listUsers, User } from '../../lib/firebase/users'
|
||||||
import {
|
import {
|
||||||
Contract,
|
Contract,
|
||||||
getContractFromSlug,
|
getContractFromSlug,
|
||||||
|
@ -30,6 +30,10 @@ import { listAllAnswers } from '../../lib/firebase/answers'
|
||||||
import { Answer } from '../../../common/answer'
|
import { Answer } from '../../../common/answer'
|
||||||
import { AnswersPanel } from '../../components/answers/answers-panel'
|
import { AnswersPanel } from '../../components/answers/answers-panel'
|
||||||
import { fromPropz, usePropz } from '../../hooks/use-propz'
|
import { fromPropz, usePropz } from '../../hooks/use-propz'
|
||||||
|
import { Leaderboard } from '../../components/leaderboard'
|
||||||
|
import _ from 'lodash'
|
||||||
|
import { calculatePayout, resolvedPayout } from '../../../common/calculate'
|
||||||
|
import { formatMoney } from '../../../common/util/format'
|
||||||
|
|
||||||
export const getStaticProps = fromPropz(getStaticPropz)
|
export const getStaticProps = fromPropz(getStaticPropz)
|
||||||
export async function getStaticPropz(props: {
|
export async function getStaticPropz(props: {
|
||||||
|
@ -147,6 +151,9 @@ export default function ContractPage(props: {
|
||||||
)}
|
)}
|
||||||
</ContractOverview>
|
</ContractOverview>
|
||||||
|
|
||||||
|
{contract.isResolved && (
|
||||||
|
<ContractLeaderboard contract={contract} bets={bets} />
|
||||||
|
)}
|
||||||
<BetsSection contract={contract} user={user ?? null} bets={bets} />
|
<BetsSection contract={contract} user={user ?? null} bets={bets} />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@ -195,6 +202,44 @@ function BetsSection(props: {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function ContractLeaderboard(props: { contract: Contract; bets: Bet[] }) {
|
||||||
|
const { contract, bets } = props
|
||||||
|
const [users, setUsers] = useState<User[]>()
|
||||||
|
|
||||||
|
// Create a map of userIds to total profits
|
||||||
|
// TODO: Are we supposed to include sales...?
|
||||||
|
const betsWithoutSales = bets.filter((bet) => !(bet.isSold || bet.sale))
|
||||||
|
const betsByUser = _.groupBy(betsWithoutSales, 'userId')
|
||||||
|
const userProfits = _.mapValues(betsByUser, (bets) =>
|
||||||
|
_.sumBy(bets, (bet) => resolvedPayout(contract, bet) - bet.amount)
|
||||||
|
)
|
||||||
|
|
||||||
|
// Find the 5 users with the most profit
|
||||||
|
const topUsers = _.entries(userProfits).sort(([i1, p1], [i2, p2]) => p2 - p1)
|
||||||
|
const top5Ids = topUsers.slice(0, 5).map(([userId]) => userId)
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
listUsers(top5Ids).then((users) => {
|
||||||
|
const sortedUsers = _.sortBy(users, (user) => -userProfits[user.id])
|
||||||
|
setUsers(sortedUsers)
|
||||||
|
})
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Leaderboard
|
||||||
|
title="Top Traders"
|
||||||
|
users={users || []}
|
||||||
|
columns={[
|
||||||
|
{
|
||||||
|
header: 'Total profit',
|
||||||
|
renderCell: (user) => formatMoney(userProfits[user.id] || 0),
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
className="mt-12 max-w-sm"
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
const getOpenGraphProps = (contract: Contract) => {
|
const getOpenGraphProps = (contract: Contract) => {
|
||||||
const { resolution, question, creatorName, creatorUsername, outcomeType } =
|
const { resolution, question, creatorName, creatorUsername, outcomeType } =
|
||||||
contract
|
contract
|
||||||
|
|
Loading…
Reference in New Issue
Block a user