Daily/Weekly/Monthly Leaderboards by Fede (#557)
* [Leaderboards] Added period toggle for leaderboards * [Leaderboards] TopBettors now calculates by period correctly * [Leaderboard] Use a subcollection for the portfolio caching * [Leaderboard] Switches to a tab view, temporarily hides the missing topBettors periods * [Leaderboard] Reverts random yarn.lock changes * Fix type error from merge * Increase timeout on update metrics * Update firebase rules to allow reading user portfolioHistory Co-authored-by: Pico2x <pico2x@gmail.com>
This commit is contained in:
parent
c58e75f49a
commit
8b1d132e17
|
@ -15,8 +15,20 @@ export type User = {
|
||||||
|
|
||||||
balance: number
|
balance: number
|
||||||
totalDeposits: number
|
totalDeposits: number
|
||||||
totalPnLCached: number
|
|
||||||
creatorVolumeCached: number
|
profitCached: {
|
||||||
|
daily: number
|
||||||
|
weekly: number
|
||||||
|
monthly: number
|
||||||
|
allTime: number
|
||||||
|
}
|
||||||
|
|
||||||
|
creatorVolumeCached: {
|
||||||
|
daily: number
|
||||||
|
weekly: number
|
||||||
|
monthly: number
|
||||||
|
allTime: number
|
||||||
|
}
|
||||||
|
|
||||||
followerCountCached: number
|
followerCountCached: number
|
||||||
|
|
||||||
|
@ -42,3 +54,11 @@ export type PrivateUser = {
|
||||||
}
|
}
|
||||||
|
|
||||||
export type notification_subscribe_types = 'all' | 'less' | 'none'
|
export type notification_subscribe_types = 'all' | 'less' | 'none'
|
||||||
|
|
||||||
|
export type PortfolioMetrics = {
|
||||||
|
investmentValue: number
|
||||||
|
balance: number
|
||||||
|
totalDeposits: number
|
||||||
|
timestamp: number
|
||||||
|
userId: string
|
||||||
|
}
|
||||||
|
|
|
@ -18,6 +18,10 @@ service cloud.firestore {
|
||||||
&& request.resource.data.diff(resource.data).affectedKeys()
|
&& request.resource.data.diff(resource.data).affectedKeys()
|
||||||
.hasOnly(['bio', 'bannerUrl', 'website', 'twitterHandle', 'discordHandle', 'followedCategories']);
|
.hasOnly(['bio', 'bannerUrl', 'website', 'twitterHandle', 'discordHandle', 'followedCategories']);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
match /{somePath=**}/portfolioHistory/{portfolioHistoryId} {
|
||||||
|
allow read;
|
||||||
|
}
|
||||||
|
|
||||||
match /users/{userId}/follows/{followUserId} {
|
match /users/{userId}/follows/{followUserId} {
|
||||||
allow read;
|
allow read;
|
||||||
|
|
|
@ -70,8 +70,8 @@ export const createUser = functions
|
||||||
balance,
|
balance,
|
||||||
totalDeposits: balance,
|
totalDeposits: balance,
|
||||||
createdTime: Date.now(),
|
createdTime: Date.now(),
|
||||||
totalPnLCached: 0,
|
profitCached: { daily: 0, weekly: 0, monthly: 0, allTime: 0 },
|
||||||
creatorVolumeCached: 0,
|
creatorVolumeCached: { daily: 0, weekly: 0, monthly: 0, allTime: 0 },
|
||||||
followerCountCached: 0,
|
followerCountCached: 0,
|
||||||
followedCategories: DEFAULT_CATEGORIES,
|
followedCategories: DEFAULT_CATEGORIES,
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
import * as admin from 'firebase-admin'
|
import * as admin from 'firebase-admin'
|
||||||
import { initAdmin } from './script-init'
|
import { initAdmin } from './script-init'
|
||||||
import { cleanDisplayName } from '../../../common/util/clean-username'
|
import { cleanDisplayName } from '../../../common/util/clean-username'
|
||||||
import { log, writeUpdatesAsync, UpdateSpec } from '../utils'
|
import { log, writeAsync, UpdateSpec } from '../utils'
|
||||||
initAdmin()
|
initAdmin()
|
||||||
const firestore = admin.firestore()
|
const firestore = admin.firestore()
|
||||||
|
|
||||||
|
@ -20,7 +20,7 @@ if (require.main === module) {
|
||||||
return acc
|
return acc
|
||||||
}, [] as UpdateSpec[])
|
}, [] as UpdateSpec[])
|
||||||
log(`Found ${updates.length} users to update:`, updates)
|
log(`Found ${updates.length} users to update:`, updates)
|
||||||
await writeUpdatesAsync(firestore, updates)
|
await writeAsync(firestore, updates)
|
||||||
log(`Updated all users.`)
|
log(`Updated all users.`)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,12 +1,13 @@
|
||||||
import * as functions from 'firebase-functions'
|
import * as functions from 'firebase-functions'
|
||||||
import * as admin from 'firebase-admin'
|
import * as admin from 'firebase-admin'
|
||||||
import { groupBy, sum, sumBy } from 'lodash'
|
import { groupBy, isEmpty, sum, sumBy } from 'lodash'
|
||||||
|
import { getValues, log, logMemory, writeAsync } from './utils'
|
||||||
import { getValues, log, logMemory, writeUpdatesAsync } from './utils'
|
|
||||||
import { Bet } from '../../common/bet'
|
import { Bet } from '../../common/bet'
|
||||||
import { Contract } from '../../common/contract'
|
import { Contract } from '../../common/contract'
|
||||||
import { User } from '../../common/user'
|
import { PortfolioMetrics, User } from '../../common/user'
|
||||||
import { calculatePayout } from '../../common/calculate'
|
import { calculatePayout } from '../../common/calculate'
|
||||||
|
import { DAY_MS } from '../../common/util/time'
|
||||||
|
import { last } from 'lodash'
|
||||||
|
|
||||||
const firestore = admin.firestore()
|
const firestore = admin.firestore()
|
||||||
|
|
||||||
|
@ -26,15 +27,25 @@ const computeInvestmentValue = (
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
const computeTotalPool = (contracts: Contract[]) => {
|
const computeTotalPool = (userContracts: Contract[], startTime = 0) => {
|
||||||
return sum(contracts.map((contract) => sum(Object.values(contract.pool))))
|
const periodFilteredContracts = userContracts.filter(
|
||||||
|
(contract) => contract.createdTime >= startTime
|
||||||
|
)
|
||||||
|
return sum(
|
||||||
|
periodFilteredContracts.map((contract) => sum(Object.values(contract.pool)))
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export const updateMetricsCore = async () => {
|
export const updateMetricsCore = async () => {
|
||||||
const [users, contracts, bets] = await Promise.all([
|
const [users, contracts, bets, allPortfolioHistories] = await Promise.all([
|
||||||
getValues<User>(firestore.collection('users')),
|
getValues<User>(firestore.collection('users')),
|
||||||
getValues<Contract>(firestore.collection('contracts')),
|
getValues<Contract>(firestore.collection('contracts')),
|
||||||
getValues<Bet>(firestore.collectionGroup('bets')),
|
getValues<Bet>(firestore.collectionGroup('bets')),
|
||||||
|
getValues<PortfolioMetrics>(
|
||||||
|
firestore
|
||||||
|
.collectionGroup('portfolioHistory')
|
||||||
|
.where('timestamp', '>', Date.now() - 31 * DAY_MS) // so it includes just over a month ago
|
||||||
|
),
|
||||||
])
|
])
|
||||||
log(
|
log(
|
||||||
`Loaded ${users.length} users, ${contracts.length} contracts, and ${bets.length} bets.`
|
`Loaded ${users.length} users, ${contracts.length} contracts, and ${bets.length} bets.`
|
||||||
|
@ -53,7 +64,7 @@ export const updateMetricsCore = async () => {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
await writeUpdatesAsync(firestore, contractUpdates)
|
await writeAsync(firestore, contractUpdates)
|
||||||
log(`Updated metrics for ${contracts.length} contracts.`)
|
log(`Updated metrics for ${contracts.length} contracts.`)
|
||||||
|
|
||||||
const contractsById = Object.fromEntries(
|
const contractsById = Object.fromEntries(
|
||||||
|
@ -61,24 +72,66 @@ export const updateMetricsCore = async () => {
|
||||||
)
|
)
|
||||||
const contractsByUser = groupBy(contracts, (contract) => contract.creatorId)
|
const contractsByUser = groupBy(contracts, (contract) => contract.creatorId)
|
||||||
const betsByUser = groupBy(bets, (bet) => bet.userId)
|
const betsByUser = groupBy(bets, (bet) => bet.userId)
|
||||||
|
const portfolioHistoryByUser = groupBy(allPortfolioHistories, (p) => p.userId)
|
||||||
const userUpdates = users.map((user) => {
|
const userUpdates = users.map((user) => {
|
||||||
const investmentValue = computeInvestmentValue(
|
const currentBets = betsByUser[user.id] ?? []
|
||||||
betsByUser[user.id] ?? [],
|
const portfolioHistory = portfolioHistoryByUser[user.id] ?? []
|
||||||
contractsById
|
const userContracts = contractsByUser[user.id] ?? []
|
||||||
|
const newCreatorVolume = calculateCreatorVolume(userContracts)
|
||||||
|
const newPortfolio = calculateNewPortfolioMetrics(
|
||||||
|
user,
|
||||||
|
contractsById,
|
||||||
|
currentBets
|
||||||
)
|
)
|
||||||
const creatorContracts = contractsByUser[user.id] ?? []
|
const lastPortfolio = last(portfolioHistory)
|
||||||
const creatorVolume = computeTotalPool(creatorContracts)
|
const didProfitChange =
|
||||||
const totalValue = user.balance + investmentValue
|
lastPortfolio === undefined ||
|
||||||
const totalPnL = totalValue - user.totalDeposits
|
lastPortfolio.balance !== newPortfolio.balance ||
|
||||||
|
lastPortfolio.totalDeposits !== newPortfolio.totalDeposits ||
|
||||||
|
lastPortfolio.investmentValue !== newPortfolio.investmentValue
|
||||||
|
|
||||||
|
const newProfit = calculateNewProfit(
|
||||||
|
portfolioHistory,
|
||||||
|
newPortfolio,
|
||||||
|
didProfitChange
|
||||||
|
)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
doc: firestore.collection('users').doc(user.id),
|
fieldUpdates: {
|
||||||
fields: {
|
doc: firestore.collection('users').doc(user.id),
|
||||||
totalPnLCached: totalPnL,
|
fields: {
|
||||||
creatorVolumeCached: creatorVolume,
|
creatorVolumeCached: newCreatorVolume,
|
||||||
|
...(didProfitChange && {
|
||||||
|
profitCached: newProfit,
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
subcollectionUpdates: {
|
||||||
|
doc: firestore
|
||||||
|
.collection('users')
|
||||||
|
.doc(user.id)
|
||||||
|
.collection('portfolioHistory')
|
||||||
|
.doc(),
|
||||||
|
fields: {
|
||||||
|
...(didProfitChange && {
|
||||||
|
...newPortfolio,
|
||||||
|
}),
|
||||||
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
await writeUpdatesAsync(firestore, userUpdates)
|
await writeAsync(
|
||||||
|
firestore,
|
||||||
|
userUpdates.map((u) => u.fieldUpdates)
|
||||||
|
)
|
||||||
|
await writeAsync(
|
||||||
|
firestore,
|
||||||
|
userUpdates
|
||||||
|
.filter((u) => !isEmpty(u.subcollectionUpdates.fields))
|
||||||
|
.map((u) => u.subcollectionUpdates),
|
||||||
|
'set'
|
||||||
|
)
|
||||||
log(`Updated metrics for ${users.length} users.`)
|
log(`Updated metrics for ${users.length} users.`)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -88,7 +141,101 @@ const computeVolume = (contractBets: Bet[], since: number) => {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const calculateProfitForPeriod = (
|
||||||
|
startTime: number,
|
||||||
|
portfolioHistory: PortfolioMetrics[],
|
||||||
|
currentProfit: number
|
||||||
|
) => {
|
||||||
|
const startingPortfolio = [...portfolioHistory]
|
||||||
|
.reverse() // so we search in descending order (most recent first), for efficiency
|
||||||
|
.find((p) => p.timestamp < startTime)
|
||||||
|
|
||||||
|
if (startingPortfolio === undefined) {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
const startingProfit = calculateTotalProfit(startingPortfolio)
|
||||||
|
|
||||||
|
return currentProfit - startingProfit
|
||||||
|
}
|
||||||
|
|
||||||
|
const calculateTotalProfit = (portfolio: PortfolioMetrics) => {
|
||||||
|
return portfolio.investmentValue + portfolio.balance - portfolio.totalDeposits
|
||||||
|
}
|
||||||
|
|
||||||
|
const calculateCreatorVolume = (userContracts: Contract[]) => {
|
||||||
|
const allTimeCreatorVolume = computeTotalPool(userContracts, 0)
|
||||||
|
const monthlyCreatorVolume = computeTotalPool(
|
||||||
|
userContracts,
|
||||||
|
Date.now() - 30 * DAY_MS
|
||||||
|
)
|
||||||
|
const weeklyCreatorVolume = computeTotalPool(
|
||||||
|
userContracts,
|
||||||
|
Date.now() - 7 * DAY_MS
|
||||||
|
)
|
||||||
|
|
||||||
|
const dailyCreatorVolume = computeTotalPool(
|
||||||
|
userContracts,
|
||||||
|
Date.now() - 1 * DAY_MS
|
||||||
|
)
|
||||||
|
|
||||||
|
return {
|
||||||
|
daily: dailyCreatorVolume,
|
||||||
|
weekly: weeklyCreatorVolume,
|
||||||
|
monthly: monthlyCreatorVolume,
|
||||||
|
allTime: allTimeCreatorVolume,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const calculateNewPortfolioMetrics = (
|
||||||
|
user: User,
|
||||||
|
contractsById: { [k: string]: Contract },
|
||||||
|
currentBets: Bet[]
|
||||||
|
) => {
|
||||||
|
const investmentValue = computeInvestmentValue(currentBets, contractsById)
|
||||||
|
const newPortfolio = {
|
||||||
|
investmentValue: investmentValue,
|
||||||
|
balance: user.balance,
|
||||||
|
totalDeposits: user.totalDeposits,
|
||||||
|
timestamp: Date.now(),
|
||||||
|
userId: user.id,
|
||||||
|
}
|
||||||
|
return newPortfolio
|
||||||
|
}
|
||||||
|
|
||||||
|
const calculateNewProfit = (
|
||||||
|
portfolioHistory: PortfolioMetrics[],
|
||||||
|
newPortfolio: PortfolioMetrics,
|
||||||
|
didProfitChange: boolean
|
||||||
|
) => {
|
||||||
|
if (!didProfitChange) {
|
||||||
|
return {} // early return for performance
|
||||||
|
}
|
||||||
|
|
||||||
|
const allTimeProfit = calculateTotalProfit(newPortfolio)
|
||||||
|
const newProfit = {
|
||||||
|
daily: calculateProfitForPeriod(
|
||||||
|
Date.now() - 1 * DAY_MS,
|
||||||
|
portfolioHistory,
|
||||||
|
allTimeProfit
|
||||||
|
),
|
||||||
|
weekly: calculateProfitForPeriod(
|
||||||
|
Date.now() - 7 * DAY_MS,
|
||||||
|
portfolioHistory,
|
||||||
|
allTimeProfit
|
||||||
|
),
|
||||||
|
monthly: calculateProfitForPeriod(
|
||||||
|
Date.now() - 30 * DAY_MS,
|
||||||
|
portfolioHistory,
|
||||||
|
allTimeProfit
|
||||||
|
),
|
||||||
|
allTime: allTimeProfit,
|
||||||
|
}
|
||||||
|
|
||||||
|
return newProfit
|
||||||
|
}
|
||||||
|
|
||||||
export const updateMetrics = functions
|
export const updateMetrics = functions
|
||||||
.runWith({ memory: '1GB' })
|
.runWith({ memory: '1GB', timeoutSeconds: 540 })
|
||||||
.pubsub.schedule('every 15 minutes')
|
.pubsub.schedule('every 15 minutes')
|
||||||
.onRun(updateMetricsCore)
|
.onRun(updateMetricsCore)
|
||||||
|
|
|
@ -20,9 +20,10 @@ export type UpdateSpec = {
|
||||||
fields: { [k: string]: unknown }
|
fields: { [k: string]: unknown }
|
||||||
}
|
}
|
||||||
|
|
||||||
export const writeUpdatesAsync = async (
|
export const writeAsync = async (
|
||||||
db: admin.firestore.Firestore,
|
db: admin.firestore.Firestore,
|
||||||
updates: UpdateSpec[],
|
updates: UpdateSpec[],
|
||||||
|
operationType: 'update' | 'set' = 'update',
|
||||||
batchSize = 500 // 500 = Firestore batch limit
|
batchSize = 500 // 500 = Firestore batch limit
|
||||||
) => {
|
) => {
|
||||||
const chunks = chunk(updates, batchSize)
|
const chunks = chunk(updates, batchSize)
|
||||||
|
@ -30,7 +31,11 @@ export const writeUpdatesAsync = async (
|
||||||
log(`${i * batchSize}/${updates.length} updates written...`)
|
log(`${i * batchSize}/${updates.length} updates written...`)
|
||||||
const batch = db.batch()
|
const batch = db.batch()
|
||||||
for (const { doc, fields } of chunks[i]) {
|
for (const { doc, fields } of chunks[i]) {
|
||||||
batch.update(doc, fields)
|
if (operationType === 'update') {
|
||||||
|
batch.update(doc, fields)
|
||||||
|
} else {
|
||||||
|
batch.set(doc, fields)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
await batch.commit()
|
await batch.commit()
|
||||||
}
|
}
|
||||||
|
|
|
@ -35,6 +35,8 @@ import { filterDefined } from 'common/util/array'
|
||||||
|
|
||||||
export type { User }
|
export type { User }
|
||||||
|
|
||||||
|
export type LeaderboardPeriod = 'daily' | 'weekly' | 'monthly' | 'allTime'
|
||||||
|
|
||||||
const db = getFirestore(app)
|
const db = getFirestore(app)
|
||||||
export const auth = getAuth(app)
|
export const auth = getAuth(app)
|
||||||
|
|
||||||
|
@ -178,22 +180,24 @@ export function listenForPrivateUsers(
|
||||||
listenForValues(q, setUsers)
|
listenForValues(q, setUsers)
|
||||||
}
|
}
|
||||||
|
|
||||||
const topTradersQuery = query(
|
export function getTopTraders(period: LeaderboardPeriod) {
|
||||||
collection(db, 'users'),
|
const topTraders = query(
|
||||||
orderBy('totalPnLCached', 'desc'),
|
collection(db, 'users'),
|
||||||
limit(21)
|
orderBy('profitCached.' + period, 'desc'),
|
||||||
)
|
limit(20)
|
||||||
|
)
|
||||||
|
|
||||||
export async function getTopTraders() {
|
return getValues(topTraders)
|
||||||
const users = await getValues<User>(topTradersQuery)
|
|
||||||
return users.slice(0, 20)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const topCreatorsQuery = query(
|
export function getTopCreators(period: LeaderboardPeriod) {
|
||||||
collection(db, 'users'),
|
const topCreators = query(
|
||||||
orderBy('creatorVolumeCached', 'desc'),
|
collection(db, 'users'),
|
||||||
limit(20)
|
orderBy('creatorVolumeCached.' + period, 'desc'),
|
||||||
)
|
limit(20)
|
||||||
|
)
|
||||||
|
return getValues(topCreators)
|
||||||
|
}
|
||||||
|
|
||||||
export async function getTopFollowed() {
|
export async function getTopFollowed() {
|
||||||
const users = await getValues<User>(topFollowedQuery)
|
const users = await getValues<User>(topFollowedQuery)
|
||||||
|
@ -206,10 +210,6 @@ const topFollowedQuery = query(
|
||||||
limit(20)
|
limit(20)
|
||||||
)
|
)
|
||||||
|
|
||||||
export function getTopCreators() {
|
|
||||||
return getValues<User>(topCreatorsQuery)
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getUsers() {
|
export function getUsers() {
|
||||||
return getValues<User>(collection(db, 'users'))
|
return getValues<User>(collection(db, 'users'))
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,28 +4,34 @@ import { Page } from 'web/components/page'
|
||||||
import {
|
import {
|
||||||
getTopCreators,
|
getTopCreators,
|
||||||
getTopTraders,
|
getTopTraders,
|
||||||
|
LeaderboardPeriod,
|
||||||
getTopFollowed,
|
getTopFollowed,
|
||||||
User,
|
User,
|
||||||
} from 'web/lib/firebase/users'
|
} from 'web/lib/firebase/users'
|
||||||
import { formatMoney } from 'common/util/format'
|
import { formatMoney } from 'common/util/format'
|
||||||
import { fromPropz, usePropz } from 'web/hooks/use-propz'
|
import { fromPropz, usePropz } from 'web/hooks/use-propz'
|
||||||
|
import { useEffect, useState } from 'react'
|
||||||
|
import { LoadingIndicator } from 'web/components/loading-indicator'
|
||||||
|
import { Title } from 'web/components/title'
|
||||||
|
import { Tabs } from 'web/components/layout/tabs'
|
||||||
import { useTracking } from 'web/hooks/use-tracking'
|
import { useTracking } from 'web/hooks/use-tracking'
|
||||||
|
|
||||||
export const getStaticProps = fromPropz(getStaticPropz)
|
export const getStaticProps = fromPropz(getStaticPropz)
|
||||||
export async function getStaticPropz() {
|
export async function getStaticPropz() {
|
||||||
|
return queryLeaderboardUsers('allTime')
|
||||||
|
}
|
||||||
|
const queryLeaderboardUsers = async (period: LeaderboardPeriod) => {
|
||||||
const [topTraders, topCreators, topFollowed] = await Promise.all([
|
const [topTraders, topCreators, topFollowed] = await Promise.all([
|
||||||
getTopTraders().catch(() => {}),
|
getTopTraders(period).catch(() => {}),
|
||||||
getTopCreators().catch(() => {}),
|
getTopCreators(period).catch(() => {}),
|
||||||
getTopFollowed().catch(() => {}),
|
getTopFollowed().catch(() => {}),
|
||||||
])
|
])
|
||||||
|
|
||||||
return {
|
return {
|
||||||
props: {
|
props: {
|
||||||
topTraders,
|
topTraders,
|
||||||
topCreators,
|
topCreators,
|
||||||
topFollowed,
|
topFollowed,
|
||||||
},
|
},
|
||||||
|
|
||||||
revalidate: 60, // regenerate after a minute
|
revalidate: 60, // regenerate after a minute
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -40,46 +46,108 @@ export default function Leaderboards(props: {
|
||||||
topCreators: [],
|
topCreators: [],
|
||||||
topFollowed: [],
|
topFollowed: [],
|
||||||
}
|
}
|
||||||
const { topTraders, topCreators, topFollowed } = props
|
const { topFollowed } = props
|
||||||
|
const [topTradersState, setTopTraders] = useState(props.topTraders)
|
||||||
|
const [topCreatorsState, setTopCreators] = useState(props.topCreators)
|
||||||
|
const [isLoading, setLoading] = useState(false)
|
||||||
|
const [period, setPeriod] = useState<LeaderboardPeriod>('allTime')
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setLoading(true)
|
||||||
|
queryLeaderboardUsers(period).then((res) => {
|
||||||
|
setTopTraders(res.props.topTraders as User[])
|
||||||
|
setTopCreators(res.props.topCreators as User[])
|
||||||
|
setLoading(false)
|
||||||
|
})
|
||||||
|
}, [period])
|
||||||
|
|
||||||
|
const LeaderboardWithPeriod = (period: LeaderboardPeriod) => {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Col className="mx-4 items-center gap-10 lg:flex-row">
|
||||||
|
{!isLoading ? (
|
||||||
|
<>
|
||||||
|
{period === 'allTime' ? ( //TODO: show other periods once they're available
|
||||||
|
<Leaderboard
|
||||||
|
title="🏅 Top bettors"
|
||||||
|
users={topTradersState}
|
||||||
|
columns={[
|
||||||
|
{
|
||||||
|
header: 'Total profit',
|
||||||
|
renderCell: (user) =>
|
||||||
|
formatMoney(user.profitCached[period]),
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<></>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<Leaderboard
|
||||||
|
title="🏅 Top creators"
|
||||||
|
users={topCreatorsState}
|
||||||
|
columns={[
|
||||||
|
{
|
||||||
|
header: 'Total bet',
|
||||||
|
renderCell: (user) =>
|
||||||
|
formatMoney(user.creatorVolumeCached[period]),
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<LoadingIndicator spinnerClassName={'border-gray-500'} />
|
||||||
|
)}
|
||||||
|
</Col>
|
||||||
|
{period === 'allTime' ? (
|
||||||
|
<Col className="mx-4 my-10 w-1/2 items-center gap-10 lg:mx-0 lg:flex-row">
|
||||||
|
<Leaderboard
|
||||||
|
title="👀 Most followed"
|
||||||
|
users={topFollowed}
|
||||||
|
columns={[
|
||||||
|
{
|
||||||
|
header: 'Number of followers',
|
||||||
|
renderCell: (user) => user.followerCountCached,
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
</Col>
|
||||||
|
) : (
|
||||||
|
<></>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
useTracking('view leaderboards')
|
useTracking('view leaderboards')
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Page>
|
<Page>
|
||||||
<Col className="mx-4 items-center gap-10 lg:mx-0 lg:flex-row">
|
<Title text={'Leaderboards'} className={'hidden md:block'} />
|
||||||
<Leaderboard
|
<Tabs
|
||||||
title="🏅 Top bettors"
|
defaultIndex={0}
|
||||||
users={topTraders}
|
onClick={(title, index) => {
|
||||||
columns={[
|
const period = ['allTime', 'monthly', 'weekly', 'daily'][index]
|
||||||
{
|
setPeriod(period as LeaderboardPeriod)
|
||||||
header: 'Total profit',
|
}}
|
||||||
renderCell: (user) => formatMoney(user.totalPnLCached),
|
tabs={[
|
||||||
},
|
{
|
||||||
]}
|
title: 'All Time',
|
||||||
/>
|
content: LeaderboardWithPeriod('allTime'),
|
||||||
<Leaderboard
|
},
|
||||||
title="🏅 Top creators"
|
{
|
||||||
users={topCreators}
|
title: 'Monthly',
|
||||||
columns={[
|
content: LeaderboardWithPeriod('monthly'),
|
||||||
{
|
},
|
||||||
header: 'Total bet',
|
{
|
||||||
renderCell: (user) => formatMoney(user.creatorVolumeCached),
|
title: 'Weekly',
|
||||||
},
|
content: LeaderboardWithPeriod('weekly'),
|
||||||
]}
|
},
|
||||||
/>
|
{
|
||||||
</Col>
|
title: 'Daily',
|
||||||
<Col className="mx-4 my-10 w-1/2 items-center gap-10 lg:mx-0 lg:flex-row">
|
content: LeaderboardWithPeriod('daily'),
|
||||||
<Leaderboard
|
},
|
||||||
title="👀 Most followed"
|
]}
|
||||||
users={topFollowed}
|
/>
|
||||||
columns={[
|
|
||||||
{
|
|
||||||
header: 'Number of followers',
|
|
||||||
renderCell: (user) => user.followerCountCached,
|
|
||||||
},
|
|
||||||
]}
|
|
||||||
/>
|
|
||||||
</Col>
|
|
||||||
</Page>
|
</Page>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue
Block a user