diff --git a/common/util/array.ts b/common/util/array.ts new file mode 100644 index 00000000..fba342aa --- /dev/null +++ b/common/util/array.ts @@ -0,0 +1,3 @@ +export function filterDefined(array: (T | null | undefined)[]) { + return array.filter((item) => item) as T[] +} diff --git a/firestore.rules b/firestore.rules index 18572047..73eafb24 100644 --- a/firestore.rules +++ b/firestore.rules @@ -52,5 +52,9 @@ service cloud.firestore { allow read; allow write: if request.auth.uid == userId; } + + match /{somePath=**}/followers/{userId} { + allow read; + } } } \ No newline at end of file diff --git a/web/components/bets-list.tsx b/web/components/bets-list.tsx index 5af4bb0d..6cff5c8b 100644 --- a/web/components/bets-list.tsx +++ b/web/components/bets-list.tsx @@ -32,12 +32,12 @@ import { OutcomeLabel, YesLabel, NoLabel, MarketLabel } from './outcome-label' export function BetsList(props: { user: User }) { const { user } = props - const bets = useUserBets(user?.id ?? '') + const bets = useUserBets(user.id) const [contracts, setContracts] = useState([]) useEffect(() => { - const loadedBets = bets === 'loading' ? [] : bets + const loadedBets = bets ? bets : [] const contractIds = _.uniq(loadedBets.map((bet) => bet.contractId)) let disposed = false @@ -52,7 +52,7 @@ export function BetsList(props: { user: User }) { } }, [bets]) - if (bets === 'loading') { + if (!bets) { return <> } diff --git a/web/hooks/use-user-bets.ts b/web/hooks/use-user-bets.ts index e52d3786..6af56902 100644 --- a/web/hooks/use-user-bets.ts +++ b/web/hooks/use-user-bets.ts @@ -1,11 +1,11 @@ import { useEffect, useState } from 'react' import { Bet, listenForUserBets } from '../lib/firebase/bets' -export const useUserBets = (userId: string) => { - const [bets, setBets] = useState('loading') +export const useUserBets = (userId: string | undefined) => { + const [bets, setBets] = useState(undefined) useEffect(() => { - return listenForUserBets(userId, setBets) + if (userId) return listenForUserBets(userId, setBets) }, [userId]) return bets diff --git a/web/lib/firebase/folds.ts b/web/lib/firebase/folds.ts index 8ff9a385..58618414 100644 --- a/web/lib/firebase/folds.ts +++ b/web/lib/firebase/folds.ts @@ -1,7 +1,9 @@ import { collection, + collectionGroup, deleteDoc, doc, + getDocs, query, setDoc, updateDoc, @@ -12,7 +14,7 @@ import { Fold } from '../../../common/fold' import { Contract, contractCollection } from './contracts' import { db } from './init' import { User } from './users' -import { getValues, listenForValue, listenForValues } from './utils' +import { getValue, getValues, listenForValue, listenForValues } from './utils' const foldCollection = collection(db, 'folds') @@ -39,6 +41,10 @@ export function listenForFolds(setFolds: (folds: Fold[]) => void) { return listenForValues(foldCollection, setFolds) } +export function getFold(foldId: string) { + return getValue(doc(foldCollection, foldId)) +} + export async function getFoldBySlug(slug: string) { const q = query(foldCollection, where('slug', '==', slug)) const folds = await getValues(q) @@ -147,3 +153,13 @@ export async function getFoldsByTags(tags: string[]) { return _.sortBy(folds, (fold) => -1 * fold.followCount) } + +export async function getFollowedFolds(userId: string) { + const snapshot = await getDocs( + query(collectionGroup(db, 'followers'), where('userId', '==', userId)) + ) + const foldIds = snapshot.docs.map( + (doc) => doc.ref.parent.parent?.id as string + ) + return foldIds +} diff --git a/web/pages/activity.tsx b/web/pages/activity.tsx index b740d580..be5a9809 100644 --- a/web/pages/activity.tsx +++ b/web/pages/activity.tsx @@ -100,7 +100,7 @@ export function ActivityFeed(props: {
diff --git a/web/pages/home.tsx b/web/pages/home.tsx index 840e1746..61d785e4 100644 --- a/web/pages/home.tsx +++ b/web/pages/home.tsx @@ -1,70 +1,123 @@ -import React from 'react' +import React, { useEffect, useState } from 'react' import Router from 'next/router' import _ from 'lodash' import { Contract, listAllContracts } from '../lib/firebase/contracts' import { Page } from '../components/page' import { ActivityFeed, findActiveContracts } from './activity' -import { - getRecentComments, - Comment, - listAllComments, -} from '../lib/firebase/comments' -import { Bet, getRecentBets, listAllBets } from '../lib/firebase/bets' +import { Comment, listAllComments } from '../lib/firebase/comments' +import { Bet, listAllBets } from '../lib/firebase/bets' import FeedCreate from '../components/feed-create' import { Spacer } from '../components/layout/spacer' import { Col } from '../components/layout/col' import { useUser } from '../hooks/use-user' import { useContracts } from '../hooks/use-contracts' +import { getFollowedFolds, listAllFolds } from '../lib/firebase/folds' +import { Fold } from '../../common/fold' +import { filterDefined } from '../../common/util/array' +import { useUserBets } from '../hooks/use-user-bets' export async function getStaticProps() { - const [contracts, recentComments, recentBets] = await Promise.all([ + const [contracts, folds] = await Promise.all([ listAllContracts().catch((_) => []), - getRecentComments().catch(() => []), - getRecentBets().catch(() => []), + listAllFolds().catch(() => []), ]) - const activeContracts = findActiveContracts( - contracts, - recentComments, - recentBets - ) - const activeContractBets = await Promise.all( - activeContracts.map((contract) => listAllBets(contract.id).catch((_) => [])) - ) - const activeContractComments = await Promise.all( - activeContracts.map((contract) => - listAllComments(contract.id).catch((_) => []) - ) - ) - return { props: { - activeContracts, - activeContractBets, - activeContractComments, + contracts, + folds, }, revalidate: 60, // regenerate after a minute } } -const Home = (props: { - activeContracts: Contract[] - activeContractBets: Bet[][] - activeContractComments: Comment[][] -}) => { - const { activeContracts, activeContractBets, activeContractComments } = props +const Home = (props: { contracts: Contract[]; folds: Fold[] }) => { + const { folds } = props const user = useUser() - const contracts = useContracts() ?? activeContracts - const contractsMap = _.fromPairs( - contracts.map((contract) => [contract.id, contract]) + const contracts = useContracts() ?? props.contracts + + const [followedFoldIds, setFollowedFoldIds] = useState( + undefined ) - const updatedContracts = activeContracts.map( - (contract) => contractsMap[contract.id] + + useEffect(() => { + if (user) { + getFollowedFolds(user.id).then((foldIds) => setFollowedFoldIds(foldIds)) + } + }, [user]) + + const followedFolds = filterDefined( + (followedFoldIds ?? []).map((id) => folds.find((fold) => fold.id === id)) ) + const tagSet = new Set( + _.flatten(followedFolds.map((fold) => fold.lowercaseTags)) + ) + + const yourBets = useUserBets(user?.id) + const yourBetContracts = new Set( + (yourBets ?? []).map((bet) => bet.contractId) + ) + + const feedContracts = + followedFoldIds && yourBets + ? contracts.filter( + (contract) => + contract.lowercaseTags.some((tag) => tagSet.has(tag)) || + yourBetContracts.has(contract.id) + ) + : undefined + + const feedContractsKey = feedContracts?.map(({ id }) => id).join(',') + + const [feedBets, setFeedBets] = useState() + const [feedComments, setFeedComments] = useState() + + useEffect(() => { + if (feedContracts) { + Promise.all( + feedContracts.map((contract) => listAllBets(contract.id)) + ).then(setFeedBets) + Promise.all( + feedContracts.map((contract) => listAllComments(contract.id)) + ).then(setFeedComments) + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [feedContractsKey]) + + const oneDayMS = 24 * 60 * 60 * 1000 + const recentBets = + feedBets && + feedBets.flat().filter((bet) => bet.createdTime > Date.now() - oneDayMS) + + const activeContracts = + feedContracts && + feedComments && + recentBets && + findActiveContracts(feedContracts, feedComments.flat(), recentBets, 365) + + const contractBets = activeContracts + ? activeContracts.map( + (contract) => feedBets[feedContracts.indexOf(contract)] + ) + : [] + const contractComments = activeContracts + ? activeContracts.map( + (contract) => feedComments[feedContracts.indexOf(contract)] + ) + : [] + + console.log({ + followedFoldIds, + followedFolds, + yourBetContracts, + feedContracts, + feedBets, + feedComments, + }) if (user === null) { Router.replace('/') @@ -74,14 +127,16 @@ const Home = (props: { return ( - + - + {activeContracts ? ( + + ) : null}