Compare commits

...

2 Commits

Author SHA1 Message Date
James Grugett
4a53000047 Prototyping the one click bet widget (incomplete) 2022-04-07 01:19:29 -05:00
James Grugett
96d7f27819 No bet panel! 2022-04-06 22:34:05 -05:00
8 changed files with 144 additions and 54 deletions

View File

@ -7,12 +7,27 @@ const formatter = new Intl.NumberFormat('en-US', {
minimumFractionDigits: 0, minimumFractionDigits: 0,
}) })
const formatterCents = new Intl.NumberFormat('en-US', {
style: 'currency',
currency: 'USD',
maximumFractionDigits: 2,
minimumFractionDigits: 2,
})
export function formatMoney(amount: number) { export function formatMoney(amount: number) {
const newAmount = Math.round(amount) === 0 ? 0 : amount // handle -0 case const newAmount = Math.round(amount) === 0 ? 0 : amount // handle -0 case
return ( return (
ENV_CONFIG.moneyMoniker + ' ' + formatter.format(newAmount).replace('$', '') ENV_CONFIG.moneyMoniker + ' ' + formatter.format(newAmount).replace('$', '')
) )
} }
export function formatCents(probability: number) {
const newAmount = Math.round(probability * 100) === 0 ? 0 : probability // handle -0 case
return (
ENV_CONFIG.moneyMoniker +
' ' +
formatterCents.format(newAmount).replace('$', '')
)
}
export function formatWithCommas(amount: number) { export function formatWithCommas(amount: number) {
return formatter.format(amount).replace('$', '') return formatter.format(amount).replace('$', '')

View File

@ -9,6 +9,7 @@ import { InfoTooltip } from './info-tooltip'
import { Spacer } from './layout/spacer' import { Spacer } from './layout/spacer'
import { calculateCpmmSale } from '../../common/calculate-cpmm' import { calculateCpmmSale } from '../../common/calculate-cpmm'
import { Binary, CPMM, FullContract } from '../../common/contract' import { Binary, CPMM, FullContract } from '../../common/contract'
import { ENV_CONFIG } from '../../common/envs/constants'
export function AmountInput(props: { export function AmountInput(props: {
amount: number | undefined amount: number | undefined
@ -50,10 +51,10 @@ export function AmountInput(props: {
return ( return (
<Col className={className}> <Col className={className}>
<label className="input-group"> <label className="input-group">
<span className="bg-gray-200 text-sm">{label}</span> <span className="bg-gray-200 text-lg">{label}</span>
<input <input
className={clsx( className={clsx(
'input input-bordered', 'input input-bordered text-lg',
error && 'input-error', error && 'input-error',
inputClassName inputClassName
)} )}
@ -119,6 +120,10 @@ export function BuyAmountInput(props: {
? Math.min(amount ?? 0, MAX_LOAN_PER_CONTRACT - prevLoanAmount) ? Math.min(amount ?? 0, MAX_LOAN_PER_CONTRACT - prevLoanAmount)
: 0 : 0
const remainingBalance = Math.floor(
(user?.balance ?? 0) - (amount ?? 0) + loanAmount
)
const onAmountChange = (amount: number | undefined) => { const onAmountChange = (amount: number | undefined) => {
onChange(amount) onChange(amount)
@ -140,7 +145,7 @@ export function BuyAmountInput(props: {
<AmountInput <AmountInput
amount={amount} amount={amount}
onChange={onAmountChange} onChange={onAmountChange}
label="M$" label={ENV_CONFIG.moneyMoniker}
error={error} error={error}
disabled={disabled} disabled={disabled}
className={className} className={className}
@ -148,10 +153,10 @@ export function BuyAmountInput(props: {
inputRef={inputRef} inputRef={inputRef}
> >
{user && ( {user && (
<Col className="gap-3 text-sm"> <Col className="gap-3">
{contractIdForLoan && ( {contractIdForLoan && (
<Row className="items-center justify-between gap-2 text-gray-500"> <Row className="items-center justify-between gap-2 text-gray-500">
<Row className="items-center gap-2"> <Row className="items-center gap-2 text-sm">
Amount loaned{' '} Amount loaned{' '}
<InfoTooltip <InfoTooltip
text={`In every market, you get an interest-free loan on the first ${formatMoney( text={`In every market, you get an interest-free loan on the first ${formatMoney(

View File

@ -103,7 +103,7 @@ export function AnswerBetPanel(props: {
<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">
<div className="text-xl"> <div className="text-xl">
Buy {isModal ? `"${answer.text}"` : 'this answer'} Bet on {isModal ? `"${answer.text}"` : 'this answer'}
</div> </div>
{!isModal && ( {!isModal && (

View File

@ -94,7 +94,7 @@ export function BetPanel(props: {
className className
)} )}
> >
<Title className={clsx('!mt-0')} text="Place a trade" /> <div className={clsx('mb-6 text-2xl')}> Place a trade</div>
<BuyPanel contract={contract} user={user} userBets={userBets ?? []} /> <BuyPanel contract={contract} user={user} userBets={userBets ?? []} />
@ -304,6 +304,12 @@ function BuyPanel(props: {
const currentReturn = betAmount ? (currentPayout - betAmount) / betAmount : 0 const currentReturn = betAmount ? (currentPayout - betAmount) / betAmount : 0
const currentReturnPercent = formatPercent(currentReturn) const currentReturnPercent = formatPercent(currentReturn)
const averagePrice = betAmount
? betAmount / currentPayout
: betChoice === 'NO'
? 1 - initialProb
: initialProb
const dpmTooltip = const dpmTooltip =
contract.mechanism === 'dpm-2' contract.mechanism === 'dpm-2'
? `Current payout for ${formatWithCommas(shares)} / ${formatWithCommas( ? `Current payout for ${formatWithCommas(shares)} / ${formatWithCommas(
@ -335,8 +341,15 @@ function BuyPanel(props: {
/> />
<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">
<div className="text-gray-500">Probability</div> <div className="text-sm text-gray-500">Average price</div>
<Row>
<div>{formatCents(averagePrice)}</div>
</Row>
</Row> */}
<Row className="items-center justify-between">
<div className="text-sm text-gray-500">Probability change</div>
<Row> <Row>
<div>{formatPercent(initialProb)}</div> <div>{formatPercent(initialProb)}</div>
<div className="mx-2"></div> <div className="mx-2"></div>
@ -344,9 +357,9 @@ function BuyPanel(props: {
</Row> </Row>
</Row> </Row>
<Row className="items-center justify-between gap-2 text-sm"> <Row className="items-center justify-between gap-2">
<Row className="flex-nowrap items-center gap-2 whitespace-nowrap text-gray-500"> <Row className="flex-nowrap items-center gap-2 whitespace-nowrap text-gray-500">
<div> <div className="text-sm">
{contract.mechanism === 'dpm-2' ? ( {contract.mechanism === 'dpm-2' ? (
<> <>
Estimated Estimated

View File

@ -10,10 +10,11 @@ import { Modal } from './layout/modal'
// Inline version of a bet panel. Opens BetPanel in a new modal. // Inline version of a bet panel. Opens BetPanel in a new modal.
export default function BetRow(props: { export default function BetRow(props: {
contract: FullContract<DPM | CPMM, Binary> contract: FullContract<DPM | CPMM, Binary>
large?: boolean
className?: string className?: string
labelClassName?: string labelClassName?: string
}) { }) {
const { className, labelClassName } = props const { large, className, labelClassName } = props
const [open, setOpen] = useState(false) const [open, setOpen] = useState(false)
const [betChoice, setBetChoice] = useState<'YES' | 'NO' | undefined>( const [betChoice, setBetChoice] = useState<'YES' | 'NO' | undefined>(
undefined undefined
@ -27,7 +28,8 @@ export default function BetRow(props: {
Place a trade Place a trade
</div> </div>
<YesNoSelector <YesNoSelector
btnClassName="btn-sm w-24" btnClassName={clsx('btn-sm w-20', large && 'w-32 h-10')}
large={large}
onSelect={(choice) => { onSelect={(choice) => {
setOpen(true) setOpen(true)
setBetChoice(choice) setBetChoice(choice)

View File

@ -1,4 +1,8 @@
import { Contract, tradingAllowed } from '../lib/firebase/contracts' import {
Contract,
getBinaryProbPercent,
tradingAllowed,
} from '../lib/firebase/contracts'
import { Col } from './layout/col' import { Col } from './layout/col'
import { Spacer } from './layout/spacer' import { Spacer } from './layout/spacer'
import { ContractProbGraph } from './contract-prob-graph' import { ContractProbGraph } from './contract-prob-graph'
@ -31,39 +35,44 @@ export const ContractOverview = (props: {
const user = useUser() const user = useUser()
const isCreator = user?.id === creatorId const isCreator = user?.id === creatorId
const isBinary = outcomeType === 'BINARY' const isBinary = outcomeType === 'BINARY'
const allowTrade = tradingAllowed(contract)
return ( return (
<Col className={clsx('mb-6', className)}> <Col className={clsx('mb-6', className)}>
<Col className="gap-4 px-2"> <Row>
<Row className="justify-between gap-4"> <Col className="gap-4 px-2">
<div className="text-2xl text-indigo-700 md:text-3xl"> <Row className="justify-between gap-4">
<Linkify text={question} /> <div className="text-2xl text-indigo-700 md:text-3xl">
</div> <Linkify text={question} />
</div>
{(isBinary || resolution) && ( {/* {(isBinary || resolution) && (
<ResolutionOrChance <ResolutionOrChance
className="hidden items-end xl:flex" className="items-end xl:flex"
contract={contract} contract={contract}
large large
/> />
)} )} */}
</Row> </Row>
<Row className="items-center justify-between gap-4 xl:hidden"> <ContractDetails contract={contract} isCreator={isCreator} />
{(isBinary || resolution) && ( </Col>
<ResolutionOrChance contract={contract} /> <QuickBetWidget contract={contract} />
)} </Row>
{isBinary && tradingAllowed(contract) && (
<BetRow contract={contract} labelClassName="hidden" />
)}
</Row>
<ContractDetails contract={contract} isCreator={isCreator} />
</Col>
<Spacer h={4} /> <Spacer h={4} />
{/* {isBinary && allowTrade && (
<Row className="my-6 items-center justify-between gap-4 lg:justify-center">
{(isBinary || resolution) && (
<ResolutionOrChance contract={contract} className="lg:hidden" />
)}
{isBinary && tradingAllowed(contract) && (
<BetRow large contract={contract} labelClassName="hidden" />
)}
</Row>
)} */}
{isBinary ? ( {isBinary ? (
<ContractProbGraph contract={contract} bets={bets} /> <ContractProbGraph contract={contract} bets={bets} />
) : ( ) : (
@ -108,3 +117,54 @@ export const ContractOverview = (props: {
</Col> </Col>
) )
} }
function Triangle(props: {
direction: 'up' | 'down'
width: number
color: string
className?: string
}) {
const { direction, width, color, className } = props
return (
<div
className={clsx(
'border-x-solid group h-0 w-0 cursor-pointer border-x-[30px] border-x-transparent transition-colors',
'relative',
className,
direction === 'up' &&
'border-b-solid border-b-[30px] border-b-gray-200 hover:border-b-green-300 focus:ring-green-500',
direction === 'down' &&
'border-t-solid border-t-[30px] border-t-gray-200 hover:border-t-red-300'
)}
tabIndex={0}
>
{/* {direction === 'up' && (
<div className="absolute top-4 -left-2 select-none text-xs text-gray-500 opacity-0 transition-opacity group-hover:opacity-100">
M$
</div>
)}
{direction === 'down' && (
<div className="absolute -top-9 -left-2 hidden select-none text-xs text-gray-500 group-hover:block">
M$
</div>
)} */}
</div>
)
}
function QuickBetWidget(props: { contract: Contract }) {
const { contract } = props
return (
<Col className="items-center gap-2">
<div className="whitespace-nowrap text-sm text-gray-500">
Click to trade
</div>
<Triangle direction="up" width={50} color="#e3e3e3" />
<div className="text-primary text-3xl">
{getBinaryProbPercent(contract)}
</div>
<Triangle direction="down" width={50} color="#e3e3e3" />
</Col>
)
}

View File

@ -7,41 +7,40 @@ import { Row } from './layout/row'
export function YesNoSelector(props: { export function YesNoSelector(props: {
selected?: 'YES' | 'NO' selected?: 'YES' | 'NO'
onSelect: (selected: 'YES' | 'NO') => void onSelect: (selected: 'YES' | 'NO') => void
large?: boolean
className?: string className?: string
btnClassName?: string btnClassName?: string
}) { }) {
const { selected, onSelect, className, btnClassName } = props const { selected, onSelect, large, className, btnClassName } = props
const commonClassNames = const commonClassNames = clsx(
'inline-flex flex-1 items-center justify-center rounded-3xl border-2 p-2' 'inline-flex flex-1 items-center justify-center rounded-3xl p-2 border-2 border-gray-300 shadow',
large && 'text-lg w-32'
)
return ( return (
<Row className={clsx('space-x-3', className)}> <Row className={clsx('space-x-3', className)}>
<button <button
className={clsx( className={clsx(
commonClassNames, commonClassNames,
'hover:bg-primary-focus border-primary hover:border-primary-focus hover:text-white', 'border-primary hover:bg-primary-focus hover:border-primary-focus hover:text-white',
selected == 'YES' selected == 'YES' ? 'bg-primary text-white' : 'text-primary bg-white',
? 'bg-primary text-white'
: 'text-primary bg-transparent',
btnClassName btnClassName
)} )}
onClick={() => onSelect('YES')} onClick={() => onSelect('YES')}
> >
Buy YES Bet YES
</button> </button>
<button <button
className={clsx( className={clsx(
commonClassNames, commonClassNames,
'border-red-400 hover:border-red-500 hover:bg-red-500 hover:text-white', 'border-red-400 hover:border-red-500 hover:bg-red-500 hover:text-white',
selected == 'NO' selected == 'NO' ? 'bg-red-400 text-white' : 'bg-white text-red-400',
? 'bg-red-400 text-white'
: 'bg-transparent text-red-400',
btnClassName btnClassName
)} )}
onClick={() => onSelect('NO')} onClick={() => onSelect('NO')}
> >
Buy NO Bet NO
</button> </button>
</Row> </Row>
) )
@ -173,7 +172,7 @@ export function BuyButton(props: { className?: string; onClick?: () => void }) {
)} )}
onClick={onClick} onClick={onClick}
> >
Buy Bet
</button> </button>
) )
} }

View File

@ -115,18 +115,14 @@ export default function ContractPage(props: {
const isCreator = user?.id === creatorId const isCreator = user?.id === creatorId
const isBinary = outcomeType === 'BINARY' const isBinary = outcomeType === 'BINARY'
const allowTrade = tradingAllowed(contract)
const allowResolve = !isResolved && isCreator && !!user const allowResolve = !isResolved && isCreator && !!user
const hasSidePanel = isBinary && (allowTrade || allowResolve) const hasSidePanel = isBinary && allowResolve
const ogCardProps = getOpenGraphProps(contract) const ogCardProps = getOpenGraphProps(contract)
const rightSidebar = hasSidePanel ? ( const rightSidebar = hasSidePanel ? (
<Col className="gap-4"> <Col className="gap-4">
{allowTrade && ( <ResolutionPanel creator={user} contract={contract} />
<BetPanel className="hidden xl:flex" contract={contract} />
)}
{allowResolve && <ResolutionPanel creator={user} contract={contract} />}
</Col> </Col>
) : null ) : null