-
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.
Merge branch 'main' into tasks-lists
- Loading branch information
Showing
7 changed files
with
335 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> | ||
) | ||
}) |
Oops, something went wrong.