Add trending groups section
This commit is contained in:
parent
a7e3414cc9
commit
7744ac84e1
|
@ -46,3 +46,10 @@ export const shuffle = (array: unknown[], rand: () => number) => {
|
|||
;[array[i], array[swapIndex]] = [array[swapIndex], array[i]]
|
||||
}
|
||||
}
|
||||
|
||||
export function chooseRandomSubset<T>(items: T[], count: number) {
|
||||
const fiveMinutes = 5 * 60 * 1000
|
||||
const seed = Math.round(Date.now() / fiveMinutes).toString()
|
||||
shuffle(items, createRNG(seed))
|
||||
return items.slice(0, count)
|
||||
}
|
||||
|
|
|
@ -108,9 +108,9 @@ const SectionItem = (props: {
|
|||
|
||||
export const getHomeItems = (groups: Group[], sections: string[]) => {
|
||||
const items = [
|
||||
{ label: 'Daily movers', id: 'daily-movers' },
|
||||
{ label: 'Trending', id: 'score' },
|
||||
{ label: 'New for you', id: 'newest' },
|
||||
{ label: 'Daily movers', id: 'daily-movers' },
|
||||
...groups.map((g) => ({
|
||||
label: g.name,
|
||||
id: g.id,
|
||||
|
|
|
@ -16,11 +16,12 @@ import {
|
|||
import { getUser } from 'web/lib/firebase/users'
|
||||
import { filterDefined } from 'common/util/array'
|
||||
import { Contract } from 'common/contract'
|
||||
import { uniq } from 'lodash'
|
||||
import { keyBy, uniq, uniqBy } from 'lodash'
|
||||
import { listenForValues } from 'web/lib/firebase/utils'
|
||||
import { useQuery } from 'react-query'
|
||||
import { useFirestoreQueryData } from '@react-query-firebase/firestore'
|
||||
import { limit, query } from 'firebase/firestore'
|
||||
import { useTrendingContracts } from './use-contracts'
|
||||
|
||||
export const useGroup = (groupId: string | undefined) => {
|
||||
const [group, setGroup] = useState<Group | null | undefined>()
|
||||
|
@ -60,6 +61,22 @@ export const useTopFollowedGroups = (count: number) => {
|
|||
return result.data
|
||||
}
|
||||
|
||||
export const useTrendingGroups = () => {
|
||||
const topGroups = useTopFollowedGroups(200)
|
||||
const groupsById = keyBy(topGroups, 'id')
|
||||
|
||||
const trendingContracts = useTrendingContracts(200)
|
||||
|
||||
const groupLinks = uniqBy(
|
||||
(trendingContracts ?? []).map((c) => c.groupLinks ?? []).flat(),
|
||||
(link) => link.groupId
|
||||
)
|
||||
|
||||
return filterDefined(
|
||||
groupLinks.map((link) => groupsById[link.groupId])
|
||||
).filter((group) => group.totalMembers >= 3)
|
||||
}
|
||||
|
||||
export const useMemberGroups = (userId: string | null | undefined) => {
|
||||
const result = useQuery(['member-groups', userId ?? ''], () =>
|
||||
getMemberGroups(userId ?? '')
|
||||
|
|
|
@ -17,7 +17,7 @@ import { partition, sortBy, sum, uniqBy } from 'lodash'
|
|||
|
||||
import { coll, getValues, listenForValue, listenForValues } from './utils'
|
||||
import { BinaryContract, Contract, CPMMContract } from 'common/contract'
|
||||
import { createRNG, shuffle } from 'common/util/random'
|
||||
import { chooseRandomSubset } from 'common/util/random'
|
||||
import { formatMoney, formatPercent } from 'common/util/format'
|
||||
import { DAY_MS } from 'common/util/time'
|
||||
import { Bet } from 'common/bet'
|
||||
|
@ -238,13 +238,6 @@ export async function unFollowContract(contractId: string, userId: string) {
|
|||
await deleteDoc(followDoc)
|
||||
}
|
||||
|
||||
function chooseRandomSubset(contracts: Contract[], count: number) {
|
||||
const fiveMinutes = 5 * 60 * 1000
|
||||
const seed = Math.round(Date.now() / fiveMinutes).toString()
|
||||
shuffle(contracts, createRNG(seed))
|
||||
return contracts.slice(0, count)
|
||||
}
|
||||
|
||||
const hotContractsQuery = query(
|
||||
contracts,
|
||||
where('isResolved', '==', false),
|
||||
|
|
|
@ -1,51 +1,37 @@
|
|||
import Masonry from 'react-masonry-css'
|
||||
import { filterDefined } from 'common/util/array'
|
||||
import { keyBy, uniqBy } from 'lodash'
|
||||
import { Col } from 'web/components/layout/col'
|
||||
import { Row } from 'web/components/layout/row'
|
||||
import { Page } from 'web/components/page'
|
||||
import { Title } from 'web/components/title'
|
||||
import { useTrendingContracts } from 'web/hooks/use-contracts'
|
||||
import { useTopFollowedGroups } from 'web/hooks/use-group'
|
||||
import { useMemberGroupIds, useTrendingGroups } from 'web/hooks/use-group'
|
||||
import { useUser } from 'web/hooks/use-user'
|
||||
import { GroupCard } from '../groups'
|
||||
|
||||
export default function Explore() {
|
||||
const user = useUser()
|
||||
|
||||
const topGroups = useTopFollowedGroups(200)
|
||||
const groupsById = keyBy(topGroups, 'id')
|
||||
|
||||
const trendingContracts = useTrendingContracts(200)
|
||||
|
||||
const groupLinks = uniqBy(
|
||||
(trendingContracts ?? []).map((c) => c.groupLinks ?? []).flat(),
|
||||
(link) => link.groupId
|
||||
)
|
||||
const groups = filterDefined(
|
||||
groupLinks.map((link) => groupsById[link.groupId])
|
||||
).filter((group) => group.totalMembers >= 3)
|
||||
const groups = useTrendingGroups()
|
||||
const memberGroupIds = useMemberGroupIds(user) || []
|
||||
|
||||
return (
|
||||
<Page>
|
||||
<Col className="pm:mx-10 gap-4 px-4 pb-12 xl:w-[115%]">
|
||||
<Row className={'w-full items-center justify-between'}>
|
||||
<Title className="!mb-0" text="Explore" />
|
||||
<Title className="!mb-0" text="Trending groups" />
|
||||
</Row>
|
||||
|
||||
<Masonry
|
||||
// Show only 1 column on tailwind's md breakpoint (768px)
|
||||
breakpointCols={{ default: 3, 1200: 2, 570: 1 }}
|
||||
className="-ml-4 flex w-auto self-center"
|
||||
columnClassName="pl-4 bg-clip-padding"
|
||||
>
|
||||
{groups.map((g) => (
|
||||
<GroupCard
|
||||
key={g.id}
|
||||
className="mb-4 !min-w-[250px]"
|
||||
group={g}
|
||||
creator={null}
|
||||
user={user}
|
||||
isMember={false}
|
||||
isMember={memberGroupIds.includes(g.id)}
|
||||
/>
|
||||
))}
|
||||
</Masonry>
|
||||
|
|
|
@ -7,6 +7,7 @@ import {
|
|||
SearchIcon,
|
||||
} from '@heroicons/react/solid'
|
||||
import clsx from 'clsx'
|
||||
import Masonry from 'react-masonry-css'
|
||||
|
||||
import { Page } from 'web/components/page'
|
||||
import { Col } from 'web/components/layout/col'
|
||||
|
@ -19,7 +20,11 @@ import { Sort } from 'web/components/contract-search'
|
|||
import { Group } from 'common/group'
|
||||
import { SiteLink } from 'web/components/site-link'
|
||||
import { useUser } from 'web/hooks/use-user'
|
||||
import { useMemberGroups } from 'web/hooks/use-group'
|
||||
import {
|
||||
useMemberGroupIds,
|
||||
useMemberGroups,
|
||||
useTrendingGroups,
|
||||
} from 'web/hooks/use-group'
|
||||
import { Button } from 'web/components/button'
|
||||
import { getHomeItems } from '../../../components/arrange-home'
|
||||
import { Title } from 'web/components/title'
|
||||
|
@ -31,6 +36,8 @@ import { formatMoney } from 'common/util/format'
|
|||
import { useProbChanges } from 'web/hooks/use-prob-changes'
|
||||
import { ProfitBadge } from 'web/components/bets-list'
|
||||
import { calculatePortfolioProfit } from 'common/calculate-metrics'
|
||||
import { GroupCard } from 'web/pages/groups'
|
||||
import { chooseRandomSubset } from 'common/util/random'
|
||||
|
||||
export default function Home() {
|
||||
const user = useUser()
|
||||
|
@ -78,6 +85,7 @@ export default function Home() {
|
|||
|
||||
return null
|
||||
})}
|
||||
<TrendingGroupsSection user={user} />
|
||||
</Col>
|
||||
<button
|
||||
type="button"
|
||||
|
@ -93,6 +101,22 @@ export default function Home() {
|
|||
)
|
||||
}
|
||||
|
||||
function SectionHeader(props: { label: string; href: string }) {
|
||||
const { label, href } = props
|
||||
|
||||
return (
|
||||
<Row className="mb-3 items-center justify-between">
|
||||
<SiteLink className="text-xl" href={href}>
|
||||
{label}{' '}
|
||||
<ArrowSmRightIcon
|
||||
className="mb-0.5 inline h-6 w-6 text-gray-500"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
</SiteLink>
|
||||
</Row>
|
||||
)
|
||||
}
|
||||
|
||||
function SearchSection(props: {
|
||||
label: string
|
||||
user: User | null | undefined | undefined
|
||||
|
@ -152,22 +176,6 @@ function DailyMoversSection(props: { userId: string | null | undefined }) {
|
|||
)
|
||||
}
|
||||
|
||||
function SectionHeader(props: { label: string; href: string }) {
|
||||
const { label, href } = props
|
||||
|
||||
return (
|
||||
<Row className="mb-3 items-center justify-between">
|
||||
<SiteLink className="text-xl" href={href}>
|
||||
{label}{' '}
|
||||
<ArrowSmRightIcon
|
||||
className="mb-0.5 inline h-6 w-6 text-gray-500"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
</SiteLink>
|
||||
</Row>
|
||||
)
|
||||
}
|
||||
|
||||
function EditButton(props: { className?: string }) {
|
||||
const { className } = props
|
||||
|
||||
|
@ -229,3 +237,38 @@ function DailyProfitAndBalance(props: {
|
|||
</Row>
|
||||
)
|
||||
}
|
||||
|
||||
function TrendingGroupsSection(props: { user: User | null | undefined }) {
|
||||
const { user } = props
|
||||
const memberGroupIds = useMemberGroupIds(user) || []
|
||||
|
||||
const groups = useTrendingGroups().filter(
|
||||
(g) => !memberGroupIds.includes(g.id)
|
||||
)
|
||||
const chosenGroups = chooseRandomSubset(groups.slice(0, 25), 9)
|
||||
|
||||
return (
|
||||
<Col>
|
||||
<SectionHeader
|
||||
label="Trending groups"
|
||||
href="/experimental/explore-groups"
|
||||
/>
|
||||
<Masonry
|
||||
breakpointCols={{ default: 3, 768: 2, 480: 1 }}
|
||||
className="-ml-4 flex w-auto self-center"
|
||||
columnClassName="pl-4 bg-clip-padding"
|
||||
>
|
||||
{chosenGroups.map((g) => (
|
||||
<GroupCard
|
||||
key={g.id}
|
||||
className="mb-4 !min-w-[250px]"
|
||||
group={g}
|
||||
creator={null}
|
||||
user={user}
|
||||
isMember={memberGroupIds.includes(g.id)}
|
||||
/>
|
||||
))}
|
||||
</Masonry>
|
||||
</Col>
|
||||
)
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue
Block a user