This commit is contained in:
		
						commit
						8a31670063
					
				
							
								
								
									
										3
									
								
								common/globalConfig.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								common/globalConfig.ts
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,3 @@ | |||
| export type GlobalConfig = { | ||||
|   pinnedItems: { itemId: string; type: 'post' | 'contract' }[] | ||||
| } | ||||
|  | @ -23,6 +23,12 @@ service cloud.firestore { | |||
|       allow read; | ||||
|     } | ||||
| 
 | ||||
|     match /globalConfig/globalConfig { | ||||
|       allow read; | ||||
|       allow update: if isAdmin() | ||||
|       allow create: if isAdmin() | ||||
|     } | ||||
| 
 | ||||
|     match /users/{userId} { | ||||
|       allow read; | ||||
|       allow update: if userId == request.auth.uid | ||||
|  |  | |||
|  | @ -231,7 +231,6 @@ export function PinnedItems(props: { | |||
|   return pinned.length > 0 || isEditable ? ( | ||||
|     <div> | ||||
|       <Row className="mb-3 items-center justify-between"> | ||||
|         <SectionHeader label={'Pinned'} /> | ||||
|         {isEditable && ( | ||||
|           <Button | ||||
|             color="gray" | ||||
|  |  | |||
|  | @ -70,7 +70,7 @@ export function PinnedSelectModal(props: { | |||
| 
 | ||||
|   return ( | ||||
|     <Modal open={open} setOpen={setOpen} className={' sm:p-0'} size={'lg'}> | ||||
|       <Col className="h-[85vh] w-full gap-4 rounded-md bg-white"> | ||||
|       <Col className=" h-[85vh] w-full gap-4 overflow-scroll rounded-md bg-white"> | ||||
|         <div className=" p-8 pb-0"> | ||||
|           <Row> | ||||
|             <div className={'text-xl text-indigo-700'}>{title}</div> | ||||
|  |  | |||
							
								
								
									
										12
									
								
								web/hooks/use-global-config.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								web/hooks/use-global-config.ts
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,12 @@ | |||
| import { GlobalConfig } from 'common/globalConfig' | ||||
| import { useEffect, useState } from 'react' | ||||
| import { listenForGlobalConfig } from 'web/lib/firebase/globalConfig' | ||||
| 
 | ||||
| export const useGlobalConfig = () => { | ||||
|   const [globalConfig, setGlobalConfig] = useState<GlobalConfig | null>(null) | ||||
| 
 | ||||
|   useEffect(() => { | ||||
|     listenForGlobalConfig(setGlobalConfig) | ||||
|   }, [globalConfig]) | ||||
|   return globalConfig | ||||
| } | ||||
|  | @ -1,6 +1,10 @@ | |||
| import { useEffect, useState } from 'react' | ||||
| import { DateDoc, Post } from 'common/post' | ||||
| import { listenForDateDocs, listenForPost } from 'web/lib/firebase/posts' | ||||
| import { | ||||
|   getAllPosts, | ||||
|   listenForDateDocs, | ||||
|   listenForPost, | ||||
| } from 'web/lib/firebase/posts' | ||||
| 
 | ||||
| export const usePost = (postId: string | undefined) => { | ||||
|   const [post, setPost] = useState<Post | null | undefined>() | ||||
|  | @ -38,6 +42,14 @@ export const usePosts = (postIds: string[]) => { | |||
|     .sort((a, b) => b.createdTime - a.createdTime) | ||||
| } | ||||
| 
 | ||||
| export const useAllPosts = () => { | ||||
|   const [posts, setPosts] = useState<Post[]>([]) | ||||
|   useEffect(() => { | ||||
|     getAllPosts().then(setPosts) | ||||
|   }, []) | ||||
|   return posts | ||||
| } | ||||
| 
 | ||||
| export const useDateDocs = () => { | ||||
|   const [dateDocs, setDateDocs] = useState<DateDoc[]>() | ||||
| 
 | ||||
|  |  | |||
							
								
								
									
										33
									
								
								web/lib/firebase/globalConfig.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										33
									
								
								web/lib/firebase/globalConfig.ts
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,33 @@ | |||
| import { | ||||
|   CollectionReference, | ||||
|   doc, | ||||
|   collection, | ||||
|   getDoc, | ||||
|   updateDoc, | ||||
| } from 'firebase/firestore' | ||||
| import { db } from 'web/lib/firebase/init' | ||||
| import { GlobalConfig } from 'common/globalConfig' | ||||
| import { listenForValue } from './utils' | ||||
| 
 | ||||
| const globalConfigCollection = collection( | ||||
|   db, | ||||
|   'globalConfig' | ||||
| ) as CollectionReference<GlobalConfig> | ||||
| const globalConfigDoc = doc(globalConfigCollection, 'globalConfig') | ||||
| 
 | ||||
| export const getGlobalConfig = async () => { | ||||
|   return (await getDoc(globalConfigDoc)).data() | ||||
| } | ||||
| 
 | ||||
| export function updateGlobalConfig( | ||||
|   globalConfig: GlobalConfig, | ||||
|   updates: Partial<GlobalConfig> | ||||
| ) { | ||||
|   return updateDoc(globalConfigDoc, updates) | ||||
| } | ||||
| 
 | ||||
| export function listenForGlobalConfig( | ||||
|   setGlobalConfig: (globalConfig: GlobalConfig | null) => void | ||||
| ) { | ||||
|   return listenForValue(globalConfigDoc, setGlobalConfig) | ||||
| } | ||||
|  | @ -52,6 +52,10 @@ export async function listPosts(postIds?: string[]) { | |||
|   return Promise.all(postIds.map(getPost)) | ||||
| } | ||||
| 
 | ||||
| export function getAllPosts() { | ||||
|   return getValues<Post>(posts) | ||||
| } | ||||
| 
 | ||||
| export async function getDateDocs() { | ||||
|   const q = query(posts, where('type', '==', 'date-doc')) | ||||
|   return getValues<DateDoc>(q) | ||||
|  |  | |||
|  | @ -1,4 +1,4 @@ | |||
| import React, { ReactNode, useEffect } from 'react' | ||||
| import React, { ReactNode, useEffect, useState } from 'react' | ||||
| import Router from 'next/router' | ||||
| import { | ||||
|   AdjustmentsIcon, | ||||
|  | @ -42,7 +42,7 @@ import { filterDefined } from 'common/util/array' | |||
| import { updateUser } from 'web/lib/firebase/users' | ||||
| import { isArray, keyBy } from 'lodash' | ||||
| import { usePrefetch } from 'web/hooks/use-prefetch' | ||||
| import { CPMMBinaryContract } from 'common/contract' | ||||
| import { Contract, CPMMBinaryContract } from 'common/contract' | ||||
| import { | ||||
|   useContractsByDailyScoreNotBetOn, | ||||
|   useContractsByDailyScoreGroups, | ||||
|  | @ -52,6 +52,16 @@ import { | |||
| import { ProfitBadge } from 'web/components/profit-badge' | ||||
| import { LoadingIndicator } from 'web/components/loading-indicator' | ||||
| import { Input } from 'web/components/input' | ||||
| import { PinnedItems } from 'web/components/groups/group-overview' | ||||
| import { updateGlobalConfig } from 'web/lib/firebase/globalConfig' | ||||
| import { getPost } from 'web/lib/firebase/posts' | ||||
| import { PostCard } from 'web/components/post-card' | ||||
| import { getContractFromId } from 'web/lib/firebase/contracts' | ||||
| import { ContractCard } from 'web/components/contract/contract-card' | ||||
| import { Post } from 'common/post' | ||||
| import { isAdmin } from 'common/envs/constants' | ||||
| import { useAllPosts } from 'web/hooks/use-post' | ||||
| import { useGlobalConfig } from 'web/hooks/use-global-config' | ||||
| 
 | ||||
| export default function Home() { | ||||
|   const user = useUser() | ||||
|  | @ -160,11 +170,12 @@ export default function Home() { | |||
| } | ||||
| 
 | ||||
| const HOME_SECTIONS = [ | ||||
|   { label: 'Featured', id: 'featured' }, | ||||
|   { label: 'Daily trending', id: 'daily-trending' }, | ||||
|   { label: 'Daily movers', id: 'daily-movers' }, | ||||
|   { label: 'Trending', id: 'score' }, | ||||
|   { label: 'New', id: 'newest' }, | ||||
| ] | ||||
| ] as const | ||||
| 
 | ||||
| export const getHomeItems = (sections: string[]) => { | ||||
|   // Accommodate old home sections.
 | ||||
|  | @ -200,17 +211,25 @@ function renderSections( | |||
|     score: CPMMBinaryContract[] | ||||
|   } | ||||
| ) { | ||||
|   type sectionTypes = typeof HOME_SECTIONS[number]['id'] | ||||
| 
 | ||||
|   return ( | ||||
|     <> | ||||
|       {sections.map((s) => { | ||||
|         const { id, label } = s as { | ||||
|           id: keyof typeof sectionContracts | ||||
|           id: sectionTypes | ||||
|           label: string | ||||
|         } | ||||
|         if (id === 'daily-movers') { | ||||
|           return <DailyMoversSection key={id} {...sectionContracts[id]} /> | ||||
|         } | ||||
| 
 | ||||
|         if (id === 'featured') { | ||||
|           // For now, only admins can see the featured section, until we all agree its ship-ready
 | ||||
|           if (!isAdmin) return <></> | ||||
|           return <FeaturedSection /> | ||||
|         } | ||||
| 
 | ||||
|         const contracts = sectionContracts[id] | ||||
| 
 | ||||
|         if (id === 'daily-trending') { | ||||
|  | @ -324,6 +343,78 @@ function SearchSection(props: { | |||
|   ) | ||||
| } | ||||
| 
 | ||||
| function FeaturedSection() { | ||||
|   const [pinned, setPinned] = useState<JSX.Element[]>([]) | ||||
|   const posts = useAllPosts() | ||||
|   const globalConfig = useGlobalConfig() | ||||
| 
 | ||||
|   useEffect(() => { | ||||
|     const pinnedItems = globalConfig?.pinnedItems | ||||
| 
 | ||||
|     async function getPinned() { | ||||
|       if (pinnedItems == null) { | ||||
|         if (globalConfig != null) { | ||||
|           updateGlobalConfig(globalConfig, { pinnedItems: [] }) | ||||
|         } | ||||
|       } else { | ||||
|         const itemComponents = await Promise.all( | ||||
|           pinnedItems.map(async (element) => { | ||||
|             if (element.type === 'post') { | ||||
|               const post = await getPost(element.itemId) | ||||
|               if (post) { | ||||
|                 return <PostCard post={post as Post} /> | ||||
|               } | ||||
|             } else if (element.type === 'contract') { | ||||
|               const contract = await getContractFromId(element.itemId) | ||||
|               if (contract) { | ||||
|                 return <ContractCard contract={contract as Contract} /> | ||||
|               } | ||||
|             } | ||||
|           }) | ||||
|         ) | ||||
|         setPinned( | ||||
|           itemComponents.filter( | ||||
|             (element) => element != undefined | ||||
|           ) as JSX.Element[] | ||||
|         ) | ||||
|       } | ||||
|     } | ||||
|     getPinned() | ||||
|   }, [globalConfig]) | ||||
| 
 | ||||
|   async function onSubmit(selectedItems: { itemId: string; type: string }[]) { | ||||
|     if (globalConfig == null) return | ||||
|     await updateGlobalConfig(globalConfig, { | ||||
|       pinnedItems: [ | ||||
|         ...(globalConfig?.pinnedItems ?? []), | ||||
|         ...(selectedItems as { itemId: string; type: 'contract' | 'post' }[]), | ||||
|       ], | ||||
|     }) | ||||
|   } | ||||
| 
 | ||||
|   function onDeleteClicked(index: number) { | ||||
|     if (globalConfig == null) return | ||||
|     const newPinned = globalConfig.pinnedItems.filter((item) => { | ||||
|       return item.itemId !== globalConfig.pinnedItems[index].itemId | ||||
|     }) | ||||
|     updateGlobalConfig(globalConfig, { pinnedItems: newPinned }) | ||||
|   } | ||||
| 
 | ||||
|   return ( | ||||
|     <Col> | ||||
|       <SectionHeader label={'Featured'} href={`#`} /> | ||||
|       <PinnedItems | ||||
|         posts={posts} | ||||
|         isEditable={true} | ||||
|         pinned={pinned} | ||||
|         onDeleteClicked={onDeleteClicked} | ||||
|         onSubmit={onSubmit} | ||||
|         modalMessage={'Pin posts or markets to the overview of this group.'} | ||||
|       /> | ||||
|     </Col> | ||||
|   ) | ||||
| } | ||||
| 
 | ||||
| function GroupSection(props: { | ||||
|   group: Group | ||||
|   user: User | ||||
|  |  | |||
		Loading…
	
		Reference in New Issue
	
	Block a user