Buy and sell tabs. Compute some sell info

This commit is contained in:
James Grugett 2022-03-20 12:27:11 -05:00
parent 49bb74cc0d
commit ce7e8900a1
3 changed files with 210 additions and 45 deletions

View File

@ -4,15 +4,17 @@ import { useUser } from '../hooks/use-user'
import { formatMoney } from '../../common/util/format'
import { Col } from './layout/col'
import { Row } from './layout/row'
import { useUserContractBets } from '../hooks/use-user-bets'
import { MAX_LOAN_PER_CONTRACT } from '../../common/bet'
import { Bet, MAX_LOAN_PER_CONTRACT } from '../../common/bet'
import { InfoTooltip } from './info-tooltip'
import { Spacer } from './layout/spacer'
import { calculateCpmmSale } from '../../common/calculate-cpmm'
import { Binary, CPMM, FullContract } from '../../common/contract'
export function AmountInput(props: {
amount: number | undefined
onChange: (newAmount: number | undefined) => void
error: string | undefined
label: string
disabled?: boolean
className?: string
inputClassName?: string
@ -24,6 +26,7 @@ export function AmountInput(props: {
amount,
onChange,
error,
label,
disabled,
className,
inputClassName,
@ -47,7 +50,7 @@ export function AmountInput(props: {
return (
<Col className={className}>
<label className="input-group">
<span className="bg-gray-200 text-sm">M$</span>
<span className="bg-gray-200 text-sm">{label}</span>
<input
className={clsx(
'input input-bordered',
@ -83,6 +86,7 @@ export function BuyAmountInput(props: {
error: string | undefined
setError: (error: string | undefined) => void
contractIdForLoan: string | undefined
userBets: Bet[]
minimumAmount?: number
disabled?: boolean
className?: string
@ -93,6 +97,7 @@ export function BuyAmountInput(props: {
const {
amount,
onChange,
userBets,
error,
setError,
contractIdForLoan,
@ -105,7 +110,6 @@ export function BuyAmountInput(props: {
const user = useUser()
const userBets = useUserContractBets(user?.id, contractIdForLoan) ?? []
const openUserBets = userBets.filter((bet) => !bet.isSold && !bet.sale)
const prevLoanAmount = _.sumBy(openUserBets, (bet) => bet.loanAmount ?? 0)
@ -137,6 +141,7 @@ export function BuyAmountInput(props: {
<AmountInput
amount={amount}
onChange={onAmountChange}
label="M$"
error={error}
disabled={disabled}
className={className}
@ -145,6 +150,12 @@ export function BuyAmountInput(props: {
>
{user && (
<Col className="gap-3 text-sm">
<Row className="items-center justify-between gap-2 text-gray-500">
Remaining balance{' '}
<span className="text-neutral">
{formatMoney(Math.floor(remainingBalance))}
</span>
</Row>
{contractIdForLoan && (
<Row className="items-center justify-between gap-2 text-gray-500">
<Row className="items-center gap-2">
@ -158,12 +169,92 @@ export function BuyAmountInput(props: {
<span className="text-neutral">{formatMoney(loanAmount)}</span>{' '}
</Row>
)}
<Row className="items-center justify-between gap-2 text-gray-500">
Remaining balance{' '}
<span className="text-neutral">
{formatMoney(Math.floor(remainingBalance))}
</span>
</Row>
</Col>
)}
</AmountInput>
)
}
export function SellAmountInput(props: {
contract: FullContract<CPMM, Binary>
amount: number | undefined
onChange: (newAmount: number | undefined) => void
userBets: Bet[]
error: string | undefined
setError: (error: string | undefined) => void
minimumAmount?: number
disabled?: boolean
className?: string
inputClassName?: string
// Needed to focus the amount input
inputRef?: React.MutableRefObject<any>
}) {
const {
contract,
amount,
onChange,
userBets,
error,
setError,
disabled,
className,
inputClassName,
minimumAmount,
inputRef,
} = props
const user = useUser()
const openUserBets = userBets.filter((bet) => !bet.isSold && !bet.sale)
const [yesBets, noBets] = _.partition(
openUserBets,
(bet) => bet.outcome === 'YES'
)
const [yesShares, noShares] = [
_.sumBy(yesBets, (bet) => bet.shares),
_.sumBy(noBets, (bet) => bet.shares),
]
const sellOutcome = yesShares ? 'YES' : noShares ? 'NO' : undefined
const prevLoanAmount = _.sumBy(openUserBets, (bet) => bet.loanAmount ?? 0)
const sharesSold = Math.min(amount ?? 0, yesShares || noShares)
const sellAmount = calculateCpmmSale(contract, {
shares: sharesSold,
outcome: sellOutcome,
} as Bet).saleValue
const loanRepaid = Math.min(prevLoanAmount, sellAmount)
return (
<AmountInput
amount={amount}
onChange={onChange}
label="Shares"
error={error}
disabled={disabled}
className={className}
inputClassName={inputClassName}
inputRef={inputRef}
>
{user && (
<Col className="gap-3 text-sm">
<Row className="items-center justify-between gap-2 text-gray-500">
Sale amount{' '}
<span className="text-neutral">{formatMoney(sellAmount)}</span>
</Row>
{prevLoanAmount && (
<Row className="items-center justify-between gap-2 text-gray-500">
<Row className="items-center gap-2">
Loan repaid{' '}
<InfoTooltip
text={`Sold shares go toward paying off loans for this market first.`}
/>
</Row>
<span className="text-neutral">{formatMoney(loanRepaid)}</span>{' '}
</Row>
)}
</Col>
)}
</AmountInput>

View File

@ -1,5 +1,6 @@
import clsx from 'clsx'
import React, { useEffect, useState } from 'react'
import _ from 'lodash'
import { useUser } from '../hooks/use-user'
import { Binary, CPMM, DPM, FullContract } from '../../common/contract'
@ -16,7 +17,7 @@ import { Title } from './title'
import { firebaseLogin } from '../lib/firebase/users'
import { Bet } from '../../common/bet'
import { placeBet } from '../lib/firebase/api-call'
import { BuyAmountInput } from './amount-input'
import { BuyAmountInput, SellAmountInput } from './amount-input'
import { InfoTooltip } from './info-tooltip'
import { OutcomeLabel } from './outcome-label'
import {
@ -26,6 +27,7 @@ import {
getOutcomeProbabilityAfterBet,
} from '../../common/calculate'
import { useFocus } from '../hooks/use-focus'
import { useUserContractBets } from '../hooks/use-user-bets'
export function BetPanel(props: {
contract: FullContract<DPM | CPMM, Binary>
@ -42,7 +44,19 @@ export function BetPanel(props: {
const { contract, className, title, selected, onBetSuccess } = props
const user = useUser()
const userBets = useUserContractBets(user?.id, contract.id) ?? []
const [yesBets, noBets] = _.partition(
userBets,
(bet) => bet.outcome === 'YES'
)
const [yesShares, noShares] = [
_.sumBy(yesBets, (bet) => bet.shares),
_.sumBy(noBets, (bet) => bet.shares),
]
const sellOutcome = yesShares ? 'YES' : noShares ? 'NO' : undefined
const [tradeType, setTradeType] = useState<'BUY' | 'SELL'>('BUY')
const [betChoice, setBetChoice] = useState<'YES' | 'NO' | undefined>(selected)
const [betAmount, setBetAmount] = useState<number | undefined>(undefined)
const [inputRef, focusAmountInput] = useFocus()
@ -137,23 +151,76 @@ export function BetPanel(props: {
text={panelTitle}
/>
<YesNoSelector
className="mb-4"
selected={betChoice}
onSelect={(choice) => onBetChoice(choice)}
/>
{contract.mechanism === 'cpmm-1' && (
<Row className="gap-2 w-full tabs mb-6">
<div
className={clsx(
'tab gap-2 tab-bordered flex-1',
tradeType === 'BUY' && 'tab-active'
)}
onClick={() => setTradeType('BUY')}
>
BUY
</div>
<div
className={clsx(
'tab gap-2 tab-bordered flex-1',
tradeType === 'SELL' && 'tab-active'
)}
onClick={() => setTradeType('SELL')}
>
SELL
</div>
</Row>
)}
<div className="my-3 text-left text-sm text-gray-500">Amount </div>
<BuyAmountInput
inputClassName="w-full"
amount={betAmount}
onChange={onBetChange}
error={error}
setError={setError}
disabled={isSubmitting}
inputRef={inputRef}
contractIdForLoan={contract.id}
/>
{tradeType === 'BUY' ? (
<>
<YesNoSelector
className="mb-4"
selected={betChoice}
onSelect={(choice) => onBetChoice(choice)}
/>
<div className="my-3 text-left text-sm text-gray-500">Buy amount</div>
<BuyAmountInput
inputClassName="w-full"
amount={betAmount}
onChange={onBetChange}
userBets={userBets}
error={error}
setError={setError}
disabled={isSubmitting}
inputRef={inputRef}
contractIdForLoan={contract.id}
/>
</>
) : sellOutcome ? (
<>
<div className="mb-3 text-left ">
You have {Math.round(yesShares || noShares)}{' '}
<OutcomeLabel outcome={sellOutcome} /> shares
</div>
<div className="my-3 text-left text-sm text-gray-500">
Sell quantity
</div>
<SellAmountInput
inputClassName="w-full"
contract={contract as FullContract<CPMM, Binary>}
amount={betAmount}
onChange={onBetChange}
userBets={userBets}
error={error}
setError={setError}
disabled={isSubmitting}
inputRef={inputRef}
/>
</>
) : (
<div className="mb-3 text-left text-gray-500">
You have don't have any shares to sell.
</div>
)}
<Col className="mt-3 w-full gap-3">
<Row className="items-center justify-between text-sm">
@ -165,21 +232,23 @@ export function BetPanel(props: {
</Row>
</Row>
<Row className="items-start justify-between gap-2 text-sm">
<Row className="flex-nowrap items-center gap-2 whitespace-nowrap text-gray-500">
<div>
Payout if <OutcomeLabel outcome={betChoice ?? 'YES'} />
</div>
{tradeType === 'BUY' && (
<Row className="items-start justify-between gap-2 text-sm">
<Row className="flex-nowrap items-center gap-2 whitespace-nowrap text-gray-500">
<div>
Payout if <OutcomeLabel outcome={betChoice ?? 'YES'} />
</div>
{tooltip && <InfoTooltip text={tooltip} />}
{tooltip && <InfoTooltip text={tooltip} />}
</Row>
<Row className="flex-wrap items-end justify-end gap-2">
<span className="whitespace-nowrap">
{formatMoney(currentPayout)}
</span>
<span>(+{currentReturnPercent})</span>
</Row>
</Row>
<Row className="flex-wrap items-end justify-end gap-2">
<span className="whitespace-nowrap">
{formatMoney(currentPayout)}
</span>
<span>(+{currentReturnPercent})</span>
</Row>
</Row>
)}
</Col>
<Spacer h={8} />

View File

@ -12,11 +12,15 @@ export function YesNoSelector(props: {
}) {
const { selected, onSelect, className, btnClassName } = props
const commonClassNames =
'inline-flex flex-1 items-center justify-center rounded-3xl border-2 p-2'
return (
<Row className={clsx('space-x-3', className)}>
<button
className={clsx(
'hover:bg-primary-focus border-primary hover:border-primary-focus inline-flex flex-1 items-center justify-center rounded-lg border-2 p-2 hover:text-white',
commonClassNames,
'hover:bg-primary-focus border-primary hover:border-primary-focus hover:text-white',
selected == 'YES'
? 'bg-primary text-white'
: 'text-primary bg-transparent',
@ -24,11 +28,12 @@ export function YesNoSelector(props: {
)}
onClick={() => onSelect('YES')}
>
Buy YES
YES
</button>
<button
className={clsx(
'inline-flex flex-1 items-center justify-center rounded-lg border-2 border-red-400 p-2 hover:border-red-500 hover:bg-red-500 hover:text-white',
commonClassNames,
'border-red-400 hover:border-red-500 hover:bg-red-500 hover:text-white',
selected == 'NO'
? 'bg-red-400 text-white'
: 'bg-transparent text-red-400',
@ -36,7 +41,7 @@ export function YesNoSelector(props: {
)}
onClick={() => onSelect('NO')}
>
Buy NO
NO
</button>
</Row>
)
@ -50,7 +55,7 @@ export function YesNoCancelSelector(props: {
}) {
const { selected, onSelect } = props
const btnClassName = clsx('px-6 flex-1', props.btnClassName)
const btnClassName = clsx('px-6 flex-1 rounded-3xl', props.btnClassName)
return (
<Col className="gap-2">