manifold/web/components/liquidity-panel.tsx
Marshall Polaris 4700ceb14c
Refactor some backend-related stuff (#639)
* web/lib/firebase/api-call -> common/api, web/lib/firebase/api

* Reuse `APIError` type in server code

* Reuse `getFunctionUrl` in server code
2022-07-10 15:03:15 -07:00

228 lines
5.7 KiB
TypeScript

import clsx from 'clsx'
import { useEffect, useState } from 'react'
import { CPMMContract } from 'common/contract'
import { formatMoney } from 'common/util/format'
import { useUser } from 'web/hooks/use-user'
import { addLiquidity, withdrawLiquidity } from 'web/lib/firebase/api'
import { AmountInput } from './amount-input'
import { Row } from './layout/row'
import { useUserLiquidity } from 'web/hooks/use-liquidity'
import { Tabs } from './layout/tabs'
import { NoLabel, YesLabel } from './outcome-label'
import { Col } from './layout/col'
import { InfoTooltip } from './info-tooltip'
import { track } from 'web/lib/service/analytics'
export function LiquidityPanel(props: { contract: CPMMContract }) {
const { contract } = props
const user = useUser()
const lpShares = useUserLiquidity(contract, user?.id ?? '')
const [showWithdrawal, setShowWithdrawal] = useState(false)
useEffect(() => {
if (!showWithdrawal && lpShares && lpShares.YES && lpShares.NO)
setShowWithdrawal(true)
}, [showWithdrawal, lpShares])
return (
<Tabs
tabs={[
{
title: 'Subsidize',
content: <AddLiquidityPanel contract={contract} />,
},
...(showWithdrawal
? [
{
title: 'Withdraw',
content: (
<WithdrawLiquidityPanel
contract={contract}
lpShares={lpShares as { YES: number; NO: number }}
/>
),
},
]
: []),
{
title: 'Pool',
content: <ViewLiquidityPanel contract={contract} />,
},
]}
/>
)
}
function AddLiquidityPanel(props: { contract: CPMMContract }) {
const { contract } = props
const { id: contractId, slug } = contract
const user = useUser()
const [amount, setAmount] = useState<number | undefined>(undefined)
const [error, setError] = useState<string | undefined>(undefined)
const [isSuccess, setIsSuccess] = useState(false)
const [isLoading, setIsLoading] = useState(false)
const onAmountChange = (amount: number | undefined) => {
setIsSuccess(false)
setAmount(amount)
// Check for errors.
if (amount !== undefined) {
if (user && user.balance < amount) {
setError('Insufficient balance')
} else if (amount < 1) {
setError('Minimum amount: ' + formatMoney(1))
} else {
setError(undefined)
}
}
}
const submit = () => {
if (!amount) return
setIsLoading(true)
setIsSuccess(false)
addLiquidity({ amount, contractId })
.then((_) => {
setIsSuccess(true)
setError(undefined)
setIsLoading(false)
})
.catch((_) => setError('Server error'))
track('add liquidity', { amount, contractId, slug })
}
return (
<>
<div className="align-center mb-4 text-gray-500">
Subsidize this market by adding M$ to the liquidity pool.{' '}
<InfoTooltip text="The greater the M$ subsidy, the greater the incentive for traders to participate, the more accurate the market will be." />
</div>
<Row>
<AmountInput
amount={amount}
onChange={onAmountChange}
label="M$"
error={error}
disabled={isLoading}
/>
<button
className={clsx('btn btn-primary ml-2', isLoading && 'btn-disabled')}
onClick={submit}
disabled={isLoading}
>
Add
</button>
</Row>
{isSuccess && amount && (
<div>Success! Added {formatMoney(amount)} in liquidity.</div>
)}
{isLoading && <div>Processing...</div>}
</>
)
}
function ViewLiquidityPanel(props: { contract: CPMMContract }) {
const { contract } = props
const { pool } = contract
const { YES: yesShares, NO: noShares } = pool
return (
<Col className="mb-4">
<div className="mb-4 text-gray-500">
The liquidity pool for this market currently contains:
</div>
<span>
{yesShares.toFixed(2)} <YesLabel /> shares
</span>
<span>
{noShares.toFixed(2)} <NoLabel /> shares
</span>
</Col>
)
}
function WithdrawLiquidityPanel(props: {
contract: CPMMContract
lpShares: { YES: number; NO: number }
}) {
const { contract, lpShares } = props
const { YES: yesShares, NO: noShares } = lpShares
const [_error, setError] = useState<string | undefined>(undefined)
const [isSuccess, setIsSuccess] = useState(false)
const [isLoading, setIsLoading] = useState(false)
const submit = () => {
setIsLoading(true)
setIsSuccess(false)
withdrawLiquidity({ contractId: contract.id })
.then((_) => {
setIsSuccess(true)
setError(undefined)
setIsLoading(false)
})
.catch((_) => setError('Server error'))
track('withdraw liquidity')
}
if (isSuccess)
return (
<div className="text-gray-500">
Success! Your liquidity was withdrawn.
</div>
)
if (!yesShares && !noShares)
return (
<div className="text-gray-500">
You do not have any liquidity positions to withdraw.
</div>
)
return (
<Col>
<div className="mb-4 text-gray-500">
Your liquidity position is currently:
</div>
<span>
{yesShares.toFixed(2)} <YesLabel /> shares
</span>
<span>
{noShares.toFixed(2)} <NoLabel /> shares
</span>
<Row className="mt-4 mb-2">
<button
className={clsx(
'btn btn-outline btn-sm ml-2',
isLoading && 'btn-disabled'
)}
onClick={submit}
disabled={isLoading}
>
Withdraw
</button>
</Row>
{isLoading && <div>Processing...</div>}
</Col>
)
}