Allow free comments with optional bets
This commit is contained in:
parent
0390ec0f47
commit
8291a05f32
|
@ -3,7 +3,7 @@
|
|||
export type Comment = {
|
||||
id: string
|
||||
contractId: string
|
||||
betId: string
|
||||
betId?: string
|
||||
userId: string
|
||||
|
||||
text: string
|
||||
|
|
3
functions/.gitignore
vendored
3
functions/.gitignore
vendored
|
@ -1,5 +1,6 @@
|
|||
# Secrets
|
||||
.env*
|
||||
.runtimeconfig.json
|
||||
|
||||
# Compiled JavaScript files
|
||||
lib/**/*.js
|
||||
|
@ -13,4 +14,4 @@ node_modules/
|
|||
|
||||
package-lock.json
|
||||
ui-debug.log
|
||||
firebase-debug.log
|
||||
firebase-debug.log
|
||||
|
|
|
@ -12,6 +12,7 @@ import {
|
|||
} from '../../../common/contract'
|
||||
import { User } from '../../../common/user'
|
||||
import { mapCommentsByBetId } from '../../lib/firebase/comments'
|
||||
import { useUser } from '../../hooks/use-user'
|
||||
|
||||
export type ActivityItem =
|
||||
| DescriptionItem
|
||||
|
@ -22,12 +23,19 @@ export type ActivityItem =
|
|||
| AnswerGroupItem
|
||||
| CloseItem
|
||||
| ResolveItem
|
||||
| CommentInputItem
|
||||
|
||||
type BaseActivityItem = {
|
||||
id: string
|
||||
contract: Contract
|
||||
}
|
||||
|
||||
export type CommentInputItem = BaseActivityItem & {
|
||||
type: 'commentInput'
|
||||
bets: Bet[]
|
||||
comments: Comment[]
|
||||
}
|
||||
|
||||
export type DescriptionItem = BaseActivityItem & {
|
||||
type: 'description'
|
||||
}
|
||||
|
@ -48,7 +56,7 @@ export type BetItem = BaseActivityItem & {
|
|||
export type CommentItem = BaseActivityItem & {
|
||||
type: 'comment'
|
||||
comment: Comment
|
||||
bet: Bet
|
||||
bet: Bet | undefined
|
||||
hideOutcome: boolean
|
||||
truncate: boolean
|
||||
smallAvatar: boolean
|
||||
|
@ -279,26 +287,53 @@ export function getAllContractActivityItems(
|
|||
]
|
||||
: [{ type: 'description', id: '0', contract }]
|
||||
|
||||
items.push(
|
||||
...(outcomeType === 'FREE_RESPONSE'
|
||||
? getAnswerGroups(
|
||||
contract as FullContract<DPM, FreeResponse>,
|
||||
bets,
|
||||
comments,
|
||||
user,
|
||||
{
|
||||
sortByProb: true,
|
||||
abbreviated,
|
||||
reversed,
|
||||
}
|
||||
)
|
||||
: groupBets(bets, comments, contract, user?.id, {
|
||||
hideOutcome: false,
|
||||
// for each comment. turn it into an activity item and add it to the items:
|
||||
const commentWithoutBetActivityItems = comments
|
||||
.filter((comment) => !comment.betId)
|
||||
.map((comment) => ({
|
||||
type: 'comment' as const,
|
||||
id: comment.id,
|
||||
contract: contract,
|
||||
comment,
|
||||
bet: undefined,
|
||||
truncate: false,
|
||||
hideOutcome: true,
|
||||
smallAvatar: false,
|
||||
}))
|
||||
const groupedBets = groupBets(bets, comments, contract, user?.id, {
|
||||
hideOutcome: false,
|
||||
abbreviated,
|
||||
smallAvatar: false,
|
||||
reversed: false,
|
||||
})
|
||||
// iterate through the bets and comment acitivity items and add them to the items in order of comment creation time:
|
||||
const allActivityItems = [...commentWithoutBetActivityItems, ...groupedBets]
|
||||
const sortedActivityItems = _.sortBy(allActivityItems, (item) => {
|
||||
if (item.type === 'comment') {
|
||||
return item.comment.createdTime
|
||||
} else if (item.type === 'bet') {
|
||||
return item.bet.createdTime
|
||||
} else if (item.type === 'betgroup') {
|
||||
return item.bets[0].createdTime
|
||||
}
|
||||
})
|
||||
items.push(...sortedActivityItems)
|
||||
|
||||
if (outcomeType === 'FREE_RESPONSE') {
|
||||
items.push(
|
||||
...getAnswerGroups(
|
||||
contract as FullContract<DPM, FreeResponse>,
|
||||
bets,
|
||||
comments,
|
||||
user,
|
||||
{
|
||||
sortByProb: true,
|
||||
abbreviated,
|
||||
smallAvatar: false,
|
||||
reversed: false,
|
||||
}))
|
||||
)
|
||||
reversed,
|
||||
}
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
if (contract.closeTime && contract.closeTime <= Date.now()) {
|
||||
items.push({ type: 'close', id: `${contract.closeTime}`, contract })
|
||||
|
@ -306,6 +341,13 @@ export function getAllContractActivityItems(
|
|||
if (contract.resolution) {
|
||||
items.push({ type: 'resolve', id: `${contract.resolutionTime}`, contract })
|
||||
}
|
||||
items.push({
|
||||
type: 'commentInput',
|
||||
id: 'commentInput',
|
||||
bets,
|
||||
comments,
|
||||
contract,
|
||||
})
|
||||
|
||||
if (reversed) items.reverse()
|
||||
|
||||
|
|
|
@ -104,24 +104,30 @@ function FeedItem(props: { item: ActivityItem }) {
|
|||
return <FeedClose {...item} />
|
||||
case 'resolve':
|
||||
return <FeedResolve {...item} />
|
||||
case 'commentInput':
|
||||
return <CommentInput {...item} />
|
||||
}
|
||||
}
|
||||
|
||||
export function FeedComment(props: {
|
||||
contract: Contract
|
||||
comment: Comment
|
||||
bet: Bet
|
||||
bet: Bet | undefined
|
||||
hideOutcome: boolean
|
||||
truncate: boolean
|
||||
smallAvatar: boolean
|
||||
}) {
|
||||
const { contract, comment, bet, hideOutcome, truncate, smallAvatar } = props
|
||||
const { amount, outcome } = bet
|
||||
let money: string | undefined
|
||||
let outcome: string | undefined
|
||||
let bought: string | undefined
|
||||
if (bet) {
|
||||
outcome = bet.outcome
|
||||
bought = bet.amount >= 0 ? 'bought' : 'sold'
|
||||
money = formatMoney(Math.abs(bet.amount))
|
||||
}
|
||||
const { text, userUsername, userName, userAvatarUrl, createdTime } = comment
|
||||
|
||||
const bought = amount >= 0 ? 'bought' : 'sold'
|
||||
const money = formatMoney(Math.abs(amount))
|
||||
|
||||
return (
|
||||
<>
|
||||
<Avatar
|
||||
|
@ -144,7 +150,7 @@ export function FeedComment(props: {
|
|||
{' '}
|
||||
of{' '}
|
||||
<OutcomeLabel
|
||||
outcome={outcome}
|
||||
outcome={outcome ? outcome : ''}
|
||||
contract={contract}
|
||||
truncate="short"
|
||||
/>
|
||||
|
@ -174,6 +180,77 @@ function RelativeTimestamp(props: { time: number }) {
|
|||
)
|
||||
}
|
||||
|
||||
export function CommentInput(props: {
|
||||
contract: Contract
|
||||
comments: Comment[]
|
||||
bets: Bet[]
|
||||
}) {
|
||||
// see if we can comment input on any bet:
|
||||
const { contract, bets, comments } = props
|
||||
const user = useUser()
|
||||
let canCommentOnBet = false
|
||||
bets.forEach((bet) => {
|
||||
// make sure there is not already a comment with a mathcing bet id:
|
||||
const matchingComment = comments.find((comment) => comment.betId === bet.id)
|
||||
if (matchingComment) {
|
||||
return
|
||||
}
|
||||
const { createdTime, userId } = bet
|
||||
const isSelf = user?.id === userId
|
||||
// You can comment if your bet was posted in the last hour
|
||||
const canComment = isSelf && Date.now() - createdTime < 60 * 60 * 1000
|
||||
if (canComment) {
|
||||
// if you can comment on this bet, then you can comment on the contract
|
||||
canCommentOnBet = true
|
||||
}
|
||||
})
|
||||
|
||||
const [comment, setComment] = useState('')
|
||||
|
||||
async function submitComment() {
|
||||
if (!user || !comment) return
|
||||
await createComment(contract.id, comment, user)
|
||||
setComment('')
|
||||
}
|
||||
|
||||
if (canCommentOnBet) return <div />
|
||||
|
||||
return (
|
||||
<>
|
||||
<div>
|
||||
<Avatar avatarUrl={user?.avatarUrl} username={user?.username} />
|
||||
</div>
|
||||
<div className={'min-w-0 flex-1 py-1.5'}>
|
||||
<div className="text-sm text-gray-500">
|
||||
{/*<span>{isSelf ? 'You' : bettor ? bettor.name : 'A trader'}</span>{' '}*/}
|
||||
|
||||
<div className="mt-2">
|
||||
<Textarea
|
||||
value={comment}
|
||||
onChange={(e) => setComment(e.target.value)}
|
||||
className="textarea textarea-bordered w-full resize-none"
|
||||
placeholder="Add a comment..."
|
||||
rows={3}
|
||||
maxLength={MAX_COMMENT_LENGTH}
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === 'Enter' && (e.ctrlKey || e.metaKey)) {
|
||||
submitComment()
|
||||
}
|
||||
}}
|
||||
/>
|
||||
<button
|
||||
className="btn btn-outline btn-sm mt-1"
|
||||
onClick={submitComment}
|
||||
>
|
||||
Comment
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export function FeedBet(props: {
|
||||
contract: Contract
|
||||
bet: Bet
|
||||
|
@ -192,7 +269,7 @@ export function FeedBet(props: {
|
|||
const [comment, setComment] = useState('')
|
||||
async function submitComment() {
|
||||
if (!user || !comment || !canComment) return
|
||||
await createComment(contract.id, id, comment, user)
|
||||
await createComment(contract.id, comment, user, id)
|
||||
}
|
||||
|
||||
const bought = amount >= 0 ? 'bought' : 'sold'
|
||||
|
|
|
@ -19,25 +19,38 @@ export const MAX_COMMENT_LENGTH = 10000
|
|||
|
||||
export async function createComment(
|
||||
contractId: string,
|
||||
betId: string,
|
||||
text: string,
|
||||
commenter: User
|
||||
commenter: User,
|
||||
betId?: string
|
||||
) {
|
||||
const ref = doc(getCommentsCollection(contractId), betId)
|
||||
|
||||
const comment: Comment = {
|
||||
id: ref.id,
|
||||
contractId,
|
||||
betId,
|
||||
userId: commenter.id,
|
||||
text: text.slice(0, MAX_COMMENT_LENGTH),
|
||||
createdTime: Date.now(),
|
||||
userName: commenter.name,
|
||||
userUsername: commenter.username,
|
||||
userAvatarUrl: commenter.avatarUrl,
|
||||
if (betId) {
|
||||
const ref = doc(getCommentsCollection(contractId), betId)
|
||||
const comment: Comment = {
|
||||
id: ref.id,
|
||||
betId: betId,
|
||||
contractId,
|
||||
userId: commenter.id,
|
||||
text: text.slice(0, MAX_COMMENT_LENGTH),
|
||||
createdTime: Date.now(),
|
||||
userName: commenter.name,
|
||||
userUsername: commenter.username,
|
||||
userAvatarUrl: commenter.avatarUrl,
|
||||
}
|
||||
return await setDoc(ref, comment)
|
||||
} else {
|
||||
const newCommentRef = doc(getCommentsCollection(contractId))
|
||||
let comment: Comment = {
|
||||
id: newCommentRef.id,
|
||||
contractId,
|
||||
userId: commenter.id,
|
||||
text: text.slice(0, MAX_COMMENT_LENGTH),
|
||||
createdTime: Date.now(),
|
||||
userName: commenter.name,
|
||||
userUsername: commenter.username,
|
||||
userAvatarUrl: commenter.avatarUrl,
|
||||
}
|
||||
return await setDoc(newCommentRef, comment)
|
||||
}
|
||||
|
||||
return await setDoc(ref, comment)
|
||||
}
|
||||
|
||||
function getCommentsCollection(contractId: string) {
|
||||
|
@ -67,7 +80,9 @@ export function listenForComments(
|
|||
export function mapCommentsByBetId(comments: Comment[]) {
|
||||
const map: Record<string, Comment> = {}
|
||||
for (const comment of comments) {
|
||||
map[comment.betId] = comment
|
||||
if (comment.betId) {
|
||||
map[comment.betId] = comment
|
||||
}
|
||||
}
|
||||
return map
|
||||
}
|
||||
|
|
|
@ -250,7 +250,10 @@ function ContractTopTrades(props: {
|
|||
const topBettor = useUserById(betsById[topBetId]?.userId)
|
||||
|
||||
// And also the commentId of the comment with the highest profit
|
||||
const topCommentId = _.sortBy(comments, (c) => -profitById[c.betId])[0]?.id
|
||||
const topCommentId = _.sortBy(
|
||||
comments,
|
||||
(c) => c.betId && -profitById[c.betId]
|
||||
)[0]?.id
|
||||
|
||||
return (
|
||||
<div className="mt-12 max-w-sm">
|
||||
|
|
Loading…
Reference in New Issue
Block a user