Loan frontend: show your loan amount in bet panel, answer bet panel
This commit is contained in:
parent
659f848bec
commit
42533c296a
|
@ -1,15 +1,20 @@
|
||||||
import clsx from 'clsx'
|
import clsx from 'clsx'
|
||||||
|
import _ from 'lodash'
|
||||||
import { useUser } from '../hooks/use-user'
|
import { useUser } from '../hooks/use-user'
|
||||||
import { formatMoney } from '../../common/util/format'
|
import { formatMoney } from '../../common/util/format'
|
||||||
import { AddFundsButton } from './add-funds-button'
|
|
||||||
import { Col } from './layout/col'
|
import { Col } from './layout/col'
|
||||||
import { Row } from './layout/row'
|
import { Row } from './layout/row'
|
||||||
|
import { useUserContractBets } from '../hooks/use-user-bets'
|
||||||
|
import { MAX_LOAN_PER_CONTRACT } from '../../common/bet'
|
||||||
|
import { InfoTooltip } from './info-tooltip'
|
||||||
|
import { Spacer } from './layout/spacer'
|
||||||
|
|
||||||
export function AmountInput(props: {
|
export function AmountInput(props: {
|
||||||
amount: number | undefined
|
amount: number | undefined
|
||||||
onChange: (newAmount: number | undefined) => void
|
onChange: (newAmount: number | undefined) => void
|
||||||
error: string | undefined
|
error: string | undefined
|
||||||
setError: (error: string | undefined) => void
|
setError: (error: string | undefined) => void
|
||||||
|
contractId: string | undefined
|
||||||
minimumAmount?: number
|
minimumAmount?: number
|
||||||
disabled?: boolean
|
disabled?: boolean
|
||||||
className?: string
|
className?: string
|
||||||
|
@ -22,6 +27,7 @@ export function AmountInput(props: {
|
||||||
onChange,
|
onChange,
|
||||||
error,
|
error,
|
||||||
setError,
|
setError,
|
||||||
|
contractId,
|
||||||
disabled,
|
disabled,
|
||||||
className,
|
className,
|
||||||
inputClassName,
|
inputClassName,
|
||||||
|
@ -31,10 +37,23 @@ export function AmountInput(props: {
|
||||||
|
|
||||||
const user = useUser()
|
const user = useUser()
|
||||||
|
|
||||||
|
const userBets = useUserContractBets(user?.id, contractId) ?? []
|
||||||
|
const prevLoanAmount = _.sumBy(userBets, (bet) => bet.loanAmount ?? 0)
|
||||||
|
|
||||||
|
const loanAmount = Math.min(
|
||||||
|
amount ?? 0,
|
||||||
|
MAX_LOAN_PER_CONTRACT - prevLoanAmount
|
||||||
|
)
|
||||||
|
|
||||||
const onAmountChange = (str: string) => {
|
const onAmountChange = (str: string) => {
|
||||||
|
if (str.includes('-')) {
|
||||||
|
onChange(undefined)
|
||||||
|
return
|
||||||
|
}
|
||||||
const amount = parseInt(str.replace(/[^\d]/, ''))
|
const amount = parseInt(str.replace(/[^\d]/, ''))
|
||||||
|
|
||||||
if (str && isNaN(amount)) return
|
if (str && isNaN(amount)) return
|
||||||
|
if (amount >= 10 ** 9) return
|
||||||
|
|
||||||
onChange(str ? amount : undefined)
|
onChange(str ? amount : undefined)
|
||||||
|
|
||||||
|
@ -47,7 +66,8 @@ export function AmountInput(props: {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const remainingBalance = Math.max(0, (user?.balance ?? 0) - (amount ?? 0))
|
const amountNetLoan = (amount ?? 0) - loanAmount
|
||||||
|
const remainingBalance = Math.max(0, (user?.balance ?? 0) - amountNetLoan)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Col className={className}>
|
<Col className={className}>
|
||||||
|
@ -68,19 +88,34 @@ export function AmountInput(props: {
|
||||||
onChange={(e) => onAmountChange(e.target.value)}
|
onChange={(e) => onAmountChange(e.target.value)}
|
||||||
/>
|
/>
|
||||||
</label>
|
</label>
|
||||||
|
|
||||||
|
<Spacer h={4} />
|
||||||
|
|
||||||
{error && (
|
{error && (
|
||||||
<div className="mr-auto mt-4 self-center whitespace-nowrap text-xs font-medium tracking-wide text-red-500">
|
<div className="mb-2 mr-auto self-center whitespace-nowrap text-xs font-medium tracking-wide text-red-500">
|
||||||
{error}
|
{error}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{user && (
|
{user && (
|
||||||
<Col className="mt-3 text-sm">
|
<Col className="text-sm gap-3">
|
||||||
<div className="mb-2 whitespace-nowrap text-gray-500">
|
{contractId && (
|
||||||
Remaining balance
|
<Row className="items-center justify-between gap-2 text-gray-500">
|
||||||
</div>
|
<Row className="items-center gap-2">
|
||||||
<Row className="gap-4">
|
Loan amount{' '}
|
||||||
<div>{formatMoney(Math.floor(remainingBalance))}</div>
|
<InfoTooltip
|
||||||
{user.balance !== 1000 && <AddFundsButton />}
|
text={`Up to ${formatMoney(
|
||||||
|
MAX_LOAN_PER_CONTRACT
|
||||||
|
)} is automatically borrowed and repaid when the market resolves.`}
|
||||||
|
/>
|
||||||
|
</Row>
|
||||||
|
<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(remainingBalance)}
|
||||||
|
</span>
|
||||||
</Row>
|
</Row>
|
||||||
</Col>
|
</Col>
|
||||||
)}
|
)}
|
||||||
|
|
|
@ -114,21 +114,21 @@ export function AnswerBetPanel(props: {
|
||||||
setError={setError}
|
setError={setError}
|
||||||
disabled={isSubmitting}
|
disabled={isSubmitting}
|
||||||
inputRef={inputRef}
|
inputRef={inputRef}
|
||||||
|
contractId={contract.id}
|
||||||
/>
|
/>
|
||||||
|
<Col className="gap-3 mt-3 w-64">
|
||||||
<Spacer h={4} />
|
<Row className="justify-between items-center text-sm">
|
||||||
|
<div className="text-gray-500">Probability</div>
|
||||||
<div className="mt-2 mb-1 text-sm text-gray-500">Implied probability</div>
|
|
||||||
<Row>
|
<Row>
|
||||||
<div>{formatPercent(initialProb)}</div>
|
<div>{formatPercent(initialProb)}</div>
|
||||||
<div className="mx-2">→</div>
|
<div className="mx-2">→</div>
|
||||||
<div>{formatPercent(resultProb)}</div>
|
<div>{formatPercent(resultProb)}</div>
|
||||||
</Row>
|
</Row>
|
||||||
|
</Row>
|
||||||
|
|
||||||
<Spacer h={4} />
|
<Row className="justify-between items-start text-sm gap-2">
|
||||||
|
<Row className="flex-nowrap whitespace-nowrap items-center gap-2 text-gray-500">
|
||||||
<Row className="mt-2 mb-1 items-center gap-2 text-sm text-gray-500">
|
<div>Payout if chosen</div>
|
||||||
Payout if chosen
|
|
||||||
<InfoTooltip
|
<InfoTooltip
|
||||||
text={`Current payout for ${formatWithCommas(
|
text={`Current payout for ${formatWithCommas(
|
||||||
shares
|
shares
|
||||||
|
@ -137,17 +137,21 @@ export function AnswerBetPanel(props: {
|
||||||
)} shares`}
|
)} shares`}
|
||||||
/>
|
/>
|
||||||
</Row>
|
</Row>
|
||||||
<div>
|
<Row className="flex-wrap justify-end items-end gap-2">
|
||||||
|
<span className="whitespace-nowrap">
|
||||||
{formatMoney(currentPayout)}
|
{formatMoney(currentPayout)}
|
||||||
<span>(+{currentReturnPercent})</span>
|
</span>
|
||||||
</div>
|
<span>(+{currentReturnPercent})</span>
|
||||||
|
</Row>
|
||||||
|
</Row>
|
||||||
|
</Col>
|
||||||
|
|
||||||
<Spacer h={6} />
|
<Spacer h={6} />
|
||||||
|
|
||||||
{user ? (
|
{user ? (
|
||||||
<button
|
<button
|
||||||
className={clsx(
|
className={clsx(
|
||||||
'btn',
|
'btn self-stretch',
|
||||||
betDisabled ? 'btn-disabled' : 'btn-primary',
|
betDisabled ? 'btn-disabled' : 'btn-primary',
|
||||||
isSubmitting ? 'loading' : ''
|
isSubmitting ? 'loading' : ''
|
||||||
)}
|
)}
|
||||||
|
@ -157,7 +161,7 @@ export function AnswerBetPanel(props: {
|
||||||
</button>
|
</button>
|
||||||
) : (
|
) : (
|
||||||
<button
|
<button
|
||||||
className="btn mt-4 whitespace-nowrap border-none bg-gradient-to-r from-teal-500 to-green-500 px-10 text-lg font-medium normal-case hover:from-teal-600 hover:to-green-600"
|
className="btn self-stretch whitespace-nowrap border-none bg-gradient-to-r from-teal-500 to-green-500 px-10 text-lg font-medium normal-case hover:from-teal-600 hover:to-green-600"
|
||||||
onClick={firebaseLogin}
|
onClick={firebaseLogin}
|
||||||
>
|
>
|
||||||
Sign in to trade!
|
Sign in to trade!
|
||||||
|
|
|
@ -101,27 +101,35 @@ export function CreateAnswerPanel(props: { contract: Contract }) {
|
||||||
setError={setAmountError}
|
setError={setAmountError}
|
||||||
minimumAmount={1}
|
minimumAmount={1}
|
||||||
disabled={isSubmitting}
|
disabled={isSubmitting}
|
||||||
|
contractId={contract.id}
|
||||||
/>
|
/>
|
||||||
</Col>
|
</Col>
|
||||||
<Col className="gap-2 mt-1">
|
<Col className="gap-3">
|
||||||
<div className="text-sm text-gray-500">Implied probability</div>
|
<Row className="justify-between items-center text-sm">
|
||||||
|
<div className="text-gray-500">Probability</div>
|
||||||
<Row>
|
<Row>
|
||||||
<div>{formatPercent(0)}</div>
|
<div>{formatPercent(0)}</div>
|
||||||
<div className="mx-2">→</div>
|
<div className="mx-2">→</div>
|
||||||
<div>{formatPercent(resultProb)}</div>
|
<div>{formatPercent(resultProb)}</div>
|
||||||
</Row>
|
</Row>
|
||||||
<Row className="mt-2 mb-1 items-center gap-2 text-sm text-gray-500">
|
</Row>
|
||||||
Payout if chosen
|
|
||||||
|
<Row className="justify-between items-start text-sm gap-2">
|
||||||
|
<Row className="flex-nowrap whitespace-nowrap items-center gap-2 text-gray-500">
|
||||||
|
<div>Payout if chosen</div>
|
||||||
<InfoTooltip
|
<InfoTooltip
|
||||||
text={`Current payout for ${formatWithCommas(
|
text={`Current payout for ${formatWithCommas(
|
||||||
shares
|
shares
|
||||||
)} / ${formatWithCommas(shares)} shares`}
|
)} / ${formatWithCommas(shares)} shares`}
|
||||||
/>
|
/>
|
||||||
</Row>
|
</Row>
|
||||||
<div>
|
<Row className="flex-wrap justify-end items-end gap-2">
|
||||||
|
<span className="whitespace-nowrap">
|
||||||
{formatMoney(currentPayout)}
|
{formatMoney(currentPayout)}
|
||||||
<span>(+{currentReturnPercent})</span>
|
</span>
|
||||||
</div>
|
<span>(+{currentReturnPercent})</span>
|
||||||
|
</Row>
|
||||||
|
</Row>
|
||||||
</Col>
|
</Col>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
|
|
@ -144,7 +144,6 @@ export function BetPanel(props: {
|
||||||
text={panelTitle}
|
text={panelTitle}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{/* <div className="mt-2 mb-1 text-sm text-gray-500">Outcome</div> */}
|
|
||||||
<YesNoSelector
|
<YesNoSelector
|
||||||
className="mb-4"
|
className="mb-4"
|
||||||
selected={betChoice}
|
selected={betChoice}
|
||||||
|
@ -160,45 +159,49 @@ export function BetPanel(props: {
|
||||||
setError={setError}
|
setError={setError}
|
||||||
disabled={isSubmitting}
|
disabled={isSubmitting}
|
||||||
inputRef={inputRef}
|
inputRef={inputRef}
|
||||||
|
contractId={contract.id}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<Spacer h={4} />
|
<Col className="gap-3 mt-3 w-64">
|
||||||
|
<Row className="justify-between items-center text-sm">
|
||||||
<div className="mt-2 mb-1 text-sm text-gray-500">Implied probability</div>
|
<div className="text-gray-500">Probability</div>
|
||||||
<Row>
|
<Row>
|
||||||
<div>{formatPercent(initialProb)}</div>
|
<div>{formatPercent(initialProb)}</div>
|
||||||
<div className="mx-2">→</div>
|
<div className="mx-2">→</div>
|
||||||
<div>{formatPercent(resultProb)}</div>
|
<div>{formatPercent(resultProb)}</div>
|
||||||
</Row>
|
</Row>
|
||||||
|
</Row>
|
||||||
|
|
||||||
{betChoice && (
|
<Row className="justify-between items-start text-sm gap-2">
|
||||||
<>
|
<Row className="flex-nowrap whitespace-nowrap items-center gap-2 text-gray-500">
|
||||||
<Spacer h={4} />
|
<div>
|
||||||
<Row className="mt-2 mb-1 items-center gap-2 text-sm text-gray-500">
|
Payout if <OutcomeLabel outcome={betChoice ?? 'YES'} />
|
||||||
Payout if <OutcomeLabel outcome={betChoice} />
|
</div>
|
||||||
<InfoTooltip
|
<InfoTooltip
|
||||||
text={`Current payout for ${formatWithCommas(
|
text={`Current payout for ${formatWithCommas(
|
||||||
shares
|
shares
|
||||||
)} / ${formatWithCommas(
|
)} / ${formatWithCommas(
|
||||||
shares +
|
shares +
|
||||||
totalShares[betChoice] -
|
totalShares[betChoice ?? 'YES'] -
|
||||||
(phantomShares ? phantomShares[betChoice] : 0)
|
(phantomShares ? phantomShares[betChoice ?? 'YES'] : 0)
|
||||||
)} ${betChoice} shares`}
|
)} ${betChoice} shares`}
|
||||||
/>
|
/>
|
||||||
</Row>
|
</Row>
|
||||||
<div>
|
<Row className="flex-wrap justify-end items-end gap-2">
|
||||||
|
<span className="whitespace-nowrap">
|
||||||
{formatMoney(currentPayout)}
|
{formatMoney(currentPayout)}
|
||||||
<span>(+{currentReturnPercent})</span>
|
</span>
|
||||||
</div>
|
<span>(+{currentReturnPercent})</span>
|
||||||
</>
|
</Row>
|
||||||
)}
|
</Row>
|
||||||
|
</Col>
|
||||||
|
|
||||||
<Spacer h={6} />
|
<Spacer h={8} />
|
||||||
|
|
||||||
{user && (
|
{user && (
|
||||||
<button
|
<button
|
||||||
className={clsx(
|
className={clsx(
|
||||||
'btn',
|
'btn flex-1',
|
||||||
betDisabled
|
betDisabled
|
||||||
? 'btn-disabled'
|
? 'btn-disabled'
|
||||||
: betChoice === 'YES'
|
: betChoice === 'YES'
|
||||||
|
@ -213,7 +216,7 @@ export function BetPanel(props: {
|
||||||
)}
|
)}
|
||||||
{user === null && (
|
{user === null && (
|
||||||
<button
|
<button
|
||||||
className="btn mt-4 whitespace-nowrap border-none bg-gradient-to-r from-teal-500 to-green-500 px-10 text-lg font-medium normal-case hover:from-teal-600 hover:to-green-600"
|
className="btn flex-1 whitespace-nowrap border-none bg-gradient-to-r from-teal-500 to-green-500 px-10 text-lg font-medium normal-case hover:from-teal-600 hover:to-green-600"
|
||||||
onClick={firebaseLogin}
|
onClick={firebaseLogin}
|
||||||
>
|
>
|
||||||
Sign in to trade!
|
Sign in to trade!
|
||||||
|
|
|
@ -1,6 +1,10 @@
|
||||||
import _ from 'lodash'
|
import _ from 'lodash'
|
||||||
import { useEffect, useState } from 'react'
|
import { useEffect, useState } from 'react'
|
||||||
import { Bet, listenForUserBets } from '../lib/firebase/bets'
|
import {
|
||||||
|
Bet,
|
||||||
|
listenForUserBets,
|
||||||
|
listenForUserContractBets,
|
||||||
|
} from '../lib/firebase/bets'
|
||||||
|
|
||||||
export const useUserBets = (userId: string | undefined) => {
|
export const useUserBets = (userId: string | undefined) => {
|
||||||
const [bets, setBets] = useState<Bet[] | undefined>(undefined)
|
const [bets, setBets] = useState<Bet[] | undefined>(undefined)
|
||||||
|
@ -12,6 +16,20 @@ export const useUserBets = (userId: string | undefined) => {
|
||||||
return bets
|
return bets
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const useUserContractBets = (
|
||||||
|
userId: string | undefined,
|
||||||
|
contractId: string | undefined
|
||||||
|
) => {
|
||||||
|
const [bets, setBets] = useState<Bet[] | undefined>(undefined)
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (userId && contractId)
|
||||||
|
return listenForUserContractBets(userId, contractId, setBets)
|
||||||
|
}, [userId, contractId])
|
||||||
|
|
||||||
|
return bets
|
||||||
|
}
|
||||||
|
|
||||||
export const useUserBetContracts = (userId: string | undefined) => {
|
export const useUserBetContracts = (userId: string | undefined) => {
|
||||||
const [contractIds, setContractIds] = useState<string[] | undefined>()
|
const [contractIds, setContractIds] = useState<string[] | undefined>()
|
||||||
|
|
||||||
|
|
|
@ -74,6 +74,21 @@ export function listenForUserBets(
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function listenForUserContractBets(
|
||||||
|
userId: string,
|
||||||
|
contractId: string,
|
||||||
|
setBets: (bets: Bet[]) => void
|
||||||
|
) {
|
||||||
|
const betsQuery = query(
|
||||||
|
collection(db, 'contracts', contractId, 'bets'),
|
||||||
|
where('userId', '==', userId)
|
||||||
|
)
|
||||||
|
return listenForValues<Bet>(betsQuery, (bets) => {
|
||||||
|
bets.sort((bet1, bet2) => bet1.createdTime - bet2.createdTime)
|
||||||
|
setBets(bets)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
export function withoutAnteBets(contract: Contract, bets?: Bet[]) {
|
export function withoutAnteBets(contract: Contract, bets?: Bet[]) {
|
||||||
const { createdTime } = contract
|
const { createdTime } = contract
|
||||||
|
|
||||||
|
|
|
@ -145,7 +145,7 @@ export default function ContractPage(props: {
|
||||||
|
|
||||||
<Col className="flex-1">
|
<Col className="flex-1">
|
||||||
{allowTrade && (
|
{allowTrade && (
|
||||||
<BetPanel className="hidden lg:inline" contract={contract} />
|
<BetPanel className="hidden lg:flex" contract={contract} />
|
||||||
)}
|
)}
|
||||||
{allowResolve && (
|
{allowResolve && (
|
||||||
<ResolutionPanel creator={user} contract={contract} />
|
<ResolutionPanel creator={user} contract={contract} />
|
||||||
|
|
|
@ -248,6 +248,7 @@ export function NewContract(props: { question: string; tag?: string }) {
|
||||||
error={anteError}
|
error={anteError}
|
||||||
setError={setAnteError}
|
setError={setAnteError}
|
||||||
disabled={isSubmitting}
|
disabled={isSubmitting}
|
||||||
|
contractId={undefined}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
|
@ -245,6 +245,7 @@ ${TEST_VALUE}
|
||||||
error={anteError}
|
error={anteError}
|
||||||
setError={setAnteError}
|
setError={setAnteError}
|
||||||
disabled={isSubmitting}
|
disabled={isSubmitting}
|
||||||
|
contractId={undefined}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue
Block a user