Show custom feed of contracts from folds your follow or have bet on.
This commit is contained in:
		
							parent
							
								
									fdbaa5270b
								
							
						
					
					
						commit
						3cf4cb7a77
					
				
							
								
								
									
										3
									
								
								common/util/array.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								common/util/array.ts
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,3 @@ | |||
| export function filterDefined<T>(array: (T | null | undefined)[]) { | ||||
|   return array.filter((item) => item) as T[] | ||||
| } | ||||
|  | @ -52,5 +52,9 @@ service cloud.firestore { | |||
|       allow read; | ||||
|       allow write: if request.auth.uid == userId; | ||||
|     } | ||||
| 
 | ||||
|     match /{somePath=**}/followers/{userId} { | ||||
|       allow read; | ||||
|     } | ||||
|   } | ||||
| } | ||||
|  | @ -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<Contract[]>([]) | ||||
| 
 | ||||
|   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 <></> | ||||
|   } | ||||
| 
 | ||||
|  |  | |||
|  | @ -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<Bet[] | 'loading'>('loading') | ||||
| export const useUserBets = (userId: string | undefined) => { | ||||
|   const [bets, setBets] = useState<Bet[] | undefined>(undefined) | ||||
| 
 | ||||
|   useEffect(() => { | ||||
|     return listenForUserBets(userId, setBets) | ||||
|     if (userId) return listenForUserBets(userId, setBets) | ||||
|   }, [userId]) | ||||
| 
 | ||||
|   return bets | ||||
|  |  | |||
|  | @ -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<Fold>(doc(foldCollection, foldId)) | ||||
| } | ||||
| 
 | ||||
| export async function getFoldBySlug(slug: string) { | ||||
|   const q = query(foldCollection, where('slug', '==', slug)) | ||||
|   const folds = await getValues<Fold>(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 | ||||
| } | ||||
|  |  | |||
|  | @ -100,7 +100,7 @@ export function ActivityFeed(props: { | |||
|             <div key={contract.id} className="py-6 px-2 sm:px-4"> | ||||
|               <ContractFeed | ||||
|                 contract={contract} | ||||
|                 bets={contractBets[i] ?? []} | ||||
|                 bets={contractBets[i]} | ||||
|                 comments={contractComments[i]} | ||||
|                 feedType="activity" | ||||
|               /> | ||||
|  |  | |||
|  | @ -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<string[] | undefined>( | ||||
|     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<Bet[][] | undefined>() | ||||
|   const [feedComments, setFeedComments] = useState<Comment[][] | undefined>() | ||||
| 
 | ||||
|   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 ( | ||||
|     <Page assertUser="signed-in"> | ||||
|       <Col className="items-center"> | ||||
|         <Col className="max-w-3xl"> | ||||
|         <Col className="max-w-3xl w-full"> | ||||
|           <FeedCreate user={user ?? undefined} /> | ||||
|           <Spacer h={4} /> | ||||
|           {activeContracts ? ( | ||||
|             <ActivityFeed | ||||
|             contracts={updatedContracts} | ||||
|             contractBets={activeContractBets} | ||||
|             contractComments={activeContractComments} | ||||
|               contracts={activeContracts} | ||||
|               contractBets={contractBets} | ||||
|               contractComments={contractComments} | ||||
|             /> | ||||
|           ) : null} | ||||
|         </Col> | ||||
|       </Col> | ||||
|     </Page> | ||||
|  |  | |||
		Loading…
	
		Reference in New Issue
	
	Block a user