manifold/web/components/limit-bets.tsx
2022-07-19 16:57:32 -05:00

207 lines
5.5 KiB
TypeScript

import { LimitBet } from 'common/bet'
import { CPMMBinaryContract, PseudoNumericContract } from 'common/contract'
import { getFormattedMappedValue } from 'common/pseudo-numeric'
import { formatMoney, formatPercent } from 'common/util/format'
import { sortBy } from 'lodash'
import { useState } from 'react'
import { useUser, useUserById } from 'web/hooks/use-user'
import { cancelBet } from 'web/lib/firebase/api'
import { Avatar } from './avatar'
import { Button } from './button'
import { Col } from './layout/col'
import { Modal } from './layout/modal'
import { Row } from './layout/row'
import { LoadingIndicator } from './loading-indicator'
import { BinaryOutcomeLabel, PseudoNumericOutcomeLabel } from './outcome-label'
import { Subtitle } from './subtitle'
import { Title } from './title'
export function LimitBets(props: {
contract: CPMMBinaryContract | PseudoNumericContract
bets: LimitBet[]
className?: string
}) {
const { contract, bets, className } = props
const sortedBets = sortBy(
bets,
(bet) => -1 * bet.limitProb,
(bet) => -1 * bet.createdTime
)
const user = useUser()
const yourBets = sortedBets.filter((bet) => bet.userId === user?.id)
return (
<Col className={className}>
{yourBets.length === 0 && (
<OrderBookButton
className="self-end"
limitBets={sortedBets}
contract={contract}
/>
)}
{yourBets.length > 0 && (
<Col
className={'mt-4 gap-2 overflow-hidden rounded bg-white px-4 py-3'}
>
<Row className="mt-2 mb-4 items-center justify-between">
<Subtitle className="!mt-0 !mb-0" text="Your orders" />
<OrderBookButton
className="self-end"
limitBets={sortedBets}
contract={contract}
/>
</Row>
<LimitOrderTable
limitBets={yourBets}
contract={contract}
isYou={true}
/>
</Col>
)}
</Col>
)
}
export function LimitOrderTable(props: {
limitBets: LimitBet[]
contract: CPMMBinaryContract | PseudoNumericContract
isYou: boolean
}) {
const { limitBets, contract, isYou } = props
const isPseudoNumeric = contract.outcomeType === 'PSEUDO_NUMERIC'
return (
<table className="table-compact table w-full rounded text-gray-500">
<thead>
<tr>
{!isYou && <th></th>}
<th>Outcome</th>
<th>{isPseudoNumeric ? 'Value' : 'Prob'}</th>
<th>Amount</th>
{isYou && <th></th>}
</tr>
</thead>
<tbody>
{limitBets.map((bet) => (
<LimitBet key={bet.id} bet={bet} contract={contract} isYou={isYou} />
))}
</tbody>
</table>
)
}
function LimitBet(props: {
contract: CPMMBinaryContract | PseudoNumericContract
bet: LimitBet
isYou: boolean
}) {
const { contract, bet, isYou } = props
const { orderAmount, amount, limitProb, outcome } = bet
const isPseudoNumeric = contract.outcomeType === 'PSEUDO_NUMERIC'
const [isCancelling, setIsCancelling] = useState(false)
const onCancel = () => {
cancelBet({ betId: bet.id })
setIsCancelling(true)
}
const user = useUserById(bet.userId)
return (
<tr>
{!isYou && (
<td>
<Avatar
size={'sm'}
avatarUrl={user?.avatarUrl}
username={user?.username}
/>
</td>
)}
<td>
<div className="pl-2">
{isPseudoNumeric ? (
<PseudoNumericOutcomeLabel outcome={outcome as 'YES' | 'NO'} />
) : (
<BinaryOutcomeLabel outcome={outcome as 'YES' | 'NO'} />
)}
</div>
</td>
<td>
{isPseudoNumeric
? getFormattedMappedValue(contract)(limitProb)
: formatPercent(limitProb)}
</td>
<td>{formatMoney(orderAmount - amount)}</td>
{isYou && (
<td>
{isCancelling ? (
<LoadingIndicator />
) : (
<button
className="btn btn-xs btn-outline my-auto normal-case"
onClick={onCancel}
>
Cancel
</button>
)}
</td>
)}
</tr>
)
}
export function OrderBookButton(props: {
limitBets: LimitBet[]
contract: CPMMBinaryContract | PseudoNumericContract
className?: string
}) {
const { limitBets, contract, className } = props
const [open, setOpen] = useState(false)
const yesBets = limitBets.filter((bet) => bet.outcome === 'YES')
const noBets = limitBets.filter((bet) => bet.outcome === 'NO').reverse()
return (
<>
<Button
className={className}
onClick={() => setOpen(true)}
size="xs"
color="blue"
>
Order book
</Button>
<Modal open={open} setOpen={setOpen} size="lg">
<Col className="rounded bg-white p-4 py-6">
<Title className="!mt-0" text="Order book" />
<Row className="hidden items-start justify-start gap-2 md:flex">
<LimitOrderTable
limitBets={yesBets}
contract={contract}
isYou={false}
/>
<LimitOrderTable
limitBets={noBets}
contract={contract}
isYou={false}
/>
</Row>
<Col className="md:hidden">
<LimitOrderTable
limitBets={limitBets}
contract={contract}
isYou={false}
/>
</Col>
</Col>
</Modal>
</>
)
}