Merge branch 'main' into theoremone
This commit is contained in:
		
						commit
						e29174d622
					
				| 
						 | 
				
			
			@ -8,6 +8,7 @@ import { Bet } from '../../common/bet'
 | 
			
		|||
import { getUser, payUser } from './utils'
 | 
			
		||||
import { sendMarketResolutionEmail } from './emails'
 | 
			
		||||
import { getPayouts, getPayoutsMultiOutcome } from '../../common/payouts'
 | 
			
		||||
import { removeUndefinedProps } from '../../common/util/object'
 | 
			
		||||
 | 
			
		||||
export const resolveMarket = functions
 | 
			
		||||
  .runWith({ minInstances: 1 })
 | 
			
		||||
| 
						 | 
				
			
			@ -31,7 +32,7 @@ export const resolveMarket = functions
 | 
			
		|||
      if (!contractSnap.exists)
 | 
			
		||||
        return { status: 'error', message: 'Invalid contract' }
 | 
			
		||||
      const contract = contractSnap.data() as Contract
 | 
			
		||||
      const { creatorId, outcomeType } = contract
 | 
			
		||||
      const { creatorId, outcomeType, closeTime } = contract
 | 
			
		||||
 | 
			
		||||
      if (outcomeType === 'BINARY') {
 | 
			
		||||
        if (!['YES', 'NO', 'MKT', 'CANCEL'].includes(outcome))
 | 
			
		||||
| 
						 | 
				
			
			@ -68,15 +69,21 @@ export const resolveMarket = functions
 | 
			
		|||
      const resolutionProbability =
 | 
			
		||||
        probabilityInt !== undefined ? probabilityInt / 100 : undefined
 | 
			
		||||
 | 
			
		||||
      await contractDoc.update({
 | 
			
		||||
        isResolved: true,
 | 
			
		||||
        resolution: outcome,
 | 
			
		||||
        resolutionTime: Date.now(),
 | 
			
		||||
        ...(resolutionProbability === undefined
 | 
			
		||||
          ? {}
 | 
			
		||||
          : { resolutionProbability }),
 | 
			
		||||
        ...(resolutions === undefined ? {} : { resolutions }),
 | 
			
		||||
      })
 | 
			
		||||
      const resolutionTime = Date.now()
 | 
			
		||||
      const newCloseTime = closeTime
 | 
			
		||||
        ? Math.min(closeTime, resolutionTime)
 | 
			
		||||
        : closeTime
 | 
			
		||||
 | 
			
		||||
      await contractDoc.update(
 | 
			
		||||
        removeUndefinedProps({
 | 
			
		||||
          isResolved: true,
 | 
			
		||||
          resolution: outcome,
 | 
			
		||||
          resolutionTime,
 | 
			
		||||
          closeTime: newCloseTime,
 | 
			
		||||
          resolutionProbability,
 | 
			
		||||
          resolutions,
 | 
			
		||||
        })
 | 
			
		||||
      )
 | 
			
		||||
 | 
			
		||||
      console.log('contract ', contractId, 'resolved to:', outcome)
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -157,8 +157,8 @@ export function BetsList(props: { user: User }) {
 | 
			
		|||
        >
 | 
			
		||||
          <option value="value">By value</option>
 | 
			
		||||
          <option value="profit">By profit</option>
 | 
			
		||||
          <option value="newest">Newest</option>
 | 
			
		||||
          <option value="settled">Settled</option>
 | 
			
		||||
          <option value="newest">Most recent</option>
 | 
			
		||||
          <option value="settled">Resolved</option>
 | 
			
		||||
        </select>
 | 
			
		||||
      </Col>
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -358,22 +358,26 @@ export function MyBetsSummary(props: {
 | 
			
		|||
                  {formatMoney(expectation)}
 | 
			
		||||
                </div>
 | 
			
		||||
              </Col> */}
 | 
			
		||||
              <Col>
 | 
			
		||||
                <div className="whitespace-nowrap text-sm text-gray-500">
 | 
			
		||||
                  Payout if <YesLabel />
 | 
			
		||||
                </div>
 | 
			
		||||
                <div className="whitespace-nowrap">
 | 
			
		||||
                  {formatMoney(yesWinnings)}
 | 
			
		||||
                </div>
 | 
			
		||||
              </Col>
 | 
			
		||||
              <Col>
 | 
			
		||||
                <div className="whitespace-nowrap text-sm text-gray-500">
 | 
			
		||||
                  Payout if <NoLabel />
 | 
			
		||||
                </div>
 | 
			
		||||
                <div className="whitespace-nowrap">
 | 
			
		||||
                  {formatMoney(noWinnings)}
 | 
			
		||||
                </div>
 | 
			
		||||
              </Col>
 | 
			
		||||
              {isBinary && (
 | 
			
		||||
                <>
 | 
			
		||||
                  <Col>
 | 
			
		||||
                    <div className="whitespace-nowrap text-sm text-gray-500">
 | 
			
		||||
                      Payout if <YesLabel />
 | 
			
		||||
                    </div>
 | 
			
		||||
                    <div className="whitespace-nowrap">
 | 
			
		||||
                      {formatMoney(yesWinnings)}
 | 
			
		||||
                    </div>
 | 
			
		||||
                  </Col>
 | 
			
		||||
                  <Col>
 | 
			
		||||
                    <div className="whitespace-nowrap text-sm text-gray-500">
 | 
			
		||||
                      Payout if <NoLabel />
 | 
			
		||||
                    </div>
 | 
			
		||||
                    <div className="whitespace-nowrap">
 | 
			
		||||
                      {formatMoney(noWinnings)}
 | 
			
		||||
                    </div>
 | 
			
		||||
                  </Col>
 | 
			
		||||
                </>
 | 
			
		||||
              )}
 | 
			
		||||
              <Col>
 | 
			
		||||
                <div className="whitespace-nowrap text-sm text-gray-500">
 | 
			
		||||
                  {isBinary ? (
 | 
			
		||||
| 
						 | 
				
			
			@ -418,9 +422,10 @@ export function ContractBetsTable(props: {
 | 
			
		|||
        <thead>
 | 
			
		||||
          <tr className="p-2">
 | 
			
		||||
            <th></th>
 | 
			
		||||
            <th>{isResolved ? <>Payout</> : <>Sale price</>}</th>
 | 
			
		||||
            <th>Outcome</th>
 | 
			
		||||
            <th>Amount</th>
 | 
			
		||||
            <th>{isResolved ? <>Payout</> : <>Sale price</>}</th>
 | 
			
		||||
            {!isResolved && <th>Payout if chosen</th>}
 | 
			
		||||
            <th>Probability</th>
 | 
			
		||||
            <th>Shares</th>
 | 
			
		||||
            <th>Date</th>
 | 
			
		||||
| 
						 | 
				
			
			@ -471,6 +476,11 @@ function BetRow(props: { bet: Bet; contract: Contract; saleBet?: Bet }) {
 | 
			
		|||
    )
 | 
			
		||||
  )
 | 
			
		||||
 | 
			
		||||
  const payoutIfChosenDisplay =
 | 
			
		||||
    bet.outcome === '0' && bet.isAnte
 | 
			
		||||
      ? 'N/A'
 | 
			
		||||
      : formatMoney(calculatePayout(contract, bet, bet.outcome))
 | 
			
		||||
 | 
			
		||||
  return (
 | 
			
		||||
    <tr>
 | 
			
		||||
      <td className="text-neutral">
 | 
			
		||||
| 
						 | 
				
			
			@ -478,11 +488,12 @@ function BetRow(props: { bet: Bet; contract: Contract; saleBet?: Bet }) {
 | 
			
		|||
          <SellButton contract={contract} bet={bet} />
 | 
			
		||||
        )}
 | 
			
		||||
      </td>
 | 
			
		||||
      <td>{saleDisplay}</td>
 | 
			
		||||
      <td>
 | 
			
		||||
        <OutcomeLabel outcome={outcome} />
 | 
			
		||||
      </td>
 | 
			
		||||
      <td>{formatMoney(amount)}</td>
 | 
			
		||||
      <td>{saleDisplay}</td>
 | 
			
		||||
      {!isResolved && <td>{payoutIfChosenDisplay}</td>}
 | 
			
		||||
      <td>
 | 
			
		||||
        {formatPercent(probBefore)} → {formatPercent(probAfter)}
 | 
			
		||||
      </td>
 | 
			
		||||
| 
						 | 
				
			
			@ -499,6 +510,7 @@ function SellButton(props: { contract: Contract; bet: Bet }) {
 | 
			
		|||
  }, [])
 | 
			
		||||
 | 
			
		||||
  const { contract, bet } = props
 | 
			
		||||
  const isBinary = contract.outcomeType === 'BINARY'
 | 
			
		||||
  const [isSubmitting, setIsSubmitting] = useState(false)
 | 
			
		||||
 | 
			
		||||
  const initialProb = getOutcomeProbability(
 | 
			
		||||
| 
						 | 
				
			
			@ -537,8 +549,9 @@ function SellButton(props: { contract: Contract; bet: Bet }) {
 | 
			
		|||
      </div>
 | 
			
		||||
 | 
			
		||||
      <div className="mt-2 mb-1 text-sm text-gray-500">
 | 
			
		||||
        Implied probability: {formatPercent(initialProb)} →{' '}
 | 
			
		||||
        {formatPercent(outcomeProb)}
 | 
			
		||||
        ({isBinary ? 'Updated' : <OutcomeLabel outcome={bet.outcome} />}{' '}
 | 
			
		||||
        probability: {formatPercent(initialProb)} → {formatPercent(outcomeProb)}
 | 
			
		||||
        )
 | 
			
		||||
      </div>
 | 
			
		||||
    </ConfirmationButton>
 | 
			
		||||
  )
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -790,3 +790,27 @@ export function ContractFeed(props: {
 | 
			
		|||
    </div>
 | 
			
		||||
  )
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function ContractSummaryFeed(props: {
 | 
			
		||||
  contract: Contract
 | 
			
		||||
  betRowClassName?: string
 | 
			
		||||
}) {
 | 
			
		||||
  const { contract, betRowClassName } = props
 | 
			
		||||
  const { outcomeType } = contract
 | 
			
		||||
  const isBinary = outcomeType === 'BINARY'
 | 
			
		||||
 | 
			
		||||
  return (
 | 
			
		||||
    <div className="flow-root pr-2 md:pr-0">
 | 
			
		||||
      <div className={clsx(tradingAllowed(contract) ? '' : '-mb-8')}>
 | 
			
		||||
        <div className="relative pb-8">
 | 
			
		||||
          <div className="relative flex items-start space-x-3">
 | 
			
		||||
            <FeedQuestion contract={contract} />
 | 
			
		||||
          </div>
 | 
			
		||||
        </div>
 | 
			
		||||
      </div>
 | 
			
		||||
      {isBinary && tradingAllowed(contract) && (
 | 
			
		||||
        <BetRow contract={contract} className={clsx('mb-2', betRowClassName)} />
 | 
			
		||||
      )}
 | 
			
		||||
    </div>
 | 
			
		||||
  )
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -205,9 +205,11 @@ export function SearchableGrid(props: {
 | 
			
		|||
}) {
 | 
			
		||||
  const { contracts, query, setQuery, sort, setSort, byOneCreator } = props
 | 
			
		||||
 | 
			
		||||
  const queryWords = query.toLowerCase().split(' ')
 | 
			
		||||
  function check(corpus: String) {
 | 
			
		||||
    return corpus.toLowerCase().includes(query.toLowerCase())
 | 
			
		||||
    return queryWords.every((word) => corpus.toLowerCase().includes(word))
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  let matches = contracts.filter(
 | 
			
		||||
    (c) =>
 | 
			
		||||
      check(c.question) ||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -32,7 +32,7 @@ function FollowFoldButton(props: {
 | 
			
		|||
      className={clsx(
 | 
			
		||||
        'rounded-full border-2 px-4 py-1 shadow-md',
 | 
			
		||||
        'cursor-pointer',
 | 
			
		||||
        followed ? 'bg-gray-300 border-gray-300' : 'bg-white'
 | 
			
		||||
        followed ? 'border-gray-300 bg-gray-300' : 'bg-white'
 | 
			
		||||
      )}
 | 
			
		||||
      onClick={onClick}
 | 
			
		||||
    >
 | 
			
		||||
| 
						 | 
				
			
			@ -101,7 +101,7 @@ export const FastFoldFollowing = (props: {
 | 
			
		|||
        ]}
 | 
			
		||||
      />
 | 
			
		||||
 | 
			
		||||
      <Spacer h={10} />
 | 
			
		||||
      <Spacer h={5} />
 | 
			
		||||
    </>
 | 
			
		||||
  )
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -54,6 +54,10 @@ function getNavigationOptions(
 | 
			
		|||
      name: 'Your trades',
 | 
			
		||||
      href: '/trades',
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
      name: 'Add funds',
 | 
			
		||||
      href: '/add-funds',
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
      name: 'Leaderboards',
 | 
			
		||||
      href: '/leaderboards',
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -5,6 +5,7 @@ import {
 | 
			
		|||
  listenForActiveContracts,
 | 
			
		||||
  listenForContracts,
 | 
			
		||||
  listenForHotContracts,
 | 
			
		||||
  listenForInactiveContracts,
 | 
			
		||||
} from '../lib/firebase/contracts'
 | 
			
		||||
import { listenForTaggedContracts } from '../lib/firebase/folds'
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -28,6 +29,16 @@ export const useActiveContracts = () => {
 | 
			
		|||
  return contracts
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export const useInactiveContracts = () => {
 | 
			
		||||
  const [contracts, setContracts] = useState<Contract[] | undefined>()
 | 
			
		||||
 | 
			
		||||
  useEffect(() => {
 | 
			
		||||
    return listenForInactiveContracts(setContracts)
 | 
			
		||||
  }, [])
 | 
			
		||||
 | 
			
		||||
  return contracts
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export const useUpdatedContracts = (initialContracts: Contract[]) => {
 | 
			
		||||
  const [contracts, setContracts] = useState(initialContracts)
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,5 +1,5 @@
 | 
			
		|||
import _ from 'lodash'
 | 
			
		||||
import { useRef } from 'react'
 | 
			
		||||
import { useMemo, useRef } from 'react'
 | 
			
		||||
 | 
			
		||||
import { Fold } from '../../common/fold'
 | 
			
		||||
import { User } from '../../common/user'
 | 
			
		||||
| 
						 | 
				
			
			@ -9,7 +9,7 @@ import { Comment, getRecentComments } from '../lib/firebase/comments'
 | 
			
		|||
import { Contract, getActiveContracts } from '../lib/firebase/contracts'
 | 
			
		||||
import { listAllFolds } from '../lib/firebase/folds'
 | 
			
		||||
import { findActiveContracts } from '../pages/activity'
 | 
			
		||||
import { useActiveContracts } from './use-contracts'
 | 
			
		||||
import { useInactiveContracts } from './use-contracts'
 | 
			
		||||
import { useFollowedFolds } from './use-fold'
 | 
			
		||||
import { useUserBetContracts } from './use-user-bets'
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -28,24 +28,15 @@ export const getAllContractInfo = async () => {
 | 
			
		|||
  return { contracts, recentBets, recentComments, folds }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export const useFindActiveContracts = (
 | 
			
		||||
  props: {
 | 
			
		||||
    contracts: Contract[]
 | 
			
		||||
    folds: Fold[]
 | 
			
		||||
    recentBets: Bet[]
 | 
			
		||||
    recentComments: Comment[]
 | 
			
		||||
  },
 | 
			
		||||
  user: User | undefined | null
 | 
			
		||||
export const useFilterYourContracts = (
 | 
			
		||||
  user: User | undefined | null,
 | 
			
		||||
  folds: Fold[],
 | 
			
		||||
  contracts: Contract[]
 | 
			
		||||
) => {
 | 
			
		||||
  const { recentBets, recentComments } = props
 | 
			
		||||
  const contracts = useActiveContracts() ?? props.contracts
 | 
			
		||||
 | 
			
		||||
  const followedFoldIds = useFollowedFolds(user)
 | 
			
		||||
 | 
			
		||||
  const followedFolds = filterDefined(
 | 
			
		||||
    (followedFoldIds ?? []).map((id) =>
 | 
			
		||||
      props.folds.find((fold) => fold.id === id)
 | 
			
		||||
    )
 | 
			
		||||
    (followedFoldIds ?? []).map((id) => folds.find((fold) => fold.id === id))
 | 
			
		||||
  )
 | 
			
		||||
 | 
			
		||||
  // Save the initial followed fold slugs.
 | 
			
		||||
| 
						 | 
				
			
			@ -64,20 +55,33 @@ export const useFindActiveContracts = (
 | 
			
		|||
    : undefined
 | 
			
		||||
 | 
			
		||||
  // Show no contracts before your info is loaded.
 | 
			
		||||
  let feedContracts: Contract[] = []
 | 
			
		||||
  let yourContracts: Contract[] = []
 | 
			
		||||
  if (yourBetContracts && followedFoldIds) {
 | 
			
		||||
    // Show all contracts if no folds are followed.
 | 
			
		||||
    if (followedFoldIds.length === 0) feedContracts = contracts
 | 
			
		||||
    if (followedFoldIds.length === 0) yourContracts = contracts
 | 
			
		||||
    else
 | 
			
		||||
      feedContracts = contracts.filter(
 | 
			
		||||
      yourContracts = contracts.filter(
 | 
			
		||||
        (contract) =>
 | 
			
		||||
          contract.lowercaseTags.some((tag) => tagSet.has(tag)) ||
 | 
			
		||||
          yourBetContracts.has(contract.id)
 | 
			
		||||
      )
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  return {
 | 
			
		||||
    yourContracts,
 | 
			
		||||
    initialFollowedFoldSlugs,
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export const useFindActiveContracts = (props: {
 | 
			
		||||
  contracts: Contract[]
 | 
			
		||||
  recentBets: Bet[]
 | 
			
		||||
  recentComments: Comment[]
 | 
			
		||||
}) => {
 | 
			
		||||
  const { contracts, recentBets, recentComments } = props
 | 
			
		||||
 | 
			
		||||
  const activeContracts = findActiveContracts(
 | 
			
		||||
    feedContracts,
 | 
			
		||||
    contracts,
 | 
			
		||||
    recentComments,
 | 
			
		||||
    recentBets
 | 
			
		||||
  )
 | 
			
		||||
| 
						 | 
				
			
			@ -101,6 +105,24 @@ export const useFindActiveContracts = (
 | 
			
		|||
    activeContracts,
 | 
			
		||||
    activeBets,
 | 
			
		||||
    activeComments,
 | 
			
		||||
    initialFollowedFoldSlugs,
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export const useExploreContracts = (maxContracts = 75) => {
 | 
			
		||||
  const inactiveContracts = useInactiveContracts()
 | 
			
		||||
 | 
			
		||||
  const contractsDict = _.fromPairs(
 | 
			
		||||
    (inactiveContracts ?? []).map((c) => [c.id, c])
 | 
			
		||||
  )
 | 
			
		||||
 | 
			
		||||
  // Preserve random ordering once inactiveContracts loaded.
 | 
			
		||||
  const exploreContractIds = useMemo(
 | 
			
		||||
    () => _.shuffle(Object.keys(contractsDict)),
 | 
			
		||||
    // eslint-disable-next-line react-hooks/exhaustive-deps
 | 
			
		||||
    [!!inactiveContracts]
 | 
			
		||||
  ).slice(0, maxContracts)
 | 
			
		||||
 | 
			
		||||
  if (!inactiveContracts) return undefined
 | 
			
		||||
 | 
			
		||||
  return filterDefined(exploreContractIds.map((id) => contractsDict[id]))
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -115,7 +115,7 @@ export function listenForContracts(
 | 
			
		|||
  return listenForValues<Contract>(q, setContracts)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const activeContracts = query(
 | 
			
		||||
const activeContractsQuery = query(
 | 
			
		||||
  contractCollection,
 | 
			
		||||
  where('isResolved', '==', false),
 | 
			
		||||
  where('visibility', '==', 'public'),
 | 
			
		||||
| 
						 | 
				
			
			@ -123,13 +123,31 @@ const activeContracts = query(
 | 
			
		|||
)
 | 
			
		||||
 | 
			
		||||
export function getActiveContracts() {
 | 
			
		||||
  return getValues<Contract>(activeContracts)
 | 
			
		||||
  return getValues<Contract>(activeContractsQuery)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function listenForActiveContracts(
 | 
			
		||||
  setContracts: (contracts: Contract[]) => void
 | 
			
		||||
) {
 | 
			
		||||
  return listenForValues<Contract>(activeContracts, setContracts)
 | 
			
		||||
  return listenForValues<Contract>(activeContractsQuery, setContracts)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const inactiveContractsQuery = query(
 | 
			
		||||
  contractCollection,
 | 
			
		||||
  where('isResolved', '==', false),
 | 
			
		||||
  where('closeTime', '>', Date.now()),
 | 
			
		||||
  where('visibility', '==', 'public'),
 | 
			
		||||
  where('volume24Hours', '==', 0)
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
export function getInactiveContracts() {
 | 
			
		||||
  return getValues<Contract>(inactiveContractsQuery)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function listenForInactiveContracts(
 | 
			
		||||
  setContracts: (contracts: Contract[]) => void
 | 
			
		||||
) {
 | 
			
		||||
  return listenForValues<Contract>(inactiveContractsQuery, setContracts)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function listenForContract(
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -188,12 +188,8 @@ function BetsSection(props: {
 | 
			
		|||
  return (
 | 
			
		||||
    <div>
 | 
			
		||||
      <Title className="px-2" text="Your trades" />
 | 
			
		||||
      {isBinary && (
 | 
			
		||||
        <>
 | 
			
		||||
          <MyBetsSummary className="px-2" contract={contract} bets={userBets} />
 | 
			
		||||
          <Spacer h={6} />
 | 
			
		||||
        </>
 | 
			
		||||
      )}
 | 
			
		||||
      <MyBetsSummary className="px-2" contract={contract} bets={userBets} />
 | 
			
		||||
      <Spacer h={6} />
 | 
			
		||||
      <ContractBetsTable contract={contract} bets={userBets} />
 | 
			
		||||
      <Spacer h={12} />
 | 
			
		||||
    </div>
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -139,6 +139,15 @@ function Contents() {
 | 
			
		|||
        bettors that are correct more often will gain influence, leading to
 | 
			
		||||
        better-calibrated forecasts over time.
 | 
			
		||||
      </p>
 | 
			
		||||
      <p>
 | 
			
		||||
        Since our launch, we've seen hundreds of users trade each day, on over a
 | 
			
		||||
        thousand different markets! You can track the popularity of our platform
 | 
			
		||||
        at{' '}
 | 
			
		||||
        <a href="http://manifold.markets/analytics">
 | 
			
		||||
          http://manifold.markets/analytics
 | 
			
		||||
        </a>
 | 
			
		||||
        .
 | 
			
		||||
      </p>
 | 
			
		||||
      <h3 id="how-are-markets-resolved-">How are markets resolved?</h3>
 | 
			
		||||
      <p>
 | 
			
		||||
        The creator of the prediction market decides the outcome and earns{' '}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,5 +1,5 @@
 | 
			
		|||
import _ from 'lodash'
 | 
			
		||||
import { ContractFeed } from '../components/contract-feed'
 | 
			
		||||
import { ContractFeed, ContractSummaryFeed } from '../components/contract-feed'
 | 
			
		||||
import { Page } from '../components/page'
 | 
			
		||||
import { Contract } from '../lib/firebase/contracts'
 | 
			
		||||
import { Comment } from '../lib/firebase/comments'
 | 
			
		||||
| 
						 | 
				
			
			@ -99,6 +99,24 @@ export function ActivityFeed(props: {
 | 
			
		|||
  )
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function SummaryActivityFeed(props: { contracts: Contract[] }) {
 | 
			
		||||
  const { contracts } = props
 | 
			
		||||
 | 
			
		||||
  return (
 | 
			
		||||
    <Col className="items-center">
 | 
			
		||||
      <Col className="w-full max-w-3xl">
 | 
			
		||||
        <Col className="w-full divide-y divide-gray-300 self-center bg-white">
 | 
			
		||||
          {contracts.map((contract) => (
 | 
			
		||||
            <div key={contract.id} className="py-6 px-2 sm:px-4">
 | 
			
		||||
              <ContractSummaryFeed contract={contract} />
 | 
			
		||||
            </div>
 | 
			
		||||
          ))}
 | 
			
		||||
        </Col>
 | 
			
		||||
      </Col>
 | 
			
		||||
    </Col>
 | 
			
		||||
  )
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export default function ActivityPage() {
 | 
			
		||||
  return (
 | 
			
		||||
    <Page>
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -19,11 +19,11 @@ export default function AddFundsPage() {
 | 
			
		|||
      <SEO title="Add funds" description="Add funds" url="/add-funds" />
 | 
			
		||||
 | 
			
		||||
      <Col className="items-center">
 | 
			
		||||
        <Col>
 | 
			
		||||
          <Title text="Get Manifold Dollars" />
 | 
			
		||||
        <Col className="h-full rounded bg-white p-4 py-8 sm:p-8 sm:shadow-md">
 | 
			
		||||
          <Title className="!mt-0" text="Get Manifold Dollars" />
 | 
			
		||||
          <img
 | 
			
		||||
            className="mt-6 block"
 | 
			
		||||
            src="/praying-mantis-light.svg"
 | 
			
		||||
            className="mb-6 block -scale-x-100 self-center"
 | 
			
		||||
            src="/stylized-crane-black.png"
 | 
			
		||||
            width={200}
 | 
			
		||||
            height={200}
 | 
			
		||||
          />
 | 
			
		||||
| 
						 | 
				
			
			@ -50,7 +50,7 @@ export default function AddFundsPage() {
 | 
			
		|||
          <form
 | 
			
		||||
            action={checkoutURL(user?.id || '', amountSelected)}
 | 
			
		||||
            method="POST"
 | 
			
		||||
            className="mt-12"
 | 
			
		||||
            className="mt-8"
 | 
			
		||||
          >
 | 
			
		||||
            <button
 | 
			
		||||
              type="submit"
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -26,6 +26,7 @@ export type LiteMarket = {
 | 
			
		|||
  volume24Hours: number
 | 
			
		||||
  isResolved: boolean
 | 
			
		||||
  resolution?: string
 | 
			
		||||
  resolutionTime?: number
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export type FullMarket = LiteMarket & {
 | 
			
		||||
| 
						 | 
				
			
			@ -54,6 +55,7 @@ export function toLiteMarket({
 | 
			
		|||
  volume24Hours,
 | 
			
		||||
  isResolved,
 | 
			
		||||
  resolution,
 | 
			
		||||
  resolutionTime,
 | 
			
		||||
}: Contract): LiteMarket {
 | 
			
		||||
  return {
 | 
			
		||||
    id,
 | 
			
		||||
| 
						 | 
				
			
			@ -61,7 +63,10 @@ export function toLiteMarket({
 | 
			
		|||
    creatorName,
 | 
			
		||||
    createdTime,
 | 
			
		||||
    creatorAvatarUrl,
 | 
			
		||||
    closeTime,
 | 
			
		||||
    closeTime:
 | 
			
		||||
      resolutionTime && closeTime
 | 
			
		||||
        ? Math.min(resolutionTime, closeTime)
 | 
			
		||||
        : closeTime,
 | 
			
		||||
    question,
 | 
			
		||||
    description,
 | 
			
		||||
    tags,
 | 
			
		||||
| 
						 | 
				
			
			@ -72,5 +77,6 @@ export function toLiteMarket({
 | 
			
		|||
    volume24Hours,
 | 
			
		||||
    isResolved,
 | 
			
		||||
    resolution,
 | 
			
		||||
    resolutionTime,
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -41,6 +41,7 @@ import { SEO } from '../../../components/SEO'
 | 
			
		|||
import { useTaggedContracts } from '../../../hooks/use-contracts'
 | 
			
		||||
import { Linkify } from '../../../components/linkify'
 | 
			
		||||
import { usePropz } from '../../../hooks/use-propz'
 | 
			
		||||
import { filterDefined } from '../../../../common/util/array'
 | 
			
		||||
 | 
			
		||||
export async function getStaticPropz(props: { params: { slugs: string[] } }) {
 | 
			
		||||
  const { slugs } = props.params
 | 
			
		||||
| 
						 | 
				
			
			@ -182,9 +183,11 @@ export default function FoldPage(props: {
 | 
			
		|||
    taggedContracts.map((contract) => [contract.id, contract])
 | 
			
		||||
  )
 | 
			
		||||
 | 
			
		||||
  const contracts = props.contracts.map((contract) => contractsMap[contract.id])
 | 
			
		||||
  const activeContracts = props.activeContracts.map(
 | 
			
		||||
    (contract) => contractsMap[contract.id]
 | 
			
		||||
  const contracts = filterDefined(
 | 
			
		||||
    props.contracts.map((contract) => contractsMap[contract.id])
 | 
			
		||||
  )
 | 
			
		||||
  const activeContracts = filterDefined(
 | 
			
		||||
    props.activeContracts.map((contract) => contractsMap[contract.id])
 | 
			
		||||
  )
 | 
			
		||||
 | 
			
		||||
  if (fold === null || !foldSubpages.includes(page) || slugs[2]) {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,9 +1,12 @@
 | 
			
		|||
import React from 'react'
 | 
			
		||||
import React, { useState } from 'react'
 | 
			
		||||
import Router from 'next/router'
 | 
			
		||||
import { SparklesIcon, GlobeAltIcon } from '@heroicons/react/solid'
 | 
			
		||||
import clsx from 'clsx'
 | 
			
		||||
import _ from 'lodash'
 | 
			
		||||
 | 
			
		||||
import { Contract } from '../lib/firebase/contracts'
 | 
			
		||||
import { Page } from '../components/page'
 | 
			
		||||
import { ActivityFeed } from './activity'
 | 
			
		||||
import { ActivityFeed, SummaryActivityFeed } from './activity'
 | 
			
		||||
import { Comment } from '../lib/firebase/comments'
 | 
			
		||||
import { Bet } from '../lib/firebase/bets'
 | 
			
		||||
import FeedCreate from '../components/feed-create'
 | 
			
		||||
| 
						 | 
				
			
			@ -13,14 +16,16 @@ import { useUser } from '../hooks/use-user'
 | 
			
		|||
import { Fold } from '../../common/fold'
 | 
			
		||||
import { LoadingIndicator } from '../components/loading-indicator'
 | 
			
		||||
import { Row } from '../components/layout/row'
 | 
			
		||||
import { SparklesIcon } from '@heroicons/react/solid'
 | 
			
		||||
import { FastFoldFollowing } from '../components/fast-fold-following'
 | 
			
		||||
import {
 | 
			
		||||
  getAllContractInfo,
 | 
			
		||||
  useExploreContracts,
 | 
			
		||||
  useFilterYourContracts,
 | 
			
		||||
  useFindActiveContracts,
 | 
			
		||||
} from '../hooks/use-active-contracts'
 | 
			
		||||
} from '../hooks/use-find-active-contracts'
 | 
			
		||||
import { useGetRecentBets } from '../hooks/use-bets'
 | 
			
		||||
import { usePropz } from '../hooks/use-propz'
 | 
			
		||||
import { useActiveContracts } from '../hooks/use-contracts'
 | 
			
		||||
 | 
			
		||||
export async function getStaticPropz() {
 | 
			
		||||
  const contractInfo = await getAllContractInfo()
 | 
			
		||||
| 
						 | 
				
			
			@ -42,21 +47,28 @@ const Home = (props: {
 | 
			
		|||
    folds: [],
 | 
			
		||||
    recentComments: [],
 | 
			
		||||
  }
 | 
			
		||||
  const { contracts, folds, recentComments } = props
 | 
			
		||||
  const { folds, recentComments } = props
 | 
			
		||||
  const user = useUser()
 | 
			
		||||
 | 
			
		||||
  const recentBets = useGetRecentBets()
 | 
			
		||||
 | 
			
		||||
  const {
 | 
			
		||||
    activeContracts,
 | 
			
		||||
    activeBets,
 | 
			
		||||
    activeComments,
 | 
			
		||||
    initialFollowedFoldSlugs,
 | 
			
		||||
  } = useFindActiveContracts(
 | 
			
		||||
    { contracts, folds, recentBets: recentBets ?? [], recentComments },
 | 
			
		||||
    user
 | 
			
		||||
  const contracts = useActiveContracts() ?? props.contracts
 | 
			
		||||
  const { yourContracts, initialFollowedFoldSlugs } = useFilterYourContracts(
 | 
			
		||||
    user,
 | 
			
		||||
    folds,
 | 
			
		||||
    contracts
 | 
			
		||||
  )
 | 
			
		||||
 | 
			
		||||
  const recentBets = useGetRecentBets()
 | 
			
		||||
  const { activeContracts, activeBets, activeComments } =
 | 
			
		||||
    useFindActiveContracts({
 | 
			
		||||
      contracts: yourContracts,
 | 
			
		||||
      recentBets: recentBets ?? [],
 | 
			
		||||
      recentComments,
 | 
			
		||||
    })
 | 
			
		||||
 | 
			
		||||
  const exploreContracts = useExploreContracts()
 | 
			
		||||
 | 
			
		||||
  const [feedMode, setFeedMode] = useState<'activity' | 'explore'>('activity')
 | 
			
		||||
 | 
			
		||||
  if (user === null) {
 | 
			
		||||
    Router.replace('/')
 | 
			
		||||
    return <></>
 | 
			
		||||
| 
						 | 
				
			
			@ -77,22 +89,52 @@ const Home = (props: {
 | 
			
		|||
              />
 | 
			
		||||
            )}
 | 
			
		||||
 | 
			
		||||
          <Spacer h={5} />
 | 
			
		||||
 | 
			
		||||
          <Col className="mx-3 mb-3 gap-2 text-sm text-gray-800 sm:flex-row">
 | 
			
		||||
            <Row className="gap-2">
 | 
			
		||||
              <SparklesIcon className="inline h-5 w-5" aria-hidden="true" />
 | 
			
		||||
              <span className="whitespace-nowrap">Recent activity</span>
 | 
			
		||||
              <div className="tabs">
 | 
			
		||||
                <div
 | 
			
		||||
                  className={clsx(
 | 
			
		||||
                    'tab gap-2',
 | 
			
		||||
                    feedMode === 'activity' && 'tab-active'
 | 
			
		||||
                  )}
 | 
			
		||||
                  onClick={() => setFeedMode('activity')}
 | 
			
		||||
                >
 | 
			
		||||
                  <SparklesIcon className="inline h-5 w-5" aria-hidden="true" />
 | 
			
		||||
                  Recent activity
 | 
			
		||||
                </div>
 | 
			
		||||
                <div
 | 
			
		||||
                  className={clsx(
 | 
			
		||||
                    'tab gap-2',
 | 
			
		||||
                    feedMode === 'explore' && 'tab-active'
 | 
			
		||||
                  )}
 | 
			
		||||
                  onClick={() => setFeedMode('explore')}
 | 
			
		||||
                >
 | 
			
		||||
                  <GlobeAltIcon className="inline h-5 w-5" aria-hidden="true" />
 | 
			
		||||
                  Explore
 | 
			
		||||
                </div>
 | 
			
		||||
              </div>
 | 
			
		||||
            </Row>
 | 
			
		||||
          </Col>
 | 
			
		||||
 | 
			
		||||
          {activeContracts && recentBets ? (
 | 
			
		||||
            <ActivityFeed
 | 
			
		||||
              contracts={activeContracts}
 | 
			
		||||
              contractBets={activeBets}
 | 
			
		||||
              contractComments={activeComments}
 | 
			
		||||
            />
 | 
			
		||||
          ) : (
 | 
			
		||||
            <LoadingIndicator className="mt-4" />
 | 
			
		||||
          )}
 | 
			
		||||
          {feedMode === 'activity' &&
 | 
			
		||||
            (recentBets ? (
 | 
			
		||||
              <ActivityFeed
 | 
			
		||||
                contracts={activeContracts}
 | 
			
		||||
                contractBets={activeBets}
 | 
			
		||||
                contractComments={activeComments}
 | 
			
		||||
              />
 | 
			
		||||
            ) : (
 | 
			
		||||
              <LoadingIndicator className="mt-4" />
 | 
			
		||||
            ))}
 | 
			
		||||
 | 
			
		||||
          {feedMode === 'explore' &&
 | 
			
		||||
            (exploreContracts ? (
 | 
			
		||||
              <SummaryActivityFeed contracts={exploreContracts} />
 | 
			
		||||
            ) : (
 | 
			
		||||
              <LoadingIndicator className="mt-4" />
 | 
			
		||||
            ))}
 | 
			
		||||
        </Col>
 | 
			
		||||
      </Col>
 | 
			
		||||
    </Page>
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										
											BIN
										
									
								
								web/public/stylized-crane-black.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								web/public/stylized-crane-black.png
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| 
		 After Width: | Height: | Size: 683 KiB  | 
		Loading…
	
		Reference in New Issue
	
	Block a user