Merge branch 'main' into folds

This commit is contained in:
jahooma 2022-01-21 17:15:50 -06:00
commit 8985a3d7c8
16 changed files with 435 additions and 272 deletions

View File

@ -43,6 +43,7 @@ export function getAnteBets(
probBefore: p,
probAfter: p,
createdTime,
isAnte: true,
}
const noBet: Bet = {
@ -55,6 +56,7 @@ export function getAnteBets(
probBefore: p,
probAfter: p,
createdTime,
isAnte: true,
}
return { yesBet, noBet }

View File

@ -17,6 +17,7 @@ export type Bet = {
}
isSold?: boolean // true if this BUY bet has been sold
isAnte?: boolean
createdTime: number
}

View File

@ -312,10 +312,26 @@ function BetRow(props: { bet: Bet; contract: Contract; saleBet?: Bet }) {
probAfter,
shares,
isSold,
isAnte,
} = bet
const { isResolved, closeTime } = contract
const isClosed = closeTime && Date.now() > closeTime
const saleAmount = saleBet?.sale?.amount
const saleDisplay = bet.isAnte ? (
'ANTE'
) : saleAmount !== undefined ? (
<>{formatMoney(saleAmount)} (sold)</>
) : (
formatMoney(
isResolved
? resolvedPayout(contract, bet)
: calculateSaleAmount(contract, bet)
)
)
return (
<tr>
<td>{dayjs(createdTime).format('MMM D, h:mma')}</td>
@ -327,19 +343,9 @@ function BetRow(props: { bet: Bet; contract: Contract; saleBet?: Bet }) {
{formatPercent(probBefore)} {formatPercent(probAfter)}
</td>
<td>{formatWithCommas(shares)}</td>
<td>
{saleBet?.sale ? (
<>{formatMoney(Math.abs(saleBet.sale.amount))} (sold)</>
) : (
formatMoney(
isResolved
? resolvedPayout(contract, bet)
: calculateSaleAmount(contract, bet)
)
)}
</td>
<td>{saleDisplay}</td>
{!isResolved && !isClosed && !isSold && (
{!isResolved && !isClosed && !isSold && !isAnte && (
<td className="text-neutral">
<SellButton contract={contract} bet={bet} />
</td>

View File

@ -156,30 +156,39 @@ export function ContractDetails(props: { contract: Contract }) {
username={creatorUsername}
/>
<div className=""></div>
<div className="whitespace-nowrap">
<DateTimeTooltip time={contract.createdTime}>
<DateTimeTooltip text="Market created:" time={contract.createdTime}>
{createdDate}
</DateTimeTooltip>
{resolvedDate && contract.resolutionTime ? (
<>
{' - '}
<DateTimeTooltip time={contract.resolutionTime}>
<DateTimeTooltip
text="Market resolved:"
time={contract.resolutionTime}
>
{resolvedDate}
</DateTimeTooltip>
</>
) : null}
</div>
{!resolvedDate && closeTime && (
<>
<div className=""></div>
<div className="whitespace-nowrap">
{closeTime > Date.now() ? 'Closes' : 'Closed'}{' '}
<DateTimeTooltip time={closeTime}>
{!resolvedDate && closeTime && (
<>
{' - '}
<DateTimeTooltip
text={
closeTime > Date.now() ? 'Trading ends:' : 'Trading ended:'
}
time={closeTime}
>
{dayjs(closeTime).format('MMM D, YYYY')}
</DateTimeTooltip>
</div>
</>
)}
</>
)}
</div>
<div className=""></div>
<div className="whitespace-nowrap">{formatMoney(truePool)} pool</div>
</Row>

View File

@ -40,7 +40,7 @@ import { Comment, mapCommentsByBetId } from '../lib/firebase/comments'
import { JoinSpans } from './join-spans'
import Textarea from 'react-expanding-textarea'
function AvatarWithIcon(props: { username: string; avatarUrl: string }) {
export function AvatarWithIcon(props: { username: string; avatarUrl: string }) {
const { username, avatarUrl } = props
return (
<SiteLink className="relative" href={`/${username}`}>

View File

@ -51,8 +51,8 @@ export function ContractsGrid(props: {
<ContractCard
contract={contract}
key={contract.id}
showHotVolume={showHotVolume}
showCloseTime={showCloseTime}
// showHotVolume={showHotVolume}
// showCloseTime={showCloseTime}
/>
))}
</ul>

View File

@ -9,14 +9,19 @@ dayjs.extend(advanced)
export function DateTimeTooltip(props: {
time: number
text?: string
children?: React.ReactNode
}) {
const { time } = props
const { time, text } = props
const formattedTime = dayjs(time).format('MMM DD, YYYY hh:mm a z')
const toolTip = text ? `${text} ${formattedTime}` : formattedTime
return (
<>
<span
className="tooltip cursor-default hidden sm:inline-block"
data-tip={dayjs(time).format('MMM DD, YYYY hh:mm a z')}
data-tip={toolTip}
>
{props.children}
</span>

View File

@ -0,0 +1,119 @@
import { useUser } from '../hooks/use-user'
import { AvatarWithIcon } from './contract-feed'
import { Title } from './title'
import Textarea from 'react-expanding-textarea'
import { useState } from 'react'
import { Spacer } from './layout/spacer'
import { NewContract } from '../pages/create'
import { firebaseLogin, User } from '../lib/firebase/users'
import { useHotContracts } from '../hooks/use-contracts'
import { ContractsGrid } from './contracts-list'
import { SiteLink } from './site-link'
import { Contract } from '../../common/contract'
export function FeedPromo(props: { hotContracts: Contract[] }) {
const contracts = useHotContracts() ?? props.hotContracts
return (
<>
<div className="w-full bg-indigo-50 p-6 sm:border-2 sm:border-indigo-100 sm:rounded-lg">
<Title
text="Bet on the future"
className="!mt-2 text-gray-800 !text-4xl"
/>
<div className="text-gray-500 mb-4">
On Manifold Markets, you can find prediction markets run by your
favorite creators.
<br />
<button
className="bg-gradient-to-r gradient-to-r from-teal-500 to-green-500 text-transparent bg-clip-text hover:underline hover:decoration-gray-300 hover:decoration-2"
onClick={firebaseLogin}
>
Sign up to get M$ 1000 for free
</button>{' '}
and start trading!
<br />
</div>
<div className="flex flex-wrap mt-2 gap-2">
{['#politics', '#covid', '#gaming', '#sports', '#meta'].map((tag) => (
<Hashtag tag={tag} />
))}
</div>
<Spacer h={4} />
<ContractsGrid contracts={contracts?.slice(0, 2) || []} showHotVolume />
</div>
<div className="text-gray-800 text-lg mb-0 mt-6 mx-6">
Recent community activity
</div>
</>
)
}
function Hashtag(props: { tag: string }) {
const { tag } = props
return (
<SiteLink href={`/tag/${tag.substring(1)}`} className="flex items-center">
<div className="bg-white hover:bg-gray-100 cursor-pointer px-4 py-2 rounded-full shadow-md">
<span className="text-gray-500">{tag}</span>
</div>
</SiteLink>
)
}
export default function FeedCreate(props: { user: User }) {
const { user } = props
const [question, setQuestion] = useState('')
const placeholders = [
'Will I make a new friend this week?',
'Will we discover that the world is a simulation?',
'Will anyone I know get engaged this year?',
'Will humans set foot on Mars by the end of 2030?',
'If I switch jobs, will I have more free time in 6 months than I do now?',
'Will any cryptocurrency eclipse Bitcoin by market cap?',
]
// Rotate through a new placeholder each day
// Easter egg idea: click your own name to shuffle the placeholder
const daysSinceEpoch = Math.floor(Date.now() / 1000 / 60 / 60 / 24)
const placeholder = placeholders[daysSinceEpoch % placeholders.length]
return (
<div className="w-full bg-indigo-50 sm:rounded-md p-4">
<div className="relative flex items-start space-x-3">
<AvatarWithIcon
username={user.username}
avatarUrl={user.avatarUrl || ''}
/>
<div className="min-w-0 flex-1">
{/* TODO: Show focus, for accessibility */}
<div>
<p className="my-0.5 text-sm">Ask a question... </p>
</div>
<Textarea
className="text-lg sm:text-xl text-indigo-700 w-full border-transparent focus:border-transparent bg-transparent p-0 appearance-none resize-none focus:ring-transparent"
placeholder={`e.g. ${placeholder}`}
value={question}
onClick={(e) => e.stopPropagation()}
onChange={(e) => setQuestion(e.target.value || '')}
/>
<Spacer h={4} />
</div>
</div>
{/* Hide component instead of deleting, so edits to NewContract don't get lost */}
<div className={question ? '' : 'hidden'}>
<NewContract question={question} />
</div>
{/* Show a fake "Create Market" button, which gets replaced with the NewContract one*/}
{!question && (
<div className="flex justify-end">
<button className="btn" disabled>
Create Market
</button>
</div>
)}
</div>
)
}

View File

@ -57,7 +57,7 @@ function NavOptions(props: { user: User | null; themeClasses: string }) {
</Link>
)}
<Link href="/folds">
{/* <Link href="/folds">
<a
className={clsx(
'text-base hidden md:block whitespace-nowrap',
@ -66,7 +66,7 @@ function NavOptions(props: { user: User | null; themeClasses: string }) {
>
Folds
</a>
</Link>
</Link> */}
<Link href="/markets">
<a
@ -81,18 +81,26 @@ function NavOptions(props: { user: User | null; themeClasses: string }) {
{user === null ? (
<>
<div
className={clsx(
'text-base font-medium cursor-pointer whitespace-nowrap',
themeClasses
)}
<button
className="btn border-none normal-case text-base font-medium px-6 bg-gradient-to-r from-teal-500 to-green-500 hover:from-teal-600 hover:to-green-600"
onClick={firebaseLogin}
>
Sign in
</div>
</button>
</>
) : (
<>
<Link href="/leaderboards">
<a
className={clsx(
'text-base hidden md:block whitespace-nowrap',
themeClasses
)}
>
Leaderboards
</a>
</Link>
<ProfileMenu user={user} />
</>
)}

View File

@ -51,10 +51,6 @@ function getNavigationOptions(user: User, options: { mobile: boolean }) {
name: 'Your markets',
href: `/${user.username}`,
},
{
name: 'Leaderboards',
href: '/leaderboards',
},
{
name: 'Discord',
href: 'https://discord.gg/eHQBNBqXuh',

View File

@ -108,39 +108,6 @@ function Contents() {
immediately! When a market creator decides an outcome in your favor,
you&#39;ll win Manifold Dollars from people who bet against you.
</p>
{/* <p>
If you run out of money, you can purchase more at a rate of $1 USD to M$
100. (Note that Manifold Dollars are not convertible to cash and can only
be used within our platform.)
</p> */}
<aside>
💡 We&#39;re still in Open Beta; we&#39;ll tweak the amounts of Manifold
Dollars given out and periodically reset balances before our official
launch.
{/* If you purchase
any M$ during the beta, we promise to honor that when we launch! */}
</aside>
{/* <h3 id="why-do-i-want-to-bet-with-play-money-">
Why do I want to bet with play-money?
</h3>
<p>
Prediction markets work best when bettors have skin in the game. By
restricting the supply of our currency, you know that the other bettors
have thought carefully about where to spend their M$, and that the
market prices line up with reality.
</p>
<p>By buying M$, you support:</p>
<ul>
<li>The continued development of Manifold Markets</li>
<li>Cash payouts to market creators (TBD)</li>
<li>Forecasting tournaments for bettors (TBD)</li>
</ul>
<p>
We also have some thoughts on how to reward bettors: physical swag,
exclusive conversations with market creators, NFTs...? If you have
ideas, let us know!
</p> */}
<h3 id="can-prediction-markets-work-without-real-money-">
Can prediction markets work without real money?
</h3>
@ -270,10 +237,7 @@ function Contents() {
</p>
<h1 id="talk-to-us-">Talk to us!</h1>
<hr />
<p>
Questions? Comments? Want to create a market? Talk to us unlike
praying mantises, we dont bite!
</p>
<p>Questions? Comments? Want to create a market? Talk to us!</p>
<ul>
<li>
Email: <code>info@manifold.markets</code>

View File

@ -72,7 +72,6 @@ export function ActivityFeed(props: {
return contracts.length > 0 ? (
<Col className="items-center">
<Col className="w-full max-w-3xl">
<Title text="Recent Activity" />
<Col className="w-full bg-white self-center divide-gray-300 divide-y">
{contracts.map((contract, i) => (
<div key={contract.id} className="py-6 px-2 sm:px-4">

View File

@ -77,7 +77,6 @@ function ContractsTable() {
let contracts = useContracts() ?? []
// Sort users by createdTime descending, by default
contracts.sort((a, b) => b.createdTime - a.createdTime)
contracts = contracts.filter((contract) => !contract.isResolved)
return (
<Grid
@ -95,13 +94,23 @@ function ContractsTable() {
{
id: 'question',
name: 'Question',
formatter: (cell) => cell,
formatter: (cell) => html(`<div class="w-60">${cell}</div>`),
},
{
id: 'volume24Hours',
name: '24 hour vol',
name: '24h vol',
formatter: (cell) => (cell as number).toFixed(0),
},
{
id: 'createdTime',
name: 'Created time',
formatter: (cell) =>
html(
`<span class="whitespace-nowrap">${dayjs(cell as number).format(
'MMM D, h:mma'
)}</span>`
),
},
{
id: 'closeTime',
name: 'Close time',
@ -112,6 +121,16 @@ function ContractsTable() {
)}</span>`
),
},
{
id: 'resolvedTime',
name: 'Resolved time',
formatter: (cell) =>
html(
`<span class="whitespace-nowrap">${dayjs(cell as number).format(
'MMM D, h:mma'
)}</span>`
),
},
{
id: 'visibility',
name: 'Visibility',

View File

@ -5,19 +5,50 @@ import dayjs from 'dayjs'
import Textarea from 'react-expanding-textarea'
import { Spacer } from '../components/layout/spacer'
import { Title } from '../components/title'
import { useUser } from '../hooks/use-user'
import { Contract, contractPath } from '../lib/firebase/contracts'
import { Page } from '../components/page'
import { createContract } from '../lib/firebase/api-call'
import { Row } from '../components/layout/row'
import { AmountInput } from '../components/amount-input'
import { MINIMUM_ANTE } from '../../common/antes'
import { InfoTooltip } from '../components/info-tooltip'
import { CREATOR_FEE } from '../../common/fees'
import { Page } from '../components/page'
import { Title } from '../components/title'
export default function Create() {
const [question, setQuestion] = useState('')
return (
<Page>
<div className="w-full max-w-2xl mx-auto">
<Title text="Create a new prediction market" />
<div className="bg-gray-100 rounded-lg shadow-md px-6 py-4">
<form>
<div className="form-control w-full">
<label className="label">
<span className="mb-1">Question</span>
</label>
<Textarea
placeholder="e.g. Will the Democrats win the 2024 US presidential election?"
className="input input-bordered resize-none"
value={question}
onChange={(e) => setQuestion(e.target.value || '')}
/>
</div>
</form>
<NewContract question={question} />
</div>
</div>
</Page>
)
}
// Allow user to create a new contract
export default function NewContract() {
export function NewContract(props: { question: string }) {
const question = props.question
const creator = useUser()
useEffect(() => {
@ -29,7 +60,6 @@ export default function NewContract() {
}, [])
const [initialProb, setInitialProb] = useState(50)
const [question, setQuestion] = useState('')
const [description, setDescription] = useState('')
const [ante, setAnte] = useState<number | undefined>(undefined)
@ -41,7 +71,9 @@ export default function NewContract() {
}, [creator])
const [anteError, setAnteError] = useState<string | undefined>()
const [closeDate, setCloseDate] = useState('')
// By default, close the market a week from today
const weekFromToday = dayjs().add(7, 'day').format('YYYY-MM-DD')
const [closeDate, setCloseDate] = useState(weekFromToday)
const [isSubmitting, setIsSubmitting] = useState(false)
@ -82,141 +114,119 @@ export default function NewContract() {
await router.push(contractPath(result.contract as Contract))
}
// const descriptionPlaceholder = `e.g. This market will resolve to “Yes” if, by June 2, 2021, 11:59:59 PM ET, Paxlovid (also known under PF-07321332)...`
const descriptionPlaceholder = `Provide more detail on how you will resolve this market. (Optional)`
const descriptionPlaceholder = `e.g. This market resolves to "YES" if, two weeks after closing, the...\n#politics #world`
if (!creator) return <></>
return (
<Page>
<div className="w-full max-w-2xl mx-auto">
<Title text="Create a new prediction market" />
<form>
<Spacer h={4} />
<div className="bg-gray-100 rounded-lg shadow-md px-6 py-4">
<form>
<div className="form-control w-full">
<label className="label">
<span className="mb-1">Question</span>
</label>
<Textarea
placeholder="e.g. Will the Democrats win the 2024 US presidential election?"
className="input input-bordered resize-none"
disabled={isSubmitting}
value={question}
onChange={(e) => setQuestion(e.target.value || '')}
/>
</div>
<Spacer h={4} />
<div className="form-control">
<label className="label">
<span className="mb-1">Initial probability</span>
</label>
<Row className="items-center gap-2">
<label className="input-group input-group-lg w-fit text-lg">
<input
type="number"
value={initialProb}
className="input input-bordered input-md text-lg"
disabled={isSubmitting}
min={1}
max={99}
onChange={(e) =>
setInitialProb(parseInt(e.target.value.substring(0, 2)))
}
/>
<span>%</span>
</label>
<input
type="range"
className="range range-primary"
min={1}
max={99}
value={initialProb}
onChange={(e) => setInitialProb(parseInt(e.target.value))}
/>
</Row>
</div>
<Spacer h={4} />
<div className="form-control">
<label className="label">
<span className="mb-1">Description</span>
</label>
<Textarea
className="textarea w-full textarea-bordered"
rows={3}
placeholder={descriptionPlaceholder}
value={description}
disabled={isSubmitting}
onClick={(e) => e.stopPropagation()}
onChange={(e) => setDescription(e.target.value || '')}
/>
</div>
<Spacer h={4} />
<div className="form-control items-start mb-1">
<label className="label gap-2 mb-1">
<span>Last trading day</span>
<InfoTooltip text="Trading allowed through 11:59 pm local time on this date." />
</label>
<input
type="date"
className="input input-bordered"
onClick={(e) => e.stopPropagation()}
onChange={(e) => setCloseDate(e.target.value || '')}
min={new Date().toISOString().split('T')[0]}
disabled={isSubmitting}
value={closeDate}
/>
</div>
<Spacer h={4} />
<div className="form-control items-start mb-1">
<label className="label gap-2 mb-1">
<span>Market ante</span>
<InfoTooltip
text={`Subsidize your market to encourage trading. Ante bets are set to match your initial probability.
You earn ${CREATOR_FEE * 100}% of trading volume.`}
/>
</label>
<AmountInput
amount={ante}
minimumAmount={MINIMUM_ANTE}
onChange={setAnte}
error={anteError}
setError={setAnteError}
disabled={isSubmitting}
/>
</div>
<Spacer h={4} />
<div className="flex justify-end my-4">
<button
type="submit"
className={clsx(
'btn btn-primary',
isSubmitting && 'loading disabled'
)}
disabled={isSubmitting || !isValid}
onClick={(e) => {
e.preventDefault()
submit()
}}
>
{isSubmitting ? 'Creating...' : 'Create market'}
</button>
</div>
</form>
</div>
<div className="form-control">
<label className="label">
<span className="mb-1">Initial probability</span>
</label>
<Row className="items-center gap-2">
<label className="input-group input-group-lg w-fit text-lg">
<input
type="number"
value={initialProb}
className="input input-bordered input-md text-lg"
disabled={isSubmitting}
min={1}
max={99}
onChange={(e) =>
setInitialProb(parseInt(e.target.value.substring(0, 2)))
}
/>
<span>%</span>
</label>
<input
type="range"
className="range range-primary"
min={1}
max={99}
value={initialProb}
onChange={(e) => setInitialProb(parseInt(e.target.value))}
/>
</Row>
</div>
</Page>
<Spacer h={4} />
<div className="form-control items-start mb-1">
<label className="label gap-2 mb-1">
<span className="mb-1">Description</span>
<InfoTooltip text="Optional. Describe how you will resolve this market." />
</label>
<Textarea
className="textarea w-full textarea-bordered"
rows={3}
placeholder={descriptionPlaceholder}
value={description}
disabled={isSubmitting}
onClick={(e) => e.stopPropagation()}
onChange={(e) => setDescription(e.target.value || '')}
/>
</div>
<Spacer h={4} />
<div className="form-control items-start mb-1">
<label className="label gap-2 mb-1">
<span>Last trading day</span>
<InfoTooltip text="Trading allowed through 11:59 pm local time on this date." />
</label>
<input
type="date"
className="input input-bordered"
onClick={(e) => e.stopPropagation()}
onChange={(e) => setCloseDate(e.target.value || '')}
min={new Date().toISOString().split('T')[0]}
disabled={isSubmitting}
value={closeDate}
/>
</div>
<Spacer h={4} />
<div className="form-control items-start mb-1">
<label className="label gap-2 mb-1">
<span>Market ante</span>
<InfoTooltip
text={`Subsidize your market to encourage trading. Ante bets are set to match your initial probability.
You earn ${CREATOR_FEE * 100}% of trading volume.`}
/>
</label>
<AmountInput
amount={ante}
minimumAmount={MINIMUM_ANTE}
onChange={setAnte}
error={anteError}
setError={setAnteError}
disabled={isSubmitting}
/>
</div>
<Spacer h={4} />
<div className="flex justify-end my-4">
<button
type="submit"
className={clsx(
'btn btn-primary',
isSubmitting && 'loading disabled'
)}
disabled={isSubmitting || !isValid}
onClick={(e) => {
e.preventDefault()
submit()
}}
>
{isSubmitting ? 'Creating...' : 'Create market'}
</button>
</div>
</form>
)
}

View File

@ -2,13 +2,10 @@ import React from 'react'
import _ from 'lodash'
import {
Contract,
getClosingSoonContracts,
getHotContracts,
listAllContracts,
} from '../lib/firebase/contracts'
import { Spacer } from '../components/layout/spacer'
import { Page } from '../components/page'
import { Title } from '../components/title'
import { ActivityFeed, findActiveContracts } from './activity'
import {
getRecentComments,
@ -16,18 +13,19 @@ import {
listAllComments,
} from '../lib/firebase/comments'
import { Bet, listAllBets } from '../lib/firebase/bets'
import { ContractsGrid } from '../components/contracts-list'
import { useContracts } from '../hooks/use-contracts'
import { useRecentComments } from '../hooks/use-comments'
import FeedCreate, { FeedPromo } from '../components/feed-create'
import { Spacer } from '../components/layout/spacer'
import { Col } from '../components/layout/col'
import { useUser } from '../hooks/use-user'
export async function getStaticProps() {
const [contracts, hotContracts, closingSoonContracts, recentComments] =
await Promise.all([
listAllContracts().catch((_) => []),
getHotContracts().catch(() => []),
getClosingSoonContracts().catch(() => []),
getRecentComments().catch(() => []),
])
const [contracts, recentComments, hotContracts] = await Promise.all([
listAllContracts().catch((_) => []),
getRecentComments().catch(() => []),
getHotContracts().catch(() => []),
])
const activeContracts = findActiveContracts(contracts, recentComments)
const activeContractBets = await Promise.all(
@ -43,7 +41,6 @@ export async function getStaticProps() {
activeContractBets,
activeContractComments,
hotContracts,
closingSoonContracts,
},
revalidate: 60, // regenerate after a minute
@ -55,14 +52,8 @@ const Home = (props: {
activeContractBets: Bet[][]
activeContractComments: Comment[][]
hotContracts: Contract[]
closingSoonContracts: Contract[]
}) => {
const {
activeContractBets,
activeContractComments,
hotContracts,
closingSoonContracts,
} = props
const { activeContractBets, activeContractComments, hotContracts } = props
const contracts = useContracts() ?? props.activeContracts
const recentComments = useRecentComments()
@ -70,43 +61,29 @@ const Home = (props: {
? findActiveContracts(contracts, recentComments)
: props.activeContracts
const user = useUser()
return (
<Page>
<HotMarkets contracts={hotContracts} />
<Spacer h={10} />
<ClosingSoonMarkets contracts={closingSoonContracts} />
<Spacer h={10} />
<ActivityFeed
contracts={activeContracts}
contractBets={activeContractBets}
contractComments={activeContractComments}
/>
<Col className="items-center">
<Col className="max-w-3xl">
<div className="-mx-2 sm:mx-0">
{user ? (
<FeedCreate user={user} />
) : (
<FeedPromo hotContracts={hotContracts} />
)}
<Spacer h={4} />
<ActivityFeed
contracts={activeContracts}
contractBets={activeContractBets}
contractComments={activeContractComments}
/>
</div>
</Col>
</Col>
</Page>
)
}
const HotMarkets = (props: { contracts: Contract[] }) => {
const { contracts } = props
if (contracts.length === 0) return <></>
return (
<div className="w-full bg-indigo-50 border-2 border-indigo-100 p-6 rounded-lg shadow-md">
<Title className="mt-0" text="🔥 Markets" />
<ContractsGrid contracts={contracts} showHotVolume />
</div>
)
}
const ClosingSoonMarkets = (props: { contracts: Contract[] }) => {
const { contracts } = props
if (contracts.length === 0) return <></>
return (
<div className="w-full bg-green-50 border-2 border-green-100 p-6 rounded-lg shadow-md">
<Title className="mt-0" text="⏰ Closing soon" />
<ContractsGrid contracts={contracts} showCloseTime />
</div>
)
}
export default Home

View File

@ -1,25 +1,44 @@
import _ from 'lodash'
import { SearchableGrid } from '../components/contracts-list'
import { ContractsGrid, SearchableGrid } from '../components/contracts-list'
import { Spacer } from '../components/layout/spacer'
import { Page } from '../components/page'
import { SEO } from '../components/SEO'
import { Title } from '../components/title'
import { useContracts } from '../hooks/use-contracts'
import { useQueryAndSortParams } from '../hooks/use-sort-and-query-params'
import { Contract, listAllContracts } from '../lib/firebase/contracts'
import {
Contract,
getClosingSoonContracts,
getHotContracts,
listAllContracts,
} from '../lib/firebase/contracts'
export async function getStaticProps() {
const contracts = await listAllContracts().catch((_) => {})
const [contracts, hotContracts, closingSoonContracts] = await Promise.all([
listAllContracts().catch((_) => []),
getHotContracts().catch(() => []),
getClosingSoonContracts().catch(() => []),
])
return {
props: {
contracts,
hotContracts,
closingSoonContracts,
},
revalidate: 60, // regenerate after a minute
}
}
export default function Markets(props: { contracts: Contract[] }) {
// TODO: Rename endpoint to "Explore"
export default function Markets(props: {
contracts: Contract[]
hotContracts: Contract[]
closingSoonContracts: Contract[]
}) {
const contracts = useContracts() ?? props.contracts
const { hotContracts, closingSoonContracts } = props
const { query, setQuery, sort, setSort } = useQueryAndSortParams()
return (
@ -29,6 +48,11 @@ export default function Markets(props: { contracts: Contract[] }) {
description="Discover what's new, trending, or soon-to-close. Or search among our hundreds of markets."
url="/markets"
/>
<HotMarkets contracts={hotContracts} />
<Spacer h={10} />
<ClosingSoonMarkets contracts={closingSoonContracts} />
<Spacer h={10} />
<SearchableGrid
contracts={contracts}
query={query}
@ -39,3 +63,27 @@ export default function Markets(props: { contracts: Contract[] }) {
</Page>
)
}
const HotMarkets = (props: { contracts: Contract[] }) => {
const { contracts } = props
if (contracts.length === 0) return <></>
return (
<div className="w-full bg-indigo-50 border-2 border-indigo-100 p-6 rounded-lg shadow-md">
<Title className="!mt-0" text="🔥 Markets" />
<ContractsGrid contracts={contracts} showHotVolume />
</div>
)
}
const ClosingSoonMarkets = (props: { contracts: Contract[] }) => {
const { contracts } = props
if (contracts.length === 0) return <></>
return (
<div className="w-full bg-green-50 border-2 border-green-100 p-6 rounded-lg shadow-md">
<Title className="!mt-0" text="⏰ Closing soon" />
<ContractsGrid contracts={contracts} showCloseTime />
</div>
)
}