-
Notifications
You must be signed in to change notification settings - Fork 159
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* site activity * get-site-activity * fix contracts issue * pass params * remove comments * adjust limits * styling * feed images * contract mention: don't open in new tab * conditionally show description * move description outside hovercard * todos * include sales * more todos
- Loading branch information
1 parent
c1cc69f
commit f612c15
Showing
7 changed files
with
332 additions
and
12 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,69 @@ | ||
import { APIHandler } from 'api/helpers/endpoint' | ||
import { createSupabaseDirectClient } from 'shared/supabase/init' | ||
import { convertContract } from 'common/supabase/contracts' | ||
import { filterDefined } from 'common/util/array' | ||
import { uniqBy } from 'lodash' | ||
import { log } from 'shared/utils' | ||
import { convertBet } from 'common/supabase/bets' | ||
import { convertContractComment } from 'common/supabase/comments' | ||
|
||
export const getSiteActivity: APIHandler<'get-site-activity'> = async (props) => { | ||
const { limit, blockedUserIds = [], blockedGroupSlugs = [], blockedContractIds = [] } = props | ||
const pg = createSupabaseDirectClient() | ||
log('getSiteActivity called', { limit }) | ||
|
||
const [recentBets, recentComments, newContracts] = await Promise.all([ | ||
// todo: show | ||
// [ ] sweepcash bets >= 5 | ||
// [ ] large limit orders | ||
// [ ] personalization based on followed users & topics | ||
pg.manyOrNone( | ||
`select * from contract_bets | ||
where abs(amount) >= 500 | ||
and user_id != all($1) | ||
and contract_id != all($2) | ||
order by created_time desc limit $3`, | ||
[blockedUserIds, blockedContractIds, limit * 5] | ||
), | ||
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] | ||
), | ||
pg.manyOrNone( | ||
`select * from contracts | ||
where visibility = 'public' | ||
and tier != 'play' | ||
and creator_id != all($1) | ||
and id != all($2) | ||
and not exists ( | ||
select 1 from group_contracts gc | ||
join groups g on g.id = gc.group_id | ||
where gc.contract_id = contracts.id | ||
and g.slug = any($3) | ||
) | ||
order by created_time desc limit $4`, | ||
[blockedUserIds, blockedContractIds, blockedGroupSlugs, limit] | ||
), | ||
]) | ||
|
||
const contractIds = uniqBy([ | ||
...recentBets.map((b) => b.contract_id), | ||
...recentComments.map((c) => c.contract_id) | ||
], id => id) | ||
|
||
const relatedContracts = await pg.manyOrNone( | ||
`select * from contracts where id = any($1)`, | ||
[contractIds] | ||
) | ||
|
||
return { | ||
bets: recentBets.map(convertBet), | ||
comments: recentComments.map(convertContractComment), | ||
newContracts: filterDefined(newContracts.map(convertContract)), | ||
relatedContracts: filterDefined(relatedContracts.map(convertContract)) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,199 @@ | ||
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 { usePrivateUser } from 'web/hooks/use-user' | ||
import { ContractMention } from './contract/contract-mention' | ||
import { FeedBet } from './feed/feed-bets' | ||
import { Col } from './layout/col' | ||
import { Row } from './layout/row' | ||
import { RelativeTimestamp } from './relative-timestamp' | ||
import { Avatar } from './widgets/avatar' | ||
import { Content } from './widgets/editor' | ||
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' | ||
|
||
export function SiteActivity(props: { | ||
className?: string | ||
blockedUserIds?: string[] | ||
}) { | ||
const { className } = props | ||
const privateUser = usePrivateUser() | ||
|
||
const blockedGroupSlugs = privateUser?.blockedGroupSlugs ?? [] | ||
const blockedContractIds = privateUser?.blockedContractIds ?? [] | ||
const blockedUserIds = (privateUser?.blockedUserIds ?? []).concat( | ||
props.blockedUserIds ?? [] | ||
) | ||
|
||
const { data, loading } = useAPIGetter('get-site-activity', { | ||
limit: 10, | ||
blockedUserIds, | ||
blockedGroupSlugs, | ||
blockedContractIds, | ||
}) | ||
|
||
if (loading || !data) return <LoadingIndicator /> | ||
|
||
const { bets, comments, newContracts, relatedContracts } = data | ||
const contracts = [...newContracts, ...relatedContracts] | ||
const contractsById = keyBy(contracts, 'id') | ||
|
||
const items = orderBy( | ||
[...bets, ...comments, ...newContracts], | ||
'createdTime', | ||
'desc' | ||
) | ||
|
||
const groups = orderBy( | ||
Object.entries( | ||
groupBy(items, (item) => | ||
'contractId' in item ? item.contractId : item.id | ||
) | ||
).map(([parentId, items]) => ({ | ||
parentId, | ||
items, | ||
})), | ||
({ items }) => Math.max(...items.map((item) => item.createdTime)), | ||
'desc' | ||
) | ||
|
||
return ( | ||
<Col className={clsx('gap-4', className)}> | ||
<Col className="gap-4"> | ||
{groups.map(({ parentId, items }) => { | ||
const contract = contractsById[parentId] as Contract | ||
|
||
return ( | ||
<Col | ||
key={parentId} | ||
className="bg-canvas-0 border-canvas-50 hover:border-primary-300 gap-2 rounded-lg border px-4 py-3 transition-colors" | ||
> | ||
<Row className="gap-2"> | ||
<Col className="flex-1 gap-2"> | ||
<ContractMention contract={contract} /> | ||
<div className="space-y-2"> | ||
{items.map((item) => | ||
'amount' in item ? ( | ||
<FeedBet | ||
className="!pt-0" | ||
key={item.id} | ||
contract={contract} | ||
bet={item} | ||
avatarSize="xs" | ||
/> | ||
) : 'question' in item ? ( | ||
<MarketCreatedLog | ||
key={item.id} | ||
contract={item} | ||
showDescription={items.length === 1} | ||
/> | ||
) : 'channelId' in item ? null : ( | ||
<CommentLog key={item.id} comment={item} /> | ||
) | ||
)} | ||
</div> | ||
</Col> | ||
{contract.coverImageUrl && ( | ||
<img | ||
src={contract.coverImageUrl} | ||
alt="" | ||
className="h-32 w-32 rounded-md object-cover" | ||
/> | ||
)} | ||
</Row> | ||
</Col> | ||
) | ||
})} | ||
</Col> | ||
</Col> | ||
) | ||
} | ||
|
||
const MarketCreatedLog = memo( | ||
(props: { contract: Contract; showDescription?: boolean }) => { | ||
const { | ||
creatorId, | ||
creatorAvatarUrl, | ||
creatorUsername, | ||
creatorName, | ||
createdTime, | ||
} = props.contract | ||
const { showDescription = false } = props | ||
|
||
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> | ||
</Row> | ||
</UserHovercard> | ||
|
||
{showDescription && props.contract.description && ( | ||
// TODO: truncate if too long | ||
<Content | ||
size="sm" | ||
content={props.contract.description} | ||
className="mt-2 text-left" | ||
/> | ||
)} | ||
</Col> | ||
) | ||
} | ||
) | ||
// todo: add liking/disliking | ||
const CommentLog = memo(function FeedComment(props: { | ||
comment: ContractComment | ||
}) { | ||
const { comment } = props | ||
const { | ||
userName, | ||
text, | ||
content, | ||
userId, | ||
userUsername, | ||
userAvatarUrl, | ||
createdTime, | ||
} = comment | ||
|
||
return ( | ||
<Col> | ||
<Row | ||
id={comment.id} | ||
className="text-ink-500 mb-1 items-center gap-2 text-sm" | ||
> | ||
<UserHovercard userId={userId}> | ||
<Avatar size="xs" username={userUsername} avatarUrl={userAvatarUrl} /> | ||
</UserHovercard> | ||
<span> | ||
<UserHovercard userId={userId}> | ||
<UserLink | ||
user={{ id: userId, name: userName, username: userUsername }} | ||
/> | ||
</UserHovercard>{' '} | ||
commented | ||
</span> | ||
<RelativeTimestamp time={createdTime} /> | ||
</Row> | ||
<Content size="sm" className="grow" content={content || text} /> | ||
</Col> | ||
) | ||
}) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
import { Col } from 'web/components/layout/col' | ||
import { Page } from 'web/components/layout/page' | ||
import { SEO } from 'web/components/SEO' | ||
import { Row } from 'web/components/layout/row' | ||
import { SiteActivity } from 'web/components/site-activity' | ||
import { TRADE_TERM } from 'common/envs/constants' | ||
|
||
export default function ActivityPage() { | ||
return ( | ||
<Page trackPageView={'activity page'}> | ||
<SEO | ||
title="Activity" | ||
description={`Watch all site activity live, including ${TRADE_TERM}s, comments, and new questions.`} | ||
url="/activity" | ||
/> | ||
|
||
<Col className="w-full max-w-3xl gap-4 self-center sm:pb-4"> | ||
<Row | ||
className={ | ||
'w-full items-center justify-between pt-1 sm:justify-start sm:gap-4' | ||
} | ||
> | ||
<span className="text-primary-700 line-clamp-1 shrink px-1 text-2xl"> | ||
Activity | ||
</span> | ||
</Row> | ||
<SiteActivity className="w-full" /> | ||
</Col> | ||
</Page> | ||
) | ||
} |