Misc comment ux improvements (#103)

* Separate comments and bets via tabs

* Normalcase comment button

* Note about abbreviated and all mode

* Revese,abbreviate,limit comments in feed
This commit is contained in:
Boa 2022-04-26 15:08:50 -06:00 committed by GitHub
parent f9f226aceb
commit 1db1a739cf
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 132 additions and 61 deletions

View File

@ -21,13 +21,24 @@ export function ContractTabs(props: {
bets.sort((bet1, bet2) => bet2.createdTime - bet1.createdTime) bets.sort((bet1, bet2) => bet2.createdTime - bet1.createdTime)
const userBets = user && bets.filter((bet) => bet.userId === user.id) const userBets = user && bets.filter((bet) => bet.userId === user.id)
const activity = ( const betActivity = (
<ContractActivity <ContractActivity
contract={contract} contract={contract}
bets={bets} bets={bets}
comments={comments} comments={comments}
user={user} user={user}
mode="all" mode="bets"
betRowClassName="!mt-0 xl:hidden"
/>
)
const commentActivity = (
<ContractActivity
contract={contract}
bets={bets}
comments={comments}
user={user}
mode="comments"
betRowClassName="!mt-0 xl:hidden" betRowClassName="!mt-0 xl:hidden"
/> />
) )
@ -48,7 +59,8 @@ export function ContractTabs(props: {
return ( return (
<Tabs <Tabs
tabs={[ tabs={[
{ title: 'Timeline', content: activity }, { title: 'Comments', content: commentActivity },
{ title: 'Bets', content: betActivity },
...(!user || !userBets?.length ...(!user || !userBets?.length
? [] ? []
: [{ title: 'Your bets', content: yourTrades }]), : [{ title: 'Your bets', content: yourTrades }]),

View File

@ -31,8 +31,6 @@ type BaseActivityItem = {
export type CommentInputItem = BaseActivityItem & { export type CommentInputItem = BaseActivityItem & {
type: 'commentInput' type: 'commentInput'
bets: Bet[]
commentsByBetId: Record<string, Comment>
} }
export type DescriptionItem = BaseActivityItem & { export type DescriptionItem = BaseActivityItem & {
@ -82,6 +80,7 @@ export type ResolveItem = BaseActivityItem & {
} }
const DAY_IN_MS = 24 * 60 * 60 * 1000 const DAY_IN_MS = 24 * 60 * 60 * 1000
const ABBREVIATED_NUM_COMMENTS_OR_BETS_TO_SHOW = 3
// Group together bets that are: // Group together bets that are:
// - Within a day of the first in the group // - Within a day of the first in the group
@ -173,7 +172,9 @@ function groupBets(
if (group.length > 0) { if (group.length > 0) {
pushGroup() pushGroup()
} }
const abbrItems = abbreviated ? items.slice(-3) : items const abbrItems = abbreviated
? items.slice(-ABBREVIATED_NUM_COMMENTS_OR_BETS_TO_SHOW)
: items
if (reversed) abbrItems.reverse() if (reversed) abbrItems.reverse()
return abbrItems return abbrItems
} }
@ -240,7 +241,8 @@ function getAnswerGroups(
reversed, reversed,
}) })
if (abbreviated) items = items.slice(-2) if (abbreviated)
items = items.slice(-ABBREVIATED_NUM_COMMENTS_OR_BETS_TO_SHOW)
return { return {
id: outcome, id: outcome,
@ -253,6 +255,8 @@ function getAnswerGroups(
}) })
.filter((group) => group.answer) .filter((group) => group.answer)
if (reversed) answerGroups.reverse()
return answerGroups return answerGroups
} }
@ -268,6 +272,7 @@ function groupBetsAndComments(
reversed: boolean reversed: boolean
} }
) { ) {
const { smallAvatar, abbreviated, reversed } = options
const commentsWithoutBets = comments const commentsWithoutBets = comments
.filter((comment) => !comment.betId) .filter((comment) => !comment.betId)
.map((comment) => ({ .map((comment) => ({
@ -276,16 +281,16 @@ function groupBetsAndComments(
contract: contract, contract: contract,
comment, comment,
bet: undefined, bet: undefined,
truncate: false, truncate: abbreviated,
hideOutcome: true, hideOutcome: true,
smallAvatar: false, smallAvatar,
})) }))
const groupedBets = groupBets(bets, comments, contract, userId, options) const groupedBets = groupBets(bets, comments, contract, userId, options)
// iterate through the bets and comment activity items and add them to the items in order of comment creation time: // iterate through the bets and comment activity items and add them to the items in order of comment creation time:
const unorderedBetsAndComments = [...commentsWithoutBets, ...groupedBets] const unorderedBetsAndComments = [...commentsWithoutBets, ...groupedBets]
const sortedBetsAndComments = _.sortBy(unorderedBetsAndComments, (item) => { let sortedBetsAndComments = _.sortBy(unorderedBetsAndComments, (item) => {
if (item.type === 'comment') { if (item.type === 'comment') {
return item.comment.createdTime return item.comment.createdTime
} else if (item.type === 'bet') { } else if (item.type === 'bet') {
@ -294,7 +299,13 @@ function groupBetsAndComments(
return item.bets[0].createdTime return item.bets[0].createdTime
} }
}) })
return sortedBetsAndComments
const abbrItems = abbreviated
? sortedBetsAndComments.slice(-ABBREVIATED_NUM_COMMENTS_OR_BETS_TO_SHOW)
: sortedBetsAndComments
if (reversed) abbrItems.reverse()
return abbrItems
} }
export function getAllContractActivityItems( export function getAllContractActivityItems(
@ -308,8 +319,7 @@ export function getAllContractActivityItems(
) { ) {
const { abbreviated } = options const { abbreviated } = options
const { outcomeType } = contract const { outcomeType } = contract
const reversed = true
const reversed = !abbreviated
bets = bets =
outcomeType === 'BINARY' outcomeType === 'BINARY'
@ -347,12 +357,9 @@ export function getAllContractActivityItems(
} }
) )
) )
const commentsByBetId = mapCommentsByBetId(comments)
items.push({ items.push({
type: 'commentInput', type: 'commentInput',
id: 'commentInput', id: 'commentInput',
bets,
commentsByBetId,
contract, contract,
}) })
} else { } else {
@ -374,12 +381,9 @@ export function getAllContractActivityItems(
} }
if (outcomeType === 'BINARY') { if (outcomeType === 'BINARY') {
const commentsByBetId = mapCommentsByBetId(comments)
items.push({ items.push({
type: 'commentInput', type: 'commentInput',
id: 'commentInput', id: 'commentInput',
bets,
commentsByBetId,
contract, contract,
}) })
} }
@ -412,25 +416,95 @@ export function getRecentContractActivityItems(
contractPath, contractPath,
} }
const items = const items = []
contract.outcomeType === 'FREE_RESPONSE' if (contract.outcomeType === 'FREE_RESPONSE') {
? getAnswerGroups( items.push(
contract as FullContract<DPM, FreeResponse>, ...getAnswerGroups(
bets, contract as FullContract<DPM, FreeResponse>,
comments, bets,
user, comments,
{ user,
sortByProb: false, {
abbreviated: true, sortByProb: false,
reversed: false, abbreviated: true,
} reversed: true,
) }
: groupBetsAndComments(bets, comments, contract, user?.id, { )
)
} else {
const onlyUsersBetsOrBetsWithComments = bets.filter((bet) =>
comments.some(
(comment) => comment.betId === bet.id || bet.userId === user?.id
)
)
items.push(
...groupBetsAndComments(
onlyUsersBetsOrBetsWithComments,
comments,
contract,
user?.id,
{
hideOutcome: false, hideOutcome: false,
abbreviated: true, abbreviated: true,
smallAvatar: false, smallAvatar: false,
reversed: false, reversed: true,
}) }
)
)
}
return [questionItem, ...items] return [questionItem, ...items]
} }
export function getSpecificContractActivityItems(
contract: Contract,
bets: Bet[],
comments: Comment[],
user: User | null | undefined,
options: {
mode: 'comments' | 'bets'
}
) {
const { mode } = options
let items = [] as ActivityItem[]
switch (mode) {
case 'bets':
items.push(
...groupBets(bets, comments, contract, user?.id, {
hideOutcome: false,
abbreviated: false,
smallAvatar: false,
reversed: false,
})
)
break
case 'comments':
const onlyBetsWithComments = bets.filter((bet) =>
comments.some((comment) => comment.betId === bet.id)
)
items.push(
...groupBetsAndComments(
onlyBetsWithComments,
comments,
contract,
user?.id,
{
hideOutcome: false,
abbreviated: false,
smallAvatar: false,
reversed: false,
}
)
)
items.push({
type: 'commentInput',
id: 'commentInput',
contract,
})
break
}
return items.reverse()
}

View File

@ -1,5 +1,3 @@
import _ from 'lodash'
import { Contract } from '../../lib/firebase/contracts' import { Contract } from '../../lib/firebase/contracts'
import { Comment } from '../../lib/firebase/comments' import { Comment } from '../../lib/firebase/comments'
import { Bet } from '../../../common/bet' import { Bet } from '../../../common/bet'
@ -8,6 +6,7 @@ import { useComments } from '../../hooks/use-comments'
import { import {
getAllContractActivityItems, getAllContractActivityItems,
getRecentContractActivityItems, getRecentContractActivityItems,
getSpecificContractActivityItems,
} from './activity-items' } from './activity-items'
import { FeedItems } from './feed-items' import { FeedItems } from './feed-items'
import { User } from '../../../common/user' import { User } from '../../../common/user'
@ -17,7 +16,7 @@ export function ContractActivity(props: {
bets: Bet[] bets: Bet[]
comments: Comment[] comments: Comment[]
user: User | null | undefined user: User | null | undefined
mode: 'only-recent' | 'abbreviated' | 'all' mode: 'only-recent' | 'abbreviated' | 'all' | 'comments' | 'bets'
contractPath?: string contractPath?: string
className?: string className?: string
betRowClassName?: string betRowClassName?: string
@ -39,7 +38,12 @@ export function ContractActivity(props: {
? getRecentContractActivityItems(contract, bets, comments, user, { ? getRecentContractActivityItems(contract, bets, comments, user, {
contractPath, contractPath,
}) })
: getAllContractActivityItems(contract, bets, comments, user, { : mode === 'comments' || mode === 'bets'
? getSpecificContractActivityItems(contract, bets, comments, user, {
mode,
})
: // only used in abbreviated mode with folds/communities, all mode isn't used
getAllContractActivityItems(contract, bets, comments, user, {
abbreviated: mode === 'abbreviated', abbreviated: mode === 'abbreviated',
}) })

View File

@ -191,31 +191,12 @@ function RelativeTimestamp(props: { time: number }) {
) )
} }
export function CommentInput(props: { export function CommentInput(props: { contract: Contract }) {
contract: Contract
commentsByBetId: Record<string, Comment>
bets: Bet[]
}) {
// see if we can comment input on any bet: // see if we can comment input on any bet:
const { contract, bets, commentsByBetId } = props const { contract } = props
const { outcomeType } = contract
const user = useUser() const user = useUser()
const [comment, setComment] = useState('') const [comment, setComment] = useState('')
let canCommentOnABet = false
bets.some((bet) => {
// make sure there is not already a comment with a matching bet id:
const matchingComment = commentsByBetId[bet.id]
if (matchingComment) {
return false
}
const { createdTime, userId } = bet
canCommentOnABet = canCommentOnBet(userId, createdTime, user)
return canCommentOnABet
})
if (canCommentOnABet) return <div />
async function submitComment() { async function submitComment() {
if (!comment) return if (!comment) return
if (!user) { if (!user) {
@ -342,7 +323,7 @@ export function FeedBet(props: {
}} }}
/> />
<button <button
className="btn btn-outline btn-sm mt-1" className="btn btn-outline btn-sm text-transform: mt-1 capitalize"
onClick={submitComment} onClick={submitComment}
disabled={!canComment} disabled={!canComment}
> >