Change Tipper
interface, memoize FeedComment
(#1000)
* Change `Tipper` interface, memoize `FeedComment` * Fixup per James feedback * More fixup per James feedback
This commit is contained in:
parent
d2273087cf
commit
ddb186dd98
|
@ -2,6 +2,7 @@ import { Answer } from 'common/answer'
|
||||||
import { FreeResponseContract } from 'common/contract'
|
import { FreeResponseContract } from 'common/contract'
|
||||||
import { ContractComment } from 'common/comment'
|
import { ContractComment } from 'common/comment'
|
||||||
import React, { useEffect, useRef, useState } from 'react'
|
import React, { useEffect, useRef, useState } from 'react'
|
||||||
|
import { sum } from 'lodash'
|
||||||
import { Col } from 'web/components/layout/col'
|
import { Col } from 'web/components/layout/col'
|
||||||
import { Row } from 'web/components/layout/row'
|
import { Row } from 'web/components/layout/row'
|
||||||
import { Avatar } from 'web/components/avatar'
|
import { Avatar } from 'web/components/avatar'
|
||||||
|
@ -14,6 +15,8 @@ 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 { useUser } from 'web/hooks/use-user'
|
||||||
|
import { useEvent } from 'web/hooks/use-event'
|
||||||
import { CommentTipMap } from 'web/hooks/use-tip-txns'
|
import { CommentTipMap } from 'web/hooks/use-tip-txns'
|
||||||
import { UserLink } from 'web/components/user-link'
|
import { UserLink } from 'web/components/user-link'
|
||||||
|
|
||||||
|
@ -27,11 +30,17 @@ export function FeedAnswerCommentGroup(props: {
|
||||||
const { username, avatarUrl, name, text } = answer
|
const { username, avatarUrl, name, text } = answer
|
||||||
|
|
||||||
const [replyTo, setReplyTo] = useState<ReplyTo>()
|
const [replyTo, setReplyTo] = useState<ReplyTo>()
|
||||||
|
const user = useUser()
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const answerElementId = `answer-${answer.id}`
|
const answerElementId = `answer-${answer.id}`
|
||||||
const highlighted = router.asPath.endsWith(`#${answerElementId}`)
|
const highlighted = router.asPath.endsWith(`#${answerElementId}`)
|
||||||
const answerRef = useRef<HTMLDivElement>(null)
|
const answerRef = useRef<HTMLDivElement>(null)
|
||||||
|
|
||||||
|
const onSubmitComment = useEvent(() => setReplyTo(undefined))
|
||||||
|
const onReplyClick = useEvent((comment: ContractComment) => {
|
||||||
|
setReplyTo({ id: comment.id, username: comment.userUsername })
|
||||||
|
})
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (highlighted && answerRef.current != null) {
|
if (highlighted && answerRef.current != null) {
|
||||||
answerRef.current.scrollIntoView(true)
|
answerRef.current.scrollIntoView(true)
|
||||||
|
@ -95,10 +104,10 @@ export function FeedAnswerCommentGroup(props: {
|
||||||
indent={true}
|
indent={true}
|
||||||
contract={contract}
|
contract={contract}
|
||||||
comment={comment}
|
comment={comment}
|
||||||
tips={tips[comment.id] ?? {}}
|
myTip={user ? tips[comment.id]?.[user.id] : undefined}
|
||||||
onReplyClick={() =>
|
totalTip={sum(Object.values(tips[comment.id] ?? {}))}
|
||||||
setReplyTo({ id: comment.id, username: comment.userUsername })
|
showTip={true}
|
||||||
}
|
onReplyClick={onReplyClick}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
</Col>
|
</Col>
|
||||||
|
@ -112,7 +121,7 @@ export function FeedAnswerCommentGroup(props: {
|
||||||
contract={contract}
|
contract={contract}
|
||||||
parentAnswerOutcome={answer.number.toString()}
|
parentAnswerOutcome={answer.number.toString()}
|
||||||
replyTo={replyTo}
|
replyTo={replyTo}
|
||||||
onSubmitComment={() => setReplyTo(undefined)}
|
onSubmitComment={onSubmitComment}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
|
@ -1,11 +1,14 @@
|
||||||
|
import React, { memo, useEffect, useRef, useState } from 'react'
|
||||||
|
import { Editor } from '@tiptap/react'
|
||||||
|
import { useRouter } from 'next/router'
|
||||||
|
import { sum } from 'lodash'
|
||||||
|
import clsx from 'clsx'
|
||||||
|
|
||||||
import { ContractComment } from 'common/comment'
|
import { ContractComment } from 'common/comment'
|
||||||
import { Contract } from 'common/contract'
|
import { Contract } from 'common/contract'
|
||||||
import React, { useEffect, useRef, useState } from 'react'
|
|
||||||
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 { Row } from 'web/components/layout/row'
|
import { Row } from 'web/components/layout/row'
|
||||||
import clsx from 'clsx'
|
|
||||||
import { Avatar } from 'web/components/avatar'
|
import { Avatar } from 'web/components/avatar'
|
||||||
import { OutcomeLabel } from 'web/components/outcome-label'
|
import { OutcomeLabel } from 'web/components/outcome-label'
|
||||||
import { CopyLinkDateTimeComponent } from 'web/components/feed/copy-link-date-time'
|
import { CopyLinkDateTimeComponent } from 'web/components/feed/copy-link-date-time'
|
||||||
|
@ -14,9 +17,9 @@ import { createCommentOnContract } from 'web/lib/firebase/comments'
|
||||||
import { Col } from 'web/components/layout/col'
|
import { Col } from 'web/components/layout/col'
|
||||||
import { track } from 'web/lib/service/analytics'
|
import { track } from 'web/lib/service/analytics'
|
||||||
import { Tipper } from '../tipper'
|
import { Tipper } from '../tipper'
|
||||||
import { CommentTipMap, CommentTips } from 'web/hooks/use-tip-txns'
|
import { CommentTipMap } from 'web/hooks/use-tip-txns'
|
||||||
|
import { useEvent } from 'web/hooks/use-event'
|
||||||
import { Content } from '../editor'
|
import { Content } from '../editor'
|
||||||
import { Editor } from '@tiptap/react'
|
|
||||||
import { UserLink } from 'web/components/user-link'
|
import { UserLink } from 'web/components/user-link'
|
||||||
import { CommentInput } from '../comment-input'
|
import { CommentInput } from '../comment-input'
|
||||||
import { AwardBountyButton } from 'web/components/award-bounty-button'
|
import { AwardBountyButton } from 'web/components/award-bounty-button'
|
||||||
|
@ -32,6 +35,12 @@ export function FeedCommentThread(props: {
|
||||||
const { contract, threadComments, tips, parentComment } = props
|
const { contract, threadComments, tips, parentComment } = props
|
||||||
const [replyTo, setReplyTo] = useState<ReplyTo>()
|
const [replyTo, setReplyTo] = useState<ReplyTo>()
|
||||||
|
|
||||||
|
const user = useUser()
|
||||||
|
const onSubmitComment = useEvent(() => setReplyTo(undefined))
|
||||||
|
const onReplyClick = useEvent((comment: ContractComment) => {
|
||||||
|
setReplyTo({ id: comment.id, username: comment.userUsername })
|
||||||
|
})
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Col className="relative w-full items-stretch gap-3 pb-4">
|
<Col className="relative w-full items-stretch gap-3 pb-4">
|
||||||
<span
|
<span
|
||||||
|
@ -44,10 +53,10 @@ export function FeedCommentThread(props: {
|
||||||
indent={commentIdx != 0}
|
indent={commentIdx != 0}
|
||||||
contract={contract}
|
contract={contract}
|
||||||
comment={comment}
|
comment={comment}
|
||||||
tips={tips[comment.id] ?? {}}
|
myTip={user ? tips[comment.id]?.[user.id] : undefined}
|
||||||
onReplyClick={() =>
|
totalTip={sum(Object.values(tips[comment.id] ?? {}))}
|
||||||
setReplyTo({ id: comment.id, username: comment.userUsername })
|
showTip={true}
|
||||||
}
|
onReplyClick={onReplyClick}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
{replyTo && (
|
{replyTo && (
|
||||||
|
@ -60,7 +69,7 @@ export function FeedCommentThread(props: {
|
||||||
contract={contract}
|
contract={contract}
|
||||||
parentCommentId={parentComment.id}
|
parentCommentId={parentComment.id}
|
||||||
replyTo={replyTo}
|
replyTo={replyTo}
|
||||||
onSubmitComment={() => setReplyTo(undefined)}
|
onSubmitComment={onSubmitComment}
|
||||||
/>
|
/>
|
||||||
</Col>
|
</Col>
|
||||||
)}
|
)}
|
||||||
|
@ -68,14 +77,17 @@ export function FeedCommentThread(props: {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export function FeedComment(props: {
|
export const FeedComment = memo(function FeedComment(props: {
|
||||||
contract: Contract
|
contract: Contract
|
||||||
comment: ContractComment
|
comment: ContractComment
|
||||||
tips?: CommentTips
|
showTip?: boolean
|
||||||
|
myTip?: number
|
||||||
|
totalTip?: number
|
||||||
indent?: boolean
|
indent?: boolean
|
||||||
onReplyClick?: () => void
|
onReplyClick?: (comment: ContractComment) => void
|
||||||
}) {
|
}) {
|
||||||
const { contract, comment, tips, indent, onReplyClick } = props
|
const { contract, comment, myTip, totalTip, showTip, indent, onReplyClick } =
|
||||||
|
props
|
||||||
const {
|
const {
|
||||||
text,
|
text,
|
||||||
content,
|
content,
|
||||||
|
@ -180,12 +192,18 @@ export function FeedComment(props: {
|
||||||
{onReplyClick && (
|
{onReplyClick && (
|
||||||
<button
|
<button
|
||||||
className="font-bold hover:underline"
|
className="font-bold hover:underline"
|
||||||
onClick={onReplyClick}
|
onClick={() => onReplyClick(comment)}
|
||||||
>
|
>
|
||||||
Reply
|
Reply
|
||||||
</button>
|
</button>
|
||||||
)}
|
)}
|
||||||
{tips && <Tipper comment={comment} tips={tips} />}
|
{showTip && (
|
||||||
|
<Tipper
|
||||||
|
comment={comment}
|
||||||
|
myTip={myTip ?? 0}
|
||||||
|
totalTip={totalTip ?? 0}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
{(contract.openCommentBounties ?? 0) > 0 && (
|
{(contract.openCommentBounties ?? 0) > 0 && (
|
||||||
<AwardBountyButton comment={comment} contract={contract} />
|
<AwardBountyButton comment={comment} contract={contract} />
|
||||||
)}
|
)}
|
||||||
|
@ -193,7 +211,7 @@ export function FeedComment(props: {
|
||||||
</div>
|
</div>
|
||||||
</Row>
|
</Row>
|
||||||
)
|
)
|
||||||
}
|
})
|
||||||
|
|
||||||
function CommentStatus(props: {
|
function CommentStatus(props: {
|
||||||
contract: Contract
|
contract: Contract
|
||||||
|
|
|
@ -1,10 +1,9 @@
|
||||||
import { useEffect, useRef, useState } from 'react'
|
import { useEffect, useRef, useState } from 'react'
|
||||||
import toast from 'react-hot-toast'
|
import toast from 'react-hot-toast'
|
||||||
import { debounce, sum } from 'lodash'
|
import { debounce } from 'lodash'
|
||||||
|
|
||||||
import { Comment } from 'common/comment'
|
import { Comment } from 'common/comment'
|
||||||
import { User } from 'common/user'
|
import { User } from 'common/user'
|
||||||
import { CommentTips } from 'web/hooks/use-tip-txns'
|
|
||||||
import { useUser } from 'web/hooks/use-user'
|
import { useUser } from 'web/hooks/use-user'
|
||||||
import { transact } from 'web/lib/firebase/api'
|
import { transact } from 'web/lib/firebase/api'
|
||||||
import { track } from 'web/lib/service/analytics'
|
import { track } from 'web/lib/service/analytics'
|
||||||
|
@ -13,25 +12,27 @@ import { Row } from './layout/row'
|
||||||
import { LIKE_TIP_AMOUNT } from 'common/like'
|
import { LIKE_TIP_AMOUNT } from 'common/like'
|
||||||
import { formatMoney } from 'common/util/format'
|
import { formatMoney } from 'common/util/format'
|
||||||
|
|
||||||
export function Tipper(prop: { comment: Comment; tips: CommentTips }) {
|
export function Tipper(prop: {
|
||||||
const { comment, tips } = prop
|
comment: Comment
|
||||||
|
myTip: number
|
||||||
|
totalTip: number
|
||||||
|
}) {
|
||||||
|
const { comment, myTip, totalTip } = prop
|
||||||
|
|
||||||
const me = useUser()
|
const me = useUser()
|
||||||
const myId = me?.id ?? ''
|
|
||||||
const savedTip = tips[myId] ?? 0
|
|
||||||
|
|
||||||
const [localTip, setLocalTip] = useState(savedTip)
|
const [localTip, setLocalTip] = useState(myTip)
|
||||||
|
|
||||||
// listen for user being set
|
// listen for user being set
|
||||||
const initialized = useRef(false)
|
const initialized = useRef(false)
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (tips[myId] && !initialized.current) {
|
if (myTip && !initialized.current) {
|
||||||
setLocalTip(tips[myId])
|
setLocalTip(myTip)
|
||||||
initialized.current = true
|
initialized.current = true
|
||||||
}
|
}
|
||||||
}, [tips, myId])
|
}, [myTip])
|
||||||
|
|
||||||
const total = sum(Object.values(tips)) - savedTip + localTip
|
const total = totalTip - myTip + localTip
|
||||||
|
|
||||||
// declare debounced function only on first render
|
// declare debounced function only on first render
|
||||||
const [saveTip] = useState(() =>
|
const [saveTip] = useState(() =>
|
||||||
|
@ -73,7 +74,7 @@ export function Tipper(prop: { comment: Comment; tips: CommentTips }) {
|
||||||
|
|
||||||
const addTip = (delta: number) => {
|
const addTip = (delta: number) => {
|
||||||
setLocalTip(localTip + delta)
|
setLocalTip(localTip + delta)
|
||||||
me && saveTip(me, comment, localTip - savedTip + delta)
|
me && saveTip(me, comment, localTip - myTip + delta)
|
||||||
toast(`You tipped ${comment.userName} ${formatMoney(LIKE_TIP_AMOUNT)}!`)
|
toast(`You tipped ${comment.userName} ${formatMoney(LIKE_TIP_AMOUNT)}!`)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import { track } from '@amplitude/analytics-browser'
|
import { track } from '@amplitude/analytics-browser'
|
||||||
import { Editor } from '@tiptap/core'
|
import { Editor } from '@tiptap/core'
|
||||||
|
import { sum } from 'lodash'
|
||||||
import clsx from 'clsx'
|
import clsx from 'clsx'
|
||||||
import { PostComment } from 'common/comment'
|
import { PostComment } from 'common/comment'
|
||||||
import { Post } from 'common/post'
|
import { Post } from 'common/post'
|
||||||
|
@ -109,6 +110,7 @@ export function PostComment(props: {
|
||||||
const { text, content, userUsername, userName, userAvatarUrl, createdTime } =
|
const { text, content, userUsername, userName, userAvatarUrl, createdTime } =
|
||||||
comment
|
comment
|
||||||
|
|
||||||
|
const me = useUser()
|
||||||
const [highlighted, setHighlighted] = useState(false)
|
const [highlighted, setHighlighted] = useState(false)
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
@ -162,7 +164,11 @@ export function PostComment(props: {
|
||||||
Reply
|
Reply
|
||||||
</button>
|
</button>
|
||||||
)}
|
)}
|
||||||
<Tipper comment={comment} tips={tips ?? {}} />
|
<Tipper
|
||||||
|
comment={comment}
|
||||||
|
myTip={me ? tips[me.id] ?? 0 : 0}
|
||||||
|
totalTip={sum(Object.values(tips))}
|
||||||
|
/>
|
||||||
</Row>
|
</Row>
|
||||||
</div>
|
</div>
|
||||||
</Row>
|
</Row>
|
||||||
|
|
Loading…
Reference in New Issue
Block a user