Merge branch 'main' into free-comments
This commit is contained in:
		
						commit
						e938e70aae
					
				|  | @ -18,7 +18,14 @@ import { | ||||||
|   getDpmProbabilityAfterSale, |   getDpmProbabilityAfterSale, | ||||||
| } from './calculate-dpm' | } from './calculate-dpm' | ||||||
| import { calculateFixedPayout } from './calculate-fixed-payouts' | import { calculateFixedPayout } from './calculate-fixed-payouts' | ||||||
| import { Binary, Contract, CPMM, DPM, FullContract } from './contract' | import { | ||||||
|  |   Binary, | ||||||
|  |   Contract, | ||||||
|  |   CPMM, | ||||||
|  |   DPM, | ||||||
|  |   FreeResponseContract, | ||||||
|  |   FullContract, | ||||||
|  | } from './contract' | ||||||
| 
 | 
 | ||||||
| export function getProbability(contract: FullContract<DPM | CPMM, Binary>) { | export function getProbability(contract: FullContract<DPM | CPMM, Binary>) { | ||||||
|   return contract.mechanism === 'cpmm-1' |   return contract.mechanism === 'cpmm-1' | ||||||
|  | @ -170,3 +177,15 @@ export function getContractBetNullMetrics() { | ||||||
|     profitPercent: 0, |     profitPercent: 0, | ||||||
|   } |   } | ||||||
| } | } | ||||||
|  | 
 | ||||||
|  | export function getTopAnswer(contract: FreeResponseContract) { | ||||||
|  |   const { answers } = contract | ||||||
|  |   const top = _.maxBy( | ||||||
|  |     answers.map((answer) => ({ | ||||||
|  |       answer, | ||||||
|  |       prob: getOutcomeProbability(contract, answer.id), | ||||||
|  |     })), | ||||||
|  |     ({ prob }) => prob | ||||||
|  |   ) | ||||||
|  |   return top?.answer | ||||||
|  | } | ||||||
|  |  | ||||||
|  | @ -10,3 +10,9 @@ export type ClickEvent = { | ||||||
|   contractId: string |   contractId: string | ||||||
|   timestamp: number |   timestamp: number | ||||||
| } | } | ||||||
|  | 
 | ||||||
|  | export type LatencyEvent = { | ||||||
|  |   type: 'feed' | 'portfolio' | ||||||
|  |   latency: number | ||||||
|  |   timestamp: number | ||||||
|  | } | ||||||
|  |  | ||||||
|  | @ -30,6 +30,10 @@ service cloud.firestore { | ||||||
|       allow create: if userId == request.auth.uid; |       allow create: if userId == request.auth.uid; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     match /private-users/{userId}/latency/{loadTimeId} { | ||||||
|  |       allow create: if userId == request.auth.uid; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|     match /contracts/{contractId} { |     match /contracts/{contractId} { | ||||||
|       allow read; |       allow read; | ||||||
|       allow update: if request.resource.data.diff(resource.data).affectedKeys() |       allow update: if request.resource.data.diff(resource.data).affectedKeys() | ||||||
|  |  | ||||||
|  | @ -37,6 +37,8 @@ import { | ||||||
|   resolvedPayout, |   resolvedPayout, | ||||||
|   getContractBetNullMetrics, |   getContractBetNullMetrics, | ||||||
| } from '../../common/calculate' | } from '../../common/calculate' | ||||||
|  | import { useTimeSinceFirstRender } from '../hooks/use-time-since-first-render' | ||||||
|  | import { trackLatency } from '../lib/firebase/tracking' | ||||||
| 
 | 
 | ||||||
| type BetSort = 'newest' | 'profit' | 'closeTime' | 'value' | type BetSort = 'newest' | 'profit' | 'closeTime' | 'value' | ||||||
| type BetFilter = 'open' | 'closed' | 'resolved' | 'all' | type BetFilter = 'open' | 'closed' | 'resolved' | 'all' | ||||||
|  | @ -67,6 +69,14 @@ export function BetsList(props: { user: User }) { | ||||||
|     } |     } | ||||||
|   }, [bets]) |   }, [bets]) | ||||||
| 
 | 
 | ||||||
|  |   const getTime = useTimeSinceFirstRender() | ||||||
|  |   useEffect(() => { | ||||||
|  |     if (bets && contracts) { | ||||||
|  |       trackLatency('portfolio', getTime()) | ||||||
|  |     } | ||||||
|  |     // eslint-disable-next-line react-hooks/exhaustive-deps
 | ||||||
|  |   }, [!!bets, !!contracts]) | ||||||
|  | 
 | ||||||
|   if (!bets || !contracts) { |   if (!bets || !contracts) { | ||||||
|     return <LoadingIndicator /> |     return <LoadingIndicator /> | ||||||
|   } |   } | ||||||
|  |  | ||||||
|  | @ -23,7 +23,7 @@ import { | ||||||
|   BinaryContractOutcomeLabel, |   BinaryContractOutcomeLabel, | ||||||
|   FreeResponseOutcomeLabel, |   FreeResponseOutcomeLabel, | ||||||
| } from '../outcome-label' | } from '../outcome-label' | ||||||
| import { getOutcomeProbability } from '../../../common/calculate' | import { getOutcomeProbability, getTopAnswer } from '../../../common/calculate' | ||||||
| import { AbbrContractDetails } from './contract-details' | import { AbbrContractDetails } from './contract-details' | ||||||
| 
 | 
 | ||||||
| export function ContractCard(props: { | export function ContractCard(props: { | ||||||
|  | @ -122,18 +122,6 @@ export function BinaryResolutionOrChance(props: { | ||||||
|   ) |   ) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| function getTopAnswer(contract: FreeResponseContract) { |  | ||||||
|   const { answers } = contract |  | ||||||
|   const top = _.maxBy( |  | ||||||
|     answers.map((answer) => ({ |  | ||||||
|       answer, |  | ||||||
|       prob: getOutcomeProbability(contract, answer.id), |  | ||||||
|     })), |  | ||||||
|     ({ prob }) => prob |  | ||||||
|   ) |  | ||||||
|   return top?.answer |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| export function FreeResponseResolutionOrChance(props: { | export function FreeResponseResolutionOrChance(props: { | ||||||
|   contract: FreeResponseContract |   contract: FreeResponseContract | ||||||
|   truncate: 'short' | 'long' | 'none' |   truncate: 'short' | 'long' | 'none' | ||||||
|  |  | ||||||
|  | @ -18,6 +18,7 @@ import { Avatar } from '../avatar' | ||||||
| import { useState } from 'react' | import { useState } from 'react' | ||||||
| import { ContractInfoDialog } from './contract-info-dialog' | import { ContractInfoDialog } from './contract-info-dialog' | ||||||
| import { Bet } from '../../../common/bet' | import { Bet } from '../../../common/bet' | ||||||
|  | import NewContractBadge from '../new-contract-badge' | ||||||
| 
 | 
 | ||||||
| export function AbbrContractDetails(props: { | export function AbbrContractDetails(props: { | ||||||
|   contract: Contract |   contract: Contract | ||||||
|  | @ -25,7 +26,8 @@ export function AbbrContractDetails(props: { | ||||||
|   showCloseTime?: boolean |   showCloseTime?: boolean | ||||||
| }) { | }) { | ||||||
|   const { contract, showHotVolume, showCloseTime } = props |   const { contract, showHotVolume, showCloseTime } = props | ||||||
|   const { volume24Hours, creatorName, creatorUsername, closeTime } = contract |   const { volume, volume24Hours, creatorName, creatorUsername, closeTime } = | ||||||
|  |     contract | ||||||
|   const { volumeLabel } = contractMetrics(contract) |   const { volumeLabel } = contractMetrics(contract) | ||||||
| 
 | 
 | ||||||
|   return ( |   return ( | ||||||
|  | @ -54,11 +56,10 @@ export function AbbrContractDetails(props: { | ||||||
|             {(closeTime || 0) < Date.now() ? 'Closed' : 'Closes'}{' '} |             {(closeTime || 0) < Date.now() ? 'Closed' : 'Closes'}{' '} | ||||||
|             {fromNow(closeTime || 0)} |             {fromNow(closeTime || 0)} | ||||||
|           </Row> |           </Row> | ||||||
|  |         ) : volume > 0 ? ( | ||||||
|  |           <Row>{volumeLabel}</Row> | ||||||
|         ) : ( |         ) : ( | ||||||
|           <Row className="gap-1"> |           <NewContractBadge /> | ||||||
|             {/* <DatabaseIcon className="h-5 w-5" /> */} |  | ||||||
|             {volumeLabel} |  | ||||||
|           </Row> |  | ||||||
|         )} |         )} | ||||||
|       </Row> |       </Row> | ||||||
|     </Col> |     </Col> | ||||||
|  |  | ||||||
|  | @ -9,6 +9,7 @@ import { | ||||||
|   UserIcon, |   UserIcon, | ||||||
|   UsersIcon, |   UsersIcon, | ||||||
|   XIcon, |   XIcon, | ||||||
|  |   SparklesIcon, | ||||||
| } from '@heroicons/react/solid' | } from '@heroicons/react/solid' | ||||||
| import clsx from 'clsx' | import clsx from 'clsx' | ||||||
| import Textarea from 'react-expanding-textarea' | import Textarea from 'react-expanding-textarea' | ||||||
|  | @ -47,6 +48,8 @@ import { User } from '../../../common/user' | ||||||
| import { Modal } from '../layout/modal' | import { Modal } from '../layout/modal' | ||||||
| import { trackClick } from '../../lib/firebase/tracking' | import { trackClick } from '../../lib/firebase/tracking' | ||||||
| import { firebaseLogin } from '../../lib/firebase/users' | import { firebaseLogin } from '../../lib/firebase/users' | ||||||
|  | import { DAY_MS } from '../../../common/util/time' | ||||||
|  | import NewContractBadge from '../new-contract-badge' | ||||||
| 
 | 
 | ||||||
| export function FeedItems(props: { | export function FeedItems(props: { | ||||||
|   contract: Contract |   contract: Contract | ||||||
|  | @ -385,19 +388,17 @@ export function FeedQuestion(props: { | ||||||
|   contractPath?: string |   contractPath?: string | ||||||
| }) { | }) { | ||||||
|   const { contract, showDescription } = props |   const { contract, showDescription } = props | ||||||
|   const { creatorName, creatorUsername, question, resolution, outcomeType } = |   const { | ||||||
|     contract |     creatorName, | ||||||
|  |     creatorUsername, | ||||||
|  |     question, | ||||||
|  |     outcomeType, | ||||||
|  |     volume, | ||||||
|  |     createdTime, | ||||||
|  |   } = contract | ||||||
|   const { volumeLabel } = contractMetrics(contract) |   const { volumeLabel } = contractMetrics(contract) | ||||||
|   const isBinary = outcomeType === 'BINARY' |   const isBinary = outcomeType === 'BINARY' | ||||||
| 
 |   const isNew = createdTime > Date.now() - DAY_MS | ||||||
|   // const closeMessage =
 |  | ||||||
|   //   contract.isResolved || !contract.closeTime ? null : (
 |  | ||||||
|   //     <>
 |  | ||||||
|   //       <span className="mx-2">•</span>
 |  | ||||||
|   //       {contract.closeTime > Date.now() ? 'Closes' : 'Closed'}
 |  | ||||||
|   //       <RelativeTimestamp time={contract.closeTime || 0} />
 |  | ||||||
|   //     </>
 |  | ||||||
|   //   )
 |  | ||||||
| 
 | 
 | ||||||
|   return ( |   return ( | ||||||
|     <> |     <> | ||||||
|  | @ -414,10 +415,15 @@ export function FeedQuestion(props: { | ||||||
|           />{' '} |           />{' '} | ||||||
|           asked |           asked | ||||||
|           {/* Currently hidden on mobile; ideally we'd fit this in somewhere. */} |           {/* Currently hidden on mobile; ideally we'd fit this in somewhere. */} | ||||||
|           <span className="float-right hidden text-gray-400 sm:inline"> |           <div className="relative -top-2 float-right "> | ||||||
|  |             {isNew || volume === 0 ? ( | ||||||
|  |               <NewContractBadge /> | ||||||
|  |             ) : ( | ||||||
|  |               <span className="hidden text-gray-400 sm:inline"> | ||||||
|                 {volumeLabel} |                 {volumeLabel} | ||||||
|             {/* {closeMessage} */} |  | ||||||
|               </span> |               </span> | ||||||
|  |             )} | ||||||
|  |           </div> | ||||||
|         </div> |         </div> | ||||||
|         <Col className="items-start justify-between gap-2 sm:flex-row sm:gap-4"> |         <Col className="items-start justify-between gap-2 sm:flex-row sm:gap-4"> | ||||||
|           <Col> |           <Col> | ||||||
|  |  | ||||||
							
								
								
									
										9
									
								
								web/components/new-contract-badge.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										9
									
								
								web/components/new-contract-badge.tsx
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,9 @@ | ||||||
|  | import { SparklesIcon } from '@heroicons/react/solid' | ||||||
|  | 
 | ||||||
|  | export default function NewContractBadge() { | ||||||
|  |   return ( | ||||||
|  |     <span className="inline-flex items-center gap-1 rounded-full bg-blue-100 px-3  py-0.5 text-sm font-medium text-blue-800"> | ||||||
|  |       <SparklesIcon className="h-4 w-4" aria-hidden="true" /> New | ||||||
|  |     </span> | ||||||
|  |   ) | ||||||
|  | } | ||||||
|  | @ -8,6 +8,14 @@ import { logInterpolation } from '../../common/util/math' | ||||||
| import { getRecommendedContracts } from '../../common/recommended-contracts' | import { getRecommendedContracts } from '../../common/recommended-contracts' | ||||||
| import { useSeenContracts } from './use-seen-contracts' | import { useSeenContracts } from './use-seen-contracts' | ||||||
| import { useGetUserBetContractIds, useUserBetContracts } from './use-user-bets' | import { useGetUserBetContractIds, useUserBetContracts } from './use-user-bets' | ||||||
|  | import { DAY_MS } from '../../common/util/time' | ||||||
|  | import { | ||||||
|  |   getProbability, | ||||||
|  |   getOutcomeProbability, | ||||||
|  |   getTopAnswer, | ||||||
|  | } from '../../common/calculate' | ||||||
|  | import { useTimeSinceFirstRender } from './use-time-since-first-render' | ||||||
|  | import { trackLatency } from '../lib/firebase/tracking' | ||||||
| 
 | 
 | ||||||
| const MAX_FEED_CONTRACTS = 75 | const MAX_FEED_CONTRACTS = 75 | ||||||
| 
 | 
 | ||||||
|  | @ -29,8 +37,15 @@ export const useAlgoFeed = ( | ||||||
| 
 | 
 | ||||||
|   const [algoFeed, setAlgoFeed] = useState<Contract[]>([]) |   const [algoFeed, setAlgoFeed] = useState<Contract[]>([]) | ||||||
| 
 | 
 | ||||||
|  |   const getTime = useTimeSinceFirstRender() | ||||||
|  | 
 | ||||||
|   useEffect(() => { |   useEffect(() => { | ||||||
|     if (initialContracts && initialBets && initialComments) { |     if ( | ||||||
|  |       initialContracts && | ||||||
|  |       initialBets && | ||||||
|  |       initialComments && | ||||||
|  |       yourBetContractIds | ||||||
|  |     ) { | ||||||
|       const eligibleContracts = initialContracts.filter( |       const eligibleContracts = initialContracts.filter( | ||||||
|         (c) => !c.isResolved && (c.closeTime ?? Infinity) > Date.now() |         (c) => !c.isResolved && (c.closeTime ?? Infinity) > Date.now() | ||||||
|       ) |       ) | ||||||
|  | @ -42,6 +57,7 @@ export const useAlgoFeed = ( | ||||||
|         seenContracts |         seenContracts | ||||||
|       ) |       ) | ||||||
|       setAlgoFeed(contracts) |       setAlgoFeed(contracts) | ||||||
|  |       trackLatency('feed', getTime()) | ||||||
|     } |     } | ||||||
|   }, [ |   }, [ | ||||||
|     initialBets, |     initialBets, | ||||||
|  | @ -49,6 +65,7 @@ export const useAlgoFeed = ( | ||||||
|     initialContracts, |     initialContracts, | ||||||
|     seenContracts, |     seenContracts, | ||||||
|     yourBetContractIds, |     yourBetContractIds, | ||||||
|  |     getTime, | ||||||
|   ]) |   ]) | ||||||
| 
 | 
 | ||||||
|   return algoFeed |   return algoFeed | ||||||
|  | @ -120,11 +137,13 @@ function getContractsActivityScores( | ||||||
|   ) |   ) | ||||||
| 
 | 
 | ||||||
|   const scoredContracts = contracts.map((contract) => { |   const scoredContracts = contracts.map((contract) => { | ||||||
|  |     const { outcomeType } = contract | ||||||
|  | 
 | ||||||
|     const seenTime = seenContracts[contract.id] |     const seenTime = seenContracts[contract.id] | ||||||
|     const lastCommentTime = contractMostRecentComment[contract.id]?.createdTime |     const lastCommentTime = contractMostRecentComment[contract.id]?.createdTime | ||||||
|     const hasNewComments = |     const hasNewComments = | ||||||
|       !seenTime || (lastCommentTime && lastCommentTime > seenTime) |       !seenTime || (lastCommentTime && lastCommentTime > seenTime) | ||||||
|     const newCommentScore = hasNewComments ? 1 : 0.75 |     const newCommentScore = hasNewComments ? 1 : 0.5 | ||||||
| 
 | 
 | ||||||
|     const commentCount = contractComments[contract.id]?.length ?? 0 |     const commentCount = contractComments[contract.id]?.length ?? 0 | ||||||
|     const betCount = contractBets[contract.id]?.length ?? 0 |     const betCount = contractBets[contract.id]?.length ?? 0 | ||||||
|  | @ -132,25 +151,39 @@ function getContractsActivityScores( | ||||||
|     const activityCountScore = |     const activityCountScore = | ||||||
|       0.5 + 0.5 * logInterpolation(0, 200, activtyCount) |       0.5 + 0.5 * logInterpolation(0, 200, activtyCount) | ||||||
| 
 | 
 | ||||||
|     const lastBetTime = contractMostRecentBet[contract.id]?.createdTime |     const lastBetTime = | ||||||
|     const timeSinceLastBet = !lastBetTime |       contractMostRecentBet[contract.id]?.createdTime ?? contract.createdTime | ||||||
|       ? contract.createdTime |     const timeSinceLastBet = Date.now() - lastBetTime | ||||||
|       : Date.now() - lastBetTime |     const daysAgo = timeSinceLastBet / DAY_MS | ||||||
|     const daysAgo = timeSinceLastBet / oneDayMs |  | ||||||
|     const timeAgoScore = 1 - logInterpolation(0, 3, daysAgo) |     const timeAgoScore = 1 - logInterpolation(0, 3, daysAgo) | ||||||
| 
 | 
 | ||||||
|     const score = newCommentScore * activityCountScore * timeAgoScore |     let prob = 0.5 | ||||||
|  |     if (outcomeType === 'BINARY') { | ||||||
|  |       prob = getProbability(contract) | ||||||
|  |     } else if (outcomeType === 'FREE_RESPONSE') { | ||||||
|  |       const topAnswer = getTopAnswer(contract) | ||||||
|  |       if (topAnswer) | ||||||
|  |         prob = Math.max(0.5, getOutcomeProbability(contract, topAnswer.id)) | ||||||
|  |     } | ||||||
|  |     const frac = 1 - Math.abs(prob - 0.5) ** 2 / 0.25 | ||||||
|  |     const probScore = 0.5 + frac * 0.5 | ||||||
|  | 
 | ||||||
|  |     const score = | ||||||
|  |       newCommentScore * activityCountScore * timeAgoScore * probScore | ||||||
| 
 | 
 | ||||||
|     // Map score to [0.5, 1] since no recent activty is not a deal breaker.
 |     // Map score to [0.5, 1] since no recent activty is not a deal breaker.
 | ||||||
|     const mappedScore = 0.5 + score / 2 |     const mappedScore = 0.5 + score / 2 | ||||||
|     return [contract.id, mappedScore] as [string, number] |     const newMappedScore = 0.75 + score / 4 | ||||||
|  | 
 | ||||||
|  |     const isNew = Date.now() < contract.createdTime + DAY_MS | ||||||
|  |     const activityScore = isNew ? newMappedScore : mappedScore | ||||||
|  | 
 | ||||||
|  |     return [contract.id, activityScore] as [string, number] | ||||||
|   }) |   }) | ||||||
| 
 | 
 | ||||||
|   return _.fromPairs(scoredContracts) |   return _.fromPairs(scoredContracts) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| const oneDayMs = 24 * 60 * 60 * 1000 |  | ||||||
| 
 |  | ||||||
| function getSeenContractsScore( | function getSeenContractsScore( | ||||||
|   contract: Contract, |   contract: Contract, | ||||||
|   seenContracts: { [contractId: string]: number } |   seenContracts: { [contractId: string]: number } | ||||||
|  | @ -160,7 +193,7 @@ function getSeenContractsScore( | ||||||
|     return 1 |     return 1 | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   const daysAgo = (Date.now() - lastSeen) / oneDayMs |   const daysAgo = (Date.now() - lastSeen) / DAY_MS | ||||||
| 
 | 
 | ||||||
|   if (daysAgo < 0.5) { |   if (daysAgo < 0.5) { | ||||||
|     const frac = logInterpolation(0, 0.5, daysAgo) |     const frac = logInterpolation(0, 0.5, daysAgo) | ||||||
|  |  | ||||||
|  | @ -6,6 +6,7 @@ import { | ||||||
|   listenForContracts, |   listenForContracts, | ||||||
|   listenForHotContracts, |   listenForHotContracts, | ||||||
|   listenForInactiveContracts, |   listenForInactiveContracts, | ||||||
|  |   listenForNewContracts, | ||||||
| } from '../lib/firebase/contracts' | } from '../lib/firebase/contracts' | ||||||
| import { listenForTaggedContracts } from '../lib/firebase/folds' | import { listenForTaggedContracts } from '../lib/firebase/folds' | ||||||
| 
 | 
 | ||||||
|  | @ -20,13 +21,22 @@ export const useContracts = () => { | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| export const useActiveContracts = () => { | export const useActiveContracts = () => { | ||||||
|   const [contracts, setContracts] = useState<Contract[] | undefined>() |   const [activeContracts, setActiveContracts] = useState< | ||||||
|  |     Contract[] | undefined | ||||||
|  |   >() | ||||||
|  |   const [newContracts, setNewContracts] = useState<Contract[] | undefined>() | ||||||
| 
 | 
 | ||||||
|   useEffect(() => { |   useEffect(() => { | ||||||
|     return listenForActiveContracts(setContracts) |     return listenForActiveContracts(setActiveContracts) | ||||||
|   }, []) |   }, []) | ||||||
| 
 | 
 | ||||||
|   return contracts |   useEffect(() => { | ||||||
|  |     return listenForNewContracts(setNewContracts) | ||||||
|  |   }, []) | ||||||
|  | 
 | ||||||
|  |   if (!activeContracts || !newContracts) return undefined | ||||||
|  | 
 | ||||||
|  |   return [...activeContracts, ...newContracts] | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| export const useInactiveContracts = () => { | export const useInactiveContracts = () => { | ||||||
|  |  | ||||||
							
								
								
									
										13
									
								
								web/hooks/use-time-since-first-render.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										13
									
								
								web/hooks/use-time-since-first-render.ts
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,13 @@ | ||||||
|  | import { useCallback, useEffect, useRef } from 'react' | ||||||
|  | 
 | ||||||
|  | export function useTimeSinceFirstRender() { | ||||||
|  |   const startTimeRef = useRef(0) | ||||||
|  |   useEffect(() => { | ||||||
|  |     startTimeRef.current = Date.now() | ||||||
|  |   }, []) | ||||||
|  | 
 | ||||||
|  |   return useCallback(() => { | ||||||
|  |     if (!startTimeRef.current) return 0 | ||||||
|  |     return Date.now() - startTimeRef.current | ||||||
|  |   }, []) | ||||||
|  | } | ||||||
|  | @ -54,14 +54,16 @@ export const useUserBetContracts = (userId: string | undefined) => { | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| export const useGetUserBetContractIds = (userId: string | undefined) => { | export const useGetUserBetContractIds = (userId: string | undefined) => { | ||||||
|   const [contractIds, setContractIds] = useState<string[]>([]) |   const [contractIds, setContractIds] = useState<string[] | undefined>() | ||||||
| 
 | 
 | ||||||
|   useEffect(() => { |   useEffect(() => { | ||||||
|  |     if (userId) { | ||||||
|       const key = `user-bet-contractIds-${userId}` |       const key = `user-bet-contractIds-${userId}` | ||||||
|       const userBetContractJson = localStorage.getItem(key) |       const userBetContractJson = localStorage.getItem(key) | ||||||
|       if (userBetContractJson) { |       if (userBetContractJson) { | ||||||
|         setContractIds(JSON.parse(userBetContractJson)) |         setContractIds(JSON.parse(userBetContractJson)) | ||||||
|       } |       } | ||||||
|  |     } | ||||||
|   }, [userId]) |   }, [userId]) | ||||||
| 
 | 
 | ||||||
|   return contractIds |   return contractIds | ||||||
|  |  | ||||||
|  | @ -22,6 +22,7 @@ import { getDpmProbability } from '../../../common/calculate-dpm' | ||||||
| import { createRNG, shuffle } from '../../../common/util/random' | import { createRNG, shuffle } from '../../../common/util/random' | ||||||
| import { getCpmmProbability } from '../../../common/calculate-cpmm' | import { getCpmmProbability } from '../../../common/calculate-cpmm' | ||||||
| import { formatMoney, formatPercent } from '../../../common/util/format' | import { formatMoney, formatPercent } from '../../../common/util/format' | ||||||
|  | import { DAY_MS } from '../../../common/util/time' | ||||||
| export type { Contract } | export type { Contract } | ||||||
| 
 | 
 | ||||||
| export function contractPath(contract: Contract) { | export function contractPath(contract: Contract) { | ||||||
|  | @ -162,6 +163,19 @@ export function listenForInactiveContracts( | ||||||
|   return listenForValues<Contract>(inactiveContractsQuery, setContracts) |   return listenForValues<Contract>(inactiveContractsQuery, setContracts) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | const newContractsQuery = query( | ||||||
|  |   contractCollection, | ||||||
|  |   where('isResolved', '==', false), | ||||||
|  |   where('volume7Days', '==', 0), | ||||||
|  |   where('createdTime', '>', Date.now() - 7 * DAY_MS) | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | export function listenForNewContracts( | ||||||
|  |   setContracts: (contracts: Contract[]) => void | ||||||
|  | ) { | ||||||
|  |   return listenForValues<Contract>(newContractsQuery, setContracts) | ||||||
|  | } | ||||||
|  | 
 | ||||||
| export function listenForContract( | export function listenForContract( | ||||||
|   contractId: string, |   contractId: string, | ||||||
|   setContract: (contract: Contract | null) => void |   setContract: (contract: Contract | null) => void | ||||||
|  |  | ||||||
|  | @ -2,7 +2,7 @@ import { doc, collection, setDoc } from 'firebase/firestore' | ||||||
| import _ from 'lodash' | import _ from 'lodash' | ||||||
| 
 | 
 | ||||||
| import { db } from './init' | import { db } from './init' | ||||||
| import { ClickEvent, View } from '../../../common/tracking' | import { ClickEvent, LatencyEvent, View } from '../../../common/tracking' | ||||||
| import { listenForLogin, User } from './users' | import { listenForLogin, User } from './users' | ||||||
| 
 | 
 | ||||||
| let user: User | null = null | let user: User | null = null | ||||||
|  | @ -34,3 +34,19 @@ export async function trackClick(contractId: string) { | ||||||
| 
 | 
 | ||||||
|   return await setDoc(ref, clickEvent) |   return await setDoc(ref, clickEvent) | ||||||
| } | } | ||||||
|  | 
 | ||||||
|  | export async function trackLatency( | ||||||
|  |   type: 'feed' | 'portfolio', | ||||||
|  |   latency: number | ||||||
|  | ) { | ||||||
|  |   if (!user) return | ||||||
|  |   const ref = doc(collection(db, 'private-users', user.id, 'latency')) | ||||||
|  | 
 | ||||||
|  |   const latencyEvent: LatencyEvent = { | ||||||
|  |     type, | ||||||
|  |     latency, | ||||||
|  |     timestamp: Date.now(), | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   return await setDoc(ref, latencyEvent) | ||||||
|  | } | ||||||
|  |  | ||||||
		Loading…
	
		Reference in New Issue
	
	Block a user