Make typing for comments more fancy (#776)

This commit is contained in:
Marshall Polaris 2022-08-19 01:06:40 -07:00 committed by GitHub
parent f2764e9258
commit 0972de9025
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
17 changed files with 91 additions and 81 deletions

View File

@ -1,15 +1,11 @@
import type { JSONContent } from '@tiptap/core'
export type AnyCommentType = OnContract | OnGroup
// Currently, comments are created after the bet, not atomically with the bet.
// They're uniquely identified by the pair contractId/betId.
export type Comment = {
export type Comment<T extends AnyCommentType = AnyCommentType> = {
id: string
commentType: 'contract' | 'group'
contractId?: string
groupId?: string
betId?: string
answerOutcome?: string
replyToCommentId?: string
userId: string
@ -22,6 +18,21 @@ export type Comment = {
userName: string
userUsername: string
userAvatarUrl?: string
contractSlug?: string
contractQuestion?: string
} & T
type OnContract = {
commentType: 'contract'
contractId: string
contractSlug: string
contractQuestion: string
answerOutcome?: string
betId?: string
}
type OnGroup = {
commentType: 'group'
groupId: string
}
export type ContractComment = Comment<OnContract>
export type GroupComment = Comment<OnGroup>

View File

@ -2,7 +2,7 @@ import * as functions from 'firebase-functions'
import * as admin from 'firebase-admin'
import { compact, uniq } from 'lodash'
import { getContract, getUser, getValues } from './utils'
import { Comment } from '../../common/comment'
import { ContractComment } from '../../common/comment'
import { sendNewCommentEmail } from './emails'
import { Bet } from '../../common/bet'
import { Answer } from '../../common/answer'
@ -29,7 +29,7 @@ export const onCreateCommentOnContract = functions
contractQuestion: contract.question,
})
const comment = change.data() as Comment
const comment = change.data() as ContractComment
const lastCommentTime = comment.createdTime
const commentCreator = await getUser(comment.userId)
@ -64,7 +64,7 @@ export const onCreateCommentOnContract = functions
: undefined
}
const comments = await getValues<Comment>(
const comments = await getValues<ContractComment>(
firestore.collection('contracts').doc(contractId).collection('comments')
)
const relatedSourceType = comment.replyToCommentId

View File

@ -1,5 +1,5 @@
import * as functions from 'firebase-functions'
import { Comment } from '../../common/comment'
import { GroupComment } from '../../common/comment'
import * as admin from 'firebase-admin'
import { Group } from '../../common/group'
import { User } from '../../common/user'
@ -14,7 +14,7 @@ export const onCreateCommentOnGroup = functions.firestore
groupId: string
}
const comment = change.data() as Comment
const comment = change.data() as GroupComment
const creatorSnapshot = await firestore
.collection('users')
.doc(comment.userId)

View File

@ -1,6 +1,6 @@
import { useEffect, useState } from 'react'
import { Comment } from 'common/comment'
import { Comment, ContractComment } from 'common/comment'
import { groupConsecutive } from 'common/util/array'
import { getUsersComments } from 'web/lib/firebase/comments'
import { SiteLink } from './site-link'
@ -16,12 +16,6 @@ import { LoadingIndicator } from './loading-indicator'
const COMMENTS_PER_PAGE = 50
type ContractComment = Comment & {
contractId: string
contractSlug: string
contractQuestion: string
}
function contractPath(slug: string) {
// by convention this includes the contract creator username, but we don't
// have that handy, so we just put /market/
@ -38,7 +32,9 @@ export function UserCommentsList(props: { user: User }) {
useEffect(() => {
getUsersComments(user.id).then((cs) => {
// we don't show comments in groups here atm, just comments on contracts
setComments(cs.filter((c) => c.contractId) as ContractComment[])
setComments(
cs.filter((c) => c.commentType == 'contract') as ContractComment[]
)
})
}, [user.id])

View File

@ -1,5 +1,5 @@
import { Bet } from 'common/bet'
import { Comment } from 'common/comment'
import { ContractComment } from 'common/comment'
import { resolvedPayout } from 'common/calculate'
import { Contract } from 'common/contract'
import { formatMoney } from 'common/util/format'
@ -65,7 +65,7 @@ export function ContractLeaderboard(props: {
export function ContractTopTrades(props: {
contract: Contract
bets: Bet[]
comments: Comment[]
comments: ContractComment[]
tips: CommentTipMap
}) {
const { contract, bets, comments, tips } = props

View File

@ -1,6 +1,6 @@
import { Bet } from 'common/bet'
import { Contract } from 'common/contract'
import { Comment } from 'web/lib/firebase/comments'
import { ContractComment } from 'common/comment'
import { User } from 'common/user'
import { ContractActivity } from '../feed/contract-activity'
import { ContractBetsTable, BetsSummary } from '../bets-list'
@ -15,7 +15,7 @@ export function ContractTabs(props: {
contract: Contract
user: User | null | undefined
bets: Bet[]
comments: Comment[]
comments: ContractComment[]
tips: CommentTipMap
}) {
const { contract, user, bets, tips } = props

View File

@ -3,7 +3,7 @@ import { uniq, sortBy } from 'lodash'
import { Answer } from 'common/answer'
import { Bet } from 'common/bet'
import { getOutcomeProbability } from 'common/calculate'
import { Comment } from 'common/comment'
import { ContractComment } from 'common/comment'
import { Contract, FreeResponseContract } from 'common/contract'
import { User } from 'common/user'
import { CommentTipMap } from 'web/hooks/use-tip-txns'
@ -28,7 +28,7 @@ type BaseActivityItem = {
export type CommentInputItem = BaseActivityItem & {
type: 'commentInput'
betsByCurrentUser: Bet[]
commentsByCurrentUser: Comment[]
commentsByCurrentUser: ContractComment[]
}
export type DescriptionItem = BaseActivityItem & {
@ -50,8 +50,8 @@ export type BetItem = BaseActivityItem & {
export type CommentThreadItem = BaseActivityItem & {
type: 'commentThread'
parentComment: Comment
comments: Comment[]
parentComment: ContractComment
comments: ContractComment[]
tips: CommentTipMap
bets: Bet[]
}
@ -60,7 +60,7 @@ export type AnswerGroupItem = BaseActivityItem & {
type: 'answergroup'
user: User | undefined | null
answer: Answer
comments: Comment[]
comments: ContractComment[]
tips: CommentTipMap
bets: Bet[]
}
@ -84,7 +84,7 @@ export type LiquidityItem = BaseActivityItem & {
function getAnswerAndCommentInputGroups(
contract: FreeResponseContract,
bets: Bet[],
comments: Comment[],
comments: ContractComment[],
tips: CommentTipMap,
user: User | undefined | null
) {
@ -116,7 +116,7 @@ function getAnswerAndCommentInputGroups(
function getCommentThreads(
bets: Bet[],
comments: Comment[],
comments: ContractComment[],
tips: CommentTipMap,
contract: Contract
) {
@ -135,7 +135,7 @@ function getCommentThreads(
return items
}
function commentIsGeneralComment(comment: Comment, contract: Contract) {
function commentIsGeneralComment(comment: ContractComment, contract: Contract) {
return (
comment.answerOutcome === undefined &&
(contract.outcomeType === 'FREE_RESPONSE'
@ -147,7 +147,7 @@ function commentIsGeneralComment(comment: Comment, contract: Contract) {
export function getSpecificContractActivityItems(
contract: Contract,
bets: Bet[],
comments: Comment[],
comments: ContractComment[],
liquidityProvisions: LiquidityProvision[],
tips: CommentTipMap,
user: User | null | undefined,

View File

@ -1,5 +1,5 @@
import { Contract } from 'web/lib/firebase/contracts'
import { Comment } from 'web/lib/firebase/comments'
import { ContractComment } from 'common/comment'
import { Bet } from 'common/bet'
import { useBets } from 'web/hooks/use-bets'
import { getSpecificContractActivityItems } from './activity-items'
@ -12,7 +12,7 @@ import { LiquidityProvision } from 'common/liquidity-provision'
export function ContractActivity(props: {
contract: Contract
bets: Bet[]
comments: Comment[]
comments: ContractComment[]
liquidityProvisions: LiquidityProvision[]
tips: CommentTipMap
user: User | null | undefined

View File

@ -1,6 +1,6 @@
import { Answer } from 'common/answer'
import { Bet } from 'common/bet'
import { Comment } from 'common/comment'
import { ContractComment } from 'common/comment'
import React, { useEffect, useState } from 'react'
import { Col } from 'web/components/layout/col'
import { Row } from 'web/components/layout/row'
@ -24,7 +24,7 @@ export function FeedAnswerCommentGroup(props: {
contract: any
user: User | undefined | null
answer: Answer
comments: Comment[]
comments: ContractComment[]
tips: CommentTipMap
bets: Bet[]
}) {
@ -69,7 +69,7 @@ export function FeedAnswerCommentGroup(props: {
])
const scrollAndOpenReplyInput = useEvent(
(comment?: Comment, answer?: Answer) => {
(comment?: ContractComment, answer?: Answer) => {
setReplyToUser(
comment
? { id: comment.userId, username: comment.userUsername }

View File

@ -1,5 +1,5 @@
import { Bet } from 'common/bet'
import { Comment } from 'common/comment'
import { ContractComment } from 'common/comment'
import { User } from 'common/user'
import { Contract } from 'common/contract'
import React, { useEffect, useState } from 'react'
@ -32,9 +32,9 @@ import { Editor } from '@tiptap/react'
export function FeedCommentThread(props: {
contract: Contract
comments: Comment[]
comments: ContractComment[]
tips: CommentTipMap
parentComment: Comment
parentComment: ContractComment
bets: Bet[]
smallAvatar?: boolean
}) {
@ -50,7 +50,7 @@ export function FeedCommentThread(props: {
)
commentsList.unshift(parentComment)
function scrollAndOpenReplyInput(comment: Comment) {
function scrollAndOpenReplyInput(comment: ContractComment) {
setReplyToUser({ id: comment.userId, username: comment.userUsername })
setShowReply(true)
}
@ -95,10 +95,10 @@ export function FeedCommentThread(props: {
export function CommentRepliesList(props: {
contract: Contract
commentsList: Comment[]
commentsList: ContractComment[]
betsByUserId: Dictionary<Bet[]>
tips: CommentTipMap
scrollAndOpenReplyInput: (comment: Comment) => void
scrollAndOpenReplyInput: (comment: ContractComment) => void
bets: Bet[]
treatFirstIndexEqually?: boolean
smallAvatar?: boolean
@ -156,12 +156,12 @@ export function CommentRepliesList(props: {
export function FeedComment(props: {
contract: Contract
comment: Comment
comment: ContractComment
tips: CommentTips
betsBySameUser: Bet[]
probAtCreatedTime?: number
smallAvatar?: boolean
onReplyClick?: (comment: Comment) => void
onReplyClick?: (comment: ContractComment) => void
}) {
const {
contract,
@ -274,7 +274,7 @@ export function FeedComment(props: {
export function getMostRecentCommentableBet(
betsByCurrentUser: Bet[],
commentsByCurrentUser: Comment[],
commentsByCurrentUser: ContractComment[],
user?: User | null,
answerOutcome?: string
) {
@ -319,7 +319,7 @@ function CommentStatus(props: {
export function CommentInput(props: {
contract: Contract
betsByCurrentUser: Bet[]
commentsByCurrentUser: Comment[]
commentsByCurrentUser: ContractComment[]
replyToUser?: { id: string; username: string }
// Reply to a free response answer
parentAnswerOutcome?: string

View File

@ -1,6 +1,6 @@
import { groupBy, mapValues, maxBy, sortBy } from 'lodash'
import { Contract } from 'web/lib/firebase/contracts'
import { Comment } from 'web/lib/firebase/comments'
import { ContractComment } from 'common/comment'
import { Bet } from 'common/bet'
const MAX_ACTIVE_CONTRACTS = 75
@ -19,7 +19,7 @@ function lastActivityTime(contract: Contract) {
// - Bet on market
export function findActiveContracts(
allContracts: Contract[],
recentComments: Comment[],
recentComments: ContractComment[],
recentBets: Bet[],
seenContracts: { [contractId: string]: number }
) {
@ -73,7 +73,7 @@ export function findActiveContracts(
)
const contractMostRecentComment = mapValues(
contractComments,
(comments) => maxBy(comments, (c) => c.createdTime) as Comment
(comments) => maxBy(comments, (c) => c.createdTime) as ContractComment
)
const prioritizedContracts = sortBy(activeContracts, (c) => {

View File

@ -4,7 +4,8 @@ import { PrivateUser, User } from 'common/user'
import React, { useEffect, memo, useState, useMemo } from 'react'
import { Avatar } from 'web/components/avatar'
import { Group } from 'common/group'
import { Comment, createCommentOnGroup } from 'web/lib/firebase/comments'
import { Comment, GroupComment } from 'common/comment'
import { createCommentOnGroup } from 'web/lib/firebase/comments'
import { CommentInputTextArea } from 'web/components/feed/feed-comments'
import { track } from 'web/lib/service/analytics'
import { firebaseLogin } from 'web/lib/firebase/users'
@ -24,7 +25,7 @@ import { setNotificationsAsSeen } from 'web/pages/notifications'
import { usePrivateUser } from 'web/hooks/use-user'
export function GroupChat(props: {
messages: Comment[]
messages: GroupComment[]
user: User | null | undefined
group: Group
tips: CommentTipMap
@ -58,7 +59,7 @@ export function GroupChat(props: {
// array of groups, where each group is an array of messages that are displayed as one
const groupedMessages = useMemo(() => {
// Group messages with createdTime within 2 minutes of each other.
const tempGrouped: Comment[][] = []
const tempGrouped: GroupComment[][] = []
for (let i = 0; i < messages.length; i++) {
const message = messages[i]
if (i === 0) tempGrouped.push([message])
@ -193,7 +194,7 @@ export function GroupChat(props: {
}
export function GroupChatInBubble(props: {
messages: Comment[]
messages: GroupComment[]
user: User | null | undefined
privateUser: PrivateUser | null | undefined
group: Group
@ -309,7 +310,7 @@ function GroupChatNotificationsIcon(props: {
const GroupMessage = memo(function GroupMessage_(props: {
user: User | null | undefined
comments: Comment[]
comments: GroupComment[]
group: Group
onReplyClick?: (comment: Comment) => void
setRef?: (ref: HTMLDivElement) => void

View File

@ -42,6 +42,10 @@ export function Tipper(prop: { comment: Comment; tips: CommentTips }) {
return
}
const contractId =
comment.commentType === 'contract' ? comment.contractId : undefined
const groupId =
comment.commentType === 'group' ? comment.groupId : undefined
await transact({
amount: change,
fromId: user.id,
@ -50,18 +54,14 @@ export function Tipper(prop: { comment: Comment; tips: CommentTips }) {
toType: 'USER',
token: 'M$',
category: 'TIP',
data: {
contractId: comment.contractId,
commentId: comment.id,
groupId: comment.groupId,
},
data: { commentId: comment.id, contractId, groupId },
description: `${user.name} tipped M$ ${change} to ${comment.userName} for a comment`,
})
track('send comment tip', {
contractId: comment.contractId,
commentId: comment.id,
groupId: comment.groupId,
contractId,
groupId,
amount: change,
fromId: user.id,
toId: comment.userId,

View File

@ -1,13 +1,13 @@
import { useEffect, useState } from 'react'
import { Comment, ContractComment, GroupComment } from 'common/comment'
import {
Comment,
listenForCommentsOnContract,
listenForCommentsOnGroup,
listenForRecentComments,
} from 'web/lib/firebase/comments'
export const useComments = (contractId: string) => {
const [comments, setComments] = useState<Comment[] | undefined>()
const [comments, setComments] = useState<ContractComment[] | undefined>()
useEffect(() => {
if (contractId) return listenForCommentsOnContract(contractId, setComments)
@ -16,7 +16,7 @@ export const useComments = (contractId: string) => {
return comments
}
export const useCommentsOnGroup = (groupId: string | undefined) => {
const [comments, setComments] = useState<Comment[] | undefined>()
const [comments, setComments] = useState<GroupComment[] | undefined>()
useEffect(() => {
if (groupId) return listenForCommentsOnGroup(groupId, setComments)

View File

@ -11,7 +11,7 @@ import {
import { getValues, listenForValues } from './utils'
import { db } from './init'
import { User } from 'common/user'
import { Comment } from 'common/comment'
import { Comment, ContractComment, GroupComment } from 'common/comment'
import { removeUndefinedProps } from 'common/util/object'
import { track } from '@amplitude/analytics-browser'
import { JSONContent } from '@tiptap/react'
@ -31,7 +31,8 @@ export async function createCommentOnContract(
const ref = betId
? doc(getCommentsCollection(contractId), betId)
: doc(getCommentsCollection(contractId))
const comment: Comment = removeUndefinedProps({
// contract slug and question are set via trigger
const comment = removeUndefinedProps({
id: ref.id,
commentType: 'contract',
contractId,
@ -60,7 +61,7 @@ export async function createCommentOnGroup(
replyToCommentId?: string
) {
const ref = doc(getCommentsOnGroupCollection(groupId))
const comment: Comment = removeUndefinedProps({
const comment = removeUndefinedProps({
id: ref.id,
commentType: 'group',
groupId,
@ -96,7 +97,7 @@ export async function listAllComments(contractId: string) {
}
export async function listAllCommentsOnGroup(groupId: string) {
const comments = await getValues<Comment>(
const comments = await getValues<GroupComment>(
getCommentsOnGroupCollection(groupId)
)
comments.sort((c1, c2) => c1.createdTime - c2.createdTime)
@ -105,9 +106,9 @@ export async function listAllCommentsOnGroup(groupId: string) {
export function listenForCommentsOnContract(
contractId: string,
setComments: (comments: Comment[]) => void
setComments: (comments: ContractComment[]) => void
) {
return listenForValues<Comment>(
return listenForValues<ContractComment>(
getCommentsCollection(contractId),
(comments) => {
comments.sort((c1, c2) => c1.createdTime - c2.createdTime)
@ -117,9 +118,9 @@ export function listenForCommentsOnContract(
}
export function listenForCommentsOnGroup(
groupId: string,
setComments: (comments: Comment[]) => void
setComments: (comments: GroupComment[]) => void
) {
return listenForValues<Comment>(
return listenForValues<GroupComment>(
getCommentsOnGroupCollection(groupId),
(comments) => {
comments.sort((c1, c2) => c1.createdTime - c2.createdTime)

View File

@ -17,7 +17,7 @@ import {
import { SEO } from 'web/components/SEO'
import { Page } from 'web/components/page'
import { Bet, listAllBets } from 'web/lib/firebase/bets'
import { Comment, listAllComments } from 'web/lib/firebase/comments'
import { listAllComments } from 'web/lib/firebase/comments'
import Custom404 from '../404'
import { AnswersPanel } from 'web/components/answers/answers-panel'
import { fromPropz, usePropz } from 'web/hooks/use-propz'
@ -38,6 +38,7 @@ import { CommentTipMap, useTipTxns } from 'web/hooks/use-tip-txns'
import { useSaveReferral } from 'web/hooks/use-save-referral'
import { getOpenGraphProps } from 'web/components/contract/contract-card-preview'
import { User } from 'common/user'
import { ContractComment } from 'common/comment'
import { listUsers } from 'web/lib/firebase/users'
import { FeedComment } from 'web/components/feed/feed-comments'
import { Title } from 'web/components/title'
@ -78,7 +79,7 @@ export default function ContractPage(props: {
contract: Contract | null
username: string
bets: Bet[]
comments: Comment[]
comments: ContractComment[]
slug: string
backToHome?: () => void
}) {
@ -314,7 +315,7 @@ function ContractLeaderboard(props: { contract: Contract; bets: Bet[] }) {
function ContractTopTrades(props: {
contract: Contract
bets: Bet[]
comments: Comment[]
comments: ContractComment[]
tips: CommentTipMap
}) {
const { contract, bets, comments, tips } = props

View File

@ -46,7 +46,7 @@ import { ENV_CONFIG } from 'common/envs/constants'
import { useSaveReferral } from 'web/hooks/use-save-referral'
import { Button } from 'web/components/button'
import { listAllCommentsOnGroup } from 'web/lib/firebase/comments'
import { Comment } from 'common/comment'
import { GroupComment } from 'common/comment'
import { GroupChat } from 'web/components/groups/group-chat'
export const getStaticProps = fromPropz(getStaticPropz)
@ -123,7 +123,7 @@ export default function GroupPage(props: {
topTraders: User[]
creatorScores: { [userId: string]: number }
topCreators: User[]
messages: Comment[]
messages: GroupComment[]
}) {
props = usePropz(props, getStaticPropz) ?? {
group: null,