Merge branch 'manifoldmarkets:main' into main

This commit is contained in:
marsteralex 2022-09-09 10:27:49 -07:00 committed by GitHub
commit 6e77b4a987
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 176 additions and 81 deletions

View File

@ -13,7 +13,10 @@ export const getRedeemableAmount = (bets: RedeemableBet[]) => {
const yesShares = sumBy(yesBets, (b) => b.shares) const yesShares = sumBy(yesBets, (b) => b.shares)
const noShares = sumBy(noBets, (b) => b.shares) const noShares = sumBy(noBets, (b) => b.shares)
const shares = Math.max(Math.min(yesShares, noShares), 0) const shares = Math.max(Math.min(yesShares, noShares), 0)
const soldFrac = shares > 0 ? Math.min(yesShares, noShares) / shares : 0 const soldFrac =
shares > 0
? Math.min(yesShares, noShares) / Math.max(yesShares, noShares)
: 0
const loanAmount = sumBy(bets, (bet) => bet.loanAmount ?? 0) const loanAmount = sumBy(bets, (bet) => bet.loanAmount ?? 0)
const loanPayment = loanAmount * soldFrac const loanPayment = loanAmount * soldFrac
const netAmount = shares - loanPayment const netAmount = shares - loanPayment

View File

@ -60,23 +60,27 @@ Parameters:
Requires no authorization. Requires no authorization.
### `GET /v0/groups/[slug]` ### `GET /v0/group/[slug]`
Gets a group by its slug. Gets a group by its slug.
Requires no authorization. Requires no authorization.
Note: group is singular in the URL.
### `GET /v0/group/by-id/[id]` ### `GET /v0/group/by-id/[id]`
Gets a group by its unique ID. Gets a group by its unique ID.
Requires no authorization. Requires no authorization.
Note: group is singular in the URL.
### `GET /v0/group/by-id/[id]/markets` ### `GET /v0/group/by-id/[id]/markets`
Gets a group's markets by its unique ID. Gets a group's markets by its unique ID.
Requires no authorization. Requires no authorization.
Note: group is singular in the URL.
### `GET /v0/markets` ### `GET /v0/markets`

View File

@ -122,6 +122,18 @@ export function BuyAmountInput(props: {
} }
} }
const parseRaw = (x: number) => {
if (x <= 100) return x
if (x <= 130) return 100 + (x - 100) * 5
return 250 + (x - 130) * 10
}
const getRaw = (x: number) => {
if (x <= 100) return x
if (x <= 250) return 100 + (x - 100) / 5
return 130 + (x - 250) / 10
}
return ( return (
<> <>
<AmountInput <AmountInput
@ -138,10 +150,10 @@ export function BuyAmountInput(props: {
<input <input
type="range" type="range"
min="0" min="0"
max="200" max="205"
value={amount ?? 0} value={getRaw(amount ?? 0)}
onChange={(e) => onAmountChange(parseInt(e.target.value))} onChange={(e) => onAmountChange(parseRaw(parseInt(e.target.value)))}
className="range range-lg z-40 mb-2 xl:hidden" className="range range-lg only-thumb z-40 mb-2 xl:hidden"
step="5" step="5"
/> />
)} )}

View File

@ -26,7 +26,7 @@ import { Bet } from 'common/bet'
import { track } from 'web/lib/service/analytics' import { track } from 'web/lib/service/analytics'
import { BetSignUpPrompt } from '../sign-up-prompt' import { BetSignUpPrompt } from '../sign-up-prompt'
import { isIOS } from 'web/lib/util/device' import { isIOS } from 'web/lib/util/device'
import { AlertBox } from '../alert-box' import { WarningConfirmationButton } from '../warning-confirmation-button'
export function AnswerBetPanel(props: { export function AnswerBetPanel(props: {
answer: Answer answer: Answer
@ -116,6 +116,15 @@ export function AnswerBetPanel(props: {
const bankrollFraction = (betAmount ?? 0) / (user?.balance ?? 1e9) const bankrollFraction = (betAmount ?? 0) / (user?.balance ?? 1e9)
const warning =
(betAmount ?? 0) > 10 && bankrollFraction >= 0.5 && bankrollFraction <= 1
? `You might not want to spend ${formatPercent(
bankrollFraction
)} of your balance on a single bet. \n\nCurrent balance: ${formatMoney(
user?.balance ?? 0
)}`
: undefined
return ( return (
<Col className={clsx('px-2 pb-2 pt-4 sm:pt-0', className)}> <Col className={clsx('px-2 pb-2 pt-4 sm:pt-0', className)}>
<Row className="items-center justify-between self-stretch"> <Row className="items-center justify-between self-stretch">
@ -148,21 +157,6 @@ export function AnswerBetPanel(props: {
showSliderOnMobile showSliderOnMobile
/> />
{(betAmount ?? 0) > 10 &&
bankrollFraction >= 0.5 &&
bankrollFraction <= 1 ? (
<AlertBox
title="Whoa, there!"
text={`You might not want to spend ${formatPercent(
bankrollFraction
)} of your balance on a single bet. \n\nCurrent balance: ${formatMoney(
user?.balance ?? 0
)}`}
/>
) : (
''
)}
<Col className="mt-3 w-full gap-3"> <Col className="mt-3 w-full gap-3">
<Row className="items-center justify-between text-sm"> <Row className="items-center justify-between text-sm">
<div className="text-gray-500">Probability</div> <div className="text-gray-500">Probability</div>
@ -198,16 +192,17 @@ export function AnswerBetPanel(props: {
<Spacer h={6} /> <Spacer h={6} />
{user ? ( {user ? (
<button <WarningConfirmationButton
className={clsx( warning={warning}
onSubmit={submitBet}
isSubmitting={isSubmitting}
disabled={!!betDisabled}
openModalButtonClass={clsx(
'btn self-stretch', 'btn self-stretch',
betDisabled ? 'btn-disabled' : 'btn-primary', betDisabled ? 'btn-disabled' : 'btn-primary',
isSubmitting ? 'loading' : '' isSubmitting ? 'loading' : ''
)} )}
onClick={betDisabled ? undefined : submitBet} />
>
{isSubmitting ? 'Submitting...' : 'Submit'}
</button>
) : ( ) : (
<BetSignUpPrompt /> <BetSignUpPrompt />
)} )}

View File

@ -40,7 +40,8 @@ import { LimitBets } from './limit-bets'
import { PillButton } from './buttons/pill-button' import { PillButton } from './buttons/pill-button'
import { YesNoSelector } from './yes-no-selector' import { YesNoSelector } from './yes-no-selector'
import { PlayMoneyDisclaimer } from './play-money-disclaimer' import { PlayMoneyDisclaimer } from './play-money-disclaimer'
import { AlertBox } from './alert-box' import { isAndroid, isIOS } from 'web/lib/util/device'
import { WarningConfirmationButton } from './warning-confirmation-button'
export function BetPanel(props: { export function BetPanel(props: {
contract: CPMMBinaryContract | PseudoNumericContract contract: CPMMBinaryContract | PseudoNumericContract
@ -184,18 +185,14 @@ function BuyPanel(props: {
const [inputRef, focusAmountInput] = useFocus() const [inputRef, focusAmountInput] = useFocus()
// useEffect(() => {
// if (selected) {
// if (isIOS()) window.scrollTo(0, window.scrollY + 200)
// focusAmountInput()
// }
// }, [selected, focusAmountInput])
function onBetChoice(choice: 'YES' | 'NO') { function onBetChoice(choice: 'YES' | 'NO') {
setOutcome(choice) setOutcome(choice)
setWasSubmitted(false) setWasSubmitted(false)
if (!isIOS() && !isAndroid()) {
focusAmountInput() focusAmountInput()
} }
}
function onBetChange(newAmount: number | undefined) { function onBetChange(newAmount: number | undefined) {
setWasSubmitted(false) setWasSubmitted(false)
@ -274,25 +271,15 @@ function BuyPanel(props: {
const bankrollFraction = (betAmount ?? 0) / (user?.balance ?? 1e9) const bankrollFraction = (betAmount ?? 0) / (user?.balance ?? 1e9)
const warning = const warning =
(betAmount ?? 0) > 10 && (betAmount ?? 0) > 10 && bankrollFraction >= 0.5 && bankrollFraction <= 1
bankrollFraction >= 0.5 && ? `You might not want to spend ${formatPercent(
bankrollFraction <= 1 ? (
<AlertBox
title="Whoa, there!"
text={`You might not want to spend ${formatPercent(
bankrollFraction bankrollFraction
)} of your balance on a single trade. \n\nCurrent balance: ${formatMoney( )} of your balance on a single trade. \n\nCurrent balance: ${formatMoney(
user?.balance ?? 0 user?.balance ?? 0
)}`} )}`
/> : (betAmount ?? 0) > 10 && probChange >= 0.3 && bankrollFraction <= 1
) : (betAmount ?? 0) > 10 && probChange >= 0.3 && bankrollFraction <= 1 ? ( ? `Are you sure you want to move the market by ${displayedDifference}?`
<AlertBox : undefined
title="Whoa, there!"
text={`Are you sure you want to move the market by ${displayedDifference}?`}
/>
) : (
<></>
)
return ( return (
<Col className={hidden ? 'hidden' : ''}> <Col className={hidden ? 'hidden' : ''}>
@ -325,8 +312,6 @@ function BuyPanel(props: {
showSliderOnMobile showSliderOnMobile
/> />
{warning}
<Col className="mt-3 w-full gap-3"> <Col className="mt-3 w-full gap-3">
<Row className="items-center justify-between text-sm"> <Row className="items-center justify-between text-sm">
<div className="text-gray-500"> <div className="text-gray-500">
@ -367,20 +352,20 @@ function BuyPanel(props: {
<Spacer h={8} /> <Spacer h={8} />
{user && ( {user && (
<button <WarningConfirmationButton
className={clsx( warning={warning}
onSubmit={submitBet}
isSubmitting={isSubmitting}
disabled={!!betDisabled}
openModalButtonClass={clsx(
'btn mb-2 flex-1', 'btn mb-2 flex-1',
betDisabled betDisabled
? 'btn-disabled' ? 'btn-disabled'
: outcome === 'YES' : outcome === 'YES'
? 'btn-primary' ? 'btn-primary'
: 'border-none bg-red-400 hover:bg-red-500', : 'border-none bg-red-400 hover:bg-red-500'
isSubmitting ? 'loading' : ''
)} )}
onClick={betDisabled ? undefined : submitBet} />
>
{isSubmitting ? 'Submitting...' : 'Submit'}
</button>
)} )}
{wasSubmitted && <div className="mt-4">Trade submitted!</div>} {wasSubmitted && <div className="mt-4">Trade submitted!</div>}

View File

@ -47,13 +47,13 @@ export function ConfirmationButton(props: {
{children} {children}
<Row className="gap-4"> <Row className="gap-4">
<div <div
className={clsx('btn normal-case', cancelBtn?.className)} className={clsx('btn', cancelBtn?.className)}
onClick={() => updateOpen(false)} onClick={() => updateOpen(false)}
> >
{cancelBtn?.label ?? 'Cancel'} {cancelBtn?.label ?? 'Cancel'}
</div> </div>
<div <div
className={clsx('btn normal-case', submitBtn?.className)} className={clsx('btn', submitBtn?.className)}
onClick={ onClick={
onSubmitWithSuccess onSubmitWithSuccess
? () => ? () =>
@ -69,7 +69,7 @@ export function ConfirmationButton(props: {
</Col> </Col>
</Modal> </Modal>
<div <div
className={clsx('btn normal-case', openModalBtn.className)} className={clsx('btn', openModalBtn.className)}
onClick={() => updateOpen(true)} onClick={() => updateOpen(true)}
> >
{openModalBtn.icon} {openModalBtn.icon}

View File

@ -13,7 +13,6 @@ import { Tabs } from '../layout/tabs'
import { Col } from '../layout/col' import { Col } from '../layout/col'
import { tradingAllowed } from 'web/lib/firebase/contracts' import { tradingAllowed } from 'web/lib/firebase/contracts'
import { CommentTipMap } from 'web/hooks/use-tip-txns' import { CommentTipMap } from 'web/hooks/use-tip-txns'
import { useBets } from 'web/hooks/use-bets'
import { useComments } from 'web/hooks/use-comments' import { useComments } from 'web/hooks/use-comments'
import { useLiquidity } from 'web/hooks/use-liquidity' import { useLiquidity } from 'web/hooks/use-liquidity'
import { BetSignUpPrompt } from '../sign-up-prompt' import { BetSignUpPrompt } from '../sign-up-prompt'
@ -27,24 +26,23 @@ export function ContractTabs(props: {
comments: ContractComment[] comments: ContractComment[]
tips: CommentTipMap tips: CommentTipMap
}) { }) {
const { contract, user, tips } = props const { contract, user, bets, tips } = props
const { outcomeType } = contract const { outcomeType } = contract
const bets = useBets(contract.id) ?? props.bets const lps = useLiquidity(contract.id)
const lps = useLiquidity(contract.id) ?? []
const userBets = const userBets =
user && bets.filter((bet) => !bet.isAnte && bet.userId === user.id) user && bets.filter((bet) => !bet.isAnte && bet.userId === user.id)
const visibleBets = bets.filter( const visibleBets = bets.filter(
(bet) => !bet.isAnte && !bet.isRedemption && bet.amount !== 0 (bet) => !bet.isAnte && !bet.isRedemption && bet.amount !== 0
) )
const visibleLps = lps.filter((l) => !l.isAnte && l.amount > 0) const visibleLps = lps?.filter((l) => !l.isAnte && l.amount > 0)
// Load comments here, so the badge count will be correct // Load comments here, so the badge count will be correct
const updatedComments = useComments(contract.id) const updatedComments = useComments(contract.id)
const comments = updatedComments ?? props.comments const comments = updatedComments ?? props.comments
const betActivity = ( const betActivity = visibleLps && (
<ContractBetsActivity <ContractBetsActivity
contract={contract} contract={contract}
bets={visibleBets} bets={visibleBets}

View File

@ -0,0 +1,74 @@
import clsx from 'clsx'
import React from 'react'
import { Row } from './layout/row'
import { ConfirmationButton } from './confirmation-button'
import { ExclamationIcon } from '@heroicons/react/solid'
export function WarningConfirmationButton(props: {
warning?: string
onSubmit: () => void
disabled?: boolean
isSubmitting: boolean
openModalButtonClass?: string
submitButtonClassName?: string
}) {
const {
onSubmit,
warning,
disabled,
isSubmitting,
openModalButtonClass,
submitButtonClassName,
} = props
if (!warning) {
return (
<button
className={clsx(
openModalButtonClass,
isSubmitting ? 'loading' : '',
disabled && 'btn-disabled'
)}
onClick={onSubmit}
disabled={disabled}
>
{isSubmitting ? 'Submitting...' : 'Submit'}
</button>
)
}
return (
<ConfirmationButton
openModalBtn={{
className: clsx(
openModalButtonClass,
isSubmitting && 'btn-disabled loading'
),
label: 'Submit',
}}
cancelBtn={{
label: 'Cancel',
className: 'btn-warning',
}}
submitBtn={{
label: 'Submit',
className: clsx(
'border-none btn-sm btn-ghost self-center',
submitButtonClassName
),
}}
onSubmit={onSubmit}
>
<Row className="items-center text-xl">
<ExclamationIcon
className="h-16 w-16 text-yellow-400"
aria-hidden="true"
/>
Whoa, there!
</Row>
<p>{warning}</p>
</ConfirmationButton>
)
}

View File

@ -12,3 +12,7 @@ export function isIOS() {
(navigator.userAgent.includes('Mac') && 'ontouchend' in document) (navigator.userAgent.includes('Mac') && 'ontouchend' in document)
) )
} }
export function isAndroid() {
return navigator.userAgent.includes('Android')
}

View File

@ -77,13 +77,21 @@ const Salem = {
const tourneys: Tourney[] = [ const tourneys: Tourney[] = [
{ {
title: 'Cause Exploration Prizes', title: 'Manifold F2P Tournament',
blurb: blurb:
'Which new charity ideas will Open Philanthropy find most promising?', 'Who can amass the most mana starting from a free-to-play (F2P) account?',
award: 'M$100k', award: 'Poem',
endTime: toDate('Sep 9, 2022'), endTime: toDate('Sep 15, 2022'),
groupId: 'cMcpBQ2p452jEcJD2SFw', groupId: '6rrIja7tVW00lUVwtsYS',
}, },
// {
// 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', title: 'Fantasy Football Stock Exchange',
blurb: 'How many points will each NFL player score this season?', blurb: 'How many points will each NFL player score this season?',
@ -135,7 +143,7 @@ export default function TournamentPage(props: { sections: SectionInfo[] }) {
title="Tournaments" title="Tournaments"
description="Win money by betting in forecasting touraments on current events, sports, science, and more" description="Win money by betting in forecasting touraments on current events, sports, science, and more"
/> />
<Col className="mx-4 mt-4 gap-10 sm:mx-10 xl:w-[125%]"> <Col className="m-4 gap-10 sm:mx-10 sm:gap-24 xl:w-[125%]">
{sections.map(({ tourney, slug, numPeople }) => ( {sections.map(({ tourney, slug, numPeople }) => (
<div key={slug}> <div key={slug}>
<SectionHeader <SectionHeader

View File

@ -60,6 +60,18 @@ module.exports = {
'overflow-wrap': 'anywhere', 'overflow-wrap': 'anywhere',
'word-break': 'break-word', // for Safari 'word-break': 'break-word', // for Safari
}, },
'.only-thumb': {
'pointer-events': 'none',
'&::-webkit-slider-thumb': {
'pointer-events': 'auto !important',
},
'&::-moz-range-thumb': {
'pointer-events': 'auto !important',
},
'&::-ms-thumb': {
'pointer-events': 'auto !important',
},
},
}) })
}), }),
], ],