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