Explore groups page
This commit is contained in:
parent
5219a63667
commit
7f9925a8c4
|
@ -2,16 +2,16 @@ import { useFirestoreQueryData } from '@react-query-firebase/firestore'
|
||||||
import { useEffect, useState } from 'react'
|
import { useEffect, useState } from 'react'
|
||||||
import {
|
import {
|
||||||
Contract,
|
Contract,
|
||||||
listenForActiveContracts,
|
|
||||||
listenForContracts,
|
listenForContracts,
|
||||||
listenForHotContracts,
|
listenForHotContracts,
|
||||||
listenForInactiveContracts,
|
listenForInactiveContracts,
|
||||||
listenForNewContracts,
|
|
||||||
getUserBetContracts,
|
getUserBetContracts,
|
||||||
getUserBetContractsQuery,
|
getUserBetContractsQuery,
|
||||||
|
trendingContractsQuery,
|
||||||
} from 'web/lib/firebase/contracts'
|
} from 'web/lib/firebase/contracts'
|
||||||
import { useQueryClient } from 'react-query'
|
import { useQueryClient } from 'react-query'
|
||||||
import { MINUTE_MS } from 'common/util/time'
|
import { MINUTE_MS } from 'common/util/time'
|
||||||
|
import { query, limit } from 'firebase/firestore'
|
||||||
|
|
||||||
export const useContracts = () => {
|
export const useContracts = () => {
|
||||||
const [contracts, setContracts] = useState<Contract[] | undefined>()
|
const [contracts, setContracts] = useState<Contract[] | undefined>()
|
||||||
|
@ -23,23 +23,12 @@ export const useContracts = () => {
|
||||||
return contracts
|
return contracts
|
||||||
}
|
}
|
||||||
|
|
||||||
export const useActiveContracts = () => {
|
export const useTrendingContracts = (maxContracts: number) => {
|
||||||
const [activeContracts, setActiveContracts] = useState<
|
const result = useFirestoreQueryData(
|
||||||
Contract[] | undefined
|
['trending-contracts', maxContracts],
|
||||||
>()
|
query(trendingContractsQuery, limit(maxContracts))
|
||||||
const [newContracts, setNewContracts] = useState<Contract[] | undefined>()
|
)
|
||||||
|
return result.data
|
||||||
useEffect(() => {
|
|
||||||
return listenForActiveContracts(setActiveContracts)
|
|
||||||
}, [])
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
return listenForNewContracts(setNewContracts)
|
|
||||||
}, [])
|
|
||||||
|
|
||||||
if (!activeContracts || !newContracts) return undefined
|
|
||||||
|
|
||||||
return [...activeContracts, ...newContracts]
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const useInactiveContracts = () => {
|
export const useInactiveContracts = () => {
|
||||||
|
|
|
@ -11,6 +11,7 @@ import {
|
||||||
listenForMemberGroupIds,
|
listenForMemberGroupIds,
|
||||||
listenForOpenGroups,
|
listenForOpenGroups,
|
||||||
listGroups,
|
listGroups,
|
||||||
|
topFollowedGroupsQuery,
|
||||||
} from 'web/lib/firebase/groups'
|
} from 'web/lib/firebase/groups'
|
||||||
import { getUser } from 'web/lib/firebase/users'
|
import { getUser } from 'web/lib/firebase/users'
|
||||||
import { filterDefined } from 'common/util/array'
|
import { filterDefined } from 'common/util/array'
|
||||||
|
@ -18,6 +19,8 @@ import { Contract } from 'common/contract'
|
||||||
import { uniq } from 'lodash'
|
import { uniq } from 'lodash'
|
||||||
import { listenForValues } from 'web/lib/firebase/utils'
|
import { listenForValues } from 'web/lib/firebase/utils'
|
||||||
import { useQuery } from 'react-query'
|
import { useQuery } from 'react-query'
|
||||||
|
import { useFirestoreQueryData } from '@react-query-firebase/firestore'
|
||||||
|
import { limit, query } from 'firebase/firestore'
|
||||||
|
|
||||||
export const useGroup = (groupId: string | undefined) => {
|
export const useGroup = (groupId: string | undefined) => {
|
||||||
const [group, setGroup] = useState<Group | null | undefined>()
|
const [group, setGroup] = useState<Group | null | undefined>()
|
||||||
|
@ -49,6 +52,14 @@ export const useOpenGroups = () => {
|
||||||
return groups
|
return groups
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const useTopFollowedGroups = (count: number) => {
|
||||||
|
const result = useFirestoreQueryData(
|
||||||
|
['top-followed-contracts', count],
|
||||||
|
query(topFollowedGroupsQuery, limit(count))
|
||||||
|
)
|
||||||
|
return result.data
|
||||||
|
}
|
||||||
|
|
||||||
export const useMemberGroups = (userId: string | null | undefined) => {
|
export const useMemberGroups = (userId: string | null | undefined) => {
|
||||||
const result = useQuery(['member-groups', userId ?? ''], () =>
|
const result = useQuery(['member-groups', userId ?? ''], () =>
|
||||||
getMemberGroups(userId ?? '')
|
getMemberGroups(userId ?? '')
|
||||||
|
|
|
@ -176,23 +176,6 @@ export function getUserBetContractsQuery(userId: string) {
|
||||||
) as Query<Contract>
|
) as Query<Contract>
|
||||||
}
|
}
|
||||||
|
|
||||||
const activeContractsQuery = query(
|
|
||||||
contracts,
|
|
||||||
where('isResolved', '==', false),
|
|
||||||
where('visibility', '==', 'public'),
|
|
||||||
where('volume7Days', '>', 0)
|
|
||||||
)
|
|
||||||
|
|
||||||
export function getActiveContracts() {
|
|
||||||
return getValues<Contract>(activeContractsQuery)
|
|
||||||
}
|
|
||||||
|
|
||||||
export function listenForActiveContracts(
|
|
||||||
setContracts: (contracts: Contract[]) => void
|
|
||||||
) {
|
|
||||||
return listenForValues<Contract>(activeContractsQuery, setContracts)
|
|
||||||
}
|
|
||||||
|
|
||||||
const inactiveContractsQuery = query(
|
const inactiveContractsQuery = query(
|
||||||
contracts,
|
contracts,
|
||||||
where('isResolved', '==', false),
|
where('isResolved', '==', false),
|
||||||
|
@ -282,16 +265,17 @@ export function listenForHotContracts(
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
const trendingContractsQuery = query(
|
export const trendingContractsQuery = query(
|
||||||
contracts,
|
contracts,
|
||||||
where('isResolved', '==', false),
|
where('isResolved', '==', false),
|
||||||
where('visibility', '==', 'public'),
|
where('visibility', '==', 'public'),
|
||||||
orderBy('popularityScore', 'desc'),
|
orderBy('popularityScore', 'desc')
|
||||||
limit(10)
|
|
||||||
)
|
)
|
||||||
|
|
||||||
export async function getTrendingContracts() {
|
export async function getTrendingContracts(maxContracts = 10) {
|
||||||
return await getValues<Contract>(trendingContractsQuery)
|
return await getValues<Contract>(
|
||||||
|
query(trendingContractsQuery, limit(maxContracts))
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getContractsBySlugs(slugs: string[]) {
|
export async function getContractsBySlugs(slugs: string[]) {
|
||||||
|
|
|
@ -6,6 +6,7 @@ import {
|
||||||
doc,
|
doc,
|
||||||
getDocs,
|
getDocs,
|
||||||
onSnapshot,
|
onSnapshot,
|
||||||
|
orderBy,
|
||||||
query,
|
query,
|
||||||
setDoc,
|
setDoc,
|
||||||
updateDoc,
|
updateDoc,
|
||||||
|
@ -257,3 +258,8 @@ export async function listMembers(group: Group) {
|
||||||
const members = await getValues<GroupMemberDoc>(groupMembers(group.id))
|
const members = await getValues<GroupMemberDoc>(groupMembers(group.id))
|
||||||
return await Promise.all(members.map((m) => m.userId).map(getUser))
|
return await Promise.all(members.map((m) => m.userId).map(getUser))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const topFollowedGroupsQuery = query(
|
||||||
|
groups,
|
||||||
|
orderBy('totalMembers', 'desc')
|
||||||
|
)
|
||||||
|
|
55
web/pages/experimental/explore-groups.tsx
Normal file
55
web/pages/experimental/explore-groups.tsx
Normal file
|
@ -0,0 +1,55 @@
|
||||||
|
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 { 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)
|
||||||
|
|
||||||
|
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" />
|
||||||
|
</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
|
||||||
|
className="mb-4 !min-w-[250px]"
|
||||||
|
group={g}
|
||||||
|
creator={null}
|
||||||
|
user={user}
|
||||||
|
isMember={false}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</Masonry>
|
||||||
|
</Col>
|
||||||
|
</Page>
|
||||||
|
)
|
||||||
|
}
|
|
@ -171,26 +171,34 @@ export default function Groups(props: {
|
||||||
|
|
||||||
export function GroupCard(props: {
|
export function GroupCard(props: {
|
||||||
group: Group
|
group: Group
|
||||||
creator: User | undefined
|
creator: User | null | undefined
|
||||||
user: User | undefined | null
|
user: User | undefined | null
|
||||||
isMember: boolean
|
isMember: boolean
|
||||||
|
className?: string
|
||||||
}) {
|
}) {
|
||||||
const { group, creator, user, isMember } = props
|
const { group, creator, user, isMember, className } = props
|
||||||
const { totalContracts } = group
|
const { totalContracts } = group
|
||||||
return (
|
return (
|
||||||
<Col className="relative min-w-[20rem] max-w-xs gap-1 rounded-xl bg-white p-8 shadow-md hover:bg-gray-100">
|
<Col
|
||||||
|
className={clsx(
|
||||||
|
'relative min-w-[20rem] max-w-xs gap-1 rounded-xl bg-white p-6 shadow-md hover:bg-gray-100',
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
>
|
||||||
<Link href={groupPath(group.slug)}>
|
<Link href={groupPath(group.slug)}>
|
||||||
<a className="absolute left-0 right-0 top-0 bottom-0 z-0" />
|
<a className="absolute left-0 right-0 top-0 bottom-0 z-0" />
|
||||||
</Link>
|
</Link>
|
||||||
<div>
|
{creator !== null && (
|
||||||
<Avatar
|
<div>
|
||||||
className={'absolute top-2 right-2 z-10'}
|
<Avatar
|
||||||
username={creator?.username}
|
className={'absolute top-2 right-2 z-10'}
|
||||||
avatarUrl={creator?.avatarUrl}
|
username={creator?.username}
|
||||||
noLink={false}
|
avatarUrl={creator?.avatarUrl}
|
||||||
size={12}
|
noLink={false}
|
||||||
/>
|
size={12}
|
||||||
</div>
|
/>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
<Row className="items-center justify-between gap-2">
|
<Row className="items-center justify-between gap-2">
|
||||||
<span className="text-xl">{group.name}</span>
|
<span className="text-xl">{group.name}</span>
|
||||||
</Row>
|
</Row>
|
||||||
|
|
Loading…
Reference in New Issue
Block a user