Adds comments to posts
This commit is contained in:
parent
8029ee49a4
commit
048de52bee
|
@ -1,6 +1,6 @@
|
|||
import type { JSONContent } from '@tiptap/core'
|
||||
|
||||
export type AnyCommentType = OnContract | OnGroup
|
||||
export type AnyCommentType = OnContract | OnGroup | OnPost
|
||||
|
||||
// Currently, comments are created after the bet, not atomically with the bet.
|
||||
// They're uniquely identified by the pair contractId/betId.
|
||||
|
@ -34,5 +34,11 @@ type OnGroup = {
|
|||
groupId: string
|
||||
}
|
||||
|
||||
type OnPost = {
|
||||
commentType: 'post'
|
||||
postId: string
|
||||
}
|
||||
|
||||
export type ContractComment = Comment<OnContract>
|
||||
export type GroupComment = Comment<OnGroup>
|
||||
export type PostComment = Comment<OnPost>
|
||||
|
|
|
@ -188,6 +188,10 @@ service cloud.firestore {
|
|||
.affectedKeys()
|
||||
.hasOnly(['name', 'content']);
|
||||
allow delete: if isAdmin() || request.auth.uid == resource.data.creatorId;
|
||||
match /comments/{commentId} {
|
||||
allow read;
|
||||
allow create: if request.auth != null && commentMatchesUser(request.auth.uid, request.resource.data) ;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -46,6 +46,7 @@ export function Tipper(prop: { comment: Comment; tips: CommentTips }) {
|
|||
comment.commentType === 'contract' ? comment.contractId : undefined
|
||||
const groupId =
|
||||
comment.commentType === 'group' ? comment.groupId : undefined
|
||||
const postId = comment.commentType === 'post' ? comment.postId : undefined
|
||||
await transact({
|
||||
amount: change,
|
||||
fromId: user.id,
|
||||
|
@ -54,7 +55,7 @@ export function Tipper(prop: { comment: Comment; tips: CommentTips }) {
|
|||
toType: 'USER',
|
||||
token: 'M$',
|
||||
category: 'TIP',
|
||||
data: { commentId: comment.id, contractId, groupId },
|
||||
data: { commentId: comment.id, contractId, groupId, postId },
|
||||
description: `${user.name} tipped M$ ${change} to ${comment.userName} for a comment`,
|
||||
})
|
||||
|
||||
|
@ -62,6 +63,7 @@ export function Tipper(prop: { comment: Comment; tips: CommentTips }) {
|
|||
commentId: comment.id,
|
||||
contractId,
|
||||
groupId,
|
||||
postId,
|
||||
amount: change,
|
||||
fromId: user.id,
|
||||
toId: comment.userId,
|
||||
|
|
|
@ -1,8 +1,14 @@
|
|||
import { useEffect, useState } from 'react'
|
||||
import { Comment, ContractComment, GroupComment } from 'common/comment'
|
||||
import {
|
||||
Comment,
|
||||
ContractComment,
|
||||
GroupComment,
|
||||
PostComment,
|
||||
} from 'common/comment'
|
||||
import {
|
||||
listenForCommentsOnContract,
|
||||
listenForCommentsOnGroup,
|
||||
listenForCommentsOnPost,
|
||||
listenForRecentComments,
|
||||
} from 'web/lib/firebase/comments'
|
||||
|
||||
|
@ -25,6 +31,16 @@ export const useCommentsOnGroup = (groupId: string | undefined) => {
|
|||
return comments
|
||||
}
|
||||
|
||||
export const useCommentsOnPost = (postId: string | undefined) => {
|
||||
const [comments, setComments] = useState<PostComment[] | undefined>()
|
||||
|
||||
useEffect(() => {
|
||||
if (postId) return listenForCommentsOnPost(postId, setComments)
|
||||
}, [postId])
|
||||
|
||||
return comments
|
||||
}
|
||||
|
||||
export const useRecentComments = () => {
|
||||
const [recentComments, setRecentComments] = useState<Comment[] | undefined>()
|
||||
useEffect(() => listenForRecentComments(setRecentComments), [])
|
||||
|
|
|
@ -4,6 +4,7 @@ import { useEffect, useMemo, useState } from 'react'
|
|||
import {
|
||||
listenForTipTxns,
|
||||
listenForTipTxnsOnGroup,
|
||||
listenForTipTxnsOnPost,
|
||||
} from 'web/lib/firebase/txns'
|
||||
|
||||
export type CommentTips = { [userId: string]: number }
|
||||
|
@ -12,14 +13,16 @@ export type CommentTipMap = { [commentId: string]: CommentTips }
|
|||
export function useTipTxns(on: {
|
||||
contractId?: string
|
||||
groupId?: string
|
||||
postId?: string
|
||||
}): CommentTipMap {
|
||||
const [txns, setTxns] = useState<TipTxn[]>([])
|
||||
const { contractId, groupId } = on
|
||||
const { contractId, groupId, postId } = on
|
||||
|
||||
useEffect(() => {
|
||||
if (contractId) return listenForTipTxns(contractId, setTxns)
|
||||
if (groupId) return listenForTipTxnsOnGroup(groupId, setTxns)
|
||||
}, [contractId, groupId, setTxns])
|
||||
if (postId) return listenForTipTxnsOnPost(postId, setTxns)
|
||||
}, [contractId, groupId, postId, setTxns])
|
||||
|
||||
return useMemo(() => {
|
||||
const byComment = groupBy(txns, 'data.commentId')
|
||||
|
|
|
@ -7,12 +7,19 @@ import {
|
|||
query,
|
||||
setDoc,
|
||||
where,
|
||||
DocumentData,
|
||||
DocumentReference,
|
||||
} from 'firebase/firestore'
|
||||
|
||||
import { getValues, listenForValues } from './utils'
|
||||
import { db } from './init'
|
||||
import { User } from 'common/user'
|
||||
import { Comment, ContractComment, GroupComment } from 'common/comment'
|
||||
import {
|
||||
Comment,
|
||||
ContractComment,
|
||||
GroupComment,
|
||||
PostComment,
|
||||
} from 'common/comment'
|
||||
import { removeUndefinedProps } from 'common/util/object'
|
||||
import { track } from '@amplitude/analytics-browser'
|
||||
import { JSONContent } from '@tiptap/react'
|
||||
|
@ -24,7 +31,7 @@ export const MAX_COMMENT_LENGTH = 10000
|
|||
export async function createCommentOnContract(
|
||||
contractId: string,
|
||||
content: JSONContent,
|
||||
commenter: User,
|
||||
user: User,
|
||||
betId?: string,
|
||||
answerOutcome?: string,
|
||||
replyToCommentId?: string
|
||||
|
@ -32,28 +39,15 @@ export async function createCommentOnContract(
|
|||
const ref = betId
|
||||
? doc(getCommentsCollection(contractId), betId)
|
||||
: doc(getCommentsCollection(contractId))
|
||||
// contract slug and question are set via trigger
|
||||
const comment = removeUndefinedProps({
|
||||
id: ref.id,
|
||||
commentType: 'contract',
|
||||
return await createComment(
|
||||
contractId,
|
||||
userId: commenter.id,
|
||||
content: content,
|
||||
createdTime: Date.now(),
|
||||
userName: commenter.name,
|
||||
userUsername: commenter.username,
|
||||
userAvatarUrl: commenter.avatarUrl,
|
||||
betId: betId,
|
||||
answerOutcome: answerOutcome,
|
||||
replyToCommentId: replyToCommentId,
|
||||
})
|
||||
track('comment', {
|
||||
contractId,
|
||||
commentId: ref.id,
|
||||
betId: betId,
|
||||
replyToCommentId: replyToCommentId,
|
||||
})
|
||||
return await setDoc(ref, comment)
|
||||
'contract',
|
||||
content,
|
||||
user,
|
||||
ref,
|
||||
replyToCommentId,
|
||||
{ answerOutcome: answerOutcome, betId: betId }
|
||||
)
|
||||
}
|
||||
export async function createCommentOnGroup(
|
||||
groupId: string,
|
||||
|
@ -62,10 +56,45 @@ export async function createCommentOnGroup(
|
|||
replyToCommentId?: string
|
||||
) {
|
||||
const ref = doc(getCommentsOnGroupCollection(groupId))
|
||||
return await createComment(
|
||||
groupId,
|
||||
'group',
|
||||
content,
|
||||
user,
|
||||
ref,
|
||||
replyToCommentId
|
||||
)
|
||||
}
|
||||
|
||||
export async function createCommentOnPost(
|
||||
postId: string,
|
||||
content: JSONContent,
|
||||
user: User,
|
||||
replyToCommentId?: string
|
||||
) {
|
||||
const ref = doc(getCommentsOnPostCollection(postId))
|
||||
|
||||
return await createComment(
|
||||
postId,
|
||||
'post',
|
||||
content,
|
||||
user,
|
||||
ref,
|
||||
replyToCommentId
|
||||
)
|
||||
}
|
||||
|
||||
async function createComment(
|
||||
surfaceId: string,
|
||||
surfaceType: 'contract' | 'group' | 'post',
|
||||
content: JSONContent,
|
||||
user: User,
|
||||
ref: DocumentReference<DocumentData>,
|
||||
replyToCommentId?: string,
|
||||
extraFields: { [key: string]: any } = {}
|
||||
) {
|
||||
const comment = removeUndefinedProps({
|
||||
id: ref.id,
|
||||
commentType: 'group',
|
||||
groupId,
|
||||
userId: user.id,
|
||||
content: content,
|
||||
createdTime: Date.now(),
|
||||
|
@ -73,11 +102,13 @@ export async function createCommentOnGroup(
|
|||
userUsername: user.username,
|
||||
userAvatarUrl: user.avatarUrl,
|
||||
replyToCommentId: replyToCommentId,
|
||||
...extraFields,
|
||||
})
|
||||
track('group message', {
|
||||
|
||||
track(`${surfaceType} message`, {
|
||||
user,
|
||||
commentId: ref.id,
|
||||
groupId,
|
||||
surfaceId,
|
||||
replyToCommentId: replyToCommentId,
|
||||
})
|
||||
return await setDoc(ref, comment)
|
||||
|
@ -91,6 +122,10 @@ function getCommentsOnGroupCollection(groupId: string) {
|
|||
return collection(db, 'groups', groupId, 'comments')
|
||||
}
|
||||
|
||||
function getCommentsOnPostCollection(postId: string) {
|
||||
return collection(db, 'posts', postId, 'comments')
|
||||
}
|
||||
|
||||
export async function listAllComments(contractId: string) {
|
||||
return await getValues<Comment>(
|
||||
query(getCommentsCollection(contractId), orderBy('createdTime', 'desc'))
|
||||
|
@ -103,6 +138,12 @@ export async function listAllCommentsOnGroup(groupId: string) {
|
|||
)
|
||||
}
|
||||
|
||||
export async function listAllCommentsOnPost(postId: string) {
|
||||
return await getValues<PostComment>(
|
||||
query(getCommentsOnPostCollection(postId), orderBy('createdTime', 'desc'))
|
||||
)
|
||||
}
|
||||
|
||||
export function listenForCommentsOnContract(
|
||||
contractId: string,
|
||||
setComments: (comments: ContractComment[]) => void
|
||||
|
@ -126,6 +167,16 @@ export function listenForCommentsOnGroup(
|
|||
)
|
||||
}
|
||||
|
||||
export function listenForCommentsOnPost(
|
||||
postId: string,
|
||||
setComments: (comments: PostComment[]) => void
|
||||
) {
|
||||
return listenForValues<PostComment>(
|
||||
query(getCommentsOnPostCollection(postId), orderBy('createdTime', 'desc')),
|
||||
setComments
|
||||
)
|
||||
}
|
||||
|
||||
const DAY_IN_MS = 24 * 60 * 60 * 1000
|
||||
|
||||
// Define "recent" as "<3 days ago" for now
|
||||
|
|
|
@ -41,6 +41,13 @@ const getTipsOnGroupQuery = (groupId: string) =>
|
|||
where('data.groupId', '==', groupId)
|
||||
)
|
||||
|
||||
const getTipsOnPostQuery = (postId: string) =>
|
||||
query(
|
||||
txns,
|
||||
where('category', '==', 'TIP'),
|
||||
where('data.postId', '==', postId)
|
||||
)
|
||||
|
||||
export function listenForTipTxns(
|
||||
contractId: string,
|
||||
setTxns: (txns: TipTxn[]) => void
|
||||
|
@ -54,6 +61,13 @@ export function listenForTipTxnsOnGroup(
|
|||
return listenForValues<TipTxn>(getTipsOnGroupQuery(groupId), setTxns)
|
||||
}
|
||||
|
||||
export function listenForTipTxnsOnPost(
|
||||
postId: string,
|
||||
setTxns: (txns: TipTxn[]) => void
|
||||
) {
|
||||
return listenForValues<TipTxn>(getTipsOnPostQuery(postId), setTxns)
|
||||
}
|
||||
|
||||
// Find all manalink Txns that are from or to this user
|
||||
export function useManalinkTxns(userId: string) {
|
||||
const [fromTxns, setFromTxns] = useState<ManalinkTxn[]>([])
|
||||
|
|
|
@ -16,17 +16,25 @@ import { Col } from 'web/components/layout/col'
|
|||
import { ENV_CONFIG } from 'common/envs/constants'
|
||||
import Custom404 from 'web/pages/404'
|
||||
import { UserLink } from 'web/components/user-link'
|
||||
import { listAllCommentsOnPost } from 'web/lib/firebase/comments'
|
||||
import { PostComment } from 'common/comment'
|
||||
import { CommentTipMap, useTipTxns } from 'web/hooks/use-tip-txns'
|
||||
import { groupBy, sortBy } from 'lodash'
|
||||
import { PostCommentThread, CommentInput } from 'web/posts/post-comments'
|
||||
import { useCommentsOnPost } from 'web/hooks/use-comments'
|
||||
|
||||
export async function getStaticProps(props: { params: { slugs: string[] } }) {
|
||||
const { slugs } = props.params
|
||||
|
||||
const post = await getPostBySlug(slugs[0])
|
||||
const creator = post ? await getUser(post.creatorId) : null
|
||||
const comments = post && (await listAllCommentsOnPost(post.id))
|
||||
|
||||
return {
|
||||
props: {
|
||||
post: post,
|
||||
creator: creator,
|
||||
comments: comments,
|
||||
},
|
||||
|
||||
revalidate: 60, // regenerate after a minute
|
||||
|
@ -37,15 +45,22 @@ export async function getStaticPaths() {
|
|||
return { paths: [], fallback: 'blocking' }
|
||||
}
|
||||
|
||||
export default function PostPage(props: { post: Post; creator: User }) {
|
||||
export default function PostPage(props: {
|
||||
post: Post
|
||||
creator: User
|
||||
comments: PostComment[]
|
||||
}) {
|
||||
const [isShareOpen, setShareOpen] = useState(false)
|
||||
|
||||
const tips = useTipTxns({ postId: props.post.id })
|
||||
const shareUrl = `https://${ENV_CONFIG.domain}${postPath(props?.post.slug)}`
|
||||
const updatedComments = useCommentsOnPost(props.post.id)
|
||||
const comments = updatedComments ?? props.comments
|
||||
|
||||
if (props.post == null) {
|
||||
return <Custom404 />
|
||||
}
|
||||
|
||||
const shareUrl = `https://${ENV_CONFIG.domain}${postPath(props?.post.slug)}`
|
||||
|
||||
return (
|
||||
<Page>
|
||||
<div className="mx-auto w-full max-w-3xl ">
|
||||
|
@ -91,7 +106,56 @@ export default function PostPage(props: { post: Post; creator: User }) {
|
|||
<Content content={props.post.content} />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Spacer h={2} />
|
||||
<div className="rounded-lg bg-white px-6 py-4 sm:py-0">
|
||||
<PostCommentsActivity
|
||||
post={props.post}
|
||||
comments={comments}
|
||||
tips={tips}
|
||||
user={props.creator}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</Page>
|
||||
)
|
||||
}
|
||||
|
||||
export function PostCommentsActivity(props: {
|
||||
post: Post
|
||||
comments: PostComment[]
|
||||
tips: CommentTipMap
|
||||
user: User | null | undefined
|
||||
}) {
|
||||
const { post, comments, user, tips } = props
|
||||
const commentsByUserId = groupBy(comments, (c) => c.userId)
|
||||
const commentsByParentId = groupBy(comments, (c) => c.replyToCommentId ?? '_')
|
||||
const topLevelComments = sortBy(
|
||||
commentsByParentId['_'] ?? [],
|
||||
(c) => -c.createdTime
|
||||
)
|
||||
|
||||
return (
|
||||
<>
|
||||
<CommentInput
|
||||
className="mb-5"
|
||||
post={post}
|
||||
commentsByCurrentUser={(user && commentsByUserId[user.id]) ?? []}
|
||||
/>
|
||||
{topLevelComments.map((parent) => (
|
||||
<PostCommentThread
|
||||
key={parent.id}
|
||||
user={user}
|
||||
post={post}
|
||||
parentComment={parent}
|
||||
threadComments={sortBy(
|
||||
commentsByParentId[parent.id] ?? [],
|
||||
(c) => c.createdTime
|
||||
)}
|
||||
tips={tips}
|
||||
commentsByUserId={commentsByUserId}
|
||||
/>
|
||||
))}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
|
216
web/posts/post-comments.tsx
Normal file
216
web/posts/post-comments.tsx
Normal file
|
@ -0,0 +1,216 @@
|
|||
import { track } from '@amplitude/analytics-browser'
|
||||
import clsx from 'clsx'
|
||||
import { PostComment } from 'common/comment'
|
||||
import { Post } from 'common/post'
|
||||
import { User } from 'common/user'
|
||||
import { Dictionary } from 'lodash'
|
||||
import { useRouter } from 'next/router'
|
||||
import { useEffect, useState } from 'react'
|
||||
import { Avatar } from 'web/components/avatar'
|
||||
import { Content, useTextEditor } from 'web/components/editor'
|
||||
import { CopyLinkDateTimeComponent } from 'web/components/feed/copy-link-date-time'
|
||||
import { CommentInputTextArea } from 'web/components/feed/feed-comments'
|
||||
import { Col } from 'web/components/layout/col'
|
||||
import { Row } from 'web/components/layout/row'
|
||||
import { Tipper } from 'web/components/tipper'
|
||||
import { UserLink } from 'web/components/user-link'
|
||||
import { CommentTipMap, CommentTips } from 'web/hooks/use-tip-txns'
|
||||
import { useUser } from 'web/hooks/use-user'
|
||||
import {
|
||||
createCommentOnPost,
|
||||
MAX_COMMENT_LENGTH,
|
||||
} from 'web/lib/firebase/comments'
|
||||
import { firebaseLogin } from 'web/lib/firebase/users'
|
||||
|
||||
export function PostCommentThread(props: {
|
||||
user: User | null | undefined
|
||||
post: Post
|
||||
threadComments: PostComment[]
|
||||
tips: CommentTipMap
|
||||
parentComment: PostComment
|
||||
commentsByUserId: Dictionary<PostComment[]>
|
||||
}) {
|
||||
const { user, post, threadComments, commentsByUserId, tips, parentComment } =
|
||||
props
|
||||
const [showReply, setShowReply] = useState(false)
|
||||
const [replyTo, setReplyTo] = useState<{ id: string; username: string }>()
|
||||
|
||||
function scrollAndOpenReplyInput(comment: PostComment) {
|
||||
setReplyTo({ id: comment.userId, username: comment.userUsername })
|
||||
setShowReply(true)
|
||||
}
|
||||
|
||||
return (
|
||||
<Col className="relative w-full items-stretch gap-3 pb-4">
|
||||
sdafasdfadsf
|
||||
<span
|
||||
className="absolute top-5 left-4 -ml-px h-[calc(100%-2rem)] w-0.5 bg-gray-200"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
{[parentComment].concat(threadComments).map((comment, commentIdx) => (
|
||||
<PostComment
|
||||
key={comment.id}
|
||||
indent={commentIdx != 0}
|
||||
post={post}
|
||||
comment={comment}
|
||||
tips={tips[comment.id]}
|
||||
onReplyClick={scrollAndOpenReplyInput}
|
||||
/>
|
||||
))}
|
||||
{showReply && (
|
||||
<Col className="-pb-2 relative ml-6">
|
||||
<span
|
||||
className="absolute -left-1 -ml-[1px] mt-[0.8rem] h-2 w-0.5 rotate-90 bg-gray-200"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
<CommentInput
|
||||
post={post}
|
||||
commentsByCurrentUser={(user && commentsByUserId[user.id]) ?? []}
|
||||
parentCommentId={parentComment.id}
|
||||
replyToUser={replyTo}
|
||||
onSubmitComment={() => setShowReply(false)}
|
||||
/>
|
||||
</Col>
|
||||
)}
|
||||
</Col>
|
||||
)
|
||||
}
|
||||
|
||||
export function PostComment(props: {
|
||||
post: Post
|
||||
comment: PostComment
|
||||
tips: CommentTips
|
||||
indent?: boolean
|
||||
probAtCreatedTime?: number
|
||||
onReplyClick?: (comment: PostComment) => void
|
||||
}) {
|
||||
const { post, comment, tips, indent, probAtCreatedTime, onReplyClick } = props
|
||||
const { text, content, userUsername, userName, userAvatarUrl, createdTime } =
|
||||
comment
|
||||
|
||||
const [highlighted, setHighlighted] = useState(false)
|
||||
const router = useRouter()
|
||||
useEffect(() => {
|
||||
if (router.asPath.endsWith(`#${comment.id}`)) {
|
||||
setHighlighted(true)
|
||||
}
|
||||
}, [comment.id, router.asPath])
|
||||
|
||||
return (
|
||||
<Row
|
||||
id={comment.id}
|
||||
className={clsx(
|
||||
'relative',
|
||||
indent ? 'ml-6' : '',
|
||||
highlighted ? `-m-1.5 rounded bg-indigo-500/[0.2] p-1.5` : ''
|
||||
)}
|
||||
>
|
||||
{/*draw a gray line from the comment to the left:*/}
|
||||
{indent ? (
|
||||
<span
|
||||
className="absolute -left-1 -ml-[1px] mt-[0.8rem] h-2 w-0.5 rotate-90 bg-gray-200"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
) : null}
|
||||
<Avatar size="sm" username={userUsername} avatarUrl={userAvatarUrl} />
|
||||
<div className="ml-1.5 min-w-0 flex-1 pl-0.5 sm:ml-3">
|
||||
<div className="mt-0.5 text-sm text-gray-500">
|
||||
<UserLink
|
||||
className="text-gray-500"
|
||||
username={userUsername}
|
||||
name={userName}
|
||||
/>{' '}
|
||||
<CopyLinkDateTimeComponent
|
||||
prefix={comment.userName}
|
||||
slug={post.slug}
|
||||
createdTime={createdTime}
|
||||
elementId={comment.id}
|
||||
/>
|
||||
</div>
|
||||
<Content
|
||||
className="mt-2 text-[15px] text-gray-700"
|
||||
content={content || text}
|
||||
smallImage
|
||||
/>
|
||||
<Row className="mt-2 items-center gap-6 text-xs text-gray-500">
|
||||
<Tipper comment={comment} tips={tips ?? {}} />
|
||||
{onReplyClick && (
|
||||
<button
|
||||
className="font-bold hover:underline"
|
||||
onClick={() => onReplyClick(comment)}
|
||||
>
|
||||
Reply
|
||||
</button>
|
||||
)}
|
||||
</Row>
|
||||
</div>
|
||||
</Row>
|
||||
)
|
||||
}
|
||||
|
||||
export function CommentInput(props: {
|
||||
post: Post
|
||||
commentsByCurrentUser: PostComment[]
|
||||
className?: string
|
||||
replyToUser?: { id: string; username: string }
|
||||
// Reply to a free response answer
|
||||
parentAnswerOutcome?: string
|
||||
// Reply to another comment
|
||||
parentCommentId?: string
|
||||
onSubmitComment?: () => void
|
||||
}) {
|
||||
const {
|
||||
post,
|
||||
className,
|
||||
parentAnswerOutcome,
|
||||
parentCommentId,
|
||||
replyToUser,
|
||||
onSubmitComment,
|
||||
} = props
|
||||
const user = useUser()
|
||||
const { editor, upload } = useTextEditor({
|
||||
simple: true,
|
||||
max: MAX_COMMENT_LENGTH,
|
||||
placeholder:
|
||||
!!parentCommentId || !!parentAnswerOutcome
|
||||
? 'Write a reply...'
|
||||
: 'Write a comment...',
|
||||
})
|
||||
const [isSubmitting, setIsSubmitting] = useState(false)
|
||||
|
||||
async function submitComment(betId: string | undefined) {
|
||||
if (!user) {
|
||||
track('sign in to comment')
|
||||
return await firebaseLogin()
|
||||
}
|
||||
if (!editor || editor.isEmpty || isSubmitting) return
|
||||
setIsSubmitting(true)
|
||||
await createCommentOnPost(post.id, editor.getJSON(), user, parentCommentId)
|
||||
onSubmitComment?.()
|
||||
setIsSubmitting(false)
|
||||
}
|
||||
|
||||
if (user?.isBannedFromPosting) return <></>
|
||||
|
||||
return (
|
||||
<Row className={clsx(className, 'mb-2 gap-1 sm:gap-2')}>
|
||||
<Avatar
|
||||
avatarUrl={user?.avatarUrl}
|
||||
username={user?.username}
|
||||
size="sm"
|
||||
className="mt-2"
|
||||
/>
|
||||
<div className="min-w-0 flex-1 pl-0.5 text-sm">
|
||||
<div className="mb-1 text-gray-500"></div>
|
||||
<CommentInputTextArea
|
||||
editor={editor}
|
||||
upload={upload}
|
||||
replyToUser={replyToUser}
|
||||
user={user}
|
||||
submitComment={submitComment}
|
||||
isSubmitting={isSubmitting}
|
||||
/>
|
||||
</div>
|
||||
</Row>
|
||||
)
|
||||
}
|
Loading…
Reference in New Issue
Block a user