Separate answers and comments in FR markets
This commit is contained in:
		
							parent
							
								
									9199b99fd0
								
							
						
					
					
						commit
						20bd97f828
					
				| 
						 | 
					@ -23,7 +23,6 @@ export type ActivityItem =
 | 
				
			||||||
  | CloseItem
 | 
					  | CloseItem
 | 
				
			||||||
  | ResolveItem
 | 
					  | ResolveItem
 | 
				
			||||||
  | CommentInputItem
 | 
					  | CommentInputItem
 | 
				
			||||||
  | AnswerItem
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
type BaseActivityItem = {
 | 
					type BaseActivityItem = {
 | 
				
			||||||
  id: string
 | 
					  id: string
 | 
				
			||||||
| 
						 | 
					@ -69,16 +68,11 @@ export type BetGroupItem = BaseActivityItem & {
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export type AnswerGroupItem = BaseActivityItem & {
 | 
					export type AnswerGroupItem = BaseActivityItem & {
 | 
				
			||||||
  type: 'answergroup'
 | 
					  type: 'answergroup' | 'answer'
 | 
				
			||||||
  answer: Answer
 | 
					  answer: Answer
 | 
				
			||||||
  items: ActivityItem[]
 | 
					  items: ActivityItem[]
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export type AnswerItem = BaseActivityItem & {
 | 
					 | 
				
			||||||
  type: 'answer'
 | 
					 | 
				
			||||||
  answer: Answer
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export type CloseItem = BaseActivityItem & {
 | 
					export type CloseItem = BaseActivityItem & {
 | 
				
			||||||
  type: 'close'
 | 
					  type: 'close'
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -239,14 +233,13 @@ function getAnswerGroups(
 | 
				
			||||||
        (answer) => answer.id === outcome
 | 
					        (answer) => answer.id === outcome
 | 
				
			||||||
      ) as Answer
 | 
					      ) as Answer
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      // let items = groupBets(answerBets, answerComments, contract, user?.id, {
 | 
					      let items = groupBets(answerBets, answerComments, contract, user?.id, {
 | 
				
			||||||
      //   hideOutcome: true,
 | 
					        hideOutcome: true,
 | 
				
			||||||
      //   abbreviated,
 | 
					        abbreviated,
 | 
				
			||||||
      //   smallAvatar: true,
 | 
					        smallAvatar: true,
 | 
				
			||||||
      //   reversed,
 | 
					        reversed,
 | 
				
			||||||
      // })
 | 
					      })
 | 
				
			||||||
      //
 | 
					
 | 
				
			||||||
      let items: ActivityItem[] = []
 | 
					 | 
				
			||||||
      if (abbreviated) items = items.slice(-2)
 | 
					      if (abbreviated) items = items.slice(-2)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      return {
 | 
					      return {
 | 
				
			||||||
| 
						 | 
					@ -262,75 +255,33 @@ function getAnswerGroups(
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  return answerGroups
 | 
					  return answerGroups
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
function getAnswers(
 | 
					function getAnswers(
 | 
				
			||||||
  contract: FullContract<DPM, FreeResponse>,
 | 
					  contract: FullContract<DPM, FreeResponse>,
 | 
				
			||||||
  bets: Bet[],
 | 
					  bets: Bet[],
 | 
				
			||||||
  comments: Comment[],
 | 
					  user: User | undefined | null
 | 
				
			||||||
  user: User | undefined | null,
 | 
					 | 
				
			||||||
  options: {
 | 
					 | 
				
			||||||
    sortByProb: boolean
 | 
					 | 
				
			||||||
    abbreviated: boolean
 | 
					 | 
				
			||||||
    reversed: boolean
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
) {
 | 
					) {
 | 
				
			||||||
  const { sortByProb, abbreviated, reversed } = options
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  let outcomes = _.uniq(bets.map((bet) => bet.outcome)).filter(
 | 
					  let outcomes = _.uniq(bets.map((bet) => bet.outcome)).filter(
 | 
				
			||||||
    (outcome) => getOutcomeProbability(contract, outcome) > 0.0001
 | 
					    (outcome) => getOutcomeProbability(contract, outcome) > 0.0001
 | 
				
			||||||
  )
 | 
					  )
 | 
				
			||||||
  if (abbreviated) {
 | 
					  outcomes = _.sortBy(outcomes, (outcome) =>
 | 
				
			||||||
    const lastComment = _.last(comments)
 | 
					    getOutcomeProbability(contract, outcome)
 | 
				
			||||||
    const lastCommentOutcome = bets.find(
 | 
					  )
 | 
				
			||||||
      (bet) => bet.id === lastComment?.betId
 | 
					 | 
				
			||||||
    )?.outcome
 | 
					 | 
				
			||||||
    const lastBetOutcome = _.last(bets)?.outcome
 | 
					 | 
				
			||||||
    if (lastCommentOutcome && lastBetOutcome) {
 | 
					 | 
				
			||||||
      outcomes = _.uniq([
 | 
					 | 
				
			||||||
        ...outcomes.filter(
 | 
					 | 
				
			||||||
          (outcome) =>
 | 
					 | 
				
			||||||
            outcome !== lastCommentOutcome && outcome !== lastBetOutcome
 | 
					 | 
				
			||||||
        ),
 | 
					 | 
				
			||||||
        lastCommentOutcome,
 | 
					 | 
				
			||||||
        lastBetOutcome,
 | 
					 | 
				
			||||||
      ])
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    outcomes = outcomes.slice(-2)
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
  if (sortByProb) {
 | 
					 | 
				
			||||||
    outcomes = _.sortBy(outcomes, (outcome) =>
 | 
					 | 
				
			||||||
      getOutcomeProbability(contract, outcome)
 | 
					 | 
				
			||||||
    )
 | 
					 | 
				
			||||||
  } else {
 | 
					 | 
				
			||||||
    // Sort by recent bet.
 | 
					 | 
				
			||||||
    outcomes = _.sortBy(outcomes, (outcome) =>
 | 
					 | 
				
			||||||
      _.findLastIndex(bets, (bet) => bet.outcome === outcome)
 | 
					 | 
				
			||||||
    )
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
  const answerGroups = outcomes
 | 
					  const answerGroups = outcomes
 | 
				
			||||||
    .map((outcome) => {
 | 
					    .map((outcome) => {
 | 
				
			||||||
      const answerBets = bets.filter((bet) => bet.outcome === outcome)
 | 
					 | 
				
			||||||
      const answerComments = comments.filter((comment) =>
 | 
					 | 
				
			||||||
        answerBets.some((bet) => bet.id === comment.betId)
 | 
					 | 
				
			||||||
      )
 | 
					 | 
				
			||||||
      const answer = contract.answers?.find(
 | 
					      const answer = contract.answers?.find(
 | 
				
			||||||
        (answer) => answer.id === outcome
 | 
					        (answer) => answer.id === outcome
 | 
				
			||||||
      ) as Answer
 | 
					      ) as Answer
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      // let items = groupBets(answerBets, answerComments, contract, user?.id, {
 | 
					 | 
				
			||||||
      //   hideOutcome: true,
 | 
					 | 
				
			||||||
      //   abbreviated,
 | 
					 | 
				
			||||||
      //   smallAvatar: true,
 | 
					 | 
				
			||||||
      //   reversed,
 | 
					 | 
				
			||||||
      // })
 | 
					 | 
				
			||||||
      //
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      return {
 | 
					      return {
 | 
				
			||||||
        id: outcome,
 | 
					        id: outcome,
 | 
				
			||||||
        type: 'answer' as const,
 | 
					        type: 'answer' as const,
 | 
				
			||||||
        contract,
 | 
					        contract,
 | 
				
			||||||
        answer,
 | 
					        answer,
 | 
				
			||||||
 | 
					        items: [] as ActivityItem[],
 | 
				
			||||||
        user,
 | 
					        user,
 | 
				
			||||||
 | 
					        className: 'border-base-200 flex-1 bg-base-200 p-3 rounded-md',
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    })
 | 
					    })
 | 
				
			||||||
    .filter((group) => group.answer)
 | 
					    .filter((group) => group.answer)
 | 
				
			||||||
| 
						 | 
					@ -410,13 +361,24 @@ export function getAllContractActivityItems(
 | 
				
			||||||
    : [{ type: 'description', id: '0', contract }]
 | 
					    : [{ type: 'description', id: '0', contract }]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  if (outcomeType === 'FREE_RESPONSE') {
 | 
					  if (outcomeType === 'FREE_RESPONSE') {
 | 
				
			||||||
 | 
					    const onlyUsersBetsOrBetsWithComments = bets.filter((bet) =>
 | 
				
			||||||
 | 
					      comments.some(
 | 
				
			||||||
 | 
					        (comment) => comment.betId === bet.id || bet.userId === user?.id
 | 
				
			||||||
 | 
					      )
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
    items.push(
 | 
					    items.push(
 | 
				
			||||||
      ...groupBetsAndComments(bets, comments, contract, user?.id, {
 | 
					      ...groupBetsAndComments(
 | 
				
			||||||
        hideOutcome: false,
 | 
					        onlyUsersBetsOrBetsWithComments,
 | 
				
			||||||
        abbreviated,
 | 
					        comments,
 | 
				
			||||||
        smallAvatar: false,
 | 
					        contract,
 | 
				
			||||||
        reversed,
 | 
					        user?.id,
 | 
				
			||||||
      })
 | 
					        {
 | 
				
			||||||
 | 
					          hideOutcome: false,
 | 
				
			||||||
 | 
					          abbreviated,
 | 
				
			||||||
 | 
					          smallAvatar: false,
 | 
				
			||||||
 | 
					          reversed,
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      )
 | 
				
			||||||
    )
 | 
					    )
 | 
				
			||||||
    const commentsByBetId = mapCommentsByBetId(comments)
 | 
					    const commentsByBetId = mapCommentsByBetId(comments)
 | 
				
			||||||
    items.push({
 | 
					    items.push({
 | 
				
			||||||
| 
						 | 
					@ -428,17 +390,7 @@ export function getAllContractActivityItems(
 | 
				
			||||||
    })
 | 
					    })
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    items.push(
 | 
					    items.push(
 | 
				
			||||||
      ...getAnswers(
 | 
					      ...getAnswers(contract as FullContract<DPM, FreeResponse>, bets, user)
 | 
				
			||||||
        contract as FullContract<DPM, FreeResponse>,
 | 
					 | 
				
			||||||
        bets,
 | 
					 | 
				
			||||||
        comments,
 | 
					 | 
				
			||||||
        user,
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
          sortByProb: true,
 | 
					 | 
				
			||||||
          abbreviated,
 | 
					 | 
				
			||||||
          reversed,
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
      )
 | 
					 | 
				
			||||||
    )
 | 
					    )
 | 
				
			||||||
  } else {
 | 
					  } else {
 | 
				
			||||||
    items.push(
 | 
					    items.push(
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,5 +1,5 @@
 | 
				
			||||||
// From https://tailwindui.com/components/application-ui/lists/feeds
 | 
					// From https://tailwindui.com/components/application-ui/lists/feeds
 | 
				
			||||||
import { Fragment, useRef, useState } from 'react'
 | 
					import React, { Fragment, useRef, useState } from 'react'
 | 
				
			||||||
import * as _ from 'lodash'
 | 
					import * as _ from 'lodash'
 | 
				
			||||||
import {
 | 
					import {
 | 
				
			||||||
  BanIcon,
 | 
					  BanIcon,
 | 
				
			||||||
| 
						 | 
					@ -110,7 +110,7 @@ function FeedItem(props: { item: ActivityItem }) {
 | 
				
			||||||
    case 'answergroup':
 | 
					    case 'answergroup':
 | 
				
			||||||
      return <FeedAnswerGroup {...item} />
 | 
					      return <FeedAnswerGroup {...item} />
 | 
				
			||||||
    case 'answer':
 | 
					    case 'answer':
 | 
				
			||||||
      return <FeedAnswer {...item} />
 | 
					      return <FeedAnswerGroup {...item} />
 | 
				
			||||||
    case 'close':
 | 
					    case 'close':
 | 
				
			||||||
      return <FeedClose {...item} />
 | 
					      return <FeedClose {...item} />
 | 
				
			||||||
    case 'resolve':
 | 
					    case 'resolve':
 | 
				
			||||||
| 
						 | 
					@ -202,10 +202,6 @@ export function CommentInput(props: {
 | 
				
			||||||
  const user = useUser()
 | 
					  const user = useUser()
 | 
				
			||||||
  const [comment, setComment] = useState('')
 | 
					  const [comment, setComment] = useState('')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  // if (outcomeType === 'FREE_RESPONSE') {
 | 
					 | 
				
			||||||
  //   return <div />
 | 
					 | 
				
			||||||
  // }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  let canCommentOnABet = false
 | 
					  let canCommentOnABet = false
 | 
				
			||||||
  bets.some((bet) => {
 | 
					  bets.some((bet) => {
 | 
				
			||||||
    // make sure there is not already a comment with a matching bet id:
 | 
					    // make sure there is not already a comment with a matching bet id:
 | 
				
			||||||
| 
						 | 
					@ -653,8 +649,9 @@ function FeedAnswerGroup(props: {
 | 
				
			||||||
  contract: FullContract<any, FreeResponse>
 | 
					  contract: FullContract<any, FreeResponse>
 | 
				
			||||||
  answer: Answer
 | 
					  answer: Answer
 | 
				
			||||||
  items: ActivityItem[]
 | 
					  items: ActivityItem[]
 | 
				
			||||||
 | 
					  className?: string
 | 
				
			||||||
}) {
 | 
					}) {
 | 
				
			||||||
  const { answer, items, contract } = props
 | 
					  const { answer, items, contract, className } = props
 | 
				
			||||||
  const { username, avatarUrl, name, text } = answer
 | 
					  const { username, avatarUrl, name, text } = answer
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  const prob = getDpmOutcomeProbability(contract.totalShares, answer.id)
 | 
					  const prob = getDpmOutcomeProbability(contract.totalShares, answer.id)
 | 
				
			||||||
| 
						 | 
					@ -662,7 +659,7 @@ function FeedAnswerGroup(props: {
 | 
				
			||||||
  const [open, setOpen] = useState(false)
 | 
					  const [open, setOpen] = useState(false)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  return (
 | 
					  return (
 | 
				
			||||||
    <Col className="flex-1 gap-2">
 | 
					    <Col className={className ? className : 'flex-1 gap-2'}>
 | 
				
			||||||
      <Modal open={open} setOpen={setOpen}>
 | 
					      <Modal open={open} setOpen={setOpen}>
 | 
				
			||||||
        <AnswerBetPanel
 | 
					        <AnswerBetPanel
 | 
				
			||||||
          answer={answer}
 | 
					          answer={answer}
 | 
				
			||||||
| 
						 | 
					@ -732,71 +729,6 @@ function FeedAnswerGroup(props: {
 | 
				
			||||||
    </Col>
 | 
					    </Col>
 | 
				
			||||||
  )
 | 
					  )
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
function FeedAnswer(props: {
 | 
					 | 
				
			||||||
  contract: FullContract<any, FreeResponse>
 | 
					 | 
				
			||||||
  answer: Answer
 | 
					 | 
				
			||||||
}) {
 | 
					 | 
				
			||||||
  const { answer, contract } = props
 | 
					 | 
				
			||||||
  const { username, avatarUrl, name, text } = answer
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  const prob = getDpmOutcomeProbability(contract.totalShares, answer.id)
 | 
					 | 
				
			||||||
  const probPercent = formatPercent(prob)
 | 
					 | 
				
			||||||
  const [open, setOpen] = useState(false)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  return (
 | 
					 | 
				
			||||||
    <Col
 | 
					 | 
				
			||||||
      className="border-base-300 flex-1 bg-white p-3"
 | 
					 | 
				
			||||||
      style={{ borderWidth: 2, borderRadius: 6 }}
 | 
					 | 
				
			||||||
    >
 | 
					 | 
				
			||||||
      <Modal open={open} setOpen={setOpen}>
 | 
					 | 
				
			||||||
        <AnswerBetPanel
 | 
					 | 
				
			||||||
          answer={answer}
 | 
					 | 
				
			||||||
          contract={contract}
 | 
					 | 
				
			||||||
          closePanel={() => setOpen(false)}
 | 
					 | 
				
			||||||
          className="sm:max-w-84 !rounded-md bg-white !px-8 !py-6"
 | 
					 | 
				
			||||||
          isModal={true}
 | 
					 | 
				
			||||||
        />
 | 
					 | 
				
			||||||
      </Modal>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      <Row className="my-4 gap-3">
 | 
					 | 
				
			||||||
        <div className="px-1">
 | 
					 | 
				
			||||||
          <div className="flex h-8 w-8 items-center justify-center rounded-full bg-gray-200">
 | 
					 | 
				
			||||||
            <Avatar username={username} avatarUrl={avatarUrl} />
 | 
					 | 
				
			||||||
          </div>
 | 
					 | 
				
			||||||
        </div>
 | 
					 | 
				
			||||||
        <Col className="min-w-0 flex-1 gap-2">
 | 
					 | 
				
			||||||
          <div className="text-sm text-gray-500">
 | 
					 | 
				
			||||||
            <UserLink username={username} name={name} /> answered
 | 
					 | 
				
			||||||
          </div>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
          <Col className="align-items justify-between gap-4 sm:flex-row">
 | 
					 | 
				
			||||||
            <span className="whitespace-pre-line text-lg">
 | 
					 | 
				
			||||||
              <Linkify text={text} />
 | 
					 | 
				
			||||||
            </span>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            <Row className="align-items justify-end gap-4">
 | 
					 | 
				
			||||||
              <span
 | 
					 | 
				
			||||||
                className={clsx(
 | 
					 | 
				
			||||||
                  'text-2xl',
 | 
					 | 
				
			||||||
                  tradingAllowed(contract) ? 'text-green-500' : 'text-gray-500'
 | 
					 | 
				
			||||||
                )}
 | 
					 | 
				
			||||||
              >
 | 
					 | 
				
			||||||
                {probPercent}
 | 
					 | 
				
			||||||
              </span>
 | 
					 | 
				
			||||||
              <BuyButton
 | 
					 | 
				
			||||||
                className={clsx(
 | 
					 | 
				
			||||||
                  'btn-sm flex-initial !px-6 sm:flex',
 | 
					 | 
				
			||||||
                  tradingAllowed(contract) ? '' : '!hidden'
 | 
					 | 
				
			||||||
                )}
 | 
					 | 
				
			||||||
                onClick={() => setOpen(true)}
 | 
					 | 
				
			||||||
              />
 | 
					 | 
				
			||||||
            </Row>
 | 
					 | 
				
			||||||
          </Col>
 | 
					 | 
				
			||||||
        </Col>
 | 
					 | 
				
			||||||
      </Row>
 | 
					 | 
				
			||||||
    </Col>
 | 
					 | 
				
			||||||
  )
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
// TODO: Should highlight the entire Feed segment
 | 
					// TODO: Should highlight the entire Feed segment
 | 
				
			||||||
function FeedExpand(props: { setExpanded: (expanded: boolean) => void }) {
 | 
					function FeedExpand(props: { setExpanded: (expanded: boolean) => void }) {
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
		Reference in New Issue
	
	Block a user