From c4cec29c62faca8b173d9af7f69cc81ac6463a91 Mon Sep 17 00:00:00 2001 From: James Grugett Date: Mon, 13 Jun 2022 20:03:09 -0500 Subject: [PATCH] Simple limit order UI --- web/components/bet-panel.tsx | 64 +++++++++++++++++++++------- web/components/probability-input.tsx | 55 ++++++++++++++++++++++++ 2 files changed, 103 insertions(+), 16 deletions(-) create mode 100644 web/components/probability-input.tsx diff --git a/web/components/bet-panel.tsx b/web/components/bet-panel.tsx index 14acaaa0..adaa9b19 100644 --- a/web/components/bet-panel.tsx +++ b/web/components/bet-panel.tsx @@ -1,6 +1,7 @@ import clsx from 'clsx' import React, { useEffect, useState } from 'react' import { partition, sumBy } from 'lodash' +import { SwitchHorizontalIcon } from '@heroicons/react/solid' import { useUser } from 'web/hooks/use-user' import { BinaryContract, CPMMBinaryContract } from 'common/contract' @@ -38,6 +39,7 @@ import { SellRow } from './sell-row' import { useSaveShares } from './use-save-shares' import { SignUpPrompt } from './sign-up-prompt' import { isIOS } from 'web/lib/util/device' +import { ProbabilityInput } from './probability-input' export function BetPanel(props: { contract: BinaryContract @@ -53,6 +55,8 @@ export function BetPanel(props: { ? 'NO' : undefined + const [isLimitOrder, setIsLimitOrder] = useState(false) + return ( -
Place your bet
- {/* */} + <button + className="btn btn-ghost btn-sm absolute right-3 top-1 mt-1 gap-2 self-end text-sm normal-case" + onClick={() => setIsLimitOrder(!isLimitOrder)} + > + <SwitchHorizontalIcon className="inline h-4 w-4" /> + {isLimitOrder ? <>Simple bet</> : <>Limit bet</>} + </button> + <div className="mb-6 text-2xl"> + {isLimitOrder ? <>Bet to a probability</> : <>Place your bet</>} + </div> - <BuyPanel contract={contract} user={user} /> + <BuyPanel contract={contract} user={user} isLimitOrder={isLimitOrder} /> <SignUpPrompt /> </Col> @@ -190,13 +202,19 @@ export function BetPanelSwitcher(props: { function BuyPanel(props: { contract: BinaryContract user: User | null | undefined + isLimitOrder?: boolean selected?: 'YES' | 'NO' onBuySuccess?: () => void }) { - const { contract, user, selected, onBuySuccess } = props + const { contract, user, isLimitOrder, selected, onBuySuccess } = props + + const initialProb = getProbability(contract) const [betChoice, setBetChoice] = useState<'YES' | 'NO' | undefined>(selected) const [betAmount, setBetAmount] = useState<number | undefined>(undefined) + const [betProb, setBetProb] = useState<number | undefined>( + Math.round(100 * initialProb) + ) const [error, setError] = useState<string | undefined>() const [isSubmitting, setIsSubmitting] = useState(false) const [wasSubmitted, setWasSubmitted] = useState(false) @@ -255,8 +273,6 @@ function BuyPanel(props: { const betDisabled = isSubmitting || !betAmount || error - const initialProb = getProbability(contract) - const outcomeProb = getOutcomeProbabilityAfterBet( contract, betChoice || 'YES', @@ -309,16 +325,32 @@ function BuyPanel(props: { disabled={isSubmitting} inputRef={inputRef} /> - - <Col className="mt-3 w-full gap-3"> - <Row className="items-center justify-between text-sm"> - <div className="text-gray-500">Probability</div> - <div> - {formatPercent(initialProb)} - <span className="mx-2">→</span> - {formatPercent(resultProb)} + {isLimitOrder && ( + <> + <div className="my-3 text-left text-sm text-gray-500"> + Probability </div> - </Row> + <ProbabilityInput + inputClassName="w-full max-w-none" + prob={betProb} + onChange={setBetProb} + error={error} + setError={setError} + disabled={isSubmitting} + /> + </> + )} + <Col className="mt-3 w-full gap-3"> + {!isLimitOrder && ( + <Row className="items-center justify-between text-sm"> + <div className="text-gray-500">Probability</div> + <div> + {formatPercent(initialProb)} + <span className="mx-2">→</span> + {formatPercent(resultProb)} + </div> + </Row> + )} <Row className="items-center justify-between gap-2 text-sm"> <Row className="flex-nowrap items-center gap-2 whitespace-nowrap text-gray-500"> diff --git a/web/components/probability-input.tsx b/web/components/probability-input.tsx new file mode 100644 index 00000000..c80ea013 --- /dev/null +++ b/web/components/probability-input.tsx @@ -0,0 +1,55 @@ +import clsx from 'clsx' +import { Col } from './layout/col' +import { Spacer } from './layout/spacer' + +export function ProbabilityInput(props: { + prob: number | undefined + onChange: (newProb: number | undefined) => void + error: string | undefined + setError: (error: string | undefined) => void + disabled?: boolean + className?: string + inputClassName?: string +}) { + const { prob, onChange, error, disabled, className, inputClassName } = props + + const onProbChange = (str: string) => { + let prob = parseInt(str.replace(/\D/g, '')) + const isInvalid = !str || isNaN(prob) + if (prob.toString().length > 2) prob = +prob.toString().slice(0, 2) + onChange(isInvalid ? undefined : prob) + } + + return ( + <Col className={className}> + <label className="input-group"> + <input + className={clsx( + 'input input-bordered max-w-[200px] text-lg', + error && 'input-error', + inputClassName + )} + type="number" + max={100} + min={0} + pattern="[0-9]*" + inputMode="numeric" + placeholder="0" + maxLength={2} + value={prob ?? ''} + disabled={disabled} + onChange={(e) => onProbChange(e.target.value)} + /> + <span className="bg-gray-200 text-sm">%</span> + </label> + + <Spacer h={4} /> + + {error && ( + <div className="mb-2 mr-auto self-center whitespace-nowrap text-xs font-medium tracking-wide text-red-500"> + {error} + </div> + )} + </Col> + ) +}