From 803091db066c07b296cff1bee43fb0d8333f97b0 Mon Sep 17 00:00:00 2001 From: Sinclair Chen Date: Fri, 26 Aug 2022 10:31:25 -0700 Subject: [PATCH] Add tournament home page (#797) * Add tournament home page * Preload markets, follow count * organize imports * Fix card width * Make entire title clickable * plural /tournament -> /tournaments * prettier * Fix /tournaments when groupIds are invalid * Restyle /tournaments page * Reintroduce Salem, tweak styles Co-authored-by: Austin Chen --- web/components/contract/contract-card.tsx | 15 +- web/pages/tournaments/index.tsx | 236 ++++++++++++++++++++++ 2 files changed, 244 insertions(+), 7 deletions(-) create mode 100644 web/pages/tournaments/index.tsx diff --git a/web/components/contract/contract-card.tsx b/web/components/contract/contract-card.tsx index 090020e0..056801eb 100644 --- a/web/components/contract/contract-card.tsx +++ b/web/components/contract/contract-card.tsx @@ -38,6 +38,7 @@ export function ContractCard(props: { showHotVolume?: boolean showTime?: ShowTime className?: string + questionClass?: string onClick?: () => void hideQuickBet?: boolean hideGroupLink?: boolean @@ -46,6 +47,7 @@ export function ContractCard(props: { showHotVolume, showTime, className, + questionClass, onClick, hideQuickBet, hideGroupLink, @@ -68,7 +70,7 @@ export function ContractCard(props: { return ( @@ -105,7 +107,10 @@ export function ContractCard(props: { className={'hidden md:inline-flex'} />

{question} @@ -165,11 +170,7 @@ export function ContractCard(props: { showQuickBet ? 'w-[85%]' : 'w-full' )} > - + dayjs(d, 'MMM D, YYYY').tz('America/Los_Angeles') + +type Tourney = { + title: string + url?: string + blurb: string // actual description in the click-through + award?: string + endTime?: Dayjs + groupId: string +} + +const Salem = { + title: 'CSPI/Salem Forecasting Tournament', + blurb: 'Top 5 traders qualify for a UT Austin research fellowship.', + url: 'https://salemcenter.manifold.markets/', + award: '$25,000', + endTime: toDate('Jul 31, 2023'), +} as const + +const tourneys: Tourney[] = [ + { + title: 'Cause Exploration Prizes', + blurb: + 'Which new charity ideas will Open Philanthropy find most promising?', + award: 'M$100k', + endTime: toDate('Sep 9, 2022'), + groupId: 'cMcpBQ2p452jEcJD2SFw', + }, + { + title: 'Fantasy Football Stock Exchange', + blurb: 'How many points will each NFL player score this season?', + award: '$2,500', + endTime: toDate('Jan 6, 2023'), + groupId: 'SxGRqXRpV3RAQKudbcNb', + }, + // { + // title: 'Clearer Thinking Regrant Project', + // blurb: 'Something amazing', + // award: '$10,000', + // endTime: toDate('Sep 22, 2022'), + // groupId: '2VsVVFGhKtIdJnQRAXVb', + // }, +] + +export async function getStaticProps() { + const groupIds = tourneys + .map((data) => data.groupId) + .filter((id) => id != undefined) as string[] + const groups = (await Promise.all(groupIds.map(getGroup))) + // Then remove undefined groups + .filter(Boolean) as Group[] + + const contracts = await Promise.all( + groups.map((g) => listContractsByGroupSlug(g?.slug ?? '')) + ) + + const markets = Object.fromEntries(groups.map((g, i) => [g.id, contracts[i]])) + + const groupMap = keyBy(groups, 'id') + const numPeople = mapValues(groupMap, (g) => g?.memberIds.length) + const slugs = mapValues(groupMap, 'slug') + + return { props: { markets, numPeople, slugs }, revalidate: 60 * 10 } +} + +export default function TournamentPage(props: { + markets: { [groupId: string]: Contract[] } + numPeople: { [groupId: string]: number } + slugs: { [groupId: string]: string } +}) { + const { markets = {}, numPeople = {}, slugs = {} } = props + + return ( + + + + {tourneys.map(({ groupId, ...data }) => ( +

+ ))} +
+ + + ) +} + +function Section(props: { + title: string + url: string + blurb: string + award?: string + ppl?: number + endTime?: Dayjs + markets: Contract[] +}) { + const { title, url, blurb, award, ppl, endTime, markets } = props + + return ( +
+ + +

+ {title} +

+ + {!!award && 🏆 {award}} + {!!ppl && ( + + + {ppl} + + )} + {endTime && ( + + + + {endTime.format('MMM D')} + + + )} + +
+ + {blurb} + +
+ {markets.length ? ( + markets.map((m) => ( + + )) + ) : ( +
+ Coming Soon... +
+ )} + +
+ ) +} + +function Carousel(props: { children: ReactNode; className?: string }) { + const { children, className } = props + + const ref = useRef(null) + + const th = (f: () => any) => throttle(f, 500, { trailing: false }) + const scrollLeft = th(() => + ref.current?.scrollBy({ left: -ref.current.clientWidth }) + ) + const scrollRight = th(() => + ref.current?.scrollBy({ left: ref.current.clientWidth }) + ) + + const [atFront, setAtFront] = useState(true) + const [atBack, setAtBack] = useState(false) + const onScroll = throttle(() => { + if (ref.current) { + const { scrollLeft, clientWidth, scrollWidth } = ref.current + setAtFront(scrollLeft < 80) + setAtBack(scrollWidth - (clientWidth + scrollLeft) < 80) + } + }, 500) + + // eslint-disable-next-line react-hooks/exhaustive-deps + useEffect(onScroll, []) + + return ( +
+ + {children} + + {!atFront && ( +
+ +
+ )} + {!atBack && ( +
+ +
+ )} +
+ ) +}