Skip to content

Commit

Permalink
New feed improvements (#3218)
Browse files Browse the repository at this point in the history
* truncate if too long

* add limit orders

* add to sidebar

* shrink imgs on mobile

* activity in bottom bar

* max w

* comment divider

* styling

* Only hide img on desktop if single bet

* infinite scroll
  • Loading branch information
mantikoros authored Dec 14, 2024
1 parent 532f9e2 commit 63226bb
Show file tree
Hide file tree
Showing 5 changed files with 109 additions and 54 deletions.
18 changes: 9 additions & 9 deletions backend/api/src/get-site-activity.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import { convertContractComment } from 'common/supabase/comments'
export const getSiteActivity: APIHandler<'get-site-activity'> = async (
props
) => {
const { limit, blockedGroupSlugs = [], blockedContractIds = [] } = props
const { limit, offset = 0, blockedGroupSlugs = [], blockedContractIds = [] } = props
log('getSiteActivity called', { limit })

const blockedUserIds = [
Expand All @@ -35,8 +35,8 @@ export const getSiteActivity: APIHandler<'get-site-activity'> = async (
and is_api is not true
and user_id != all($1)
and contract_id != all($2)
order by created_time desc limit $3`,
[blockedUserIds, blockedContractIds, limit]
order by created_time desc limit $3 offset $4`,
[blockedUserIds, blockedContractIds, limit, offset]
),
pg.manyOrNone(
`select * from contract_bets
Expand All @@ -54,16 +54,16 @@ export const getSiteActivity: APIHandler<'get-site-activity'> = async (
and (data->>'isCancelled')::boolean = false
and user_id != all($1)
and contract_id != all($2)
order by created_time desc limit $3`,
[blockedUserIds, blockedContractIds, limit]
order by created_time desc limit $3 offset $4`,
[blockedUserIds, blockedContractIds, limit, offset]
),
pg.manyOrNone(
`select * from contract_comments
where (likes - coalesce(dislikes, 0)) >= 2
and user_id != all($1)
and contract_id != all($2)
order by created_time desc limit $3`,
[blockedUserIds, blockedContractIds, limit]
order by created_time desc limit $3 offset $4`,
[blockedUserIds, blockedContractIds, limit, offset]
),
pg.manyOrNone(
`select * from contracts
Expand All @@ -77,8 +77,8 @@ export const getSiteActivity: APIHandler<'get-site-activity'> = async (
where gc.contract_id = contracts.id
and g.slug = any($3)
)
order by created_time desc limit $4`,
[blockedUserIds, blockedContractIds, blockedGroupSlugs, limit]
order by created_time desc limit $4 offset $5`,
[blockedUserIds, blockedContractIds, blockedGroupSlugs, limit, offset]
),
])

Expand Down
1 change: 1 addition & 0 deletions common/src/api/schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2000,6 +2000,7 @@ export const API = (_apiTypeCheck = {
props: z
.object({
limit: z.coerce.number().default(10),
offset: z.coerce.number().default(0),
blockedUserIds: z.array(z.string()).optional(),
blockedGroupSlugs: z.array(z.string()).optional(),
blockedContractIds: z.array(z.string()).optional(),
Expand Down
8 changes: 4 additions & 4 deletions web/components/nav/bottom-nav-bar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import {
TransitionChild,
} from '@headlessui/react'
import {
GlobeAltIcon,
LightningBoltIcon,
NewspaperIcon,
QuestionMarkCircleIcon,
SearchIcon,
Expand Down Expand Up @@ -48,9 +48,9 @@ function getNavigation(user: User) {
icon: SearchIcon,
},
{
name: 'Explore',
href: '/explore',
icon: GlobeAltIcon,
name: 'Activity',
href: '/activity',
icon: LightningBoltIcon,
},
{
name: 'Profile',
Expand Down
1 change: 1 addition & 0 deletions web/components/nav/sidebar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -211,6 +211,7 @@ const getMobileNav = (
return buildArray<NavItem>(
{ name: 'Activity', href: '/activity', icon: LightningBoltIcon },
{ name: 'Leagues', href: '/leagues', icon: TrophyIcon },
{ name: 'Explore', href: '/explore', icon: GlobeAltIcon },
// {
// name: 'AI',
// href: '/ai',
Expand Down
135 changes: 94 additions & 41 deletions web/components/site-activity.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import clsx from 'clsx'
import { ContractComment } from 'common/comment'
import { Contract } from 'common/contract'
import { groupBy, keyBy, orderBy } from 'lodash'
import { memo } from 'react'
import { groupBy, keyBy, orderBy, uniqBy } from 'lodash'
import { memo, useEffect, useState } from 'react'
import { usePrivateUser } from 'web/hooks/use-user'
import { ContractMention } from './contract/contract-mention'
import { FeedBet } from './feed/feed-bets'
Expand All @@ -15,6 +15,7 @@ import { LoadingIndicator } from './widgets/loading-indicator'
import { UserLink } from './widgets/user-link'
import { UserHovercard } from './user/user-hovercard'
import { useAPIGetter } from 'web/hooks/use-api-getter'
import { VisibilityObserver } from './widgets/visibility-observer'

export function SiteActivity(props: {
className?: string
Expand All @@ -29,16 +30,36 @@ export function SiteActivity(props: {
props.blockedUserIds ?? []
)

const [offset, setOffset] = useState(0)
const limit = 10

const { data, loading } = useAPIGetter('get-site-activity', {
limit: 10,
limit,
offset,
blockedUserIds,
blockedGroupSlugs,
blockedContractIds,
})

if (loading || !data) return <LoadingIndicator />
const [allData, setAllData] = useState<typeof data>()

const { bets, comments, newContracts, relatedContracts } = data
useEffect(() => {
if (data) {
setAllData((prev) => {
if (!prev) return data
return {
bets: uniqBy([...prev.bets, ...data.bets], 'id'),
comments: uniqBy([...prev.comments, ...data.comments], 'id'),
newContracts: uniqBy([...prev.newContracts, ...data.newContracts], 'id'),
relatedContracts: uniqBy([...prev.relatedContracts, ...data.relatedContracts], 'id')
}
})
}
}, [data])

if (!allData) return <LoadingIndicator />

const { bets, comments, newContracts, relatedContracts } = allData
const contracts = [...newContracts, ...relatedContracts]
const contractsById = keyBy(contracts, 'id')

Expand Down Expand Up @@ -76,7 +97,7 @@ export function SiteActivity(props: {
<Col className="flex-1 gap-2">
<ContractMention contract={contract} />
<div className="space-y-2">
{items.map((item) =>
{items.map((item, i) =>
'amount' in item ? (
<FeedBet
className="!pt-0"
Expand All @@ -92,23 +113,46 @@ export function SiteActivity(props: {
showDescription={items.length === 1}
/>
) : 'channelId' in item ? null : (
<CommentLog key={item.id} comment={item} />
<CommentLog
key={item.id}
comment={item}
showDivider={i !== items.length - 1}
/>
)
)}
</div>
</Col>

{contract.coverImageUrl && (
<img
src={contract.coverImageUrl}
alt=""
className="h-32 w-32 rounded-md object-cover"
className={clsx(
'rounded-md object-cover',
'h-12 w-12 sm:h-32 sm:w-32',
// Only hide on desktop if single bet
items.length === 1 &&
'amount' in items[0] &&
'sm:hidden'
)}
/>
)}
</Row>
</Col>
)
})}
</Col>
<div className="relative">
{loading && <LoadingIndicator />}
<VisibilityObserver
className="pointer-events-none absolute bottom-0 h-screen w-full select-none"
onVisibilityUpdated={(visible) => {
if (visible && !loading) {
setOffset((prev: number) => prev + limit)
}
}}
/>
</div>
</Col>
)
}
Expand All @@ -126,29 +170,31 @@ const MarketCreatedLog = memo(

return (
<Col className="gap-2">
<UserHovercard userId={creatorId} className="flex-col">
<Row className="text-ink-600 items-center gap-2 text-sm">
<Avatar
avatarUrl={creatorAvatarUrl}
username={creatorUsername}
size="xs"
/>
<UserLink
user={{
id: creatorId,
name: creatorName,
username: creatorUsername,
}}
/>
<Row className="text-ink-400">
created
<RelativeTimestamp time={createdTime} />
<Row className="text-ink-1000 items-center gap-2 text-sm">
<UserHovercard userId={creatorId}>
<Row className="items-center gap-2 font-semibold">
<Avatar
avatarUrl={creatorAvatarUrl}
username={creatorUsername}
size="xs"
/>
<UserLink
user={{
id: creatorId,
name: creatorName,
username: creatorUsername,
}}
/>
</Row>
</UserHovercard>
<div className="-ml-1">created this market</div>
<Row className="text-ink-400">
<RelativeTimestamp time={createdTime} shortened />
</Row>
</UserHovercard>
</Row>

{showDescription && props.contract.description && (
<div className="relative max-h-[120px] overflow-hidden">
<div className="relative max-h-[120px] max-w-xs overflow-hidden sm:max-w-none">
<Content
size="sm"
content={props.contract.description}
Expand All @@ -164,8 +210,9 @@ const MarketCreatedLog = memo(
// todo: add liking/disliking
const CommentLog = memo(function FeedComment(props: {
comment: ContractComment
showDivider?: boolean
}) {
const { comment } = props
const { comment, showDivider = true } = props
const {
userName,
text,
Expand All @@ -178,24 +225,30 @@ const CommentLog = memo(function FeedComment(props: {

return (
<Col>
<Row
id={comment.id}
className="text-ink-500 mb-1 items-center gap-2 text-sm"
>
<Row id={comment.id} className="mb-1 items-center gap-2 text-sm">
<UserHovercard userId={userId}>
<Avatar size="xs" username={userUsername} avatarUrl={userAvatarUrl} />
</UserHovercard>
<span>
<UserHovercard userId={userId}>
<Row className="items-center gap-2 font-semibold">
<Avatar
avatarUrl={userAvatarUrl}
username={userUsername}
size="xs"
/>
<UserLink
user={{ id: userId, name: userName, username: userUsername }}
user={{
id: userId,
name: userName,
username: userUsername,
}}
/>
</UserHovercard>{' '}
commented
</span>
<RelativeTimestamp time={createdTime} />
</Row>
</UserHovercard>
<div className="-ml-1">commented</div>
<Row className="text-ink-400">
<RelativeTimestamp time={createdTime} shortened />
</Row>
</Row>
<Content size="sm" className="grow" content={content || text} />
{showDivider && <div className="border-ink-200/30 mt-4 border-b" />}
</Col>
)
})

0 comments on commit 63226bb

Please sign in to comment.