import { ClockIcon } from '@heroicons/react/outline' import { ChevronLeftIcon, ChevronRightIcon, UsersIcon, } from '@heroicons/react/solid' import clsx from 'clsx' import { BinaryContract, Contract, PseudoNumericContract, } from 'common/contract' import { Group } from 'common/group' import dayjs, { Dayjs } from 'dayjs' import customParseFormat from 'dayjs/plugin/customParseFormat' import timezone from 'dayjs/plugin/timezone' import utc from 'dayjs/plugin/utc' import { keyBy, mapValues, sortBy, throttle } from 'lodash' import Image, { ImageProps, StaticImageData } from 'next/image' import Link from 'next/link' import { ReactNode, useEffect, useRef, useState } from 'react' import { ContractCard } from 'web/components/contract/contract-card' import { DateTimeTooltip } from 'web/components/datetime-tooltip' import { Col } from 'web/components/layout/col' import { Row } from 'web/components/layout/row' import { Page } from 'web/components/page' import { SEO } from 'web/components/SEO' import { listContractsByGroupSlug } from 'web/lib/firebase/contracts' import { getGroup, groupPath } from 'web/lib/firebase/groups' import elon_pic from './_cspi/Will_Elon_Buy_Twitter.png' import china_pic from './_cspi/Chinese_Military_Action_against_Taiwan.png' import mpox_pic from './_cspi/Monkeypox_Cases.png' import race_pic from './_cspi/Supreme_Court_Ban_Race_in_College_Admissions.png' import { SiteLink } from 'web/components/site-link' import { getProbability } from 'common/calculate' dayjs.extend(utc) dayjs.extend(timezone) dayjs.extend(customParseFormat) const toDate = (d: string) => 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'), markets: [], images: [ { marketUrl: 'https://salemcenter.manifold.markets/SalemCenter/will-elon-musk-buy-twitter', image: elon_pic, }, { marketUrl: 'https://salemcenter.manifold.markets/SalemCenter/chinese-military-action-against-tai', image: china_pic, }, { marketUrl: 'https://salemcenter.manifold.markets/SalemCenter/over-100000-monkeypox-cases-in-2022', image: mpox_pic, }, { marketUrl: 'https://salemcenter.manifold.markets/SalemCenter/over-100000-monkeypox-cases-in-2022', image: race_pic, }, ], } 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: 'SF 2022 Ballot', blurb: 'Which ballot initiatives will pass this year in SF and CA?', award: '', endTime: toDate('Nov 8, 2022'), groupId: 'VkWZyS5yxs8XWUJrX9eq', }, // { // 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[] images?: { marketUrl: string; image: StaticImageData }[] // hack for cspi }) { const { title, url, blurb, award, ppl, endTime, images } = props // Sort markets by probability, highest % first const markets = sortBy(props.markets, (c) => getProbability(c as BinaryContract | PseudoNumericContract) ) .reverse() .filter((c) => !c.isResolved) return (

{title}

{!!award && 🏆 {award}} {!!ppl && ( {ppl} )} {endTime && ( {endTime.format('MMM D')} )}
{blurb}
{markets.length ? ( markets.map((m) => ( )) ) : ( <> {images?.map(({ marketUrl, image }) => ( ))} See more )}
) } // stole: https://stackoverflow.com/questions/66845889/next-js-image-how-to-maintain-aspect-ratio-and-add-letterboxes-when-needed const NaturalImage = (props: ImageProps) => { const [ratio, setRatio] = useState(4 / 1) return ( setRatio(naturalWidth / naturalHeight) } /> ) } 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 && (
)}
) }