Merge branch 'manifoldmarkets:main' into main
This commit is contained in:
		
						commit
						ad7cc194aa
					
				| 
						 | 
				
			
			@ -100,6 +100,20 @@
 | 
			
		|||
        }
 | 
			
		||||
      ]
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
      "collectionGroup": "comments",
 | 
			
		||||
      "queryScope": "COLLECTION",
 | 
			
		||||
      "fields": [
 | 
			
		||||
        {
 | 
			
		||||
          "fieldPath": "userId",
 | 
			
		||||
          "order": "ASCENDING"
 | 
			
		||||
        },
 | 
			
		||||
        {
 | 
			
		||||
          "fieldPath": "createdTime",
 | 
			
		||||
          "order": "ASCENDING"
 | 
			
		||||
        }
 | 
			
		||||
      ]
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
      "collectionGroup": "comments",
 | 
			
		||||
      "queryScope": "COLLECTION_GROUP",
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -22,6 +22,60 @@ import { addUserToContractFollowers } from './follow-market'
 | 
			
		|||
 | 
			
		||||
const firestore = admin.firestore()
 | 
			
		||||
 | 
			
		||||
function getMostRecentCommentableBet(
 | 
			
		||||
  before: number,
 | 
			
		||||
  betsByCurrentUser: Bet[],
 | 
			
		||||
  commentsByCurrentUser: ContractComment[],
 | 
			
		||||
  answerOutcome?: string
 | 
			
		||||
) {
 | 
			
		||||
  let sortedBetsByCurrentUser = betsByCurrentUser.sort(
 | 
			
		||||
    (a, b) => b.createdTime - a.createdTime
 | 
			
		||||
  )
 | 
			
		||||
  if (answerOutcome) {
 | 
			
		||||
    sortedBetsByCurrentUser = sortedBetsByCurrentUser.slice(0, 1)
 | 
			
		||||
  }
 | 
			
		||||
  return sortedBetsByCurrentUser
 | 
			
		||||
    .filter((bet) => {
 | 
			
		||||
      const { createdTime, isRedemption } = bet
 | 
			
		||||
      // You can comment on bets posted in the last hour
 | 
			
		||||
      const commentable = !isRedemption && before - createdTime < 60 * 60 * 1000
 | 
			
		||||
      const alreadyCommented = commentsByCurrentUser.some(
 | 
			
		||||
        (comment) => comment.createdTime > bet.createdTime
 | 
			
		||||
      )
 | 
			
		||||
      if (commentable && !alreadyCommented) {
 | 
			
		||||
        if (!answerOutcome) return true
 | 
			
		||||
        return answerOutcome === bet.outcome
 | 
			
		||||
      }
 | 
			
		||||
      return false
 | 
			
		||||
    })
 | 
			
		||||
    .pop()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
async function getPriorUserComments(
 | 
			
		||||
  contractId: string,
 | 
			
		||||
  userId: string,
 | 
			
		||||
  before: number
 | 
			
		||||
) {
 | 
			
		||||
  const priorCommentsQuery = await firestore
 | 
			
		||||
    .collection('contracts')
 | 
			
		||||
    .doc(contractId)
 | 
			
		||||
    .collection('comments')
 | 
			
		||||
    .where('createdTime', '<', before)
 | 
			
		||||
    .where('userId', '==', userId)
 | 
			
		||||
    .get()
 | 
			
		||||
  return priorCommentsQuery.docs.map((d) => d.data() as ContractComment)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
async function getPriorContractBets(contractId: string, before: number) {
 | 
			
		||||
  const priorBetsQuery = await firestore
 | 
			
		||||
    .collection('contracts')
 | 
			
		||||
    .doc(contractId)
 | 
			
		||||
    .collection('bets')
 | 
			
		||||
    .where('createdTime', '<', before)
 | 
			
		||||
    .get()
 | 
			
		||||
  return priorBetsQuery.docs.map((d) => d.data() as Bet)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export const onCreateCommentOnContract = functions
 | 
			
		||||
  .runWith({ secrets: ['MAILGUN_KEY'] })
 | 
			
		||||
  .firestore.document('contracts/{contractId}/comments/{commentId}')
 | 
			
		||||
| 
						 | 
				
			
			@ -55,17 +109,33 @@ export const onCreateCommentOnContract = functions
 | 
			
		|||
      .doc(contract.id)
 | 
			
		||||
      .update({ lastCommentTime, lastUpdatedTime: Date.now() })
 | 
			
		||||
 | 
			
		||||
    const previousBetsQuery = await firestore
 | 
			
		||||
      .collection('contracts')
 | 
			
		||||
      .doc(contractId)
 | 
			
		||||
      .collection('bets')
 | 
			
		||||
      .where('createdTime', '<', comment.createdTime)
 | 
			
		||||
      .get()
 | 
			
		||||
    const previousBets = previousBetsQuery.docs.map((d) => d.data() as Bet)
 | 
			
		||||
    const position = getLargestPosition(
 | 
			
		||||
      contract,
 | 
			
		||||
      previousBets.filter((b) => b.userId === comment.userId && !b.isAnte)
 | 
			
		||||
    const priorBets = await getPriorContractBets(
 | 
			
		||||
      contractId,
 | 
			
		||||
      comment.createdTime
 | 
			
		||||
    )
 | 
			
		||||
    const priorUserBets = priorBets.filter(
 | 
			
		||||
      (b) => b.userId === comment.userId && !b.isAnte
 | 
			
		||||
    )
 | 
			
		||||
    const priorUserComments = await getPriorUserComments(
 | 
			
		||||
      contractId,
 | 
			
		||||
      comment.userId,
 | 
			
		||||
      comment.createdTime
 | 
			
		||||
    )
 | 
			
		||||
    const bet = getMostRecentCommentableBet(
 | 
			
		||||
      comment.createdTime,
 | 
			
		||||
      priorUserBets,
 | 
			
		||||
      priorUserComments,
 | 
			
		||||
      comment.answerOutcome
 | 
			
		||||
    )
 | 
			
		||||
    if (bet) {
 | 
			
		||||
      await change.ref.update({
 | 
			
		||||
        betId: bet.id,
 | 
			
		||||
        betOutcome: bet.outcome,
 | 
			
		||||
        betAmount: bet.amount,
 | 
			
		||||
      })
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    const position = getLargestPosition(contract, priorUserBets)
 | 
			
		||||
    if (position) {
 | 
			
		||||
      const fields: { [k: string]: unknown } = {
 | 
			
		||||
        commenterPositionShares: position.shares,
 | 
			
		||||
| 
						 | 
				
			
			@ -73,7 +143,7 @@ export const onCreateCommentOnContract = functions
 | 
			
		|||
      }
 | 
			
		||||
      const previousProb =
 | 
			
		||||
        contract.outcomeType === 'BINARY'
 | 
			
		||||
          ? maxBy(previousBets, (bet) => bet.createdTime)?.probAfter
 | 
			
		||||
          ? maxBy(priorBets, (bet) => bet.createdTime)?.probAfter
 | 
			
		||||
          : undefined
 | 
			
		||||
      if (previousProb != null) {
 | 
			
		||||
        fields.commenterPositionProb = previousProb
 | 
			
		||||
| 
						 | 
				
			
			@ -81,7 +151,6 @@ export const onCreateCommentOnContract = functions
 | 
			
		|||
      await change.ref.update(fields)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    let bet: Bet | undefined
 | 
			
		||||
    let answer: Answer | undefined
 | 
			
		||||
    if (comment.answerOutcome) {
 | 
			
		||||
      answer =
 | 
			
		||||
| 
						 | 
				
			
			@ -90,23 +159,6 @@ export const onCreateCommentOnContract = functions
 | 
			
		|||
              (answer) => answer.id === comment.answerOutcome
 | 
			
		||||
            )
 | 
			
		||||
          : undefined
 | 
			
		||||
    } else if (comment.betId) {
 | 
			
		||||
      const betSnapshot = await firestore
 | 
			
		||||
        .collection('contracts')
 | 
			
		||||
        .doc(contractId)
 | 
			
		||||
        .collection('bets')
 | 
			
		||||
        .doc(comment.betId)
 | 
			
		||||
        .get()
 | 
			
		||||
      bet = betSnapshot.data() as Bet
 | 
			
		||||
      answer =
 | 
			
		||||
        contract.outcomeType === 'FREE_RESPONSE' && contract.answers
 | 
			
		||||
          ? contract.answers.find((answer) => answer.id === bet?.outcome)
 | 
			
		||||
          : undefined
 | 
			
		||||
 | 
			
		||||
      await change.ref.update({
 | 
			
		||||
        betOutcome: bet.outcome,
 | 
			
		||||
        betAmount: bet.amount,
 | 
			
		||||
      })
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    const comments = await getValues<ContractComment>(
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -16,17 +16,11 @@ export function CommentInput(props: {
 | 
			
		|||
  parentAnswerOutcome?: string
 | 
			
		||||
  // Reply to another comment
 | 
			
		||||
  parentCommentId?: string
 | 
			
		||||
  onSubmitComment?: (editor: Editor, betId: string | undefined) => void
 | 
			
		||||
  onSubmitComment?: (editor: Editor) => void
 | 
			
		||||
  className?: string
 | 
			
		||||
  presetId?: string
 | 
			
		||||
}) {
 | 
			
		||||
  const {
 | 
			
		||||
    parentAnswerOutcome,
 | 
			
		||||
    parentCommentId,
 | 
			
		||||
    replyToUser,
 | 
			
		||||
    onSubmitComment,
 | 
			
		||||
    presetId,
 | 
			
		||||
  } = props
 | 
			
		||||
  const { parentAnswerOutcome, parentCommentId, replyToUser, onSubmitComment } =
 | 
			
		||||
    props
 | 
			
		||||
  const user = useUser()
 | 
			
		||||
 | 
			
		||||
  const { editor, upload } = useTextEditor({
 | 
			
		||||
| 
						 | 
				
			
			@ -40,10 +34,10 @@ export function CommentInput(props: {
 | 
			
		|||
 | 
			
		||||
  const [isSubmitting, setIsSubmitting] = useState(false)
 | 
			
		||||
 | 
			
		||||
  async function submitComment(betId: string | undefined) {
 | 
			
		||||
  async function submitComment() {
 | 
			
		||||
    if (!editor || editor.isEmpty || isSubmitting) return
 | 
			
		||||
    setIsSubmitting(true)
 | 
			
		||||
    onSubmitComment?.(editor, betId)
 | 
			
		||||
    onSubmitComment?.(editor)
 | 
			
		||||
    setIsSubmitting(false)
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -65,7 +59,6 @@ export function CommentInput(props: {
 | 
			
		|||
          user={user}
 | 
			
		||||
          submitComment={submitComment}
 | 
			
		||||
          isSubmitting={isSubmitting}
 | 
			
		||||
          presetId={presetId}
 | 
			
		||||
        />
 | 
			
		||||
      </div>
 | 
			
		||||
    </Row>
 | 
			
		||||
| 
						 | 
				
			
			@ -77,25 +70,17 @@ export function CommentInputTextArea(props: {
 | 
			
		|||
  replyToUser?: { id: string; username: string }
 | 
			
		||||
  editor: Editor | null
 | 
			
		||||
  upload: Parameters<typeof TextEditor>[0]['upload']
 | 
			
		||||
  submitComment: (id?: string) => void
 | 
			
		||||
  submitComment: () => void
 | 
			
		||||
  isSubmitting: boolean
 | 
			
		||||
  presetId?: string
 | 
			
		||||
}) {
 | 
			
		||||
  const {
 | 
			
		||||
    user,
 | 
			
		||||
    editor,
 | 
			
		||||
    upload,
 | 
			
		||||
    submitComment,
 | 
			
		||||
    presetId,
 | 
			
		||||
    isSubmitting,
 | 
			
		||||
    replyToUser,
 | 
			
		||||
  } = props
 | 
			
		||||
  const { user, editor, upload, submitComment, isSubmitting, replyToUser } =
 | 
			
		||||
    props
 | 
			
		||||
  useEffect(() => {
 | 
			
		||||
    editor?.setEditable(!isSubmitting)
 | 
			
		||||
  }, [isSubmitting, editor])
 | 
			
		||||
 | 
			
		||||
  const submit = () => {
 | 
			
		||||
    submitComment(presetId)
 | 
			
		||||
    submitComment()
 | 
			
		||||
    editor?.commands?.clearContent()
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -151,14 +136,14 @@ export function CommentInputTextArea(props: {
 | 
			
		|||
        )}
 | 
			
		||||
 | 
			
		||||
        {isSubmitting && (
 | 
			
		||||
          <LoadingIndicator spinnerClassName={'border-gray-500'} />
 | 
			
		||||
          <LoadingIndicator spinnerClassName="border-gray-500" />
 | 
			
		||||
        )}
 | 
			
		||||
      </TextEditor>
 | 
			
		||||
      <Row>
 | 
			
		||||
        {!user && (
 | 
			
		||||
          <button
 | 
			
		||||
            className={'btn btn-outline btn-sm mt-2 normal-case'}
 | 
			
		||||
            onClick={() => submitComment(presetId)}
 | 
			
		||||
            className="btn btn-outline btn-sm mt-2 normal-case"
 | 
			
		||||
            onClick={submitComment}
 | 
			
		||||
          >
 | 
			
		||||
            Add my comment
 | 
			
		||||
          </button>
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -59,7 +59,6 @@ export function ContractTabs(props: {
 | 
			
		|||
    />
 | 
			
		||||
  )
 | 
			
		||||
 | 
			
		||||
  const generalBets = outcomeType === 'FREE_RESPONSE' ? [] : visibleBets
 | 
			
		||||
  const generalComments = comments.filter(
 | 
			
		||||
    (comment) =>
 | 
			
		||||
      comment.answerOutcome === undefined &&
 | 
			
		||||
| 
						 | 
				
			
			@ -71,36 +70,24 @@ export function ContractTabs(props: {
 | 
			
		|||
      <>
 | 
			
		||||
        <FreeResponseContractCommentsActivity
 | 
			
		||||
          contract={contract}
 | 
			
		||||
          betsByCurrentUser={
 | 
			
		||||
            user ? visibleBets.filter((b) => b.userId === user.id) : []
 | 
			
		||||
          }
 | 
			
		||||
          comments={comments}
 | 
			
		||||
          tips={tips}
 | 
			
		||||
          user={user}
 | 
			
		||||
        />
 | 
			
		||||
        <Col className={'mt-8 flex w-full '}>
 | 
			
		||||
          <div className={'text-md mt-8 mb-2 text-left'}>General Comments</div>
 | 
			
		||||
          <div className={'mb-4 w-full border-b border-gray-200'} />
 | 
			
		||||
        <Col className="mt-8 flex w-full">
 | 
			
		||||
          <div className="text-md mt-8 mb-2 text-left">General Comments</div>
 | 
			
		||||
          <div className="mb-4 w-full border-b border-gray-200" />
 | 
			
		||||
          <ContractCommentsActivity
 | 
			
		||||
            contract={contract}
 | 
			
		||||
            betsByCurrentUser={
 | 
			
		||||
              user ? generalBets.filter((b) => b.userId === user.id) : []
 | 
			
		||||
            }
 | 
			
		||||
            comments={generalComments}
 | 
			
		||||
            tips={tips}
 | 
			
		||||
            user={user}
 | 
			
		||||
          />
 | 
			
		||||
        </Col>
 | 
			
		||||
      </>
 | 
			
		||||
    ) : (
 | 
			
		||||
      <ContractCommentsActivity
 | 
			
		||||
        contract={contract}
 | 
			
		||||
        betsByCurrentUser={
 | 
			
		||||
          user ? visibleBets.filter((b) => b.userId === user.id) : []
 | 
			
		||||
        }
 | 
			
		||||
        comments={comments}
 | 
			
		||||
        tips={tips}
 | 
			
		||||
        user={user}
 | 
			
		||||
      />
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -120,7 +107,7 @@ export function ContractTabs(props: {
 | 
			
		|||
 | 
			
		||||
  return (
 | 
			
		||||
    <Tabs
 | 
			
		||||
      currentPageForAnalytics={'contract'}
 | 
			
		||||
      currentPageForAnalytics="contract"
 | 
			
		||||
      tabs={[
 | 
			
		||||
        {
 | 
			
		||||
          title: 'Comments',
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -8,7 +8,6 @@ import { FeedBet } from './feed-bets'
 | 
			
		|||
import { FeedLiquidity } from './feed-liquidity'
 | 
			
		||||
import { FeedAnswerCommentGroup } from './feed-answer-comment-group'
 | 
			
		||||
import { FeedCommentThread, ContractCommentInput } from './feed-comments'
 | 
			
		||||
import { User } from 'common/user'
 | 
			
		||||
import { CommentTipMap } from 'web/hooks/use-tip-txns'
 | 
			
		||||
import { LiquidityProvision } from 'common/liquidity-provision'
 | 
			
		||||
import { groupBy, sortBy } from 'lodash'
 | 
			
		||||
| 
						 | 
				
			
			@ -72,13 +71,10 @@ export function ContractBetsActivity(props: {
 | 
			
		|||
 | 
			
		||||
export function ContractCommentsActivity(props: {
 | 
			
		||||
  contract: Contract
 | 
			
		||||
  betsByCurrentUser: Bet[]
 | 
			
		||||
  comments: ContractComment[]
 | 
			
		||||
  tips: CommentTipMap
 | 
			
		||||
  user: User | null | undefined
 | 
			
		||||
}) {
 | 
			
		||||
  const { betsByCurrentUser, contract, comments, user, tips } = props
 | 
			
		||||
  const commentsByUserId = groupBy(comments, (c) => c.userId)
 | 
			
		||||
  const { contract, comments, tips } = props
 | 
			
		||||
  const commentsByParentId = groupBy(comments, (c) => c.replyToCommentId ?? '_')
 | 
			
		||||
  const topLevelComments = sortBy(
 | 
			
		||||
    commentsByParentId['_'] ?? [],
 | 
			
		||||
| 
						 | 
				
			
			@ -87,16 +83,10 @@ export function ContractCommentsActivity(props: {
 | 
			
		|||
 | 
			
		||||
  return (
 | 
			
		||||
    <>
 | 
			
		||||
      <ContractCommentInput
 | 
			
		||||
        className="mb-5"
 | 
			
		||||
        contract={contract}
 | 
			
		||||
        betsByCurrentUser={betsByCurrentUser}
 | 
			
		||||
        commentsByCurrentUser={(user && commentsByUserId[user.id]) ?? []}
 | 
			
		||||
      />
 | 
			
		||||
      <ContractCommentInput className="mb-5" contract={contract} />
 | 
			
		||||
      {topLevelComments.map((parent) => (
 | 
			
		||||
        <FeedCommentThread
 | 
			
		||||
          key={parent.id}
 | 
			
		||||
          user={user}
 | 
			
		||||
          contract={contract}
 | 
			
		||||
          parentComment={parent}
 | 
			
		||||
          threadComments={sortBy(
 | 
			
		||||
| 
						 | 
				
			
			@ -104,8 +94,6 @@ export function ContractCommentsActivity(props: {
 | 
			
		|||
            (c) => c.createdTime
 | 
			
		||||
          )}
 | 
			
		||||
          tips={tips}
 | 
			
		||||
          betsByCurrentUser={betsByCurrentUser}
 | 
			
		||||
          commentsByUserId={commentsByUserId}
 | 
			
		||||
        />
 | 
			
		||||
      ))}
 | 
			
		||||
    </>
 | 
			
		||||
| 
						 | 
				
			
			@ -114,18 +102,15 @@ export function ContractCommentsActivity(props: {
 | 
			
		|||
 | 
			
		||||
export function FreeResponseContractCommentsActivity(props: {
 | 
			
		||||
  contract: FreeResponseContract
 | 
			
		||||
  betsByCurrentUser: Bet[]
 | 
			
		||||
  comments: ContractComment[]
 | 
			
		||||
  tips: CommentTipMap
 | 
			
		||||
  user: User | null | undefined
 | 
			
		||||
}) {
 | 
			
		||||
  const { betsByCurrentUser, contract, comments, user, tips } = props
 | 
			
		||||
  const { contract, comments, tips } = props
 | 
			
		||||
 | 
			
		||||
  const sortedAnswers = sortBy(
 | 
			
		||||
    contract.answers,
 | 
			
		||||
    (answer) => -getOutcomeProbability(contract, answer.number.toString())
 | 
			
		||||
  )
 | 
			
		||||
  const commentsByUserId = groupBy(comments, (c) => c.userId)
 | 
			
		||||
  const commentsByOutcome = groupBy(
 | 
			
		||||
    comments,
 | 
			
		||||
    (c) => c.answerOutcome ?? c.betOutcome ?? '_'
 | 
			
		||||
| 
						 | 
				
			
			@ -134,22 +119,19 @@ export function FreeResponseContractCommentsActivity(props: {
 | 
			
		|||
  return (
 | 
			
		||||
    <>
 | 
			
		||||
      {sortedAnswers.map((answer) => (
 | 
			
		||||
        <div key={answer.id} className={'relative pb-4'}>
 | 
			
		||||
        <div key={answer.id} className="relative pb-4">
 | 
			
		||||
          <span
 | 
			
		||||
            className="absolute top-5 left-5 -ml-px h-[calc(100%-2rem)] w-0.5 bg-gray-200"
 | 
			
		||||
            aria-hidden="true"
 | 
			
		||||
          />
 | 
			
		||||
          <FeedAnswerCommentGroup
 | 
			
		||||
            contract={contract}
 | 
			
		||||
            user={user}
 | 
			
		||||
            answer={answer}
 | 
			
		||||
            answerComments={sortBy(
 | 
			
		||||
              commentsByOutcome[answer.number.toString()] ?? [],
 | 
			
		||||
              (c) => c.createdTime
 | 
			
		||||
            )}
 | 
			
		||||
            tips={tips}
 | 
			
		||||
            betsByCurrentUser={betsByCurrentUser}
 | 
			
		||||
            commentsByUserId={commentsByUserId}
 | 
			
		||||
          />
 | 
			
		||||
        </div>
 | 
			
		||||
      ))}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,5 +1,4 @@
 | 
			
		|||
import { Answer } from 'common/answer'
 | 
			
		||||
import { Bet } from 'common/bet'
 | 
			
		||||
import { FreeResponseContract } from 'common/contract'
 | 
			
		||||
import { ContractComment } from 'common/comment'
 | 
			
		||||
import React, { useEffect, useState } from 'react'
 | 
			
		||||
| 
						 | 
				
			
			@ -14,7 +13,6 @@ import {
 | 
			
		|||
} from 'web/components/feed/feed-comments'
 | 
			
		||||
import { CopyLinkDateTimeComponent } from 'web/components/feed/copy-link-date-time'
 | 
			
		||||
import { useRouter } from 'next/router'
 | 
			
		||||
import { Dictionary } from 'lodash'
 | 
			
		||||
import { User } from 'common/user'
 | 
			
		||||
import { useEvent } from 'web/hooks/use-event'
 | 
			
		||||
import { CommentTipMap } from 'web/hooks/use-tip-txns'
 | 
			
		||||
| 
						 | 
				
			
			@ -22,22 +20,11 @@ import { UserLink } from 'web/components/user-link'
 | 
			
		|||
 | 
			
		||||
export function FeedAnswerCommentGroup(props: {
 | 
			
		||||
  contract: FreeResponseContract
 | 
			
		||||
  user: User | undefined | null
 | 
			
		||||
  answer: Answer
 | 
			
		||||
  answerComments: ContractComment[]
 | 
			
		||||
  tips: CommentTipMap
 | 
			
		||||
  betsByCurrentUser: Bet[]
 | 
			
		||||
  commentsByUserId: Dictionary<ContractComment[]>
 | 
			
		||||
}) {
 | 
			
		||||
  const {
 | 
			
		||||
    answer,
 | 
			
		||||
    contract,
 | 
			
		||||
    answerComments,
 | 
			
		||||
    tips,
 | 
			
		||||
    betsByCurrentUser,
 | 
			
		||||
    commentsByUserId,
 | 
			
		||||
    user,
 | 
			
		||||
  } = props
 | 
			
		||||
  const { answer, contract, answerComments, tips } = props
 | 
			
		||||
  const { username, avatarUrl, name, text } = answer
 | 
			
		||||
 | 
			
		||||
  const [replyToUser, setReplyToUser] =
 | 
			
		||||
| 
						 | 
				
			
			@ -47,7 +34,6 @@ export function FeedAnswerCommentGroup(props: {
 | 
			
		|||
  const router = useRouter()
 | 
			
		||||
 | 
			
		||||
  const answerElementId = `answer-${answer.id}`
 | 
			
		||||
  const commentsByCurrentUser = (user && commentsByUserId[user.id]) ?? []
 | 
			
		||||
 | 
			
		||||
  const scrollAndOpenReplyInput = useEvent(
 | 
			
		||||
    (comment?: ContractComment, answer?: Answer) => {
 | 
			
		||||
| 
						 | 
				
			
			@ -133,8 +119,6 @@ export function FeedAnswerCommentGroup(props: {
 | 
			
		|||
          />
 | 
			
		||||
          <ContractCommentInput
 | 
			
		||||
            contract={contract}
 | 
			
		||||
            betsByCurrentUser={betsByCurrentUser}
 | 
			
		||||
            commentsByCurrentUser={commentsByCurrentUser}
 | 
			
		||||
            parentAnswerOutcome={answer.number.toString()}
 | 
			
		||||
            replyToUser={replyToUser}
 | 
			
		||||
            onSubmitComment={() => setShowReply(false)}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,9 +1,6 @@
 | 
			
		|||
import { Bet } from 'common/bet'
 | 
			
		||||
import { ContractComment } from 'common/comment'
 | 
			
		||||
import { User } from 'common/user'
 | 
			
		||||
import { Contract } from 'common/contract'
 | 
			
		||||
import React, { useEffect, useState } from 'react'
 | 
			
		||||
import { Dictionary } from 'lodash'
 | 
			
		||||
import { useUser } from 'web/hooks/use-user'
 | 
			
		||||
import { formatMoney } from 'common/util/format'
 | 
			
		||||
import { useRouter } from 'next/router'
 | 
			
		||||
| 
						 | 
				
			
			@ -24,23 +21,12 @@ import { UserLink } from 'web/components/user-link'
 | 
			
		|||
import { CommentInput } from '../comment-input'
 | 
			
		||||
 | 
			
		||||
export function FeedCommentThread(props: {
 | 
			
		||||
  user: User | null | undefined
 | 
			
		||||
  contract: Contract
 | 
			
		||||
  threadComments: ContractComment[]
 | 
			
		||||
  tips: CommentTipMap
 | 
			
		||||
  parentComment: ContractComment
 | 
			
		||||
  betsByCurrentUser: Bet[]
 | 
			
		||||
  commentsByUserId: Dictionary<ContractComment[]>
 | 
			
		||||
}) {
 | 
			
		||||
  const {
 | 
			
		||||
    user,
 | 
			
		||||
    contract,
 | 
			
		||||
    threadComments,
 | 
			
		||||
    commentsByUserId,
 | 
			
		||||
    betsByCurrentUser,
 | 
			
		||||
    tips,
 | 
			
		||||
    parentComment,
 | 
			
		||||
  } = props
 | 
			
		||||
  const { contract, threadComments, tips, parentComment } = props
 | 
			
		||||
  const [showReply, setShowReply] = useState(false)
 | 
			
		||||
  const [replyTo, setReplyTo] = useState<{ id: string; username: string }>()
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -73,11 +59,8 @@ export function FeedCommentThread(props: {
 | 
			
		|||
          />
 | 
			
		||||
          <ContractCommentInput
 | 
			
		||||
            contract={contract}
 | 
			
		||||
            betsByCurrentUser={(user && betsByCurrentUser) ?? []}
 | 
			
		||||
            commentsByCurrentUser={(user && commentsByUserId[user.id]) ?? []}
 | 
			
		||||
            parentCommentId={parentComment.id}
 | 
			
		||||
            replyToUser={replyTo}
 | 
			
		||||
            parentAnswerOutcome={parentComment.answerOutcome}
 | 
			
		||||
            onSubmitComment={() => {
 | 
			
		||||
              setShowReply(false)
 | 
			
		||||
            }}
 | 
			
		||||
| 
						 | 
				
			
			@ -202,34 +185,6 @@ export function FeedComment(props: {
 | 
			
		|||
  )
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function getMostRecentCommentableBet(
 | 
			
		||||
  betsByCurrentUser: Bet[],
 | 
			
		||||
  commentsByCurrentUser: ContractComment[],
 | 
			
		||||
  user?: User | null,
 | 
			
		||||
  answerOutcome?: string
 | 
			
		||||
) {
 | 
			
		||||
  let sortedBetsByCurrentUser = betsByCurrentUser.sort(
 | 
			
		||||
    (a, b) => b.createdTime - a.createdTime
 | 
			
		||||
  )
 | 
			
		||||
  if (answerOutcome) {
 | 
			
		||||
    sortedBetsByCurrentUser = sortedBetsByCurrentUser.slice(0, 1)
 | 
			
		||||
  }
 | 
			
		||||
  return sortedBetsByCurrentUser
 | 
			
		||||
    .filter((bet) => {
 | 
			
		||||
      if (
 | 
			
		||||
        canCommentOnBet(bet, user) &&
 | 
			
		||||
        !commentsByCurrentUser.some(
 | 
			
		||||
          (comment) => comment.createdTime > bet.createdTime
 | 
			
		||||
        )
 | 
			
		||||
      ) {
 | 
			
		||||
        if (!answerOutcome) return true
 | 
			
		||||
        return answerOutcome === bet.outcome
 | 
			
		||||
      }
 | 
			
		||||
      return false
 | 
			
		||||
    })
 | 
			
		||||
    .pop()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function CommentStatus(props: {
 | 
			
		||||
  contract: Contract
 | 
			
		||||
  outcome: string
 | 
			
		||||
| 
						 | 
				
			
			@ -247,8 +202,6 @@ function CommentStatus(props: {
 | 
			
		|||
 | 
			
		||||
export function ContractCommentInput(props: {
 | 
			
		||||
  contract: Contract
 | 
			
		||||
  betsByCurrentUser: Bet[]
 | 
			
		||||
  commentsByCurrentUser: ContractComment[]
 | 
			
		||||
  className?: string
 | 
			
		||||
  parentAnswerOutcome?: string | undefined
 | 
			
		||||
  replyToUser?: { id: string; username: string }
 | 
			
		||||
| 
						 | 
				
			
			@ -256,7 +209,7 @@ export function ContractCommentInput(props: {
 | 
			
		|||
  onSubmitComment?: () => void
 | 
			
		||||
}) {
 | 
			
		||||
  const user = useUser()
 | 
			
		||||
  async function onSubmitComment(editor: Editor, betId: string | undefined) {
 | 
			
		||||
  async function onSubmitComment(editor: Editor) {
 | 
			
		||||
    if (!user) {
 | 
			
		||||
      track('sign in to comment')
 | 
			
		||||
      return await firebaseLogin()
 | 
			
		||||
| 
						 | 
				
			
			@ -265,22 +218,12 @@ export function ContractCommentInput(props: {
 | 
			
		|||
      props.contract.id,
 | 
			
		||||
      editor.getJSON(),
 | 
			
		||||
      user,
 | 
			
		||||
      betId,
 | 
			
		||||
      props.parentAnswerOutcome,
 | 
			
		||||
      props.parentCommentId
 | 
			
		||||
    )
 | 
			
		||||
    props.onSubmitComment?.()
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  const mostRecentCommentableBet = getMostRecentCommentableBet(
 | 
			
		||||
    props.betsByCurrentUser,
 | 
			
		||||
    props.commentsByCurrentUser,
 | 
			
		||||
    user,
 | 
			
		||||
    props.parentAnswerOutcome
 | 
			
		||||
  )
 | 
			
		||||
 | 
			
		||||
  const { id } = mostRecentCommentableBet || { id: undefined }
 | 
			
		||||
 | 
			
		||||
  return (
 | 
			
		||||
    <CommentInput
 | 
			
		||||
      replyToUser={props.replyToUser}
 | 
			
		||||
| 
						 | 
				
			
			@ -288,14 +231,6 @@ export function ContractCommentInput(props: {
 | 
			
		|||
      parentCommentId={props.parentCommentId}
 | 
			
		||||
      onSubmitComment={onSubmitComment}
 | 
			
		||||
      className={props.className}
 | 
			
		||||
      presetId={id}
 | 
			
		||||
    />
 | 
			
		||||
  )
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function canCommentOnBet(bet: Bet, user?: User | null) {
 | 
			
		||||
  const { userId, createdTime, isRedemption } = bet
 | 
			
		||||
  const isSelf = user?.id === userId
 | 
			
		||||
  // You can comment if your bet was posted in the last hour
 | 
			
		||||
  return !isRedemption && isSelf && Date.now() - createdTime < 60 * 60 * 1000
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -35,17 +35,13 @@ export async function createCommentOnContract(
 | 
			
		|||
  contractId: string,
 | 
			
		||||
  content: JSONContent,
 | 
			
		||||
  user: User,
 | 
			
		||||
  betId?: string,
 | 
			
		||||
  answerOutcome?: string,
 | 
			
		||||
  replyToCommentId?: string
 | 
			
		||||
) {
 | 
			
		||||
  const ref = betId
 | 
			
		||||
    ? doc(getCommentsCollection(contractId), betId)
 | 
			
		||||
    : doc(getCommentsCollection(contractId))
 | 
			
		||||
  const ref = doc(getCommentsCollection(contractId))
 | 
			
		||||
  const onContract = {
 | 
			
		||||
    commentType: 'contract',
 | 
			
		||||
    contractId,
 | 
			
		||||
    betId,
 | 
			
		||||
    answerOutcome,
 | 
			
		||||
  } as OnContract
 | 
			
		||||
  return await createComment(
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
		Reference in New Issue
	
	Block a user