diff --git a/web/hooks/use-contracts.ts b/web/hooks/use-contracts.ts index 1ea2f232..6b2a5a9a 100644 --- a/web/hooks/use-contracts.ts +++ b/web/hooks/use-contracts.ts @@ -2,16 +2,16 @@ import { useFirestoreQueryData } from '@react-query-firebase/firestore' import { useEffect, useState } from 'react' import { Contract, - listenForActiveContracts, listenForContracts, listenForHotContracts, listenForInactiveContracts, - listenForNewContracts, getUserBetContracts, getUserBetContractsQuery, + trendingContractsQuery, } from 'web/lib/firebase/contracts' import { useQueryClient } from 'react-query' import { MINUTE_MS } from 'common/util/time' +import { query, limit } from 'firebase/firestore' export const useContracts = () => { const [contracts, setContracts] = useState() @@ -23,23 +23,12 @@ export const useContracts = () => { return contracts } -export const useActiveContracts = () => { - const [activeContracts, setActiveContracts] = useState< - Contract[] | undefined - >() - const [newContracts, setNewContracts] = useState() - - useEffect(() => { - return listenForActiveContracts(setActiveContracts) - }, []) - - useEffect(() => { - return listenForNewContracts(setNewContracts) - }, []) - - if (!activeContracts || !newContracts) return undefined - - return [...activeContracts, ...newContracts] +export const useTrendingContracts = (maxContracts: number) => { + const result = useFirestoreQueryData( + ['trending-contracts', maxContracts], + query(trendingContractsQuery, limit(maxContracts)) + ) + return result.data } export const useInactiveContracts = () => { diff --git a/web/hooks/use-group.ts b/web/hooks/use-group.ts index 781da9cb..3948bd4a 100644 --- a/web/hooks/use-group.ts +++ b/web/hooks/use-group.ts @@ -11,6 +11,7 @@ import { listenForMemberGroupIds, listenForOpenGroups, listGroups, + topFollowedGroupsQuery, } from 'web/lib/firebase/groups' import { getUser } from 'web/lib/firebase/users' import { filterDefined } from 'common/util/array' @@ -18,6 +19,8 @@ import { Contract } from 'common/contract' import { uniq } 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' export const useGroup = (groupId: string | undefined) => { const [group, setGroup] = useState() @@ -49,6 +52,14 @@ export const useOpenGroups = () => { 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) => { const result = useQuery(['member-groups', userId ?? ''], () => getMemberGroups(userId ?? '') diff --git a/web/lib/firebase/contracts.ts b/web/lib/firebase/contracts.ts index 51ec3108..bdb25684 100644 --- a/web/lib/firebase/contracts.ts +++ b/web/lib/firebase/contracts.ts @@ -176,23 +176,6 @@ export function getUserBetContractsQuery(userId: string) { ) as Query } -const activeContractsQuery = query( - contracts, - where('isResolved', '==', false), - where('visibility', '==', 'public'), - where('volume7Days', '>', 0) -) - -export function getActiveContracts() { - return getValues(activeContractsQuery) -} - -export function listenForActiveContracts( - setContracts: (contracts: Contract[]) => void -) { - return listenForValues(activeContractsQuery, setContracts) -} - const inactiveContractsQuery = query( contracts, where('isResolved', '==', false), @@ -282,16 +265,17 @@ export function listenForHotContracts( }) } -const trendingContractsQuery = query( +export const trendingContractsQuery = query( contracts, where('isResolved', '==', false), where('visibility', '==', 'public'), - orderBy('popularityScore', 'desc'), - limit(10) + orderBy('popularityScore', 'desc') ) -export async function getTrendingContracts() { - return await getValues(trendingContractsQuery) +export async function getTrendingContracts(maxContracts = 10) { + return await getValues( + query(trendingContractsQuery, limit(maxContracts)) + ) } export async function getContractsBySlugs(slugs: string[]) { diff --git a/web/lib/firebase/groups.ts b/web/lib/firebase/groups.ts index 7a372d9a..89607d15 100644 --- a/web/lib/firebase/groups.ts +++ b/web/lib/firebase/groups.ts @@ -6,6 +6,7 @@ import { doc, getDocs, onSnapshot, + orderBy, query, setDoc, updateDoc, @@ -257,3 +258,8 @@ export async function listMembers(group: Group) { const members = await getValues(groupMembers(group.id)) return await Promise.all(members.map((m) => m.userId).map(getUser)) } + +export const topFollowedGroupsQuery = query( + groups, + orderBy('totalMembers', 'desc') +) diff --git a/web/pages/experimental/explore-groups.tsx b/web/pages/experimental/explore-groups.tsx new file mode 100644 index 00000000..26a848b3 --- /dev/null +++ b/web/pages/experimental/explore-groups.tsx @@ -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 ( + + + + + </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> + ) +} diff --git a/web/pages/groups.tsx b/web/pages/groups.tsx index f39a7647..1854da34 100644 --- a/web/pages/groups.tsx +++ b/web/pages/groups.tsx @@ -171,26 +171,34 @@ export default function Groups(props: { export function GroupCard(props: { group: Group - creator: User | undefined + creator: User | null | undefined user: User | undefined | null isMember: boolean + className?: string }) { - const { group, creator, user, isMember } = props + const { group, creator, user, isMember, className } = props const { totalContracts } = group 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)}> <a className="absolute left-0 right-0 top-0 bottom-0 z-0" /> </Link> - <div> - <Avatar - className={'absolute top-2 right-2 z-10'} - username={creator?.username} - avatarUrl={creator?.avatarUrl} - noLink={false} - size={12} - /> - </div> + {creator !== null && ( + <div> + <Avatar + className={'absolute top-2 right-2 z-10'} + username={creator?.username} + avatarUrl={creator?.avatarUrl} + noLink={false} + size={12} + /> + </div> + )} <Row className="items-center justify-between gap-2"> <span className="text-xl">{group.name}</span> </Row>