adding replies toggle, filtering out responses that don't have replies
This commit is contained in:
parent
c115b5cca7
commit
8dd5b477d9
23
web/components/comments/comments.tsx
Normal file
23
web/components/comments/comments.tsx
Normal file
|
@ -0,0 +1,23 @@
|
|||
import clsx from 'clsx'
|
||||
import TriangleDownFillIcon from 'web/lib/icons/triangle-down-fill-icon'
|
||||
import { Row } from '../layout/row'
|
||||
|
||||
export function ReplyToggle(props: {
|
||||
seeReplies: boolean
|
||||
numComments: number
|
||||
onClick: () => void
|
||||
}) {
|
||||
const { seeReplies, numComments, onClick } = props
|
||||
return (
|
||||
<button className="text-left text-sm text-indigo-600" onClick={onClick}>
|
||||
<Row className="items-center gap-1">
|
||||
<div>
|
||||
{numComments} {numComments === 1 ? 'Reply' : 'Replies'}
|
||||
</div>
|
||||
<TriangleDownFillIcon
|
||||
className={clsx('h-2 w-2', seeReplies ? 'rotate-180' : '')}
|
||||
/>
|
||||
</Row>
|
||||
</button>
|
||||
)
|
||||
}
|
|
@ -36,6 +36,7 @@ import {
|
|||
usePersistentState,
|
||||
} from 'web/hooks/use-persistent-state'
|
||||
import { safeLocalStorage } from 'web/lib/util/local'
|
||||
import TriangleDownFillIcon from 'web/lib/icons/triangle-down-fill-icon'
|
||||
|
||||
export function ContractTabs(props: {
|
||||
contract: Contract
|
||||
|
@ -123,24 +124,28 @@ const CommentsTabContent = memo(function CommentsTabContent(props: {
|
|||
const topLevelComments = commentsByParent['_'] ?? []
|
||||
|
||||
const sortRow = comments.length > 0 && (
|
||||
<Row className="mb-4 items-center">
|
||||
<Button
|
||||
size={'xs'}
|
||||
color={'gray-white'}
|
||||
onClick={() => setSort(sort === 'Newest' ? 'Best' : 'Newest')}
|
||||
>
|
||||
<Tooltip
|
||||
text={
|
||||
sort === 'Best'
|
||||
? 'Highest tips + bounties first. Your new comments briefly appear to you first.'
|
||||
: ''
|
||||
}
|
||||
>
|
||||
Sort by: {sort}
|
||||
</Tooltip>
|
||||
</Button>
|
||||
|
||||
<Row className="mb-4 items-center justify-end gap-4">
|
||||
<BountiedContractSmallBadge contract={contract} showAmount />
|
||||
<Row className="items-center gap-1">
|
||||
<div className="text-greyscale-4 text-sm">Sort by:</div>
|
||||
<button
|
||||
className="text-greyscale-6 w-20 text-sm"
|
||||
onClick={() => setSort(sort === 'Newest' ? 'Best' : 'Newest')}
|
||||
>
|
||||
<Tooltip
|
||||
text={
|
||||
sort === 'Best'
|
||||
? 'Highest tips + bounties first. Your new comments briefly appear to you first.'
|
||||
: ''
|
||||
}
|
||||
>
|
||||
<Row className="items-center gap-1">
|
||||
{sort}
|
||||
<TriangleDownFillIcon className=" h-2 w-2" />
|
||||
</Row>
|
||||
</Tooltip>
|
||||
</button>
|
||||
</Row>
|
||||
</Row>
|
||||
)
|
||||
|
||||
|
@ -159,24 +164,32 @@ const CommentsTabContent = memo(function CommentsTabContent(props: {
|
|||
|
||||
return (
|
||||
<>
|
||||
{sortRow}
|
||||
{sortedAnswers.map((answer) => (
|
||||
<div key={answer.id} className="relative pb-4">
|
||||
<span
|
||||
className="absolute top-5 left-5 -ml-px h-[calc(100%-2rem)] w-0.5 bg-gray-200"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
<FeedAnswerCommentGroup
|
||||
contract={contract}
|
||||
answer={answer}
|
||||
answerComments={commentsByOutcome[answer.number.toString()] ?? []}
|
||||
tips={tips}
|
||||
/>
|
||||
</div>
|
||||
))}
|
||||
<Col className="mt-8 flex w-full">
|
||||
<div className="text-md mt-8 mb-2 text-left">General Comments</div>
|
||||
<div className="mb-4 w-full border-b border-gray-200" />
|
||||
<Col className="flex w-full">
|
||||
<div className="mb-4 w-full border-gray-200" />
|
||||
{sortedAnswers.map((answer) => {
|
||||
const answerComments =
|
||||
commentsByOutcome[answer.number.toString()] ?? []
|
||||
if (answerComments.length > 0) {
|
||||
return (
|
||||
<div key={answer.id} className="relative pb-4">
|
||||
<span
|
||||
className="absolute top-5 left-5 -ml-px h-[calc(100%-2rem)] w-0.5 bg-gray-200"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
<FeedAnswerCommentGroup
|
||||
contract={contract}
|
||||
answer={answer}
|
||||
answerComments={
|
||||
commentsByOutcome[answer.number.toString()] ?? []
|
||||
}
|
||||
tips={tips}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
} else {
|
||||
return <></>
|
||||
}
|
||||
})}
|
||||
{sortRow}
|
||||
<ContractCommentInput className="mb-5" contract={contract} />
|
||||
{generalTopLevelComments.map((comment) => (
|
||||
|
|
|
@ -16,6 +16,8 @@ import { CopyLinkDateTimeComponent } from 'web/components/feed/copy-link-date-ti
|
|||
import { useRouter } from 'next/router'
|
||||
import { CommentTipMap } from 'web/hooks/use-tip-txns'
|
||||
import { UserLink } from 'web/components/user-link'
|
||||
import TriangleDownFillIcon from 'web/lib/icons/triangle-down-fill-icon'
|
||||
import { ReplyToggle } from '../comments/comments'
|
||||
|
||||
export function FeedAnswerCommentGroup(props: {
|
||||
contract: FreeResponseContract
|
||||
|
@ -26,6 +28,8 @@ export function FeedAnswerCommentGroup(props: {
|
|||
const { answer, contract, answerComments, tips } = props
|
||||
const { username, avatarUrl, name, text } = answer
|
||||
|
||||
const [seeReplies, setSeeReplies] = useState(false)
|
||||
|
||||
const [replyTo, setReplyTo] = useState<ReplyTo>()
|
||||
const router = useRouter()
|
||||
const answerElementId = `answer-${answer.id}`
|
||||
|
@ -37,7 +41,6 @@ export function FeedAnswerCommentGroup(props: {
|
|||
answerRef.current.scrollIntoView(true)
|
||||
}
|
||||
}, [highlighted])
|
||||
|
||||
return (
|
||||
<Col className="relative flex-1 items-stretch gap-3">
|
||||
<Row
|
||||
|
@ -49,7 +52,6 @@ export function FeedAnswerCommentGroup(props: {
|
|||
id={answerElementId}
|
||||
>
|
||||
<Avatar username={username} avatarUrl={avatarUrl} />
|
||||
|
||||
<Col className="min-w-0 flex-1 lg:gap-1">
|
||||
<div className="text-sm text-gray-500">
|
||||
<UserLink username={username} name={name} /> answered
|
||||
|
@ -60,12 +62,11 @@ export function FeedAnswerCommentGroup(props: {
|
|||
elementId={answerElementId}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<Col className="align-items justify-between gap-2 sm:flex-row">
|
||||
<Row className="align-items justify-between gap-2 sm:flex-row">
|
||||
<span className="whitespace-pre-line text-lg">
|
||||
<Linkify text={text} />
|
||||
</span>
|
||||
<div className="sm:hidden">
|
||||
<div>
|
||||
<button
|
||||
className="text-xs font-bold text-gray-500 hover:underline"
|
||||
onClick={() =>
|
||||
|
@ -75,33 +76,30 @@ export function FeedAnswerCommentGroup(props: {
|
|||
Reply
|
||||
</button>
|
||||
</div>
|
||||
</Col>
|
||||
<div className="justify-initial hidden sm:block">
|
||||
<button
|
||||
className="text-xs font-bold text-gray-500 hover:underline"
|
||||
onClick={() =>
|
||||
setReplyTo({ id: answer.id, username: answer.username })
|
||||
}
|
||||
>
|
||||
Reply
|
||||
</button>
|
||||
</div>
|
||||
</Row>
|
||||
<ReplyToggle
|
||||
seeReplies={seeReplies}
|
||||
numComments={answerComments.length}
|
||||
onClick={() => setSeeReplies(!seeReplies)}
|
||||
/>
|
||||
</Col>
|
||||
</Row>
|
||||
<Col className="gap-3 pl-1">
|
||||
{answerComments.map((comment) => (
|
||||
<FeedComment
|
||||
key={comment.id}
|
||||
indent={true}
|
||||
contract={contract}
|
||||
comment={comment}
|
||||
tips={tips[comment.id] ?? {}}
|
||||
onReplyClick={() =>
|
||||
setReplyTo({ id: comment.id, username: comment.userUsername })
|
||||
}
|
||||
/>
|
||||
))}
|
||||
</Col>
|
||||
{seeReplies && (
|
||||
<Col className="gap-3 pl-1">
|
||||
{answerComments.map((comment) => (
|
||||
<FeedComment
|
||||
key={comment.id}
|
||||
indent={true}
|
||||
contract={contract}
|
||||
comment={comment}
|
||||
tips={tips[comment.id] ?? {}}
|
||||
onReplyClick={() =>
|
||||
setReplyTo({ id: comment.id, username: comment.userUsername })
|
||||
}
|
||||
/>
|
||||
))}
|
||||
</Col>
|
||||
)}
|
||||
{replyTo && (
|
||||
<div className="relative ml-7">
|
||||
<span
|
||||
|
|
Loading…
Reference in New Issue
Block a user