Load bets and comments tabs data on user page independently

This commit is contained in:
Marshall Polaris 2022-08-12 00:59:13 -07:00
parent 79be0c555b
commit 374f5e6b2f
3 changed files with 92 additions and 123 deletions

View File

@ -1,5 +1,14 @@
import Link from 'next/link' import Link from 'next/link'
import { groupBy, mapValues, sortBy, partition, sumBy } from 'lodash' import {
Dictionary,
keyBy,
groupBy,
mapValues,
sortBy,
partition,
sumBy,
uniq,
} from 'lodash'
import dayjs from 'dayjs' import dayjs from 'dayjs'
import { useEffect, useMemo, useState } from 'react' import { useEffect, useMemo, useState } from 'react'
import clsx from 'clsx' import clsx from 'clsx'
@ -19,6 +28,7 @@ import {
Contract, Contract,
contractPath, contractPath,
getBinaryProbPercent, getBinaryProbPercent,
getContractFromId,
} from 'web/lib/firebase/contracts' } from 'web/lib/firebase/contracts'
import { Row } from './layout/row' import { Row } from './layout/row'
import { UserLink } from './user-page' import { UserLink } from './user-page'
@ -41,10 +51,12 @@ import { trackLatency } from 'web/lib/firebase/tracking'
import { NumericContract } from 'common/contract' import { NumericContract } from 'common/contract'
import { formatNumericProbability } from 'common/pseudo-numeric' import { formatNumericProbability } from 'common/pseudo-numeric'
import { useUser } from 'web/hooks/use-user' import { useUser } from 'web/hooks/use-user'
import { useUserBets } from 'web/hooks/use-user-bets'
import { SellSharesModal } from './sell-modal' import { SellSharesModal } from './sell-modal'
import { useUnfilledBets } from 'web/hooks/use-bets' import { useUnfilledBets } from 'web/hooks/use-bets'
import { LimitBet } from 'common/bet' import { LimitBet } from 'common/bet'
import { floatingEqual } from 'common/util/math' import { floatingEqual } from 'common/util/math'
import { filterDefined } from 'common/util/array'
import { Pagination } from './pagination' import { Pagination } from './pagination'
import { LimitOrderTable } from './limit-bets' import { LimitOrderTable } from './limit-bets'
@ -52,25 +64,35 @@ type BetSort = 'newest' | 'profit' | 'closeTime' | 'value'
type BetFilter = 'open' | 'limit_bet' | 'sold' | 'closed' | 'resolved' | 'all' type BetFilter = 'open' | 'limit_bet' | 'sold' | 'closed' | 'resolved' | 'all'
const CONTRACTS_PER_PAGE = 50 const CONTRACTS_PER_PAGE = 50
const JUNE_1_2022 = new Date('2022-06-01T00:00:00.000Z').valueOf()
export function BetsList(props: { export function BetsList(props: { user: User }) {
user: User const { user } = props
bets: Bet[] | undefined
contractsById: { [id: string]: Contract } | undefined
hideBetsBefore?: number
}) {
const { user, bets: allBets, contractsById, hideBetsBefore } = props
const signedInUser = useUser() const signedInUser = useUser()
const isYourBets = user.id === signedInUser?.id const isYourBets = user.id === signedInUser?.id
const hideBetsBefore = isYourBets ? 0 : JUNE_1_2022
const userBets = useUserBets(user.id, { includeRedemptions: true })
const [contractsById, setContractsById] = useState<
Dictionary<Contract> | undefined
>()
// Hide bets before 06-01-2022 if this isn't your own profile // Hide bets before 06-01-2022 if this isn't your own profile
// NOTE: This means public profits also begin on 06-01-2022 as well. // NOTE: This means public profits also begin on 06-01-2022 as well.
const bets = useMemo( const bets = useMemo(
() => allBets?.filter((bet) => bet.createdTime >= (hideBetsBefore ?? 0)), () => userBets?.filter((bet) => bet.createdTime >= (hideBetsBefore ?? 0)),
[allBets, hideBetsBefore] [userBets, hideBetsBefore]
) )
useEffect(() => {
if (bets) {
const contractIds = uniq(bets.map((b) => getContractFromId(b.contractId)))
Promise.all(contractIds).then((contracts) => {
setContractsById(keyBy(filterDefined(contracts), 'id'))
})
}
}, [bets])
const [sort, setSort] = useState<BetSort>('newest') const [sort, setSort] = useState<BetSort>('newest')
const [filter, setFilter] = useState<BetFilter>('open') const [filter, setFilter] = useState<BetFilter>('open')
const [page, setPage] = useState(0) const [page, setPage] = useState(0)

View File

@ -1,6 +1,12 @@
import { useEffect, useState } from 'react'
import { Dictionary, groupBy, keyBy } from 'lodash'
import { Comment } from 'common/comment' import { Comment } from 'common/comment'
import { Contract } from 'common/contract' import { Contract } from 'common/contract'
import { filterDefined } from 'common/util/array'
import { contractPath } from 'web/lib/firebase/contracts' import { contractPath } from 'web/lib/firebase/contracts'
import { getUsersComments } from 'web/lib/firebase/comments'
import { getContractFromId } from 'web/lib/firebase/contracts'
import { SiteLink } from './site-link' import { SiteLink } from './site-link'
import { Row } from './layout/row' import { Row } from './layout/row'
import { Avatar } from './avatar' import { Avatar } from './avatar'
@ -8,24 +14,41 @@ import { RelativeTimestamp } from './relative-timestamp'
import { UserLink } from './user-page' import { UserLink } from './user-page'
import { User } from 'common/user' import { User } from 'common/user'
import { Col } from './layout/col' import { Col } from './layout/col'
import { groupBy } from 'lodash'
import { Content } from './editor' import { Content } from './editor'
import { LoadingIndicator } from './loading-indicator'
export function UserCommentsList(props: { export function UserCommentsList(props: { user: User }) {
user: User const { user } = props
comments: Comment[] const [comments, setComments] = useState<Dictionary<Comment[]> | undefined>()
contractsById: { [id: string]: Contract } const [contracts, setContracts] = useState<Dictionary<Contract> | undefined>()
}) {
const { comments, contractsById } = props
// we don't show comments in groups here atm, just comments on contracts useEffect(() => {
const contractComments = comments.filter((c) => c.contractId) getUsersComments(user.id).then((cs) => {
const commentsByContract = groupBy(contractComments, 'contractId') // we don't show comments in groups here atm, just comments on contracts
const contractComments = cs.filter((c) => c.contractId)
const commentsByContractId = groupBy(contractComments, 'contractId')
setComments(commentsByContractId)
})
}, [user.id])
useEffect(() => {
if (comments) {
Promise.all(Object.keys(comments).map(getContractFromId)).then(
(contracts) => {
setContracts(keyBy(filterDefined(contracts), 'id'))
}
)
}
}, [comments])
if (comments == null || contracts == null) {
return <LoadingIndicator />
}
return ( return (
<Col className={'bg-white'}> <Col className={'bg-white'}>
{Object.entries(commentsByContract).map(([contractId, comments]) => { {Object.entries(comments).map(([contractId, comments]) => {
const contract = contractsById[contractId] const contract = contracts[contractId]
return ( return (
<div key={contractId} className="border-b p-5"> <div key={contractId} className="border-b p-5">
<SiteLink <SiteLink

View File

@ -1,5 +1,4 @@
import clsx from 'clsx' import clsx from 'clsx'
import { Dictionary, keyBy, uniq } from 'lodash'
import { useEffect, useState } from 'react' import { useEffect, useState } from 'react'
import { useRouter } from 'next/router' import { useRouter } from 'next/router'
import { LinkIcon } from '@heroicons/react/solid' import { LinkIcon } from '@heroicons/react/solid'
@ -18,18 +17,12 @@ import { Row } from './layout/row'
import { genHash } from 'common/util/random' import { genHash } from 'common/util/random'
import { QueryUncontrolledTabs } from './layout/tabs' import { QueryUncontrolledTabs } from './layout/tabs'
import { UserCommentsList } from './comments-list' import { UserCommentsList } from './comments-list'
import { Comment, getUsersComments } from 'web/lib/firebase/comments'
import { Contract } from 'common/contract'
import { getContractFromId, listContracts } from 'web/lib/firebase/contracts'
import { LoadingIndicator } from './loading-indicator'
import { FullscreenConfetti } from 'web/components/fullscreen-confetti' import { FullscreenConfetti } from 'web/components/fullscreen-confetti'
import { BetsList } from './bets-list' import { BetsList } from './bets-list'
import { FollowersButton, FollowingButton } from './following-button' import { FollowersButton, FollowingButton } from './following-button'
import { UserFollowButton } from './follow-button' import { UserFollowButton } from './follow-button'
import { GroupsButton } from 'web/components/groups/groups-button' import { GroupsButton } from 'web/components/groups/groups-button'
import { PortfolioValueSection } from './portfolio/portfolio-value-section' import { PortfolioValueSection } from './portfolio/portfolio-value-section'
import { filterDefined } from 'common/util/array'
import { useUserBets } from 'web/hooks/use-user-bets'
import { ReferralsButton } from 'web/components/referrals-button' import { ReferralsButton } from 'web/components/referrals-button'
import { formatMoney } from 'common/util/format' import { formatMoney } from 'common/util/format'
import { ShareIconButton } from 'web/components/share-icon-button' import { ShareIconButton } from 'web/components/share-icon-button'
@ -56,26 +49,12 @@ export function UserLink(props: {
} }
export const TAB_IDS = ['markets', 'comments', 'bets', 'groups'] export const TAB_IDS = ['markets', 'comments', 'bets', 'groups']
const JUNE_1_2022 = new Date('2022-06-01T00:00:00.000Z').valueOf()
export function UserPage(props: { user: User; currentUser?: User }) { export function UserPage(props: { user: User; currentUser?: User }) {
const { user, currentUser } = props const { user, currentUser } = props
const router = useRouter() const router = useRouter()
const isCurrentUser = user.id === currentUser?.id const isCurrentUser = user.id === currentUser?.id
const bannerUrl = user.bannerUrl ?? defaultBannerUrl(user.id) const bannerUrl = user.bannerUrl ?? defaultBannerUrl(user.id)
const [usersComments, setUsersComments] = useState<Comment[] | undefined>()
const [usersContracts, setUsersContracts] = useState<Contract[] | 'loading'>(
'loading'
)
const userBets = useUserBets(user.id, { includeRedemptions: true })
const betCount =
userBets === undefined
? 0
: userBets.filter((bet) => !bet.isRedemption && bet.amount !== 0).length
const [contractsById, setContractsById] = useState<
Dictionary<Contract> | undefined
>()
const [showConfetti, setShowConfetti] = useState(false) const [showConfetti, setShowConfetti] = useState(false)
useEffect(() => { useEffect(() => {
@ -83,30 +62,6 @@ export function UserPage(props: { user: User; currentUser?: User }) {
setShowConfetti(claimedMana) setShowConfetti(claimedMana)
}, [router]) }, [router])
useEffect(() => {
if (!user) return
getUsersComments(user.id).then(setUsersComments)
listContracts(user.id).then(setUsersContracts)
}, [user])
// TODO: display comments on groups
useEffect(() => {
if (usersComments && userBets) {
const uniqueContractIds = uniq([
...usersComments.map((comment) => comment.contractId),
...(userBets?.map((bet) => bet.contractId) ?? []),
])
Promise.all(
uniqueContractIds.map((contractId) =>
contractId ? getContractFromId(contractId) : undefined
)
).then((contracts) => {
const contractsById = keyBy(filterDefined(contracts), 'id')
setContractsById(contractsById)
})
}
}, [userBets, usersComments])
const profit = user.profitCached.allTime const profit = user.profitCached.allTime
return ( return (
@ -163,9 +118,7 @@ export function UserPage(props: { user: User; currentUser?: User }) {
</span>{' '} </span>{' '}
profit profit
</span> </span>
<Spacer h={4} /> <Spacer h={4} />
{user.bio && ( {user.bio && (
<> <>
<div> <div>
@ -174,7 +127,6 @@ export function UserPage(props: { user: User; currentUser?: User }) {
<Spacer h={4} /> <Spacer h={4} />
</> </>
)} )}
<Col className="flex-wrap gap-2 sm:flex-row sm:items-center sm:gap-4"> <Col className="flex-wrap gap-2 sm:flex-row sm:items-center sm:gap-4">
<Row className="gap-4"> <Row className="gap-4">
<FollowingButton user={user} /> <FollowingButton user={user} />
@ -236,7 +188,6 @@ export function UserPage(props: { user: User; currentUser?: User }) {
</SiteLink> </SiteLink>
)} )}
</Col> </Col>
<Spacer h={5} /> <Spacer h={5} />
{currentUser?.id === user.id && ( {currentUser?.id === user.id && (
<Row <Row
@ -259,58 +210,31 @@ export function UserPage(props: { user: User; currentUser?: User }) {
</Row> </Row>
)} )}
<Spacer h={5} /> <Spacer h={5} />
<QueryUncontrolledTabs
{usersContracts !== 'loading' && contractsById && usersComments ? ( currentPageForAnalytics={'profile'}
<QueryUncontrolledTabs labelClassName={'pb-2 pt-1 '}
currentPageForAnalytics={'profile'} tabs={[
labelClassName={'pb-2 pt-1 '} {
tabs={[ title: 'Markets',
{ content: (
title: 'Markets', <CreatorContractsList user={currentUser} creator={user} />
content: ( ),
<CreatorContractsList user={currentUser} creator={user} /> },
), {
tabIcon: ( title: 'Comments',
<span className="px-0.5 font-bold"> content: <UserCommentsList user={user} />,
{usersContracts.length} },
</span> {
), title: 'Bets',
}, content: (
{ <>
title: 'Comments', <PortfolioValueSection userId={user.id} />
content: ( <BetsList user={user} />
<UserCommentsList </>
user={user} ),
contractsById={contractsById} },
comments={usersComments} ]}
/> />
),
tabIcon: (
<span className="px-0.5 font-bold">
{usersComments.length}
</span>
),
},
{
title: 'Bets',
content: (
<div>
<PortfolioValueSection userId={user.id} />
<BetsList
user={user}
bets={userBets}
hideBetsBefore={isCurrentUser ? 0 : JUNE_1_2022}
contractsById={contractsById}
/>
</div>
),
tabIcon: <span className="px-0.5 font-bold">{betCount}</span>,
},
]}
/>
) : (
<LoadingIndicator />
)}
</Col> </Col>
</Page> </Page>
) )