diff --git a/common/antes.ts b/common/antes.ts index e2c7028c..3604d941 100644 --- a/common/antes.ts +++ b/common/antes.ts @@ -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 } diff --git a/common/bet.ts b/common/bet.ts index 9cbecc9d..a875102c 100644 --- a/common/bet.ts +++ b/common/bet.ts @@ -17,6 +17,7 @@ export type Bet = { } isSold?: boolean // true if this BUY bet has been sold + isAnte?: boolean createdTime: number } diff --git a/web/components/bets-list.tsx b/web/components/bets-list.tsx index c375e167..c237d17c 100644 --- a/web/components/bets-list.tsx +++ b/web/components/bets-list.tsx @@ -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 ( {dayjs(createdTime).format('MMM D, h:mma')} @@ -327,19 +343,9 @@ function BetRow(props: { bet: Bet; contract: Contract; saleBet?: Bet }) { {formatPercent(probBefore)} β†’ {formatPercent(probAfter)} {formatWithCommas(shares)} - - {saleBet?.sale ? ( - <>{formatMoney(Math.abs(saleBet.sale.amount))} (sold) - ) : ( - formatMoney( - isResolved - ? resolvedPayout(contract, bet) - : calculateSaleAmount(contract, bet) - ) - )} - + {saleDisplay} - {!isResolved && !isClosed && !isSold && ( + {!isResolved && !isClosed && !isSold && !isAnte && ( diff --git a/web/components/contract-card.tsx b/web/components/contract-card.tsx index 3bbceb63..90238b3f 100644 --- a/web/components/contract-card.tsx +++ b/web/components/contract-card.tsx @@ -156,30 +156,39 @@ export function ContractDetails(props: { contract: Contract }) { username={creatorUsername} />
β€’
+
- + {createdDate} + {resolvedDate && contract.resolutionTime ? ( <> {' - '} - + {resolvedDate} ) : null} -
- {!resolvedDate && closeTime && ( - <> -
β€’
-
- {closeTime > Date.now() ? 'Closes' : 'Closed'}{' '} - + + {!resolvedDate && closeTime && ( + <> + {' - '} + Date.now() ? 'Trading ends:' : 'Trading ended:' + } + time={closeTime} + > {dayjs(closeTime).format('MMM D, YYYY')} -
- - )} + + )} + +
β€’
{formatMoney(truePool)} pool
diff --git a/web/components/contract-feed.tsx b/web/components/contract-feed.tsx index 064ca7fc..c97a3b29 100644 --- a/web/components/contract-feed.tsx +++ b/web/components/contract-feed.tsx @@ -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 ( diff --git a/web/components/contracts-list.tsx b/web/components/contracts-list.tsx index c71a5c9c..0246e092 100644 --- a/web/components/contracts-list.tsx +++ b/web/components/contracts-list.tsx @@ -51,8 +51,8 @@ export function ContractsGrid(props: { ))} diff --git a/web/components/datetime-tooltip.tsx b/web/components/datetime-tooltip.tsx index c6bd1723..064943ff 100644 --- a/web/components/datetime-tooltip.tsx +++ b/web/components/datetime-tooltip.tsx @@ -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 ( <> {props.children} diff --git a/web/components/feed-create.tsx b/web/components/feed-create.tsx new file mode 100644 index 00000000..aac155ef --- /dev/null +++ b/web/components/feed-create.tsx @@ -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="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> + ) +} diff --git a/web/components/nav-bar.tsx b/web/components/nav-bar.tsx index 1d4a257d..74a54c8b 100644 --- a/web/components/nav-bar.tsx +++ b/web/components/nav-bar.tsx @@ -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} /> </> )} diff --git a/web/components/profile-menu.tsx b/web/components/profile-menu.tsx index e97a337e..1d685769 100644 --- a/web/components/profile-menu.tsx +++ b/web/components/profile-menu.tsx @@ -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', diff --git a/web/pages/about.tsx b/web/pages/about.tsx index 2d906b2e..9dfcbecb 100644 --- a/web/pages/about.tsx +++ b/web/pages/about.tsx @@ -108,39 +108,6 @@ function Contents() { immediately! When a market creator decides an outcome in your favor, you'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're still in Open Beta; we'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 don’t bite! - </p> + <p>Questions? Comments? Want to create a market? Talk to us!</p> <ul> <li> Email: <code>info@manifold.markets</code> diff --git a/web/pages/activity.tsx b/web/pages/activity.tsx index 06337d95..359990dd 100644 --- a/web/pages/activity.tsx +++ b/web/pages/activity.tsx @@ -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"> diff --git a/web/pages/admin.tsx b/web/pages/admin.tsx index d0346ee1..992d8bee 100644 --- a/web/pages/admin.tsx +++ b/web/pages/admin.tsx @@ -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', diff --git a/web/pages/create.tsx b/web/pages/create.tsx index ac1c9c51..870c2f8c 100644 --- a/web/pages/create.tsx +++ b/web/pages/create.tsx @@ -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> ) } diff --git a/web/pages/index.tsx b/web/pages/index.tsx index 313ad86a..40c68f68 100644 --- a/web/pages/index.tsx +++ b/web/pages/index.tsx @@ -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 diff --git a/web/pages/markets.tsx b/web/pages/markets.tsx index a5fdd818..64531b79 100644 --- a/web/pages/markets.tsx +++ b/web/pages/markets.tsx @@ -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> + ) +}