2021-12-10 17:14:05 +00:00
import clsx from 'clsx'
2022-03-15 22:27:51 +00:00
import React , { useEffect , useState } from 'react'
2022-07-23 02:03:07 +00:00
import { clamp , partition , sum , sumBy } from 'lodash'
2021-12-11 00:06:17 +00:00
2022-05-09 13:04:36 +00:00
import { useUser } from 'web/hooks/use-user'
2022-07-10 18:05:44 +00:00
import { CPMMBinaryContract , PseudoNumericContract } from 'common/contract'
2021-12-10 14:56:17 +00:00
import { Col } from './layout/col'
2021-12-10 15:51:48 +00:00
import { Row } from './layout/row'
2021-12-10 14:56:17 +00:00
import { Spacer } from './layout/spacer'
2022-01-05 18:23:44 +00:00
import {
formatMoney ,
2022-06-17 21:28:12 +00:00
formatMoneyWithDecimals ,
2022-01-05 18:23:44 +00:00
formatPercent ,
formatWithCommas ,
2022-05-09 13:04:36 +00:00
} from 'common/util/format'
2022-07-22 05:57:56 +00:00
import { getBinaryBetStats , getBinaryCpmmBetInfo } from 'common/new-bet'
2022-05-25 16:25:39 +00:00
import { User } from 'web/lib/firebase/users'
2022-07-10 18:05:44 +00:00
import { Bet , LimitBet } from 'common/bet'
2022-07-10 22:03:15 +00:00
import { APIError , placeBet } from 'web/lib/firebase/api'
import { sellShares } from 'web/lib/firebase/api'
2022-05-17 03:27:37 +00:00
import { AmountInput , BuyAmountInput } from './amount-input'
2022-01-15 21:28:19 +00:00
import { InfoTooltip } from './info-tooltip'
2022-07-22 21:03:52 +00:00
import {
BinaryOutcomeLabel ,
HigherLabel ,
LowerLabel ,
NoLabel ,
YesLabel ,
} from './outcome-label'
2022-07-10 18:05:44 +00:00
import { getProbability } from 'common/calculate'
2022-05-09 13:04:36 +00:00
import { useFocus } from 'web/hooks/use-focus'
import { useUserContractBets } from 'web/hooks/use-user-bets'
2022-07-11 15:54:37 +00:00
import { calculateCpmmSale , getCpmmProbability } from 'common/calculate-cpmm'
2022-07-22 21:30:07 +00:00
import { getFormattedMappedValue } from 'common/pseudo-numeric'
2022-04-20 14:13:39 +00:00
import { SellRow } from './sell-row'
2022-07-10 18:05:44 +00:00
import { useSaveBinaryShares } from './use-save-binary-shares'
2022-05-25 16:25:39 +00:00
import { SignUpPrompt } from './sign-up-prompt'
2022-06-10 16:35:53 +00:00
import { isIOS } from 'web/lib/util/device'
2022-07-22 05:57:56 +00:00
import { ProbabilityOrNumericInput } from './probability-input'
2022-06-15 03:00:36 +00:00
import { track } from 'web/lib/service/analytics'
2022-07-10 18:05:44 +00:00
import { useUnfilledBets } from 'web/hooks/use-bets'
import { LimitBets } from './limit-bets'
2022-07-13 23:23:03 +00:00
import { PillButton } from './buttons/pill-button'
2022-07-15 16:03:42 +00:00
import { YesNoSelector } from './yes-no-selector'
2022-07-25 06:28:05 +00:00
import { InfoBox } from './info-box'
2022-01-26 20:08:03 +00:00
export function BetPanel ( props : {
2022-07-10 18:05:44 +00:00
contract : CPMMBinaryContract | PseudoNumericContract
2022-01-26 20:08:03 +00:00
className? : string
2022-03-29 19:56:56 +00:00
} ) {
const { contract , className } = props
const user = useUser ( )
const userBets = useUserContractBets ( user ? . id , contract . id )
2022-07-10 18:05:44 +00:00
const unfilledBets = useUnfilledBets ( contract . id ) ? ? [ ]
const { sharesOutcome } = useSaveBinaryShares ( contract , userBets )
const [ isLimitOrder , setIsLimitOrder ] = useState ( false )
2022-03-29 19:56:56 +00:00
return (
< Col className = { className } >
2022-04-20 14:13:39 +00:00
< SellRow
contract = { contract }
user = { user }
2022-07-10 18:05:44 +00:00
className = { 'rounded-t-md bg-gray-100 px-4 py-5' }
2022-04-20 14:13:39 +00:00
/ >
2022-03-29 19:56:56 +00:00
< Col
className = { clsx (
2022-07-13 23:23:03 +00:00
'relative rounded-b-md bg-white px-6 py-6' ,
2022-03-29 19:56:56 +00:00
! sharesOutcome && 'rounded-t-md' ,
className
) }
>
2022-07-13 23:28:33 +00:00
< QuickOrLimitBet
isLimitOrder = { isLimitOrder }
setIsLimitOrder = { setIsLimitOrder }
2022-07-25 06:28:05 +00:00
hideToggle = { ! user }
2022-07-13 23:28:33 +00:00
/ >
2022-07-10 18:05:44 +00:00
< BuyPanel
2022-07-22 05:57:56 +00:00
hidden = { isLimitOrder }
contract = { contract }
user = { user }
unfilledBets = { unfilledBets }
/ >
< LimitOrderPanel
hidden = { ! isLimitOrder }
2022-07-10 18:05:44 +00:00
contract = { contract }
user = { user }
unfilledBets = { unfilledBets }
/ >
2022-07-25 06:28:05 +00:00
2022-05-25 16:25:39 +00:00
< SignUpPrompt / >
2022-07-25 06:28:05 +00:00
{ ! user && < PlayMoneyDisclaimer / > }
2022-03-29 19:56:56 +00:00
< / Col >
2022-07-19 20:01:13 +00:00
{ unfilledBets . length > 0 && (
2022-07-13 17:14:58 +00:00
< LimitBets className = "mt-4" contract = { contract } bets = { unfilledBets } / >
2022-07-10 18:05:44 +00:00
) }
2022-03-29 19:56:56 +00:00
< / Col >
)
}
2022-07-25 06:28:05 +00:00
const PlayMoneyDisclaimer = ( ) = > (
< InfoBox
2022-07-25 06:38:57 +00:00
title = "It's play-money"
2022-07-25 06:28:05 +00:00
className = "mt-4 max-w-md"
2022-07-25 06:38:57 +00:00
text = "Manifold Dollars (M$) are the play currency used by our platform to keep track of your bets. It's completely free for you and your friends to get started!"
2022-07-25 06:28:05 +00:00
/ >
)
2022-07-10 18:05:44 +00:00
export function SimpleBetPanel ( props : {
contract : CPMMBinaryContract | PseudoNumericContract
2022-03-29 19:56:56 +00:00
className? : string
2022-01-26 20:08:03 +00:00
selected ? : 'YES' | 'NO'
2022-07-12 22:34:10 +00:00
hasShares? : boolean
2022-02-05 18:26:11 +00:00
onBetSuccess ? : ( ) = > void
2022-01-26 20:08:03 +00:00
} ) {
2022-07-12 22:34:10 +00:00
const { contract , className , selected , hasShares , onBetSuccess } = props
2022-03-29 19:56:56 +00:00
2021-12-10 17:14:05 +00:00
const user = useUser ( )
2022-07-10 18:05:44 +00:00
const [ isLimitOrder , setIsLimitOrder ] = useState ( false )
2022-03-29 19:56:56 +00:00
2022-07-10 18:05:44 +00:00
const unfilledBets = useUnfilledBets ( contract . id ) ? ? [ ]
2022-03-29 19:56:56 +00:00
return (
< Col className = { className } >
2022-07-12 22:34:10 +00:00
< SellRow
contract = { contract }
user = { user }
className = { 'rounded-t-md bg-gray-100 px-4 py-5' }
/ >
< Col
className = { clsx (
! hasShares && 'rounded-t-md' ,
'rounded-b-md bg-white px-8 py-6'
) }
>
2022-07-13 23:28:33 +00:00
< QuickOrLimitBet
isLimitOrder = { isLimitOrder }
setIsLimitOrder = { setIsLimitOrder }
2022-07-25 06:28:05 +00:00
hideToggle = { ! user }
2022-07-13 23:28:33 +00:00
/ >
2022-07-10 18:05:44 +00:00
< BuyPanel
2022-07-22 05:57:56 +00:00
hidden = { isLimitOrder }
2022-07-10 18:05:44 +00:00
contract = { contract }
user = { user }
unfilledBets = { unfilledBets }
selected = { selected }
onBuySuccess = { onBetSuccess }
2022-03-29 19:56:56 +00:00
/ >
2022-07-22 05:57:56 +00:00
< LimitOrderPanel
hidden = { ! isLimitOrder }
contract = { contract }
user = { user }
unfilledBets = { unfilledBets }
onBuySuccess = { onBetSuccess }
/ >
2022-07-25 06:28:05 +00:00
2022-05-25 16:25:39 +00:00
< SignUpPrompt / >
2022-07-25 06:28:05 +00:00
{ ! user && < PlayMoneyDisclaimer / > }
2022-03-29 19:56:56 +00:00
< / Col >
2022-07-10 18:05:44 +00:00
2022-07-19 20:01:13 +00:00
{ unfilledBets . length > 0 && (
2022-07-13 17:14:58 +00:00
< LimitBets className = "mt-4" contract = { contract } bets = { unfilledBets } / >
2022-07-10 18:05:44 +00:00
) }
2022-03-29 19:56:56 +00:00
< / Col >
)
}
function BuyPanel ( props : {
2022-07-10 18:05:44 +00:00
contract : CPMMBinaryContract | PseudoNumericContract
2022-03-29 19:56:56 +00:00
user : User | null | undefined
2022-07-10 18:05:44 +00:00
unfilledBets : Bet [ ]
2022-07-22 05:57:56 +00:00
hidden : boolean
2022-03-29 19:56:56 +00:00
selected ? : 'YES' | 'NO'
onBuySuccess ? : ( ) = > void
} ) {
2022-07-22 05:57:56 +00:00
const { contract , user , unfilledBets , hidden , selected , onBuySuccess } = props
2022-07-10 18:05:44 +00:00
const initialProb = getProbability ( contract )
2022-07-02 19:37:59 +00:00
const isPseudoNumeric = contract . outcomeType === 'PSEUDO_NUMERIC'
2021-12-10 17:14:05 +00:00
2022-07-22 05:57:56 +00:00
const [ outcome , setOutcome ] = useState < 'YES' | 'NO' | undefined > ( selected )
2021-12-10 15:51:48 +00:00
const [ betAmount , setBetAmount ] = useState < number | undefined > ( undefined )
2021-12-15 00:08:55 +00:00
const [ error , setError ] = useState < string | undefined > ( )
2021-12-10 17:14:05 +00:00
const [ isSubmitting , setIsSubmitting ] = useState ( false )
const [ wasSubmitted , setWasSubmitted ] = useState ( false )
2022-03-29 19:56:56 +00:00
const [ inputRef , focusAmountInput ] = useFocus ( )
useEffect ( ( ) = > {
2022-06-10 16:35:53 +00:00
if ( selected ) {
if ( isIOS ( ) ) window . scrollTo ( 0 , window . scrollY + 200 )
focusAmountInput ( )
}
2022-03-29 19:56:56 +00:00
} , [ selected , focusAmountInput ] )
2021-12-11 04:09:32 +00:00
function onBetChoice ( choice : 'YES' | 'NO' ) {
2022-07-22 05:57:56 +00:00
setOutcome ( choice )
2021-12-11 04:09:32 +00:00
setWasSubmitted ( false )
2022-01-26 20:08:03 +00:00
focusAmountInput ( )
2021-12-11 04:09:32 +00:00
}
2022-01-11 03:41:42 +00:00
function onBetChange ( newAmount : number | undefined ) {
2021-12-15 00:08:55 +00:00
setWasSubmitted ( false )
2022-01-11 03:41:42 +00:00
setBetAmount ( newAmount )
2022-07-22 05:57:56 +00:00
if ( ! outcome ) {
setOutcome ( 'YES' )
2022-01-26 20:08:03 +00:00
}
2021-12-10 16:04:59 +00:00
}
2021-12-10 17:14:05 +00:00
async function submitBet() {
if ( ! user || ! betAmount ) return
2021-12-15 00:08:55 +00:00
setError ( undefined )
2021-12-10 17:14:05 +00:00
setIsSubmitting ( true )
2022-07-22 05:57:56 +00:00
placeBet ( {
outcome ,
amount : betAmount ,
contractId : contract.id ,
} )
2022-05-19 22:04:34 +00:00
. then ( ( r ) = > {
console . log ( 'placed bet. Result:' , r )
setIsSubmitting ( false )
setWasSubmitted ( true )
setBetAmount ( undefined )
if ( onBuySuccess ) onBuySuccess ( )
} )
. catch ( ( e ) = > {
if ( e instanceof APIError ) {
setError ( e . toString ( ) )
} else {
console . error ( e )
setError ( 'Error placing bet' )
}
setIsSubmitting ( false )
} )
2022-06-15 03:00:36 +00:00
track ( 'bet' , {
location : 'bet panel' ,
outcomeType : contract.outcomeType ,
slug : contract.slug ,
contractId : contract.id ,
amount : betAmount ,
2022-07-22 05:57:56 +00:00
outcome ,
isLimitOrder : false ,
2022-06-15 03:00:36 +00:00
} )
2021-12-10 17:14:05 +00:00
}
2021-12-15 00:08:55 +00:00
const betDisabled = isSubmitting || ! betAmount || error
2021-12-10 17:14:05 +00:00
2022-07-10 18:05:44 +00:00
const { newPool , newP , newBet } = getBinaryCpmmBetInfo (
2022-07-22 05:57:56 +00:00
outcome ? ? 'YES' ,
2022-07-10 18:05:44 +00:00
betAmount ? ? 0 ,
2022-03-15 22:27:51 +00:00
contract ,
2022-07-22 05:57:56 +00:00
undefined ,
2022-07-10 18:05:44 +00:00
unfilledBets as LimitBet [ ]
2021-12-15 22:57:40 +00:00
)
2021-12-11 03:47:46 +00:00
2022-07-10 18:05:44 +00:00
const resultProb = getCpmmProbability ( newPool , newP )
2022-07-13 23:39:32 +00:00
const probStayedSame =
formatPercent ( resultProb ) === formatPercent ( initialProb )
2022-07-22 05:57:56 +00:00
const currentPayout = newBet . shares
2022-01-12 19:01:04 +00:00
2022-01-15 21:28:19 +00:00
const currentReturn = betAmount ? ( currentPayout - betAmount ) / betAmount : 0
2022-03-19 03:02:04 +00:00
const currentReturnPercent = formatPercent ( currentReturn )
2022-03-15 22:27:51 +00:00
2022-07-11 15:54:37 +00:00
const totalFees = sum ( Object . values ( newBet . fees ) )
2022-07-02 19:37:59 +00:00
const format = getFormattedMappedValue ( contract )
2021-12-10 14:56:17 +00:00
return (
2022-07-22 05:57:56 +00:00
< Col className = { hidden ? 'hidden' : '' } >
2022-07-15 16:03:42 +00:00
< div className = "my-3 text-left text-sm text-gray-500" >
{ isPseudoNumeric ? 'Direction' : 'Outcome' }
< / div >
< YesNoSelector
className = "mb-4"
btnClassName = "flex-1"
2022-07-22 05:57:56 +00:00
selected = { outcome }
2022-07-15 16:03:42 +00:00
onSelect = { ( choice ) = > onBetChoice ( choice ) }
isPseudoNumeric = { isPseudoNumeric }
/ >
2022-07-13 23:23:03 +00:00
2022-03-29 19:56:56 +00:00
< div className = "my-3 text-left text-sm text-gray-500" > Amount < / div >
< BuyAmountInput
2022-05-02 16:15:00 +00:00
inputClassName = "w-full max-w-none"
2022-01-11 03:41:42 +00:00
amount = { betAmount }
onChange = { onBetChange }
error = { error }
setError = { setError }
disabled = { isSubmitting }
2022-01-26 20:08:03 +00:00
inputRef = { inputRef }
2022-01-11 03:41:42 +00:00
/ >
2022-03-03 09:09:32 +00:00
< Col className = "mt-3 w-full gap-3" >
2022-07-22 05:57:56 +00:00
< Row className = "items-center justify-between text-sm" >
< div className = "text-gray-500" >
{ isPseudoNumeric ? 'Estimated value' : 'Probability' }
< / div >
{ probStayedSame ? (
< div > { format ( initialProb ) } < / div >
) : (
< div >
{ format ( initialProb ) }
< span className = "mx-2" > → < / span >
{ format ( resultProb ) }
2022-07-10 18:05:44 +00:00
< / div >
2022-07-22 05:57:56 +00:00
) }
< / Row >
2021-12-10 17:14:05 +00:00
2022-03-22 05:18:08 +00:00
< Row className = "items-center justify-between gap-2 text-sm" >
2022-03-03 09:09:32 +00:00
< Row className = "flex-nowrap items-center gap-2 whitespace-nowrap text-gray-500" >
2022-03-02 03:31:48 +00:00
< div >
2022-07-10 18:05:44 +00:00
{ isPseudoNumeric ? (
2022-07-02 19:37:59 +00:00
'Max payout'
2022-03-22 05:18:08 +00:00
) : (
< >
2022-07-22 05:57:56 +00:00
Payout if < BinaryOutcomeLabel outcome = { outcome ? ? 'YES' } / >
2022-03-22 05:18:08 +00:00
< / >
) }
2022-03-02 03:31:48 +00:00
< / div >
2022-07-10 18:05:44 +00:00
< InfoTooltip
2022-07-11 15:54:37 +00:00
text = { ` Includes ${ formatMoneyWithDecimals ( totalFees ) } in fees ` }
2022-07-10 18:05:44 +00:00
/ >
2022-01-26 20:08:03 +00:00
< / Row >
2022-05-15 21:10:26 +00:00
< div >
< span className = "mr-2 whitespace-nowrap" >
2022-03-02 03:31:48 +00:00
{ formatMoney ( currentPayout ) }
< / span >
2022-05-15 21:10:26 +00:00
( + { currentReturnPercent } )
< / div >
2022-03-02 03:31:48 +00:00
< / Row >
< / Col >
2021-12-11 03:47:46 +00:00
2022-03-02 03:31:48 +00:00
< Spacer h = { 8 } / >
2021-12-11 03:47:46 +00:00
2022-07-22 05:57:56 +00:00
{ user && (
< button
className = { clsx (
'btn flex-1' ,
betDisabled
? 'btn-disabled'
: outcome === 'YES'
? 'btn-primary'
: 'border-none bg-red-400 hover:bg-red-500' ,
isSubmitting ? 'loading' : ''
) }
onClick = { betDisabled ? undefined : submitBet }
>
{ isSubmitting ? 'Submitting...' : 'Submit bet' }
< / button >
) }
{ wasSubmitted && < div className = "mt-4" > Bet submitted ! < / div > }
< / Col >
)
}
function LimitOrderPanel ( props : {
contract : CPMMBinaryContract | PseudoNumericContract
user : User | null | undefined
unfilledBets : Bet [ ]
hidden : boolean
onBuySuccess ? : ( ) = > void
} ) {
const { contract , user , unfilledBets , hidden , onBuySuccess } = props
const initialProb = getProbability ( contract )
const isPseudoNumeric = contract . outcomeType === 'PSEUDO_NUMERIC'
const [ betAmount , setBetAmount ] = useState < number | undefined > ( undefined )
const [ lowLimitProb , setLowLimitProb ] = useState < number | undefined > ( )
const [ highLimitProb , setHighLimitProb ] = useState < number | undefined > ( )
const betChoice = 'YES'
const [ error , setError ] = useState < string | undefined > ( )
const [ isSubmitting , setIsSubmitting ] = useState ( false )
const [ wasSubmitted , setWasSubmitted ] = useState ( false )
const rangeError =
lowLimitProb !== undefined &&
highLimitProb !== undefined &&
lowLimitProb >= highLimitProb
const outOfRangeError =
( lowLimitProb !== undefined &&
( lowLimitProb <= 0 || lowLimitProb >= 100 ) ) ||
( highLimitProb !== undefined &&
( highLimitProb <= 0 || highLimitProb >= 100 ) )
const hasYesLimitBet = lowLimitProb !== undefined && ! ! betAmount
const hasNoLimitBet = highLimitProb !== undefined && ! ! betAmount
const hasTwoBets = hasYesLimitBet && hasNoLimitBet
const betDisabled =
isSubmitting ||
! betAmount ||
rangeError ||
outOfRangeError ||
error ||
( ! hasYesLimitBet && ! hasNoLimitBet )
const yesLimitProb =
2022-07-23 03:44:21 +00:00
lowLimitProb === undefined
? undefined
: clamp ( lowLimitProb / 100 , 0.001 , 0.999 )
2022-07-22 05:57:56 +00:00
const noLimitProb =
2022-07-23 03:44:21 +00:00
highLimitProb === undefined
? undefined
: clamp ( highLimitProb / 100 , 0.001 , 0.999 )
2022-07-22 05:57:56 +00:00
2022-07-23 02:03:07 +00:00
const amount = betAmount ? ? 0
2022-07-22 05:57:56 +00:00
const shares =
yesLimitProb !== undefined && noLimitProb !== undefined
2022-07-23 02:03:07 +00:00
? Math . min ( amount / yesLimitProb , amount / ( 1 - noLimitProb ) )
: yesLimitProb !== undefined
? amount / yesLimitProb
: noLimitProb !== undefined
? amount / ( 1 - noLimitProb )
: 0
2022-07-22 05:57:56 +00:00
const yesAmount = shares * ( yesLimitProb ? ? 1 )
2022-07-23 02:03:07 +00:00
const noAmount = shares * ( 1 - ( noLimitProb ? ? 0 ) )
2022-07-22 05:57:56 +00:00
const profitIfBothFilled = shares - ( yesAmount + noAmount )
function onBetChange ( newAmount : number | undefined ) {
setWasSubmitted ( false )
setBetAmount ( newAmount )
}
async function submitBet() {
if ( ! user || betDisabled ) return
setError ( undefined )
setIsSubmitting ( true )
const betsPromise = hasTwoBets
? Promise . all ( [
placeBet ( {
outcome : 'YES' ,
amount : yesAmount ,
limitProb : yesLimitProb ,
contractId : contract.id ,
} ) ,
placeBet ( {
outcome : 'NO' ,
amount : noAmount ,
limitProb : noLimitProb ,
contractId : contract.id ,
} ) ,
] )
: placeBet ( {
outcome : hasYesLimitBet ? 'YES' : 'NO' ,
amount : betAmount ,
contractId : contract.id ,
limitProb : hasYesLimitBet ? yesLimitProb : noLimitProb ,
} )
betsPromise
. catch ( ( e ) = > {
if ( e instanceof APIError ) {
setError ( e . toString ( ) )
} else {
console . error ( e )
setError ( 'Error placing bet' )
}
setIsSubmitting ( false )
} )
. then ( ( r ) = > {
console . log ( 'placed bet. Result:' , r )
setIsSubmitting ( false )
setWasSubmitted ( true )
setBetAmount ( undefined )
if ( onBuySuccess ) onBuySuccess ( )
} )
if ( hasYesLimitBet ) {
track ( 'bet' , {
location : 'bet panel' ,
outcomeType : contract.outcomeType ,
slug : contract.slug ,
contractId : contract.id ,
amount : yesAmount ,
outcome : 'YES' ,
limitProb : yesLimitProb ,
isLimitOrder : true ,
isRangeOrder : hasTwoBets ,
} )
}
if ( hasNoLimitBet ) {
track ( 'bet' , {
location : 'bet panel' ,
outcomeType : contract.outcomeType ,
slug : contract.slug ,
contractId : contract.id ,
amount : noAmount ,
outcome : 'NO' ,
limitProb : noLimitProb ,
isLimitOrder : true ,
isRangeOrder : hasTwoBets ,
} )
}
}
const {
currentPayout : yesPayout ,
currentReturn : yesReturn ,
totalFees : yesFees ,
newBet : yesBet ,
} = getBinaryBetStats (
'YES' ,
yesAmount ,
contract ,
2022-07-23 02:03:07 +00:00
yesLimitProb ? ? initialProb ,
2022-07-22 05:57:56 +00:00
unfilledBets as LimitBet [ ]
)
const yesReturnPercent = formatPercent ( yesReturn )
const {
currentPayout : noPayout ,
currentReturn : noReturn ,
totalFees : noFees ,
newBet : noBet ,
} = getBinaryBetStats (
'NO' ,
noAmount ,
contract ,
2022-07-23 02:03:07 +00:00
noLimitProb ? ? initialProb ,
2022-07-22 05:57:56 +00:00
unfilledBets as LimitBet [ ]
)
const noReturnPercent = formatPercent ( noReturn )
return (
< Col className = { hidden ? 'hidden' : '' } >
2022-07-22 21:03:52 +00:00
< Row className = "mt-1 items-center gap-4" >
2022-07-22 05:57:56 +00:00
< Col className = "gap-2" >
2022-07-22 21:03:52 +00:00
< div className = "relative ml-1 text-sm text-gray-500" >
2022-07-22 21:07:59 +00:00
Bet { isPseudoNumeric ? < HigherLabel / > : < YesLabel / > } at
2022-07-22 21:03:52 +00:00
< / div >
2022-07-22 05:57:56 +00:00
< ProbabilityOrNumericInput
contract = { contract }
prob = { lowLimitProb }
setProb = { setLowLimitProb }
isSubmitting = { isSubmitting }
/ >
< / Col >
< Col className = "gap-2" >
2022-07-22 21:03:52 +00:00
< div className = "ml-1 text-sm text-gray-500" >
2022-07-22 21:07:59 +00:00
Bet { isPseudoNumeric ? < LowerLabel / > : < NoLabel / > } at
2022-07-22 21:03:52 +00:00
< / div >
2022-07-22 05:57:56 +00:00
< ProbabilityOrNumericInput
contract = { contract }
prob = { highLimitProb }
setProb = { setHighLimitProb }
isSubmitting = { isSubmitting }
/ >
< / Col >
< / Row >
2022-07-23 02:03:07 +00:00
{ outOfRangeError && (
2022-07-22 05:57:56 +00:00
< div className = "mb-2 mr-auto self-center whitespace-nowrap text-xs font-medium tracking-wide text-red-500" >
2022-07-23 02:03:07 +00:00
Limit is out of range
2022-07-22 05:57:56 +00:00
< / div >
) }
2022-07-23 02:03:07 +00:00
{ rangeError && ! outOfRangeError && (
2022-07-22 05:57:56 +00:00
< div className = "mb-2 mr-auto self-center whitespace-nowrap text-xs font-medium tracking-wide text-red-500" >
2022-07-23 02:03:07 +00:00
{ isPseudoNumeric ? 'HIGHER' : 'YES' } limit must be less than { ' ' }
{ isPseudoNumeric ? 'LOWER' : 'NO' } limit
2022-07-22 05:57:56 +00:00
< / div >
) }
2022-07-22 21:07:59 +00:00
< div className = "mt-1 mb-3 text-left text-sm text-gray-500" >
2022-07-22 05:57:56 +00:00
Max amount < span className = "ml-1 text-red-500" > * < / span >
< / div >
< BuyAmountInput
inputClassName = "w-full max-w-none"
amount = { betAmount }
onChange = { onBetChange }
error = { error }
setError = { setError }
disabled = { isSubmitting }
/ >
< Col className = "mt-3 w-full gap-3" >
{ ( hasTwoBets || ( hasYesLimitBet && yesBet . amount !== 0 ) ) && (
< Row className = "items-center justify-between gap-2 text-sm" >
< div className = "whitespace-nowrap text-gray-500" >
{ isPseudoNumeric ? (
< HigherLabel / >
) : (
< BinaryOutcomeLabel outcome = { 'YES' } / >
) } { ' ' }
2022-07-22 21:03:52 +00:00
filled now
2022-07-22 05:57:56 +00:00
< / div >
< div className = "mr-2 whitespace-nowrap" >
{ formatMoney ( yesBet . amount ) } of { ' ' }
{ formatMoney ( yesBet . orderAmount ? ? 0 ) }
< / div >
< / Row >
) }
{ ( hasTwoBets || ( hasNoLimitBet && noBet . amount !== 0 ) ) && (
< Row className = "items-center justify-between gap-2 text-sm" >
< div className = "whitespace-nowrap text-gray-500" >
{ isPseudoNumeric ? (
< LowerLabel / >
) : (
< BinaryOutcomeLabel outcome = { 'NO' } / >
) } { ' ' }
2022-07-22 21:03:52 +00:00
filled now
2022-07-22 05:57:56 +00:00
< / div >
< div className = "mr-2 whitespace-nowrap" >
{ formatMoney ( noBet . amount ) } of { ' ' }
{ formatMoney ( noBet . orderAmount ? ? 0 ) }
< / div >
< / Row >
) }
{ hasTwoBets && (
< Row className = "items-center justify-between gap-2 text-sm" >
< div className = "whitespace-nowrap text-gray-500" >
Profit if both orders filled
< / div >
< div className = "mr-2 whitespace-nowrap" >
{ formatMoney ( profitIfBothFilled ) }
< / div >
< / Row >
) }
{ hasYesLimitBet && ! hasTwoBets && (
< Row className = "items-center justify-between gap-2 text-sm" >
< Row className = "flex-nowrap items-center gap-2 whitespace-nowrap text-gray-500" >
< div >
{ isPseudoNumeric ? (
'Max payout'
) : (
< >
Max < BinaryOutcomeLabel outcome = { 'YES' } / > payout
< / >
) }
< / div >
< InfoTooltip
text = { ` Includes ${ formatMoneyWithDecimals ( yesFees ) } in fees ` }
/ >
< / Row >
< div >
< span className = "mr-2 whitespace-nowrap" >
{ formatMoney ( yesPayout ) }
< / span >
( + { yesReturnPercent } )
< / div >
< / Row >
) }
{ hasNoLimitBet && ! hasTwoBets && (
< Row className = "items-center justify-between gap-2 text-sm" >
< Row className = "flex-nowrap items-center gap-2 whitespace-nowrap text-gray-500" >
< div >
{ isPseudoNumeric ? (
'Max payout'
) : (
< >
Max < BinaryOutcomeLabel outcome = { 'NO' } / > payout
< / >
) }
< / div >
< InfoTooltip
text = { ` Includes ${ formatMoneyWithDecimals ( noFees ) } in fees ` }
/ >
< / Row >
< div >
< span className = "mr-2 whitespace-nowrap" >
{ formatMoney ( noPayout ) }
< / span >
( + { noReturnPercent } )
< / div >
< / Row >
) }
< / Col >
{ ( hasYesLimitBet || hasNoLimitBet ) && < Spacer h = { 8 } / > }
2022-02-21 04:37:53 +00:00
{ user && (
2021-12-18 07:09:11 +00:00
< button
className = { clsx (
2022-03-02 03:31:48 +00:00
'btn flex-1' ,
2021-12-18 07:09:11 +00:00
betDisabled
? 'btn-disabled'
: betChoice === 'YES'
? 'btn-primary'
2022-02-11 18:40:22 +00:00
: 'border-none bg-red-400 hover:bg-red-500' ,
2021-12-18 07:09:11 +00:00
isSubmitting ? 'loading' : ''
) }
onClick = { betDisabled ? undefined : submitBet }
>
2022-07-16 19:37:03 +00:00
{ isSubmitting
? 'Submitting...'
2022-07-22 05:57:56 +00:00
: ` Submit order ${ hasTwoBets ? 's' : '' } ` }
2021-12-18 07:09:11 +00:00
< / button >
) }
2021-12-11 03:47:46 +00:00
2022-07-22 05:57:56 +00:00
{ wasSubmitted && < div className = "mt-4" > Order submitted ! < / div > }
< / Col >
2022-03-29 19:56:56 +00:00
)
}
2022-07-13 23:28:33 +00:00
function QuickOrLimitBet ( props : {
isLimitOrder : boolean
setIsLimitOrder : ( isLimitOrder : boolean ) = > void
2022-07-25 06:28:05 +00:00
hideToggle? : boolean
2022-07-13 23:28:33 +00:00
} ) {
2022-07-25 06:28:05 +00:00
const { isLimitOrder , setIsLimitOrder , hideToggle } = props
2022-07-13 23:28:33 +00:00
return (
< Row className = "align-center mb-4 justify-between" >
< div className = "text-4xl" > Bet < / div >
2022-07-25 06:28:05 +00:00
{ ! hideToggle && (
< Row className = "mt-1 items-center gap-2" >
< PillButton
selected = { ! isLimitOrder }
onSelect = { ( ) = > {
setIsLimitOrder ( false )
track ( 'select quick order' )
} }
>
Quick
< / PillButton >
< PillButton
selected = { isLimitOrder }
onSelect = { ( ) = > {
setIsLimitOrder ( true )
track ( 'select limit order' )
} }
>
Limit
< / PillButton >
< / Row >
) }
2022-07-13 23:28:33 +00:00
< / Row >
)
}
2022-04-20 14:13:39 +00:00
export function SellPanel ( props : {
2022-07-02 19:37:59 +00:00
contract : CPMMBinaryContract | PseudoNumericContract
2022-03-29 19:56:56 +00:00
userBets : Bet [ ]
shares : number
sharesOutcome : 'YES' | 'NO'
user : User
onSellSuccess ? : ( ) = > void
} ) {
const { contract , shares , sharesOutcome , userBets , user , onSellSuccess } =
props
const [ amount , setAmount ] = useState < number | undefined > ( shares )
const [ error , setError ] = useState < string | undefined > ( )
const [ isSubmitting , setIsSubmitting ] = useState ( false )
const [ wasSubmitted , setWasSubmitted ] = useState ( false )
2022-07-10 18:05:44 +00:00
const unfilledBets = useUnfilledBets ( contract . id ) ? ? [ ]
2022-03-29 19:56:56 +00:00
const betDisabled = isSubmitting || ! amount || error
2022-07-10 18:05:44 +00:00
// Sell all shares if remaining shares would be < 1
const sellQuantity = amount === Math . floor ( shares ) ? shares : amount
2022-03-29 19:56:56 +00:00
async function submitSell() {
if ( ! user || ! amount ) return
setError ( undefined )
setIsSubmitting ( true )
2022-06-07 20:54:58 +00:00
await sellShares ( {
2022-07-10 18:05:44 +00:00
shares : sellQuantity ,
2022-03-29 19:56:56 +00:00
outcome : sharesOutcome ,
contractId : contract.id ,
2022-06-07 20:54:58 +00:00
} )
. then ( ( r ) = > {
console . log ( 'Sold shares. Result:' , r )
setIsSubmitting ( false )
setWasSubmitted ( true )
setAmount ( undefined )
if ( onSellSuccess ) onSellSuccess ( )
} )
. catch ( ( e ) = > {
if ( e instanceof APIError ) {
setError ( e . toString ( ) )
} else {
console . error ( e )
setError ( 'Error selling' )
}
setIsSubmitting ( false )
} )
2022-06-15 03:00:36 +00:00
track ( 'sell shares' , {
outcomeType : contract.outcomeType ,
slug : contract.slug ,
contractId : contract.id ,
2022-07-10 18:05:44 +00:00
shares : sellQuantity ,
2022-06-15 03:00:36 +00:00
outcome : sharesOutcome ,
} )
2022-03-29 19:56:56 +00:00
}
const initialProb = getProbability ( contract )
2022-07-10 18:05:44 +00:00
const { cpmmState , saleValue } = calculateCpmmSale (
2022-03-30 02:30:04 +00:00
contract ,
2022-07-10 18:05:44 +00:00
sellQuantity ? ? 0 ,
sharesOutcome ,
unfilledBets
2022-03-30 02:30:04 +00:00
)
2022-07-10 18:05:44 +00:00
const resultProb = getCpmmProbability ( cpmmState . pool , cpmmState . p )
2022-03-29 19:56:56 +00:00
2022-05-17 03:27:37 +00:00
const openUserBets = userBets . filter ( ( bet ) = > ! bet . isSold && ! bet . sale )
2022-05-22 08:36:05 +00:00
const [ yesBets , noBets ] = partition (
2022-05-17 03:27:37 +00:00
openUserBets ,
( bet ) = > bet . outcome === 'YES'
)
const [ yesShares , noShares ] = [
2022-05-22 08:36:05 +00:00
sumBy ( yesBets , ( bet ) = > bet . shares ) ,
sumBy ( noBets , ( bet ) = > bet . shares ) ,
2022-05-17 03:27:37 +00:00
]
const ownedShares = Math . round ( yesShares ) || Math . round ( noShares )
const onAmountChange = ( amount : number | undefined ) = > {
setAmount ( amount )
// Check for errors.
if ( amount !== undefined ) {
if ( amount > ownedShares ) {
setError ( ` Maximum ${ formatWithCommas ( Math . floor ( ownedShares ) ) } shares ` )
} else {
setError ( undefined )
}
}
}
2022-07-02 19:37:59 +00:00
const { outcomeType } = contract
const isPseudoNumeric = outcomeType === 'PSEUDO_NUMERIC'
const format = getFormattedMappedValue ( contract )
2022-03-29 19:56:56 +00:00
return (
< >
2022-05-17 03:27:37 +00:00
< AmountInput
2022-05-04 15:47:45 +00:00
amount = {
amount
? Math . round ( amount ) === 0
? 0
: Math . floor ( amount )
: undefined
}
2022-05-17 03:27:37 +00:00
onChange = { onAmountChange }
label = "Qty"
2022-03-29 19:56:56 +00:00
error = { error }
disabled = { isSubmitting }
2022-05-17 03:27:37 +00:00
inputClassName = "w-full"
2022-03-29 19:56:56 +00:00
/ >
2022-05-17 03:27:37 +00:00
< Col className = "mt-3 w-full gap-3 text-sm" >
< Row className = "items-center justify-between gap-2 text-gray-500" >
Sale proceeds
< span className = "text-neutral" > { formatMoney ( saleValue ) } < / span >
< / Row >
< Row className = "items-center justify-between" >
2022-07-02 19:37:59 +00:00
< div className = "text-gray-500" >
{ isPseudoNumeric ? 'Estimated value' : 'Probability' }
< / div >
2022-05-15 21:10:26 +00:00
< div >
2022-07-02 19:37:59 +00:00
{ format ( initialProb ) }
2022-05-15 21:10:26 +00:00
< span className = "mx-2" > → < / span >
2022-07-02 19:37:59 +00:00
{ format ( resultProb ) }
2022-05-15 21:10:26 +00:00
< / div >
2022-03-29 19:56:56 +00:00
< / Row >
< / Col >
< Spacer h = { 8 } / >
< button
className = { clsx (
'btn flex-1' ,
betDisabled
? 'btn-disabled'
: sharesOutcome === 'YES'
? 'btn-primary'
: 'border-none bg-red-400 hover:bg-red-500' ,
isSubmitting ? 'loading' : ''
) }
onClick = { betDisabled ? undefined : submitSell }
>
{ isSubmitting ? 'Submitting...' : 'Submit sell' }
< / button >
{ wasSubmitted && < div className = "mt-4" > Sell submitted ! < / div > }
< / >
)
}