Merge branch 'main' into create-user
This commit is contained in:
		
						commit
						552ee6b068
					
				|  | @ -12,15 +12,16 @@ import { | ||||||
| import { Col } from './layout/col' | import { Col } from './layout/col' | ||||||
| import { parseTags } from '../lib/util/parse' | import { parseTags } from '../lib/util/parse' | ||||||
| import dayjs from 'dayjs' | import dayjs from 'dayjs' | ||||||
| import { TrendingUpIcon } from '@heroicons/react/solid' | import { TrendingUpIcon, ClockIcon } from '@heroicons/react/solid' | ||||||
| import { DateTimeTooltip } from './datetime-tooltip' | import { DateTimeTooltip } from './datetime-tooltip' | ||||||
| 
 | 
 | ||||||
| export function ContractCard(props: { | export function ContractCard(props: { | ||||||
|   contract: Contract |   contract: Contract | ||||||
|   showHotVolume?: boolean |   showHotVolume?: boolean | ||||||
|  |   showCloseTime?: boolean | ||||||
|   className?: string |   className?: string | ||||||
| }) { | }) { | ||||||
|   const { contract, showHotVolume, className } = props |   const { contract, showHotVolume, showCloseTime, className } = props | ||||||
|   const { question, resolution } = contract |   const { question, resolution } = contract | ||||||
|   const { probPercent } = contractMetrics(contract) |   const { probPercent } = contractMetrics(contract) | ||||||
| 
 | 
 | ||||||
|  | @ -42,7 +43,11 @@ export function ContractCard(props: { | ||||||
|           probPercent={probPercent} |           probPercent={probPercent} | ||||||
|         /> |         /> | ||||||
|       </Row> |       </Row> | ||||||
|       <AbbrContractDetails contract={contract} showHotVolume={showHotVolume} /> |       <AbbrContractDetails | ||||||
|  |         contract={contract} | ||||||
|  |         showHotVolume={showHotVolume} | ||||||
|  |         showCloseTime={showCloseTime} | ||||||
|  |       /> | ||||||
|     </div> |     </div> | ||||||
|   ) |   ) | ||||||
| } | } | ||||||
|  | @ -99,9 +104,10 @@ export function ResolutionOrChance(props: { | ||||||
| export function AbbrContractDetails(props: { | export function AbbrContractDetails(props: { | ||||||
|   contract: Contract |   contract: Contract | ||||||
|   showHotVolume?: boolean |   showHotVolume?: boolean | ||||||
|  |   showCloseTime?: boolean | ||||||
| }) { | }) { | ||||||
|   const { contract, showHotVolume } = props |   const { contract, showHotVolume, showCloseTime } = props | ||||||
|   const { volume24Hours, creatorName, creatorUsername } = contract |   const { volume24Hours, creatorName, creatorUsername, closeTime } = contract | ||||||
|   const { truePool } = contractMetrics(contract) |   const { truePool } = contractMetrics(contract) | ||||||
| 
 | 
 | ||||||
|   return ( |   return ( | ||||||
|  | @ -118,6 +124,11 @@ export function AbbrContractDetails(props: { | ||||||
|             <TrendingUpIcon className="h-5 w-5 text-gray-500 inline" />{' '} |             <TrendingUpIcon className="h-5 w-5 text-gray-500 inline" />{' '} | ||||||
|             {formatMoney(volume24Hours)} |             {formatMoney(volume24Hours)} | ||||||
|           </div> |           </div> | ||||||
|  |         ) : showCloseTime ? ( | ||||||
|  |           <div className="whitespace-nowrap"> | ||||||
|  |             <ClockIcon className="h-5 w-5 text-gray-400 inline" />{' '} | ||||||
|  |             {dayjs(closeTime).format('MMM D')} | ||||||
|  |           </div> | ||||||
|         ) : ( |         ) : ( | ||||||
|           <div className="whitespace-nowrap">{formatMoney(truePool)} pool</div> |           <div className="whitespace-nowrap">{formatMoney(truePool)} pool</div> | ||||||
|         )} |         )} | ||||||
|  |  | ||||||
|  | @ -1,5 +1,6 @@ | ||||||
| // From https://tailwindui.com/components/application-ui/lists/feeds
 | // From https://tailwindui.com/components/application-ui/lists/feeds
 | ||||||
| import { useState } from 'react' | import { useState } from 'react' | ||||||
|  | import _ from 'lodash' | ||||||
| import { | import { | ||||||
|   BanIcon, |   BanIcon, | ||||||
|   ChatAltIcon, |   ChatAltIcon, | ||||||
|  | @ -10,11 +11,11 @@ import { | ||||||
|   UsersIcon, |   UsersIcon, | ||||||
|   XIcon, |   XIcon, | ||||||
| } from '@heroicons/react/solid' | } from '@heroicons/react/solid' | ||||||
| import { useBets } from '../hooks/use-bets' | 
 | ||||||
| import { Bet, withoutAnteBets } from '../lib/firebase/bets' |  | ||||||
| import { Comment, mapCommentsByBetId } from '../lib/firebase/comments' |  | ||||||
| import dayjs from 'dayjs' | import dayjs from 'dayjs' | ||||||
| import relativeTime from 'dayjs/plugin/relativeTime' | import relativeTime from 'dayjs/plugin/relativeTime' | ||||||
|  | dayjs.extend(relativeTime) | ||||||
|  | 
 | ||||||
| import { OutcomeLabel } from './outcome-label' | import { OutcomeLabel } from './outcome-label' | ||||||
| import { | import { | ||||||
|   contractMetrics, |   contractMetrics, | ||||||
|  | @ -33,11 +34,18 @@ import { SiteLink } from './site-link' | ||||||
| import { Col } from './layout/col' | import { Col } from './layout/col' | ||||||
| import { UserLink } from './user-page' | import { UserLink } from './user-page' | ||||||
| import { DateTimeTooltip } from './datetime-tooltip' | import { DateTimeTooltip } from './datetime-tooltip' | ||||||
| dayjs.extend(relativeTime) | import { useBets } from '../hooks/use-bets' | ||||||
|  | import { Bet, withoutAnteBets } from '../lib/firebase/bets' | ||||||
|  | import { Comment, mapCommentsByBetId } from '../lib/firebase/comments' | ||||||
|  | import { JoinSpans } from './join-spans' | ||||||
| 
 | 
 | ||||||
| function FeedComment(props: { activityItem: any }) { | function FeedComment(props: { activityItem: any }) { | ||||||
|   const { activityItem } = props |   const { activityItem } = props | ||||||
|   const { person, text, amount, outcome, createdTime } = activityItem |   const { person, text, amount, outcome, createdTime } = activityItem | ||||||
|  | 
 | ||||||
|  |   const bought = amount >= 0 ? 'bought' : 'sold' | ||||||
|  |   const money = formatMoney(Math.abs(amount)) | ||||||
|  | 
 | ||||||
|   return ( |   return ( | ||||||
|     <> |     <> | ||||||
|       <SiteLink className="relative" href={`/${person.username}`}> |       <SiteLink className="relative" href={`/${person.username}`}> | ||||||
|  | @ -59,7 +67,7 @@ function FeedComment(props: { activityItem: any }) { | ||||||
|               username={person.username} |               username={person.username} | ||||||
|               name={person.name} |               name={person.name} | ||||||
|             />{' '} |             />{' '} | ||||||
|             placed {formatMoney(amount)} on <OutcomeLabel outcome={outcome} />{' '} |             {bought} {money} of <OutcomeLabel outcome={outcome} />{' '} | ||||||
|             <Timestamp time={createdTime} /> |             <Timestamp time={createdTime} /> | ||||||
|           </p> |           </p> | ||||||
|         </div> |         </div> | ||||||
|  | @ -97,6 +105,10 @@ function FeedBet(props: { activityItem: any }) { | ||||||
|     if (!user || !comment) return |     if (!user || !comment) return | ||||||
|     await createComment(contractId, id, comment, user) |     await createComment(contractId, id, comment, user) | ||||||
|   } |   } | ||||||
|  | 
 | ||||||
|  |   const bought = amount >= 0 ? 'bought' : 'sold' | ||||||
|  |   const money = formatMoney(Math.abs(amount)) | ||||||
|  | 
 | ||||||
|   return ( |   return ( | ||||||
|     <> |     <> | ||||||
|       <div> |       <div> | ||||||
|  | @ -108,9 +120,8 @@ function FeedBet(props: { activityItem: any }) { | ||||||
|       </div> |       </div> | ||||||
|       <div className="min-w-0 flex-1 py-1.5"> |       <div className="min-w-0 flex-1 py-1.5"> | ||||||
|         <div className="text-sm text-gray-500"> |         <div className="text-sm text-gray-500"> | ||||||
|           <span>{isCreator ? 'You' : 'A trader'}</span> placed{' '} |           <span>{isCreator ? 'You' : 'A trader'}</span> {bought} {money} of{' '} | ||||||
|           {formatMoney(amount)} on <OutcomeLabel outcome={outcome} />{' '} |           <OutcomeLabel outcome={outcome} /> <Timestamp time={createdTime} /> | ||||||
|           <Timestamp time={createdTime} /> |  | ||||||
|           {canComment && ( |           {canComment && ( | ||||||
|             // Allow user to comment in an textarea if they are the creator
 |             // Allow user to comment in an textarea if they are the creator
 | ||||||
|             <div className="mt-2"> |             <div className="mt-2"> | ||||||
|  | @ -452,44 +463,51 @@ function groupBets( | ||||||
|   return items as ActivityItem[] |   return items as ActivityItem[] | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | function BetGroupSpan(props: { bets: Bet[]; outcome: 'YES' | 'NO' }) { | ||||||
|  |   const { bets, outcome } = props | ||||||
|  | 
 | ||||||
|  |   const numberTraders = _.uniqBy(bets, (b) => b.userId).length | ||||||
|  | 
 | ||||||
|  |   const [buys, sells] = _.partition(bets, (bet) => bet.amount >= 0) | ||||||
|  |   const buyTotal = _.sumBy(buys, (b) => b.amount) | ||||||
|  |   const sellTotal = _.sumBy(sells, (b) => -b.amount) | ||||||
|  | 
 | ||||||
|  |   return ( | ||||||
|  |     <span> | ||||||
|  |       {numberTraders} {numberTraders > 1 ? 'traders' : 'trader'}{' '} | ||||||
|  |       <JoinSpans> | ||||||
|  |         {buyTotal > 0 && <>bought {formatMoney(buyTotal)} </>} | ||||||
|  |         {sellTotal > 0 && <>sold {formatMoney(sellTotal)} </>} | ||||||
|  |       </JoinSpans> | ||||||
|  |       of <OutcomeLabel outcome={outcome} /> | ||||||
|  |     </span> | ||||||
|  |   ) | ||||||
|  | } | ||||||
|  | 
 | ||||||
| // TODO: Make this expandable to show all grouped bets?
 | // TODO: Make this expandable to show all grouped bets?
 | ||||||
| function FeedBetGroup(props: { activityItem: any }) { | function FeedBetGroup(props: { activityItem: any }) { | ||||||
|   const { activityItem } = props |   const { activityItem } = props | ||||||
|   const bets: Bet[] = activityItem.bets |   const bets: Bet[] = activityItem.bets | ||||||
| 
 | 
 | ||||||
|   const yesAmount = bets |   const [yesBets, noBets] = _.partition(bets, (bet) => bet.outcome === 'YES') | ||||||
|     .filter((b) => b.outcome == 'YES') | 
 | ||||||
|     .reduce((acc, bet) => acc + bet.amount, 0) |  | ||||||
|   const yesSpan = yesAmount ? ( |  | ||||||
|     <span> |  | ||||||
|       {formatMoney(yesAmount)} on <OutcomeLabel outcome={'YES'} /> |  | ||||||
|     </span> |  | ||||||
|   ) : null |  | ||||||
|   const noAmount = bets |  | ||||||
|     .filter((b) => b.outcome == 'NO') |  | ||||||
|     .reduce((acc, bet) => acc + bet.amount, 0) |  | ||||||
|   const noSpan = noAmount ? ( |  | ||||||
|     <span> |  | ||||||
|       {formatMoney(noAmount)} on <OutcomeLabel outcome={'NO'} /> |  | ||||||
|     </span> |  | ||||||
|   ) : null |  | ||||||
|   const traderCount = bets.length |  | ||||||
|   const createdTime = bets[0].createdTime |   const createdTime = bets[0].createdTime | ||||||
| 
 | 
 | ||||||
|   return ( |   return ( | ||||||
|     <> |     <> | ||||||
|       <div> |       <div> | ||||||
|         <div className="relative px-1"> |         <div className="relative px-1"> | ||||||
|           <div className="h-8 w-8 bg-gray-200 rounded-full ring-8 ring-gray-50 flex items-center justify-center"> |           <div className="h-10 w-10 bg-gray-200 rounded-full ring-8 ring-gray-50 flex items-center justify-center"> | ||||||
|             <UsersIcon className="h-5 w-5 text-gray-500" aria-hidden="true" /> |             <UsersIcon className="h-6 w-6 text-gray-500" aria-hidden="true" /> | ||||||
|           </div> |           </div> | ||||||
|         </div> |         </div> | ||||||
|       </div> |       </div> | ||||||
|       <div className="min-w-0 flex-1 py-1.5"> |       <div className="min-w-0 flex-1 py-1.5"> | ||||||
|         <div className="text-sm text-gray-500"> |         <div className="text-sm text-gray-500"> | ||||||
|           <span>{traderCount} traders</span> placed {yesSpan} |           {yesBets.length > 0 && <BetGroupSpan outcome="YES" bets={yesBets} />} | ||||||
|           {yesAmount && noAmount ? ' and ' : ''} |           {yesBets.length > 0 && noBets.length > 0 && <br />} | ||||||
|           {noSpan} <Timestamp time={createdTime} /> |           {noBets.length > 0 && <BetGroupSpan outcome="NO" bets={noBets} />} | ||||||
|  |           <Timestamp time={createdTime} /> | ||||||
|         </div> |         </div> | ||||||
|       </div> |       </div> | ||||||
|     </> |     </> | ||||||
|  |  | ||||||
|  | @ -18,8 +18,9 @@ import { Sort, useQueryAndSortParams } from '../hooks/use-sort-and-query-params' | ||||||
| export function ContractsGrid(props: { | export function ContractsGrid(props: { | ||||||
|   contracts: Contract[] |   contracts: Contract[] | ||||||
|   showHotVolume?: boolean |   showHotVolume?: boolean | ||||||
|  |   showCloseTime?: boolean | ||||||
| }) { | }) { | ||||||
|   const { showHotVolume } = props |   const { showHotVolume, showCloseTime } = props | ||||||
| 
 | 
 | ||||||
|   const [resolvedContracts, activeContracts] = _.partition( |   const [resolvedContracts, activeContracts] = _.partition( | ||||||
|     props.contracts, |     props.contracts, | ||||||
|  | @ -51,6 +52,7 @@ export function ContractsGrid(props: { | ||||||
|           contract={contract} |           contract={contract} | ||||||
|           key={contract.id} |           key={contract.id} | ||||||
|           showHotVolume={showHotVolume} |           showHotVolume={showHotVolume} | ||||||
|  |           showCloseTime={showCloseTime} | ||||||
|         /> |         /> | ||||||
|       ))} |       ))} | ||||||
|     </ul> |     </ul> | ||||||
|  |  | ||||||
|  | @ -13,11 +13,14 @@ export function DateTimeTooltip(props: { | ||||||
| }) { | }) { | ||||||
|   const { time } = props |   const { time } = props | ||||||
|   return ( |   return ( | ||||||
|     <span |     <> | ||||||
|       className="tooltip cursor-default" |       <span | ||||||
|       data-tip={dayjs(time).format('MMM DD, YYYY hh:mm a z')} |         className="tooltip cursor-default hidden sm:inline-block" | ||||||
|     > |         data-tip={dayjs(time).format('MMM DD, YYYY hh:mm a z')} | ||||||
|       {props.children} |       > | ||||||
|     </span> |         {props.children} | ||||||
|  |       </span> | ||||||
|  |       <span className="sm:hidden">{props.children}</span> | ||||||
|  |     </> | ||||||
|   ) |   ) | ||||||
| } | } | ||||||
|  |  | ||||||
							
								
								
									
										31
									
								
								web/components/join-spans.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										31
									
								
								web/components/join-spans.tsx
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,31 @@ | ||||||
|  | export const JoinSpans = (props: { | ||||||
|  |   children: any[] | ||||||
|  |   separator?: JSX.Element | string | ||||||
|  | }) => { | ||||||
|  |   const { separator } = props | ||||||
|  |   const children = props.children.filter((x) => !!x) | ||||||
|  | 
 | ||||||
|  |   if (children.length === 0) return <></> | ||||||
|  |   if (children.length === 1) return children[0] | ||||||
|  |   if (children.length === 2) | ||||||
|  |     return ( | ||||||
|  |       <> | ||||||
|  |         {children[0]} and {children[1]} | ||||||
|  |       </> | ||||||
|  |     ) | ||||||
|  | 
 | ||||||
|  |   const head = children.slice(0, -1).map((child) => ( | ||||||
|  |     <> | ||||||
|  |       {child} | ||||||
|  |       {separator || ','}{' '} | ||||||
|  |     </> | ||||||
|  |   )) | ||||||
|  | 
 | ||||||
|  |   const tail = children[children.length - 1] | ||||||
|  | 
 | ||||||
|  |   return ( | ||||||
|  |     <> | ||||||
|  |       {head}and {tail} | ||||||
|  |     </> | ||||||
|  |   ) | ||||||
|  | } | ||||||
|  | @ -51,6 +51,10 @@ function getNavigationOptions(user: User, options: { mobile: boolean }) { | ||||||
|       name: 'Your markets', |       name: 'Your markets', | ||||||
|       href: `/${user.username}`, |       href: `/${user.username}`, | ||||||
|     }, |     }, | ||||||
|  |     { | ||||||
|  |       name: 'Leaderboards', | ||||||
|  |       href: '/leaderboards', | ||||||
|  |     }, | ||||||
|     { |     { | ||||||
|       name: 'Discord', |       name: 'Discord', | ||||||
|       href: 'https://discord.gg/eHQBNBqXuh', |       href: 'https://discord.gg/eHQBNBqXuh', | ||||||
|  |  | ||||||
|  | @ -14,6 +14,7 @@ import { | ||||||
|   updateDoc, |   updateDoc, | ||||||
|   limit, |   limit, | ||||||
| } from 'firebase/firestore' | } from 'firebase/firestore' | ||||||
|  | import _ from 'lodash' | ||||||
| 
 | 
 | ||||||
| import { app } from './init' | import { app } from './init' | ||||||
| import { getValues, listenForValues } from './utils' | import { getValues, listenForValues } from './utils' | ||||||
|  | @ -123,6 +124,13 @@ export function listenForContract( | ||||||
|   }) |   }) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | function chooseRandomSubset(contracts: Contract[], count: number) { | ||||||
|  |   const fiveMinutes = 5 * 60 * 1000 | ||||||
|  |   const seed = Math.round(Date.now() / fiveMinutes).toString() | ||||||
|  |   shuffle(contracts, createRNG(seed)) | ||||||
|  |   return contracts.slice(0, count) | ||||||
|  | } | ||||||
|  | 
 | ||||||
| const hotContractsQuery = query( | const hotContractsQuery = query( | ||||||
|   contractCollection, |   contractCollection, | ||||||
|   where('isResolved', '==', false), |   where('isResolved', '==', false), | ||||||
|  | @ -131,21 +139,38 @@ const hotContractsQuery = query( | ||||||
|   limit(16) |   limit(16) | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| function chooseHotContracts(contracts: Contract[]) { |  | ||||||
|   const fiveMinutes = 5 * 60 * 1000 |  | ||||||
|   const seed = Math.round(Date.now() / fiveMinutes).toString() |  | ||||||
|   shuffle(contracts, createRNG(seed)) |  | ||||||
|   return contracts.slice(0, 4) |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| export function listenForHotContracts( | export function listenForHotContracts( | ||||||
|   setHotContracts: (contracts: Contract[]) => void |   setHotContracts: (contracts: Contract[]) => void | ||||||
| ) { | ) { | ||||||
|   return listenForValues<Contract>(hotContractsQuery, (contracts) => |   return listenForValues<Contract>(hotContractsQuery, (contracts) => { | ||||||
|     setHotContracts(chooseHotContracts(contracts)) |     const hotContracts = _.sortBy( | ||||||
|  |       chooseRandomSubset(contracts, 4), | ||||||
|  |       (contract) => contract.volume24Hours | ||||||
|  |     ) | ||||||
|  |     setHotContracts(hotContracts) | ||||||
|  |   }) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | export async function getHotContracts() { | ||||||
|  |   const contracts = await getValues<Contract>(hotContractsQuery) | ||||||
|  |   return _.sortBy( | ||||||
|  |     chooseRandomSubset(contracts, 4), | ||||||
|  |     (contract) => -1 * contract.volume24Hours | ||||||
|   ) |   ) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| export function getHotContracts() { | const closingSoonQuery = query( | ||||||
|   return getValues<Contract>(hotContractsQuery).then(chooseHotContracts) |   contractCollection, | ||||||
|  |   where('isResolved', '==', false), | ||||||
|  |   where('closeTime', '>', Date.now()), | ||||||
|  |   orderBy('closeTime', 'asc'), | ||||||
|  |   limit(6) | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | export async function getClosingSoonContracts() { | ||||||
|  |   const contracts = await getValues<Contract>(closingSoonQuery) | ||||||
|  |   return _.sortBy( | ||||||
|  |     chooseRandomSubset(contracts, 2), | ||||||
|  |     (contract) => contract.closeTime | ||||||
|  |   ) | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -9,6 +9,7 @@ import { | ||||||
|   where, |   where, | ||||||
|   limit, |   limit, | ||||||
|   getDocs, |   getDocs, | ||||||
|  |   orderBy, | ||||||
| } from 'firebase/firestore' | } from 'firebase/firestore' | ||||||
| import { getAuth } from 'firebase/auth' | import { getAuth } from 'firebase/auth' | ||||||
| import { ref, getStorage, uploadBytes, getDownloadURL } from 'firebase/storage' | import { ref, getStorage, uploadBytes, getDownloadURL } from 'firebase/storage' | ||||||
|  | @ -20,8 +21,8 @@ import { | ||||||
| 
 | 
 | ||||||
| import { app } from './init' | import { app } from './init' | ||||||
| import { User } from '../../../common/user' | import { User } from '../../../common/user' | ||||||
| import { listenForValues } from './utils' |  | ||||||
| import { createUser } from './api-call' | import { createUser } from './api-call' | ||||||
|  | import { getValues, listenForValues } from './utils' | ||||||
| export type { User } | export type { User } | ||||||
| 
 | 
 | ||||||
| const db = getFirestore(app) | const db = getFirestore(app) | ||||||
|  | @ -123,3 +124,24 @@ export function listenForAllUsers(setUsers: (users: User[]) => void) { | ||||||
|   const q = query(userCollection) |   const q = query(userCollection) | ||||||
|   listenForValues(q, setUsers) |   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) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | @ -2,6 +2,7 @@ import React from 'react' | ||||||
| import _ from 'lodash' | import _ from 'lodash' | ||||||
| import { | import { | ||||||
|   Contract, |   Contract, | ||||||
|  |   getClosingSoonContracts, | ||||||
|   getHotContracts, |   getHotContracts, | ||||||
|   listAllContracts, |   listAllContracts, | ||||||
| } from '../lib/firebase/contracts' | } from '../lib/firebase/contracts' | ||||||
|  | @ -14,16 +15,17 @@ import { | ||||||
|   Comment, |   Comment, | ||||||
|   listAllComments, |   listAllComments, | ||||||
| } from '../lib/firebase/comments' | } from '../lib/firebase/comments' | ||||||
| import { Col } from '../components/layout/col' |  | ||||||
| import { ContractCard } from '../components/contract-card' |  | ||||||
| import { Bet, listAllBets } from '../lib/firebase/bets' | import { Bet, listAllBets } from '../lib/firebase/bets' | ||||||
|  | import { ContractsGrid } from '../components/contracts-list' | ||||||
| 
 | 
 | ||||||
| export async function getStaticProps() { | export async function getStaticProps() { | ||||||
|   const [contracts, hotContracts, recentComments] = await Promise.all([ |   const [contracts, hotContracts, closingSoonContracts, recentComments] = | ||||||
|     listAllContracts().catch((_) => []), |     await Promise.all([ | ||||||
|     getHotContracts().catch(() => []), |       listAllContracts().catch((_) => []), | ||||||
|     getRecentComments().catch(() => []), |       getHotContracts().catch(() => []), | ||||||
|   ]) |       getClosingSoonContracts().catch(() => []), | ||||||
|  |       getRecentComments().catch(() => []), | ||||||
|  |     ]) | ||||||
| 
 | 
 | ||||||
|   const activeContracts = findActiveContracts(contracts, recentComments) |   const activeContracts = findActiveContracts(contracts, recentComments) | ||||||
|   const activeContractBets = await Promise.all( |   const activeContractBets = await Promise.all( | ||||||
|  | @ -39,6 +41,7 @@ export async function getStaticProps() { | ||||||
|       activeContractBets, |       activeContractBets, | ||||||
|       activeContractComments, |       activeContractComments, | ||||||
|       hotContracts, |       hotContracts, | ||||||
|  |       closingSoonContracts, | ||||||
|     }, |     }, | ||||||
| 
 | 
 | ||||||
|     revalidate: 60, // regenerate after a minute
 |     revalidate: 60, // regenerate after a minute
 | ||||||
|  | @ -50,17 +53,21 @@ const Home = (props: { | ||||||
|   activeContractBets: Bet[][] |   activeContractBets: Bet[][] | ||||||
|   activeContractComments: Comment[][] |   activeContractComments: Comment[][] | ||||||
|   hotContracts: Contract[] |   hotContracts: Contract[] | ||||||
|  |   closingSoonContracts: Contract[] | ||||||
| }) => { | }) => { | ||||||
|   const { |   const { | ||||||
|     activeContracts, |     activeContracts, | ||||||
|     activeContractBets, |     activeContractBets, | ||||||
|     activeContractComments, |     activeContractComments, | ||||||
|     hotContracts, |     hotContracts, | ||||||
|  |     closingSoonContracts, | ||||||
|   } = props |   } = props | ||||||
| 
 | 
 | ||||||
|   return ( |   return ( | ||||||
|     <Page> |     <Page> | ||||||
|       <HotMarkets hotContracts={hotContracts} /> |       <HotMarkets contracts={hotContracts} /> | ||||||
|  |       <Spacer h={10} /> | ||||||
|  |       <ClosingSoonMarkets contracts={closingSoonContracts} /> | ||||||
|       <Spacer h={10} /> |       <Spacer h={10} /> | ||||||
|       <ActivityFeed |       <ActivityFeed | ||||||
|         contracts={activeContracts} |         contracts={activeContracts} | ||||||
|  | @ -71,25 +78,26 @@ const Home = (props: { | ||||||
|   ) |   ) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| const HotMarkets = (props: { hotContracts: Contract[] }) => { | const HotMarkets = (props: { contracts: Contract[] }) => { | ||||||
|   const { hotContracts } = props |   const { contracts } = props | ||||||
|   if (hotContracts.length < 4) return <></> |   if (contracts.length === 0) return <></> | ||||||
| 
 |  | ||||||
|   const [c1, c2, c3, c4] = hotContracts |  | ||||||
| 
 | 
 | ||||||
|   return ( |   return ( | ||||||
|     <div className="w-full bg-indigo-50 border-2 border-indigo-100 p-6 rounded-lg shadow-md"> |     <div className="w-full bg-indigo-50 border-2 border-indigo-100 p-6 rounded-lg shadow-md"> | ||||||
|       <Title className="mt-0" text="🔥 Markets" /> |       <Title className="mt-0" text="🔥 Markets" /> | ||||||
|       <Col className="gap-6"> |       <ContractsGrid contracts={contracts} showHotVolume /> | ||||||
|         <Col className="md:flex-row items-start gap-6"> |     </div> | ||||||
|           <ContractCard className="flex-1" contract={c1} showHotVolume /> |   ) | ||||||
|           <ContractCard className="flex-1" contract={c2} showHotVolume /> | } | ||||||
|         </Col> | 
 | ||||||
|         <Col className="md:flex-row items-start gap-6"> | const ClosingSoonMarkets = (props: { contracts: Contract[] }) => { | ||||||
|           <ContractCard className="flex-1" contract={c3} showHotVolume /> |   const { contracts } = props | ||||||
|           <ContractCard className="flex-1" contract={c4} showHotVolume /> |   if (contracts.length === 0) return <></> | ||||||
|         </Col> | 
 | ||||||
|       </Col> |   return ( | ||||||
|  |     <div className="w-full bg-green-50 border-2 border-green-100 p-6 rounded-lg shadow-md"> | ||||||
|  |       <Title className="mt-0" text="⏰ Closing soon" /> | ||||||
|  |       <ContractsGrid contracts={contracts} showCloseTime /> | ||||||
|     </div> |     </div> | ||||||
|   ) |   ) | ||||||
| } | } | ||||||
|  |  | ||||||
							
								
								
									
										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