From d772a20d5ee607dac36469be725e15de6e7b2292 Mon Sep 17 00:00:00 2001 From: James Grugett Date: Tue, 29 Mar 2022 14:44:21 -0500 Subject: [PATCH] Use modal for sell shares on desktop. --- web/components/amount-input.tsx | 1 - web/components/bet-panel.tsx | 111 +++++++++++++++++++++++++++++ web/components/bet-row.tsx | 64 ++--------------- web/components/feed/feed-items.tsx | 3 +- web/components/layout/modal.tsx | 56 +++++++++++++++ 5 files changed, 174 insertions(+), 61 deletions(-) create mode 100644 web/components/layout/modal.tsx diff --git a/web/components/amount-input.tsx b/web/components/amount-input.tsx index 6c42a370..e77802bc 100644 --- a/web/components/amount-input.tsx +++ b/web/components/amount-input.tsx @@ -224,7 +224,6 @@ export function SellAmountInput(props: { // Check for errors. if (amount !== undefined) { - console.log(shares, amount) if (amount > shares) { setError(`Maximum ${formatWithCommas(Math.floor(shares))} shares`) } else { diff --git a/web/components/bet-panel.tsx b/web/components/bet-panel.tsx index 075439b8..8175a3ed 100644 --- a/web/components/bet-panel.tsx +++ b/web/components/bet-panel.tsx @@ -32,10 +32,88 @@ import { calculateCpmmSale, getCpmmProbability, } from '../../common/calculate-cpmm' +import { Modal } from './layout/modal' export function BetPanel(props: { contract: FullContract className?: string +}) { + const { contract, className } = props + const { mechanism } = contract + + const user = useUser() + const userBets = useUserContractBets(user?.id, contract.id) + + const [showSellModal, setShowSellModal] = useState(false) + + const { yesShares, noShares } = useSaveShares(contract, userBets) + + const shares = yesShares || noShares + const sharesOutcome = yesShares ? 'YES' : noShares ? 'NO' : undefined + + return ( + + {sharesOutcome && user && mechanism === 'cpmm-1' && ( + + +
+ You have {formatWithCommas(Math.floor(shares))}{' '} + shares +
+ + + + {showSellModal && ( + } + user={user} + userBets={userBets ?? []} + shares={shares} + sharesOutcome={sharesOutcome} + setOpen={setShowSellModal} + /> + )} +
+ + )} + + + + + <BuyPanel contract={contract} user={user} userBets={userBets ?? []} /> + + {user === null && ( + <button + 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} + > + Sign in to trade! + </button> + )} + </Col> + </Col> + ) +} + +export function BetPanelSwitcher(props: { + contract: FullContract<DPM | CPMM, Binary> + className?: string title?: string // Set if BetPanel is on a feed modal selected?: 'YES' | 'NO' onBetSuccess?: () => void @@ -451,3 +529,36 @@ const useSaveShares = ( if (userBets) return { yesShares, noShares } return savedShares ?? { yesShares: 0, noShares: 0 } } + +function SellSharesModal(props: { + contract: FullContract<CPMM, Binary> + userBets: Bet[] + shares: number + sharesOutcome: 'YES' | 'NO' + user: User + setOpen: (open: boolean) => void +}) { + const { contract, shares, sharesOutcome, userBets, user, setOpen } = props + + return ( + <Modal open={true} setOpen={setOpen}> + <Col className="rounded-md bg-white px-8 py-6"> + <Title className="!mt-0" text={'Sell shares'} /> + + <div className="mb-6"> + You have {formatWithCommas(Math.floor(shares))}{' '} + <OutcomeLabel outcome={sharesOutcome} /> shares + </div> + + <SellPanel + contract={contract} + shares={shares} + sharesOutcome={sharesOutcome} + user={user} + userBets={userBets ?? []} + onSellSuccess={() => setOpen(false)} + /> + </Col> + </Modal> + ) +} diff --git a/web/components/bet-row.tsx b/web/components/bet-row.tsx index f766e51d..3dc1cadc 100644 --- a/web/components/bet-row.tsx +++ b/web/components/bet-row.tsx @@ -1,11 +1,11 @@ import clsx from 'clsx' -import { Fragment, useState } from 'react' -import { Dialog, Transition } from '@headlessui/react' +import { useState } from 'react' -import { BetPanel } from './bet-panel' +import { BetPanelSwitcher } from './bet-panel' import { Row } from './layout/row' import { YesNoSelector } from './yes-no-selector' import { Binary, CPMM, DPM, FullContract } from '../../common/contract' +import { Modal } from './layout/modal' // Inline version of a bet panel. Opens BetPanel in a new modal. export default function BetRow(props: { @@ -27,7 +27,7 @@ export default function BetRow(props: { Place a trade </div> <YesNoSelector - btnClassName="btn-sm w-20" + btnClassName="btn-sm w-24" onSelect={(choice) => { setOpen(true) setBetChoice(choice) @@ -35,7 +35,7 @@ export default function BetRow(props: { /> </Row> <Modal open={open} setOpen={setOpen}> - <BetPanel + <BetPanelSwitcher contract={props.contract} title={props.contract.question} selected={betChoice} @@ -46,57 +46,3 @@ export default function BetRow(props: { </> ) } - -// From https://tailwindui.com/components/application-ui/overlays/modals -export function Modal(props: { - children: React.ReactNode - open: boolean - setOpen: (open: boolean) => void -}) { - const { children, open, setOpen } = props - - return ( - <Transition.Root show={open} as={Fragment}> - <Dialog - as="div" - className="fixed inset-0 z-50 overflow-y-auto" - onClose={setOpen} - > - <div className="flex min-h-screen items-end justify-center px-4 pt-4 pb-20 text-center sm:block sm:p-0"> - <Transition.Child - as={Fragment} - enter="ease-out duration-300" - enterFrom="opacity-0" - enterTo="opacity-100" - leave="ease-in duration-200" - leaveFrom="opacity-100" - leaveTo="opacity-0" - > - <Dialog.Overlay className="fixed inset-0 bg-gray-500 bg-opacity-75 transition-opacity" /> - </Transition.Child> - - {/* This element is to trick the browser into centering the modal contents. */} - <span - className="hidden sm:inline-block sm:h-screen sm:align-middle" - aria-hidden="true" - > - ​ - </span> - <Transition.Child - as={Fragment} - enter="ease-out duration-300" - enterFrom="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95" - enterTo="opacity-100 translate-y-0 sm:scale-100" - leave="ease-in duration-200" - leaveFrom="opacity-100 translate-y-0 sm:scale-100" - leaveTo="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95" - > - <div className="inline-block transform overflow-hidden text-left align-bottom transition-all sm:my-8 sm:w-full sm:max-w-md sm:p-6 sm:align-middle"> - {children} - </div> - </Transition.Child> - </div> - </Dialog> - </Transition.Root> - ) -} diff --git a/web/components/feed/feed-items.tsx b/web/components/feed/feed-items.tsx index 883b917b..f9f9225d 100644 --- a/web/components/feed/feed-items.tsx +++ b/web/components/feed/feed-items.tsx @@ -36,7 +36,7 @@ import { DateTimeTooltip } from '../datetime-tooltip' import { Bet } from '../../lib/firebase/bets' import { JoinSpans } from '../join-spans' import { fromNow } from '../../lib/util/time' -import BetRow, { Modal } from '../bet-row' +import BetRow from '../bet-row' import { parseTags } from '../../../common/util/parse' import { Avatar } from '../avatar' import { useAdmin } from '../../hooks/use-admin' @@ -48,6 +48,7 @@ import { getDpmOutcomeProbability } from '../../../common/calculate-dpm' import { AnswerBetPanel } from '../answers/answer-bet-panel' import { useSaveSeenContract } from '../../hooks/use-seen-contracts' import { User } from '../../../common/user' +import { Modal } from '../layout/modal' export function FeedItems(props: { contract: Contract diff --git a/web/components/layout/modal.tsx b/web/components/layout/modal.tsx new file mode 100644 index 00000000..6c6b1af4 --- /dev/null +++ b/web/components/layout/modal.tsx @@ -0,0 +1,56 @@ +import { Fragment } from 'react' +import { Dialog, Transition } from '@headlessui/react' + +// From https://tailwindui.com/components/application-ui/overlays/modals +export function Modal(props: { + children: React.ReactNode + open: boolean + setOpen: (open: boolean) => void +}) { + const { children, open, setOpen } = props + + return ( + <Transition.Root show={open} as={Fragment}> + <Dialog + as="div" + className="fixed inset-0 z-50 overflow-y-auto" + onClose={setOpen} + > + <div className="flex min-h-screen items-end justify-center px-4 pt-4 pb-20 text-center sm:block sm:p-0"> + <Transition.Child + as={Fragment} + enter="ease-out duration-300" + enterFrom="opacity-0" + enterTo="opacity-100" + leave="ease-in duration-200" + leaveFrom="opacity-100" + leaveTo="opacity-0" + > + <Dialog.Overlay className="fixed inset-0 bg-gray-500 bg-opacity-75 transition-opacity" /> + </Transition.Child> + + {/* This element is to trick the browser into centering the modal contents. */} + <span + className="hidden sm:inline-block sm:h-screen sm:align-middle" + aria-hidden="true" + > + ​ + </span> + <Transition.Child + as={Fragment} + enter="ease-out duration-300" + enterFrom="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95" + enterTo="opacity-100 translate-y-0 sm:scale-100" + leave="ease-in duration-200" + leaveFrom="opacity-100 translate-y-0 sm:scale-100" + leaveTo="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95" + > + <div className="inline-block transform overflow-hidden text-left align-bottom transition-all sm:my-8 sm:w-full sm:max-w-md sm:p-6 sm:align-middle"> + {children} + </div> + </Transition.Child> + </div> + </Dialog> + </Transition.Root> + ) +}