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,
})
const formatterCents = new Intl.NumberFormat('en-US', {
style: 'currency',
currency: 'USD',
maximumFractionDigits: 2,
minimumFractionDigits: 2,
})
export function formatMoney(amount: number) {
const newAmount = Math.round(amount) === 0 ? 0 : amount // handle -0 case
return (
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) {
return formatter.format(amount).replace('$', '')

View File

@ -9,6 +9,7 @@ import { InfoTooltip } from './info-tooltip'
import { Spacer } from './layout/spacer'
import { calculateCpmmSale } from '../../common/calculate-cpmm'
import { Binary, CPMM, FullContract } from '../../common/contract'
import { ENV_CONFIG } from '../../common/envs/constants'
export function AmountInput(props: {
amount: number | undefined
@ -50,10 +51,10 @@ export function AmountInput(props: {
return (
<Col className={className}>
<label className="input-group">
<span className="bg-gray-200 text-sm">{label}</span>
<span className="bg-gray-200 text-lg">{label}</span>
<input
className={clsx(
'input input-bordered',
'input input-bordered text-lg',
error && 'input-error',
inputClassName
)}
@ -119,6 +120,10 @@ export function BuyAmountInput(props: {
? Math.min(amount ?? 0, MAX_LOAN_PER_CONTRACT - prevLoanAmount)
: 0
const remainingBalance = Math.floor(
(user?.balance ?? 0) - (amount ?? 0) + loanAmount
)
const onAmountChange = (amount: number | undefined) => {
onChange(amount)
@ -140,7 +145,7 @@ export function BuyAmountInput(props: {
<AmountInput
amount={amount}
onChange={onAmountChange}
label="M$"
label={ENV_CONFIG.moneyMoniker}
error={error}
disabled={disabled}
className={className}
@ -148,10 +153,10 @@ export function BuyAmountInput(props: {
inputRef={inputRef}
>
{user && (
<Col className="gap-3 text-sm">
<Col className="gap-3">
{contractIdForLoan && (
<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{' '}
<InfoTooltip
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)}>
<Row className="items-center justify-between self-stretch">
<div className="text-xl">
Buy {isModal ? `"${answer.text}"` : 'this answer'}
Bet on {isModal ? `"${answer.text}"` : 'this answer'}
</div>
{!isModal && (

View File

@ -94,7 +94,7 @@ export function BetPanel(props: {
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 ?? []} />
@ -304,6 +304,12 @@ function BuyPanel(props: {
const currentReturn = betAmount ? (currentPayout - betAmount) / betAmount : 0
const currentReturnPercent = formatPercent(currentReturn)
const averagePrice = betAmount
? betAmount / currentPayout
: betChoice === 'NO'
? 1 - initialProb
: initialProb
const dpmTooltip =
contract.mechanism === 'dpm-2'
? `Current payout for ${formatWithCommas(shares)} / ${formatWithCommas(
@ -335,8 +341,15 @@ function BuyPanel(props: {
/>
<Col className="mt-3 w-full gap-3">
<Row className="items-center justify-between text-sm">
<div className="text-gray-500">Probability</div>
{/* <Row className="items-center justify-between">
<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>
<div>{formatPercent(initialProb)}</div>
<div className="mx-2"></div>
@ -344,9 +357,9 @@ function BuyPanel(props: {
</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">
<div>
<div className="text-sm">
{contract.mechanism === 'dpm-2' ? (
<>
Estimated

View File

@ -10,10 +10,11 @@ import { Modal } from './layout/modal'
// Inline version of a bet panel. Opens BetPanel in a new modal.
export default function BetRow(props: {
contract: FullContract<DPM | CPMM, Binary>
large?: boolean
className?: string
labelClassName?: string
}) {
const { className, labelClassName } = props
const { large, className, labelClassName } = props
const [open, setOpen] = useState(false)
const [betChoice, setBetChoice] = useState<'YES' | 'NO' | undefined>(
undefined
@ -27,7 +28,8 @@ export default function BetRow(props: {
Place a trade
</div>
<YesNoSelector
btnClassName="btn-sm w-24"
btnClassName={clsx('btn-sm w-20', large && 'w-32 h-10')}
large={large}
onSelect={(choice) => {
setOpen(true)
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 { Spacer } from './layout/spacer'
import { ContractProbGraph } from './contract-prob-graph'
@ -31,39 +35,44 @@ export const ContractOverview = (props: {
const user = useUser()
const isCreator = user?.id === creatorId
const isBinary = outcomeType === 'BINARY'
const allowTrade = tradingAllowed(contract)
return (
<Col className={clsx('mb-6', className)}>
<Col className="gap-4 px-2">
<Row className="justify-between gap-4">
<div className="text-2xl text-indigo-700 md:text-3xl">
<Linkify text={question} />
</div>
<Row>
<Col className="gap-4 px-2">
<Row className="justify-between gap-4">
<div className="text-2xl text-indigo-700 md:text-3xl">
<Linkify text={question} />
</div>
{(isBinary || resolution) && (
{/* {(isBinary || resolution) && (
<ResolutionOrChance
className="hidden items-end xl:flex"
className="items-end xl:flex"
contract={contract}
large
/>
)}
</Row>
)} */}
</Row>
<Row className="items-center justify-between gap-4 xl:hidden">
{(isBinary || resolution) && (
<ResolutionOrChance contract={contract} />
)}
{isBinary && tradingAllowed(contract) && (
<BetRow contract={contract} labelClassName="hidden" />
)}
</Row>
<ContractDetails contract={contract} isCreator={isCreator} />
</Col>
<ContractDetails contract={contract} isCreator={isCreator} />
</Col>
<QuickBetWidget contract={contract} />
</Row>
<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 ? (
<ContractProbGraph contract={contract} bets={bets} />
) : (
@ -108,3 +117,54 @@ export const ContractOverview = (props: {
</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: {
selected?: 'YES' | 'NO'
onSelect: (selected: 'YES' | 'NO') => void
large?: boolean
className?: string
btnClassName?: string
}) {
const { selected, onSelect, className, btnClassName } = props
const { selected, onSelect, large, className, btnClassName } = props
const commonClassNames =
'inline-flex flex-1 items-center justify-center rounded-3xl border-2 p-2'
const commonClassNames = clsx(
'inline-flex flex-1 items-center justify-center rounded-3xl p-2 border-2 border-gray-300 shadow',
large && 'text-lg w-32'
)
return (
<Row className={clsx('space-x-3', className)}>
<button
className={clsx(
commonClassNames,
'hover:bg-primary-focus border-primary hover:border-primary-focus hover:text-white',
selected == 'YES'
? 'bg-primary text-white'
: 'text-primary bg-transparent',
'border-primary hover:bg-primary-focus hover:border-primary-focus hover:text-white',
selected == 'YES' ? 'bg-primary text-white' : 'text-primary bg-white',
btnClassName
)}
onClick={() => onSelect('YES')}
>
Buy YES
Bet YES
</button>
<button
className={clsx(
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',
selected == 'NO' ? 'bg-red-400 text-white' : 'bg-white text-red-400',
btnClassName
)}
onClick={() => onSelect('NO')}
>
Buy NO
Bet NO
</button>
</Row>
)
@ -173,7 +172,7 @@ export function BuyButton(props: { className?: string; onClick?: () => void }) {
)}
onClick={onClick}
>
Buy
Bet
</button>
)
}

View File

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