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