wip: wire up deposit withdraw modals

This commit is contained in:
Tyler Shipe 2021-04-09 10:27:49 -04:00
parent 10e4fff211
commit b5562c161c
13 changed files with 409 additions and 109 deletions

View File

@ -0,0 +1,77 @@
import xw from 'xwind'
import { Listbox, Transition } from '@headlessui/react'
import { ChevronDownIcon, ChevronUpIcon } from '@heroicons/react/solid'
import useMarketList from '../hooks/useMarketList'
// import useMarket from '../hooks/useMarket'
import useMangoStore from '../stores/useMangoStore'
import { Account } from '@solana/web3.js'
interface MarketSelectProps {
accounts?: any[]
selectedAccount?: Account
onSelectAccount: () => void
}
const AccountSelect = ({accounts, selectedAccount, onSelectAccount}): MarketSelectProps => {
const handleChange = (value) => {
onSelectAccount(value)
}
return (
<div css={xw`ml-4 relative inline-block -mb-1`}>
<Listbox value={selectedAccount} onChange={handleChange}>
{({ open }) => (
<>
<Listbox.Button
css={xw`border border-mango-dark-lighter focus:outline-none focus:ring-1 focus:ring-mango-yellow p-2 w-56`}
>
<div
css={xw`flex items-center text-lg justify-between font-light`}
>
{selectedAccount}
{open ? (
<ChevronUpIcon css={xw`h-5 w-5 mr-1`} />
) : (
<ChevronDownIcon css={xw`h-5 w-5 mr-1`} />
)}
</div>
</Listbox.Button>
<Transition
show={open}
enter="transition duration-100 ease-out"
enterFrom="transform scale-95 opacity-0"
enterTo="transform scale-100 opacity-100"
leave="transition duration-75 ease-out"
leaveFrom="transform scale-100 opacity-100"
leaveTo="transform scale-95 opacity-0"
>
<Listbox.Options
static
css={xw`z-20 p-1 absolute left-0 w-56 mt-1 bg-mango-dark-light origin-top-left divide-y divide-mango-dark-lighter shadow-lg outline-none`}
>
<div css={xw`opacity-50 p-2`}>Markets</div>
{Object.entries(accounts).map((account) => (
<Listbox.Option key={account} value={account}>
{({ selected }) => (
<div
css={[
xw`p-2 text-base hover:bg-mango-dark-lighter hover:cursor-pointer tracking-wider font-light`,
selected &&
xw`text-mango-yellow bg-mango-dark-lighter`,
]}
>
{account}
</div>
)}
</Listbox.Option>
))}
</Listbox.Options>
</Transition>
</>
)}
</Listbox>
</div>
)
}
export default AccountSelect

View File

View File

@ -2,13 +2,14 @@ import xw from 'xwind'
import { percentFormat } from '../utils/index'
import useSrmAccount from '../hooks/useSrmAccount'
import useMangoStore from '../stores/useMangoStore'
import { Button } from './styles'
const FeeDiscountsTable = () => {
const { totalSrm, rates } = useSrmAccount()
const connected = useMangoStore((s) => s.wallet.connected)
return (
<div css={xw`flex bg-mango-dark-light py-6`}>
<div css={xw`flex bg-mango-dark-light py-6 mt-4`}>
<div
css={xw`flex flex-col justify-center m-auto text-gray-300 text-base font-light text-center`}
>
@ -24,20 +25,15 @@ const FeeDiscountsTable = () => {
<div css={xw`bg-mango-dark p-6`}>
<div css={xw`text-gray-500`}>Your contributed SRM: 0</div>
<div css={xw`flex space-x-4 mt-8`}>
<button
css={xw`px-8 py-2 border border-mango-dark-lighter bg-mango-dark-light text-mango-yellow hover:bg-mango-dark-lighter`}
>
Deposit
</button>
<button
css={xw`px-8 py-2 border border-mango-dark-lighter bg-mango-dark-light text-mango-yellow hover:bg-mango-dark-lighter`}
>
Withdraw
</button>
<Button>Deposit</Button>
<Button>Withdraw</Button>
</div>
</div>
) : (
<button disabled css={xw`px-8 cursor-default py-2 bg-mango-dark`}>
<button
disabled
css={xw`px-8 cursor-default font-light py-2.5 bg-mango-dark text-mango-med-dark text-lg`}
>
Connect a wallet to deposit SRM
</button>
)}

View File

@ -0,0 +1,200 @@
import { Popover } from 'antd'
import { useState } from 'react'
import xw from 'xwind'
// import { nativeToUi } from '@blockworks-foundation/mango-client/lib/utils'
import {
ExternalLinkIcon,
InformationCircleIcon,
} from '@heroicons/react/outline'
// import useConnection from '../hooks/useConnection'
// import useMarginAccount from '../hooks/useMarginAcccount'
import FloatingElement from './FloatingElement'
import { Button, ElementTitle } from './styles'
import useMangoStore from '../stores/useMangoStore'
import useMarketList from '../hooks/useMarketList'
import { tokenPrecision } from '../utils/index'
import Modal from './Modal'
export default function MarginStats() {
// const { connection } = useConnection()
// const { marginAccount, mangoGroup } = useMarginAccount()
const selectedMangoGroup = useMangoStore((s) => s.selectedMangoGroup.current)
const selectedMarginAccount = useMangoStore(
(s) => s.selectedMarginAccount.current
)
const { symbols } = useMarketList()
const [showDepositModal, setShowDepositModal] = useState(false)
const [showWithdrawModal, setShowWithdrawModal] = useState(false)
const handleDeposit = () => {
setShowDepositModal(true)
}
const handleWithdraw = () => {
setShowWithdrawModal(true)
}
return (
<>
<FloatingElement>
<ElementTitle>
Margin Account
<Popover
content={
<AddressTooltip
owner={selectedMarginAccount?.owner.toString()}
marginAccount={selectedMarginAccount?.publicKey.toString()}
/>
}
placement="topLeft"
trigger="hover"
>
<InformationCircleIcon
css={xw`h-5 w-5 ml-2 text-mango-yellow cursor-help`}
/>
</Popover>
</ElementTitle>
{selectedMangoGroup ? (
<table css={xw`min-w-full`}>
<thead>
<tr css={xw`text-center text-mango-med-dark mb-2`}>
<th scope="col" css={xw`flex-auto`}>
Assets
</th>
<th scope="col" css={xw`flex-auto`}>
Deposits
</th>
<th scope="col" css={xw`flex-auto`}>
Borrows
</th>
<th scope="col" css={xw`flex-auto`}>
Interest
</th>
</tr>
</thead>
<tbody>
{Object.entries(symbols).map(([name], i) => (
<tr
key={name}
css={xw`text-mango-med-dark text-gray-300 tracking-wide`}
>
<td css={xw`flex items-center py-2`}>
<img
alt=""
width="20"
height="20"
src={`/assets/icons/${name.toLowerCase()}.svg`}
css={xw`mr-4`}
/>
<span>{name}</span>
</td>
<td css={xw`text-center`}>
{selectedMarginAccount
? selectedMarginAccount
.getUiDeposit(selectedMangoGroup, i)
.toFixed(tokenPrecision[name])
: (0).toFixed(tokenPrecision[name])}
</td>
<td css={xw`text-center`}>
{selectedMarginAccount
? selectedMarginAccount
.getUiDeposit(selectedMangoGroup, i)
.toFixed(tokenPrecision[name])
: (0).toFixed(tokenPrecision[name])}
</td>
<td css={xw`text-center`}>
<span css={xw`text-mango-green`}>
+{(selectedMangoGroup.getDepositRate(i) * 100).toFixed(2)}
%
</span>
<span>{' / '}</span>
<span css={xw`text-mango-red`}>
-{(selectedMangoGroup.getBorrowRate(i) * 100).toFixed(2)}%
</span>
</td>
</tr>
))}
</tbody>
</table>
) : null}
<div css={xw`flex justify-around items-center mt-4`}>
<div>
<Button onClick={handleDeposit}>
<span>Deposit</span>
</Button>
</div>
<div>
<Button onClick={handleWithdraw} css={xw`ml-4`}>
<span>Withdraw</span>
</Button>
</div>
</div>
<div css={xw`text-center mt-4 text-mango-med tracking-wider`}>
Settle funds in the Balances tab
</div>
</FloatingElement>
{showDepositModal && (
<Modal
isOpen={showDepositModal}
onClose={() => setShowDepositModal(false)}
/>
)}
{showWithdrawModal && (
<Modal
isOpen={showWithdrawModal}
onClose={() => setShowWithdrawModal(false)}
/>
)}
</>
)
}
const AddressTooltip = ({
owner,
marginAccount,
}: {
owner?: string
marginAccount?: string
}) => {
return (
<>
{owner && marginAccount ? (
<>
<div css={xw`flex`}>
Margin Account:
<a
href={'https://explorer.solana.com/address/' + marginAccount}
target="_blank"
rel="noopener noreferrer"
>
<div css={xw`ml-4 flex`}>
<ExternalLinkIcon css={xw`h-5 w-5 mr-1 text-mango-yellow`} />
<span css={xw`text-mango-yellow hover:opacity-50`}>
{marginAccount}
</span>
</div>
</a>
</div>
<div css={xw`flex mt-2`}>
Account Owner:
<a
href={'https://explorer.solana.com/address/' + owner}
target="_blank"
rel="noopener noreferrer"
>
<div css={xw`ml-4 flex`}>
<ExternalLinkIcon css={xw`h-5 w-5 mr-1 text-mango-yellow`} />
<span css={xw`text-mango-yellow hover:opacity-50`}>
{owner}
</span>
</div>
</a>
</div>
</>
) : (
'Connect a wallet and deposit funds to start trading'
)}
</>
)
}

View File

@ -1,5 +1,5 @@
import { Popover } from 'antd'
import React, { useEffect, useState } from 'react'
import { useEffect, useState } from 'react'
import xw from 'xwind'
import { nativeToUi } from '@blockworks-foundation/mango-client/lib/utils'
import { groupBy } from '../utils'
@ -53,7 +53,7 @@ const calculatePNL = (tradeHistory, prices, mangoGroup) => {
return total.toFixed(2)
}
export default function MarginInfo() {
export default function MarginStats() {
const { connection } = useConnection()
const { marginAccount, mangoGroup } = useMarginAccount()
const [mAccountInfo, setMAccountInfo] = useState<
@ -144,10 +144,10 @@ export default function MarginInfo() {
}, [marginAccount, mangoGroup])
return (
<FloatingElement>
<React.Fragment>
<>
{mAccountInfo
? mAccountInfo.map((entry, i) => (
<div css={xw`flex justify-between pt-2 pb-3`} key={i}>
<div css={xw`flex justify-between pt-2 pb-2`} key={i}>
<Popover
content={entry.desc}
placement="topLeft"
@ -162,7 +162,7 @@ export default function MarginInfo() {
</div>
))
: null}
</React.Fragment>
</>
</FloatingElement>
)
}

79
components/Modal.tsx Normal file
View File

@ -0,0 +1,79 @@
// import { useState } from 'react'
import xw from 'xwind'
import { Portal } from 'react-portal'
import { Button } from './styles'
const Modal = ({ isOpen, onClose }) => {
const handleClick = () => {
onClose()
}
return (
<Portal>
<div
css={xw`fixed z-10 inset-0 overflow-y-auto`}
aria-labelledby="modal-title"
role="dialog"
aria-modal="true"
>
<div
css={xw`flex items-end justify-center min-h-screen pt-4 px-4 pb-20 text-center sm:block sm:p-0`}
>
{isOpen ? (
<div
css={xw`fixed inset-0 bg-black bg-opacity-40 transition-opacity`}
aria-hidden="true"
onClick={onClose}
></div>
) : null}
<span
css={xw`hidden sm:inline-block sm:align-middle sm:h-screen`}
aria-hidden="true"
>
&#8203;
</span>
{isOpen ? (
<div
css={xw`inline-block align-bottom bg-mango-dark rounded-lg text-left overflow-hidden shadow-xl transform transition-all sm:my-8 sm:align-middle sm:max-w-lg sm:w-full`}
>
<div css={xw`pb-6 px-8`}>
<div css={xw`mt-3 text-center sm:mt-5`}>
<div css={xw`mt-6 bg-mango-dark-light rounded-md`}>
<label htmlFor=""></label>
<input
type="text"
css={xw`outline-none bg-mango-dark-light w-full py-4 mx-3 text-2xl text-gray-300`}
placeholder="0.00"
></input>
</div>
</div>
<div css={xw`mt-5 sm:mt-6 flex justify-center space-x-4`}>
<Button type="button" onClick={handleClick}>
Max
</Button>
<Button type="button" onClick={handleClick}>
Deposit
</Button>
</div>
</div>
</div>
) : null}
</div>
</div>
</Portal>
)
}
const Header = ({ children }) => {
return (
<div css={xw`flex items-center bg-mango-dark-light py-4 px-8`}>
{children}
</div>
)
}
Modal.Header = Header
export default Modal

View File

@ -1,83 +0,0 @@
import { useCallback, useEffect, useRef } from 'react'
import xw from 'xwind'
import { getDecimalCount } from '../utils'
import { ChartTradeType } from '../@types/types'
import FloatingElement from './FloatingElement'
import useMarket from '../hooks/useMarket'
import useInterval from '../hooks/useInterval'
import ChartApi from '../utils/chartDataConnector'
import { ElementTitle } from './styles'
import useSerumStore from '../stores/useSerumStore'
export default function PublicTrades() {
const { baseCurrency, quoteCurrency, market, marketAddress } = useMarket()
const setSerumStore = useSerumStore((s) => s.set)
const fetchTrades = useCallback(async () => {
const trades = await ChartApi.getRecentTrades(marketAddress)
setSerumStore((state) => {
state.chartApiTrades = trades
})
}, [marketAddress])
// fetch trades on load
useEffect(() => {
fetchTrades()
}, [])
// refresh trades on interval
useInterval(async () => {
fetchTrades()
}, 5000)
const tradesRef = useRef(useSerumStore.getState().chartApiTrades)
const trades = tradesRef.current
useEffect(
() =>
useSerumStore.subscribe(
(trades) => (tradesRef.current = trades as any[]),
(state) => state.chartApiTrades
),
[]
)
return (
<FloatingElement>
<ElementTitle>Recent Market Trades</ElementTitle>
<div css={xw`grid grid-cols-3 text-gray-500 mb-2`}>
<div>Price ({quoteCurrency}) </div>
<div css={xw`text-right`}>Size ({baseCurrency})</div>
<div css={xw`text-right`}>Time</div>
</div>
{!!trades.length && (
<div>
{trades.map((trade: ChartTradeType, i: number) => (
<div key={i} css={xw`mb-2 font-light grid grid-cols-3`}>
<div
style={{
color: trade.side === 'buy' ? '#AFD803' : '#E54033',
}}
>
{market?.tickSize && !isNaN(trade.price)
? Number(trade.price).toFixed(
getDecimalCount(market.tickSize)
)
: trade.price}
</div>
<div css={xw`text-right`}>
{market?.minOrderSize && !isNaN(trade.size)
? Number(trade.size).toFixed(
getDecimalCount(market.minOrderSize)
)
: trade.size}
</div>
<div css={xw`text-right text-gray-500`}>
{trade.time && new Date(trade.time).toLocaleTimeString()}
</div>
</div>
))}
</div>
)}
</FloatingElement>
)
}

View File

@ -8,7 +8,8 @@ const TVChartContainer = dynamic(
)
import FloatingElement from '../components/FloatingElement'
import Orderbook from '../components/Orderbook'
import MarginInfo from './MarginInfo'
import MarginStats from './MarginStats'
import MarginBalances from './MarginBalances'
import TradeForm from './TradeForm'
import UserInfo from './UserInfo'
import RecentMarketTrades from './RecentMarketTrades'
@ -20,15 +21,15 @@ const layouts = {
{ i: 'tvChart', x: 0, y: 0, w: 3, h: 30 },
{ i: 'orderbook', x: 3, y: 0, w: 1, h: 17 },
{ i: 'tradeForm', x: 4, y: 0, w: 1, h: 17 },
{ i: 'marginInfo', x: 4, y: 1, w: 1, h: 13 },
{ i: 'marginStats', x: 4, y: 2, w: 1, h: 12 },
{ i: 'marketTrades', x: 3, y: 1, w: 1, h: 13 },
{ i: 'userInfo', x: 0, y: 2, w: 4, h: 17 },
{ i: 'balanceInfo', x: 4, y: 2, w: 1, h: 13 },
{ i: 'balanceInfo', x: 4, y: 1, w: 1, h: 13 },
],
lg: [
{ i: 'tvChart', x: 0, y: 0, w: 2, h: 24 },
{ i: 'balanceInfo', x: 2, y: 0, w: 1, h: 12 },
{ i: 'marginInfo', x: 2, y: 1, w: 1, h: 12 },
{ i: 'marginStats', x: 2, y: 1, w: 1, h: 12 },
{ i: 'orderbook', x: 0, y: 2, w: 1, h: 17 },
{ i: 'tradeForm', x: 1, y: 2, w: 1, h: 17 },
{ i: 'marketTrades', x: 2, y: 2, w: 1, h: 17 },
@ -59,13 +60,15 @@ const TradePageGrid = () => {
<div key="tradeForm">
<TradeForm />
</div>
<div key="marginInfo">
<MarginInfo />
<div key="marginStats">
<MarginStats />
</div>
<div key="userInfo">
<UserInfo />
</div>
<div key="balanceInfo">6</div>
<div key="balanceInfo">
<MarginBalances />
</div>
<div key="marketTrades">
<RecentMarketTrades />
</div>

View File

@ -1,4 +1,13 @@
import styled from '@emotion/styled'
import xw from 'xwind'
export const ElementTitle = styled.div(xw`flex justify-center mb-4 text-lg`)
export const ElementTitle = styled.div(
xw`flex justify-center mb-4 text-lg items-center`
)
export const Button = styled.button(xw`
px-8 py-2
border border-mango-dark-lighter
bg-mango-dark-light hover:bg-mango-dark-lighter
text-mango-yellow
focus:outline-none active:border-mango-yellow`)

View File

@ -45,6 +45,7 @@
"react-cool-dimensions": "^2.0.1",
"react-dom": "^17.0.1",
"react-grid-layout": "^1.2.4",
"react-portal": "^4.2.1",
"recharts": "^2.0.9",
"zustand": "^3.3.3"
},

View File

@ -65,6 +65,7 @@ interface MangoStore extends State {
current: MarginAccount | null
}
tradeForm: {
side: string
currency: string
size: number
}
@ -110,6 +111,7 @@ const useMangoStore = create<MangoStore>(
current: null,
},
tradeForm: {
side: 'buy',
size: 0,
currency: 'BTC',
},

View File

@ -134,3 +134,12 @@ export const calculateMarketPrice = (
return selectedOrder[0] * 0.95
}
}
// Precision for our mango group token
export const tokenPrecision = {
BTC: 4,
ETH: 3,
USDC: 2,
USDT: 2,
WUSDT: 2,
}

View File

@ -6919,7 +6919,7 @@ prompts@^2.0.1:
kleur "^3.0.3"
sisteransi "^1.0.5"
prop-types@15.7.2, prop-types@15.x, prop-types@^15.0.0, prop-types@^15.6.0, prop-types@^15.6.2, prop-types@^15.7.2:
prop-types@15.7.2, prop-types@15.x, prop-types@^15.0.0, prop-types@^15.5.8, prop-types@^15.6.0, prop-types@^15.6.2, prop-types@^15.7.2:
version "15.7.2"
resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.7.2.tgz#52c41e75b8c87e72b9d9360e0206b99dcbffa6c5"
integrity sha512-8QQikdH7//R2vurIJSutZ1smHYTcLpRWEOlHnzcWHmBYrOGUysKwSsrC89BCiFj3CbrfJ/nXFdJepOVrY1GCHQ==
@ -7441,6 +7441,13 @@ react-lifecycles-compat@^3.0.4:
resolved "https://registry.yarnpkg.com/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz#4f1a273afdfc8f3488a8c516bfda78f872352362"
integrity sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA==
react-portal@^4.2.1:
version "4.2.1"
resolved "https://registry.yarnpkg.com/react-portal/-/react-portal-4.2.1.tgz#12c1599238c06fb08a9800f3070bea2a3f78b1a6"
integrity sha512-fE9kOBagwmTXZ3YGRYb4gcMy+kSA+yLO0xnPankjRlfBv4uCpFXqKPfkpsGQQR15wkZ9EssnvTOl1yMzbkxhPQ==
dependencies:
prop-types "^15.5.8"
react-refresh@0.8.3:
version "0.8.3"
resolved "https://registry.yarnpkg.com/react-refresh/-/react-refresh-0.8.3.tgz#721d4657672d400c5e3c75d063c4a85fb2d5d68f"