Borrow modal adjustments (#128)

*   add Draggable

* 💄 Add martian red

* 🍱 Add Slider component

* 🔒 update yarn

* 🔥 remove unused components

* 🩹 fix graphql errors

* tidy: tailwind refactor

* set default port to 3001

* 🍱 add Divider and Container

* 🍱 add NumberInput

* allow left and right icon for button

* 🍱 add TokenInput, expand borrowModal

* update borrow modal, adjust modal component, remove container

* fix value change

* update svgs

---------

Co-authored-by: Linkie Link <linkielink.dev@gmail.com>
This commit is contained in:
Bob van der Helm 2023-03-22 15:12:19 +01:00 committed by GitHub
parent 21d31c0f79
commit 740e982956
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
38 changed files with 25002 additions and 2004 deletions

23390
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

View File

@ -28,6 +28,7 @@
"react": "^18.2.0",
"react-device-detect": "^2.2.3",
"react-dom": "^18.2.0",
"react-draggable": "^4.4.5",
"react-number-format": "^5.1.4",
"react-spring": "^9.7.1",
"react-toastify": "^9.1.1",

92
public/tokens/juno.svg Normal file
View File

@ -0,0 +1,92 @@
<svg xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns="http://www.w3.org/2000/svg" xmlns:svg="http://www.w3.org/2000/svg" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns:cc="http://creativecommons.org/ns#" xmlns:dc="http://purl.org/dc/elements/1.1/" id="svg8" version="1.1" viewBox="0 0 192.6982 188.16626" height="711.17957" width="728.30817" sodipodi:docname="juno.svg" inkscape:version="1.1.1 (c3084ef, 2021-09-22)"><script xmlns=""/><script xmlns="" id="argent-x-extension" data-extension-id="dlcobpjiigpikoobohmabehhmhfoodbb"/>
<sodipodi:namedview id="namedview175" pagecolor="#ffffff" bordercolor="#666666" borderopacity="1.0" inkscape:pageshadow="2" inkscape:pageopacity="0.0" inkscape:pagecheckerboard="0" showgrid="false" inkscape:zoom="0.774" inkscape:cx="370.80103" inkscape:cy="344.96124" inkscape:window-width="1323" inkscape:window-height="958" inkscape:window-x="0" inkscape:window-y="25" inkscape:window-maximized="0" inkscape:current-layer="svg8"/>
<defs id="defs2">
<linearGradient id="linearGradient982" inkscape:swatch="gradient">
<stop id="stop978" offset="0" style="stop-color:#f0827d;stop-opacity:1;"/>
<stop id="stop980" offset="1" style="stop-color:#f0827d;stop-opacity:0;"/>
</linearGradient>
<linearGradient id="linearGradient923">
<stop id="stop919" offset="0" style="stop-color:#000000;stop-opacity:1;"/>
<stop id="stop921" offset="1" style="stop-color:#000000;stop-opacity:0;"/>
</linearGradient>
<linearGradient id="linearGradient915">
<stop id="stop911" offset="0" style="stop-color:#ff6255;stop-opacity:1;"/>
<stop id="stop913" offset="1" style="stop-color:#ff6255;stop-opacity:0;"/>
</linearGradient>
<linearGradient id="linearGradient868" inkscape:swatch="solid">
<stop id="stop866" offset="0" style="stop-color:#000000;stop-opacity:1;"/>
</linearGradient>
<linearGradient id="linearGradient1607" inkscape:swatch="gradient">
<stop id="stop1603" offset="0" style="stop-color:#ff6255;stop-opacity:1"/>
<stop id="stop1605" offset="1" style="stop-color:#da5b51;stop-opacity:0;"/>
</linearGradient>
<linearGradient id="linearGradient6891-7">
<stop style="stop-color:#ff8c8c;stop-opacity:1;" offset="0" id="stop6887"/>
<stop style="stop-color:#ff8c8c;stop-opacity:0;" offset="1" id="stop6889"/>
</linearGradient>
<linearGradient xlink:href="#linearGradient6891-7" id="linearGradient6893" x1="116.28441" y1="131.06845" x2="330.48346" y2="131.06845" gradientUnits="userSpaceOnUse" gradientTransform="matrix(1.0176293,0,0,1.043284,-52.786314,-431.941)"/>
<radialGradient gradientUnits="userSpaceOnUse" gradientTransform="matrix(1,0,0,1.4006544,0,-10.327923)" r="11.343032" fy="25.777636" fx="25.051212" cy="25.777636" cx="25.051212" id="radialGradient1672" xlink:href="#linearGradient923"/>
<linearGradient gradientUnits="userSpaceOnUse" y2="-360.58929" x2="294.47836" y1="-366.6369" x1="-5.883111" id="linearGradient917" xlink:href="#linearGradient915"/>
<linearGradient gradientTransform="translate(-161.77381,-52.916668)" gradientUnits="userSpaceOnUse" y2="137.55695" x2="1492.8649" y1="137.55695" x1="956.32697" id="linearGradient925" xlink:href="#linearGradient923"/>
<radialGradient gradientUnits="userSpaceOnUse" gradientTransform="matrix(1,0,0,1.4006544,0,-10.327923)" r="11.343032" fy="25.777636" fx="25.051212" cy="25.777636" cx="25.051212" id="radialGradient1672-5" xlink:href="#linearGradient923"/>
<radialGradient gradientUnits="userSpaceOnUse" gradientTransform="matrix(1,0,0,1.4006544,0,-10.327923)" r="11.343032" fy="25.777636" fx="25.051212" cy="25.777636" cx="25.051212" id="radialGradient1672-0" xlink:href="#linearGradient923"/>
<radialGradient gradientUnits="userSpaceOnUse" gradientTransform="matrix(1,0,0,1.4006544,0,-10.327923)" r="11.343032" fy="25.777636" fx="25.051212" cy="25.777636" cx="25.051212" id="radialGradient1672-5-6" xlink:href="#linearGradient923"/>
<radialGradient gradientUnits="userSpaceOnUse" gradientTransform="matrix(1,0,0,1.4006544,0,-10.327923)" r="11.343032" fy="25.777636" fx="25.051212" cy="25.777636" cx="25.051212" id="radialGradient1672-8" xlink:href="#linearGradient923"/>
<radialGradient gradientUnits="userSpaceOnUse" gradientTransform="matrix(1,0,0,1.4006544,0,-10.327923)" r="11.343032" fy="25.777636" fx="25.051212" cy="25.777636" cx="25.051212" id="radialGradient1672-4" xlink:href="#linearGradient923"/>
<radialGradient gradientUnits="userSpaceOnUse" gradientTransform="matrix(1,0,0,1.4006544,0,-10.327923)" r="11.343032" fy="25.777636" fx="25.051212" cy="25.777636" cx="25.051212" id="radialGradient1672-5-62" xlink:href="#linearGradient923"/>
<radialGradient gradientUnits="userSpaceOnUse" gradientTransform="matrix(1,0,0,1.4006544,0,-10.327923)" r="11.343032" fy="25.777636" fx="25.051212" cy="25.777636" cx="25.051212" id="radialGradient1672-3" xlink:href="#linearGradient923"/>
<radialGradient gradientUnits="userSpaceOnUse" gradientTransform="matrix(1,0,0,1.4006544,0,-10.327923)" r="11.343032" fy="25.777636" fx="25.051212" cy="25.777636" cx="25.051212" id="radialGradient1672-5-8" xlink:href="#linearGradient923"/>
<radialGradient gradientUnits="userSpaceOnUse" gradientTransform="matrix(1,0,0,1.4006544,0,-10.327923)" r="11.343032" fy="25.777636" fx="25.051212" cy="25.777636" cx="25.051212" id="radialGradient1672-37" xlink:href="#linearGradient923"/>
<radialGradient gradientUnits="userSpaceOnUse" gradientTransform="matrix(1,0,0,1.4006544,0,-10.327923)" r="11.343032" fy="25.777636" fx="25.051212" cy="25.777636" cx="25.051212" id="radialGradient1672-5-5" xlink:href="#linearGradient923"/>
<radialGradient gradientUnits="userSpaceOnUse" gradientTransform="matrix(1,0,0,1.4006544,0,-10.327923)" r="11.343032" fy="25.777636" fx="25.051212" cy="25.777636" cx="25.051212" id="radialGradient1672-0-4" xlink:href="#linearGradient923"/>
<radialGradient gradientUnits="userSpaceOnUse" gradientTransform="matrix(1,0,0,1.4006544,0,-10.327923)" r="11.343032" fy="25.777636" fx="25.051212" cy="25.777636" cx="25.051212" id="radialGradient1672-5-6-1" xlink:href="#linearGradient923"/>
<radialGradient gradientUnits="userSpaceOnUse" gradientTransform="matrix(1,0,0,1.4006544,0,-10.327923)" r="11.343032" fy="25.777636" fx="25.051212" cy="25.777636" cx="25.051212" id="radialGradient1672-8-7" xlink:href="#linearGradient923"/>
<radialGradient gradientUnits="userSpaceOnUse" gradientTransform="matrix(1,0,0,1.4006544,0,-10.327923)" r="11.343032" fy="25.777636" fx="25.051212" cy="25.777636" cx="25.051212" id="radialGradient1672-4-6" xlink:href="#linearGradient923"/>
<radialGradient gradientUnits="userSpaceOnUse" gradientTransform="matrix(1,0,0,1.4006544,0,-10.327923)" r="11.343032" fy="25.777636" fx="25.051212" cy="25.777636" cx="25.051212" id="radialGradient1672-5-62-8" xlink:href="#linearGradient923"/>
<radialGradient gradientUnits="userSpaceOnUse" gradientTransform="matrix(1,0,0,1.4006544,0,-10.327923)" r="11.343032" fy="25.777636" fx="25.051212" cy="25.777636" cx="25.051212" id="radialGradient1672-3-6" xlink:href="#linearGradient923"/>
<radialGradient gradientUnits="userSpaceOnUse" gradientTransform="matrix(1,0,0,1.4006544,0,-10.327923)" r="11.343032" fy="25.777636" fx="25.051212" cy="25.777636" cx="25.051212" id="radialGradient1672-5-8-6" xlink:href="#linearGradient923"/>
<radialGradient gradientUnits="userSpaceOnUse" gradientTransform="matrix(1,0,0,1.4006544,0,-10.327923)" r="11.343032" fy="25.777636" fx="25.051212" cy="25.777636" cx="25.051212" id="radialGradient1672-2" xlink:href="#linearGradient923"/>
<radialGradient gradientUnits="userSpaceOnUse" gradientTransform="matrix(1,0,0,1.4006544,0,-10.327923)" r="11.343032" fy="25.777636" fx="25.051212" cy="25.777636" cx="25.051212" id="radialGradient1672-5-0" xlink:href="#linearGradient923"/>
<radialGradient gradientUnits="userSpaceOnUse" gradientTransform="matrix(1,0,0,1.4006544,0,-10.327923)" r="11.343032" fy="25.777636" fx="25.051212" cy="25.777636" cx="25.051212" id="radialGradient1672-0-9" xlink:href="#linearGradient923"/>
<radialGradient gradientUnits="userSpaceOnUse" gradientTransform="matrix(1,0,0,1.4006544,0,-10.327923)" r="11.343032" fy="25.777636" fx="25.051212" cy="25.777636" cx="25.051212" id="radialGradient1672-5-6-4" xlink:href="#linearGradient923"/>
<radialGradient gradientUnits="userSpaceOnUse" gradientTransform="matrix(1,0,0,1.4006544,0,-10.327923)" r="11.343032" fy="25.777636" fx="25.051212" cy="25.777636" cx="25.051212" id="radialGradient1672-8-0" xlink:href="#linearGradient923"/>
<radialGradient gradientUnits="userSpaceOnUse" gradientTransform="matrix(1,0,0,1.4006544,0,-10.327923)" r="11.343032" fy="25.777636" fx="25.051212" cy="25.777636" cx="25.051212" id="radialGradient1672-4-9" xlink:href="#linearGradient923"/>
<radialGradient gradientUnits="userSpaceOnUse" gradientTransform="matrix(1,0,0,1.4006544,0,-10.327923)" r="11.343032" fy="25.777636" fx="25.051212" cy="25.777636" cx="25.051212" id="radialGradient1672-5-62-5" xlink:href="#linearGradient923"/>
<radialGradient gradientUnits="userSpaceOnUse" gradientTransform="matrix(1,0,0,1.4006544,0,-10.327923)" r="11.343032" fy="25.777636" fx="25.051212" cy="25.777636" cx="25.051212" id="radialGradient1672-3-1" xlink:href="#linearGradient923"/>
<radialGradient gradientUnits="userSpaceOnUse" gradientTransform="matrix(1,0,0,1.4006544,0,-10.327923)" r="11.343032" fy="25.777636" fx="25.051212" cy="25.777636" cx="25.051212" id="radialGradient1672-5-8-8" xlink:href="#linearGradient923"/>
</defs>
<metadata id="metadata5">
<rdf:RDF>
<cc:Work rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type rdf:resource="http://purl.org/dc/dcmitype/StillImage"/>
<dc:title/>
</cc:Work>
</rdf:RDF>
</metadata>
<g transform="translate(-34.189243,-73.416721)" id="layer1">
<ellipse ry="90.662651" rx="92.928627" style="opacity:1;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:13.7355;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;paint-order:normal" id="circle1199" cx="130.53835" cy="169.52222"/>
<ellipse ry="90.662651" rx="92.928627" cy="167.49985" cx="130.53835" id="path1185" style="opacity:1;fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:#ffffff;stroke-width:6.84095;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;paint-order:normal"/>
<g transform="matrix(2.4170273,0,0,2.5557268,70.439041,106.28282)" id="g1265" style="clip-rule:evenodd;fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2">
<g style="fill:#ffffff;fill-opacity:1" transform="matrix(0.721351,0,0,0.811444,6.54562,5.10326)" id="g1229">
<path style="fill:#ffffff;fill-opacity:1" d="m 27.411,7.92 c 0,-0.84 -0.766,-1.521 -1.711,-1.521 0,0 -10e-4,0 -0.002,0 -0.944,0 -1.71,0.681 -1.71,1.521 0,6.129 0,28.987 0,35.117 0,0.84 0.766,1.521 1.71,1.521 0.001,0 0.002,0 0.002,0 0.945,0 1.711,-0.681 1.711,-1.521 0,-6.13 0,-28.988 0,-35.117 z" id="path1227"/>
</g>
<g style="fill:#ffffff;fill-opacity:1" transform="matrix(5.99295e-4,-0.721351,0.258566,2.14816e-4,18.4279,54.7844)" id="g1233">
<path style="fill:#ffffff;fill-opacity:1" d="m 27.411,11.171 c 0,-2.635 -0.766,-4.772 -1.711,-4.772 0,0 -10e-4,0 -0.002,0 -0.944,0 -1.71,2.137 -1.71,4.772 0,7.331 0,21.283 0,28.614 0,2.636 0.766,4.773 1.71,4.773 0.001,0 0.002,0 0.002,0 0.945,0 1.711,-2.137 1.711,-4.773 0,-7.331 0,-21.283 0,-28.614 z" id="path1231"/>
</g>
<g style="fill:#ffffff;fill-opacity:1" transform="matrix(5.99295e-4,-0.721351,0.579332,4.81307e-4,10.2753,38.6396)" id="g1237">
<path style="fill:#ffffff;fill-opacity:1" d="m 27.411,8.529 c 0,-1.177 -0.766,-2.13 -1.711,-2.13 0,0 -10e-4,0 -0.002,0 -0.944,0 -1.71,0.953 -1.71,2.13 0,6.687 0,27.212 0,33.899 0,1.176 0.766,2.13 1.71,2.13 0.001,0 0.002,0 0.002,0 0.945,0 1.711,-0.954 1.711,-2.13 0,-6.687 0,-27.212 0,-33.899 z" id="path1235"/>
</g>
<g style="fill:#ffffff;fill-opacity:1" transform="matrix(0.509521,-0.510622,0.393855,0.393005,1.94156,23.2426)" id="g1241">
<path style="fill:#ffffff;fill-opacity:1" d="m 27.411,8.617 c 0,-1.225 -0.766,-2.218 -1.711,-2.218 0,0 -10e-4,0 -0.002,0 -0.944,0 -1.71,0.993 -1.71,2.218 0,6.747 0,26.976 0,33.723 0,1.225 0.766,2.218 1.71,2.218 0.001,0 0.002,0 0.002,0 0.945,0 1.711,-0.993 1.711,-2.218 0,-6.747 0,-26.976 0,-33.723 z" id="path1239"/>
</g>
<g style="fill:#ffffff;fill-opacity:1" transform="matrix(-0.51096,-0.509182,0.392744,-0.394115,28.1955,43.2602)" id="g1245">
<path style="fill:#ffffff;fill-opacity:1" d="m 27.411,8.617 c 0,-1.225 -0.766,-2.218 -1.711,-2.218 0,0 -10e-4,0 -0.002,0 -0.944,0 -1.71,0.993 -1.71,2.218 0,6.747 0,26.976 0,33.723 0,1.225 0.766,2.218 1.71,2.218 0.001,0 0.002,0 0.002,0 0.945,0 1.711,-0.993 1.711,-2.218 0,-6.747 0,-26.976 0,-33.723 z" id="path1243"/>
</g>
</g>
<ellipse ry="79.882423" rx="81.878975" cy="168.1187" cx="130.57381" id="path1267" style="opacity:1;fill:none;fill-opacity:1;fill-rule:nonzero;stroke:#f0827d;stroke-width:3.92991;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;paint-order:normal"/>
</g>
<style type="text/css" id="style1053">
.st0{fill:#043BEA;}
</style>
</svg>

After

Width:  |  Height:  |  Size: 13 KiB

View File

@ -61,7 +61,7 @@ export const AccountNavigation = () => {
variant='solid'
color='tertiary'
className='flex flex-1 flex-nowrap'
icon={<Account />}
leftIcon={<Account />}
onClick={() => setShowMenu(!showMenu)}
hasSubmenu
>
@ -77,7 +77,7 @@ export const AccountNavigation = () => {
<Button
className='flex w-[115px] items-center justify-center pl-0 pr-2'
text='Fund'
icon={<ArrowUpLine />}
leftIcon={<ArrowUpLine />}
onClick={() => {
useStore.setState({ fundAccountModal: true })
setShowMenu(false)
@ -86,7 +86,7 @@ export const AccountNavigation = () => {
<Button
className='flex w-[115px] items-center justify-center pl-0 pr-2'
color='secondary'
icon={<ArrowDownLine />}
leftIcon={<ArrowDownLine />}
text='Withdraw'
onClick={() => {
useStore.setState({ withdrawModal: true })
@ -104,7 +104,7 @@ export const AccountNavigation = () => {
setShowMenu(false)
createAccountHandler()
}}
icon={<Add />}
leftIcon={<Add />}
/>
<Button
className='w-full whitespace-nowrap py-2'
@ -115,7 +115,7 @@ export const AccountNavigation = () => {
setShowMenu(false)
deleteAccountHandler()
}}
icon={<Rubbish />}
leftIcon={<Rubbish />}
/>
<Button
className='w-full whitespace-nowrap py-2'
@ -126,7 +126,7 @@ export const AccountNavigation = () => {
setShowMenu(false)
/* TODO: add Transfer Balance Function */
}}
icon={<ArrowsLeftRight />}
leftIcon={<ArrowsLeftRight />}
/>
</div>
</div>
@ -156,7 +156,7 @@ export const AccountNavigation = () => {
</Overlay>
</div>
) : (
<Button onClick={createAccountHandler} icon={<Add />} color='tertiary'>
<Button onClick={createAccountHandler} leftIcon={<Add />} color='tertiary'>
Create Account
</Button>
)}

View File

@ -0,0 +1,12 @@
import Card from 'components/Card'
export default function AccountSummary() {
return (
<Card className='min-w-fit bg-white/10' contentClassName='flex'>
<div className='border-r border-r-white/10 px-4 py-2'>Hello</div>
<div className='border-r border-r-white/10 px-4 py-2'>Hello</div>
<div className='border-r border-r-white/10 px-4 py-2'>Hello</div>
<div className='px-4 py-2'>Hello</div>
</Card>
)
}

View File

@ -1,40 +0,0 @@
'use client'
import classNames from 'classnames'
import { CircularProgress } from 'components/CircularProgress'
import { MarsProtocol } from 'components/Icons'
import { Modal } from 'components/Modal'
import { Text } from 'components/Text'
import useStore from 'store'
export const ConfirmModal = () => {
const createOpen = useStore((s) => s.createAccountModal)
const deleteOpen = useStore((s) => s.deleteAccountModal)
return (
<Modal title='Confirm' open={createOpen || deleteOpen}>
<div
className={classNames(
'relative flex h-[630px] w-full flex-wrap items-center justify-center p-6',
createOpen && 'bg-create-modal',
deleteOpen && 'bg-delete-modal',
)}
>
<div className='w-full flex-wrap'>
<div className='flex w-full justify-center pb-6'>
<CircularProgress size={40} />
</div>
<Text size='2xl' uppercase={true} className='w-full text-center'>
{createOpen &&
'A small step for a Smart Contract but a big leap for your financial freedom.'}
{deleteOpen && 'Some rovers have to be recycled once in a while...'}
</Text>
</div>
<div className='absolute bottom-8 left-8 flex w-[150px]'>
<MarsProtocol />
</div>
</div>
</Modal>
)
}

View File

@ -1,219 +0,0 @@
'use client'
import { useEffect, useMemo, useState } from 'react'
import useSWR from 'swr'
import { Button } from 'components/Button'
import { CircularProgress } from 'components/CircularProgress'
import { MarsProtocol } from 'components/Icons'
import { Modal } from 'components/Modal'
import { Slider } from 'components/Slider'
import Switch from 'components/Switch'
import { Text } from 'components/Text'
import useParams from 'hooks/useParams'
import useStore from 'store'
import { getWalletBalances } from 'utils/api'
import { getMarketAssets } from 'utils/assets'
import { hardcodedFee } from 'utils/contants'
import { convertFromGwei, convertToGwei } from 'utils/formatters'
import { getTokenDecimals, getTokenSymbol } from 'utils/tokens'
export const FundAccountModal = () => {
// ---------------
// STORE
// ---------------
const open = useStore((s) => s.fundAccountModal)
const params = useParams()
const depositCreditAccount = useStore((s) => s.depositCreditAccount)
const address = useStore((s) => s.client?.recentWallet.account?.address)
const { data: balancesData, isLoading: balanceIsLoading } = useSWR(address, getWalletBalances)
const selectedAccount = useStore((s) => s.selectedAccount)
const marketAssets = getMarketAssets()
const [lendAssets, setLendAssets] = useState(false)
// ---------------
// LOCAL STATE
// ---------------
const [amount, setAmount] = useState(0)
const [selectedToken, setSelectedToken] = useState('')
// ---------------
// FUNCTIONS
// ---------------
async function depositAccountHandler() {
if (!selectedToken) return
const deposit = {
amount: convertToGwei(amount, selectedToken, marketAssets).toString(),
denom: selectedToken,
}
const isSuccess = await depositCreditAccount({
fee: hardcodedFee,
accountId: params.account,
deposit,
})
if (isSuccess) {
useStore.setState({ fundAccountModal: false })
}
}
useEffect(() => {
if (!marketAssets || !balancesData || selectedToken !== '') return
let found = false
marketAssets.map((asset) => {
if (found) return
if (balancesData?.find((balance) => balance.denom === asset.denom)?.amount ?? 0 > 0) {
setSelectedToken(asset.denom)
found = true
}
})
}, [balancesData, marketAssets, selectedToken])
// ---------------
// VARIABLES
// ---------------
const walletAmount = useMemo(() => {
if (!selectedToken) return 0
const walletAmount =
balancesData?.find((balance) => balance.denom === selectedToken)?.amount ?? 0
return convertFromGwei(walletAmount, selectedToken, marketAssets)
}, [balancesData, selectedToken, marketAssets])
const handleValueChange = (value: number) => {
if (value > walletAmount) {
setAmount(walletAmount)
return
}
setAmount(value)
}
const setOpen = (open: boolean) => {
useStore.setState({ fundAccountModal: open })
}
const percentageValue = isNaN(amount) ? 0 : (amount * 100) / walletAmount
return (
<Modal title='Fund account' open={open} setOpen={setOpen}>
<div className='flex min-h-[520px] w-full'>
{balanceIsLoading && (
<div className='absolute inset-0 z-40 grid place-items-center bg-black/50'>
<CircularProgress />
</div>
)}
<div className='flex flex-1 flex-col items-start justify-between bg-fund-modal bg-cover p-6'>
<div>
<Text size='2xs' uppercase className='mb-3 text-white'>
Fund
</Text>
<Text size='lg' className='mb-4 text-white'>
Fund your Account to enable its borrowing and lending capabilities.
</Text>
</div>
<div className='w-[153px] text-white'>
<MarsProtocol />
</div>
</div>
<div className='flex flex-1 flex-col p-6'>
<Text size='xl' uppercase className='mb-4 text-white'>
Account {selectedAccount}
</Text>
<div className='p-3" mb-2'>
<Text size='sm' uppercase className='mb-1 text-white'>
Fund Account
</Text>
<Text size='sm' className='mb-6 text-white/60'>
Transfer assets from your osmosis wallet to your Mars credit account. If you dont
have any assets in your osmosis wallet use the osmosis bridge to transfer funds to
your osmosis wallet.
</Text>
<>
<div className='mb-4 rounded-base border border-white/20'>
<div className='mb-1 flex justify-between border-b border-white/20 p-2'>
<Text size='sm' className='text-white'>
Asset:
</Text>
<select
className='bg-transparent text-white outline-0'
onChange={(e) => {
setSelectedToken(e.target.value)
if (e.target.value !== selectedToken) setAmount(0)
}}
value={selectedToken}
>
{marketAssets?.map((entry) => {
return (
balancesData?.find((balance) => balance.denom === selectedToken)
?.amount && (
<option key={entry.denom} value={entry.denom}>
{entry.symbol}
</option>
)
)
})}
</select>
</div>
<div className='flex justify-between p-2'>
<Text size='sm' className='text-white'>
Amount:
</Text>
<input
type='number'
className='appearance-none bg-transparent text-right text-white'
value={amount}
min='0'
onChange={(e) => handleValueChange(e.target.valueAsNumber)}
onBlur={(e) => {
if (e.target.value === '') setAmount(0)
}}
/>
</div>
</div>
</>
<Text size='xs' uppercase className='mb-2 text-white/60'>
{`In wallet: ${walletAmount.toLocaleString()} ${getTokenSymbol(
selectedToken,
marketAssets,
)}`}
</Text>
<Slider
className='mb-6'
value={percentageValue}
onChange={(value) => {
const decimal = value[0] / 100
const tokenDecimals = getTokenDecimals(selectedToken, marketAssets)
// limit decimal precision based on token contract decimals
const newAmount = Number((decimal * walletAmount).toFixed(tokenDecimals))
setAmount(newAmount)
}}
onMaxClick={() => setAmount(walletAmount)}
/>
</div>
<div className='mb-2 flex items-center justify-between'>
<div>
<Text size='sm' uppercase className='mb-1 text-white'>
Lending Assets
</Text>
<Text size='sm' className='text-white/60'>
Lend assets from account to earn yield.
</Text>
</div>
<Switch name='lendAssets' checked={lendAssets} onChange={setLendAssets} />
</div>
<Button
className='mt-auto w-full'
onClick={depositAccountHandler}
disabled={amount === 0 || !amount}
>
Fund Account
</Button>
</div>
</div>
</Modal>
)
}

View File

@ -1,321 +0,0 @@
import BigNumber from 'bignumber.js'
import classNames from 'classnames'
import React, { useEffect, useMemo, useState } from 'react'
import { BorrowCapacity } from 'components/BorrowCapacity'
import { Button } from 'components/Button'
import { CircularProgress } from 'components/CircularProgress'
import { FormattedNumber } from 'components/FormattedNumber'
import { Gauge } from 'components/Gauge'
import { LabelValuePair } from 'components/LabelValuePair'
import { Modal } from 'components/Modal'
import { Slider } from 'components/Slider'
import { Text } from 'components/Text'
import { useAccountStats } from 'hooks/data/useAccountStats'
import { useCalculateMaxWithdrawAmount } from 'hooks/data/useCalculateMaxWithdrawAmount'
import { useWithdrawFunds } from 'hooks/mutations/useWithdrawFunds'
import { useCreditAccountPositions } from 'hooks/queries/useCreditAccountPositions'
import { useTokenPrices } from 'hooks/queries/useTokenPrices'
import useStore from 'store'
import { getBaseAsset, getMarketAssets } from 'utils/assets'
import { convertFromGwei, formatLeverage, formatValue } from 'utils/formatters'
import { getTokenDecimals, getTokenSymbol } from 'utils/tokens'
import Switch from 'components/Switch'
export const WithdrawModal = () => {
// ---------------
// STORE
// ---------------
const open = useStore((s) => s.withdrawModal)
const selectedAccount = useStore((s) => s.selectedAccount)
const { data: positionsData } = useCreditAccountPositions(selectedAccount ?? '')
const marketAssets = getMarketAssets()
const baseAsset = getBaseAsset()
// ---------------
// LOCAL STATE
// ---------------
const [amount, setAmount] = useState(0)
const [selectedToken, setSelectedToken] = useState('')
const [isBorrowEnabled, setIsBorrowEnabled] = useState(false)
// ---------------
// EXTERNAL HOOKS
// ---------------
const { data: tokenPrices } = useTokenPrices()
const selectedTokenSymbol = getTokenSymbol(selectedToken, marketAssets)
const selectedTokenDecimals = getTokenDecimals(selectedToken, marketAssets)
const tokenAmountInCreditAccount = useMemo(() => {
return BigNumber(positionsData?.coins.find((coin) => coin.denom === selectedToken)?.amount ?? 0)
.div(10 ** selectedTokenDecimals)
.toNumber()
}, [positionsData, selectedTokenDecimals, selectedToken])
const { actions, borrowAmount, withdrawAmount } = useMemo(() => {
const borrowAmount =
amount > tokenAmountInCreditAccount
? BigNumber(amount)
.minus(tokenAmountInCreditAccount)
.times(10 ** selectedTokenDecimals)
.toNumber()
: 0
const withdrawAmount = BigNumber(amount)
.times(10 ** selectedTokenDecimals)
.toNumber()
return {
borrowAmount,
withdrawAmount,
actions: [
{
type: 'borrow',
amount: borrowAmount,
denom: selectedToken,
},
{
type: 'withdraw',
amount: withdrawAmount,
denom: selectedToken,
},
] as AccountStatsAction[],
}
}, [amount, selectedToken, selectedTokenDecimals, tokenAmountInCreditAccount])
const accountStats = useAccountStats(actions)
const { mutate, isLoading } = useWithdrawFunds(withdrawAmount, borrowAmount, selectedToken, {
onSuccess: () => {
useStore.setState({
withdrawModal: false,
toast: {
message: `${amount} ${selectedTokenSymbol} successfully withdrawn`,
},
})
},
})
const maxWithdrawAmount = useCalculateMaxWithdrawAmount(selectedToken, isBorrowEnabled)
useEffect(() => {
if (positionsData && positionsData.coins.length > 0) {
// initialize selected token when allowedCoins fetch data is available
setSelectedToken(positionsData.coins[0].denom)
}
}, [positionsData])
const handleTokenChange = (e: React.ChangeEvent<HTMLSelectElement>) => {
setSelectedToken(e.target.value)
if (e.target.value !== selectedToken) setAmount(0)
}
const handleValueChange = (value: number) => {
if (value > maxWithdrawAmount) {
setAmount(maxWithdrawAmount)
return
}
setAmount(value)
}
const handleBorrowChange = () => {
setIsBorrowEnabled((c) => !c)
// reset amount due to max value calculations changing depending on wheter the user is borrowing or not
setAmount(0)
}
const getTokenTotalUSDValue = (amount: string, denom: string, marketAssets: Asset[]) => {
// early return if prices are not fetched yet
if (!tokenPrices) return 0
return (
BigNumber(amount)
.div(10 ** getTokenDecimals(denom, marketAssets))
.toNumber() * tokenPrices[denom]
)
}
const percentageValue = useMemo(() => {
if (isNaN(amount) || maxWithdrawAmount === 0) return 0
return (amount * 100) / maxWithdrawAmount
}, [amount, maxWithdrawAmount])
const setOpen = (open: boolean) => {
useStore.setState({ withdrawModal: open })
}
return (
<Modal title='Withdraw' open={open} setOpen={setOpen}>
<div className='flex min-h-[470px] w-full flex-wrap'>
{isLoading && (
<div className='absolute inset-0 z-40 grid place-items-center bg-black/50'>
<CircularProgress />
</div>
)}
<Text
className='flex w-full border-b border-white/20 px-8 pt-4 pb-2 text-white'
size='2xl'
uppercase
>
Withdraw from Account {selectedAccount}
</Text>
<div className='flex w-full'>
<div className='flex flex-1 flex-col border-r border-white/20'>
<div className='border-b border-white/20 p-6'>
<div className='mb-4 rounded-base border border-white/20'>
<div className='mb-1 flex justify-between border-b border-white/20 p-2'>
<Text size='sm' className='text-white'>
Asset:
</Text>
<select
className='bg-transparent text-white outline-0'
onChange={handleTokenChange}
value={selectedToken}
>
{positionsData?.coins?.map((coin) => (
<option key={coin.denom} value={coin.denom}>
{getTokenSymbol(coin.denom, marketAssets)}
</option>
))}
</select>
</div>
<div className='flex justify-between p-2'>
<Text size='sm' className='text-white'>
Amount:
</Text>
<input
type='number'
className='appearance-none bg-transparent text-right text-white'
value={amount}
min='0'
onChange={(e) => handleValueChange(e.target.valueAsNumber)}
onBlur={(e) => {
if (e.target.value === '') setAmount(0)
}}
/>
</div>
</div>
<Text size='xs' uppercase className='mb-2 text-white/60'>
Available: {formatValue(maxWithdrawAmount, { minDecimals: 0, maxDecimals: 4 })}
</Text>
<Slider
className='mb-6'
value={percentageValue}
onChange={(value) => {
const decimal = value[0] / 100
// limit decimal precision based on token contract decimals
const newAmount = Number(
(decimal * maxWithdrawAmount).toFixed(selectedTokenDecimals),
)
setAmount(newAmount)
}}
onMaxClick={() => setAmount(maxWithdrawAmount)}
/>
</div>
<div className='flex items-center justify-between p-6'>
<div className='flex flex-1 flex-wrap'>
<Text size='sm' className='text-white' uppercase>
Withdraw with borrowing
</Text>
<Text size='sm' className='text-white/60'>
Borrow assets from account to withdraw to your wallet
</Text>
</div>
<Switch name='borrowAssets' checked={isBorrowEnabled} onChange={handleBorrowChange} />
</div>
<div className='flex p-6'>
<Button className='mt-auto w-full' onClick={() => mutate()}>
Withdraw
</Button>
</div>
</div>
<div className='flex w-[430px] flex-wrap content-start bg-black/60'>
<div className='flex w-full flex-wrap border-b border-white/20 p-6'>
<Text size='xl' className='mb-4 flex w-full text-white'>
Account {selectedAccount}
</Text>
{accountStats && (
<div className='flex w-full items-center gap-x-3'>
<Text size='sm' className='flex flex-grow text-white'>
<FormattedNumber
amount={BigNumber(accountStats.netWorth)
.dividedBy(10 ** baseAsset.decimals)
.toNumber()}
options={{ prefix: '$: ' }}
animate
/>
</Text>
<Gauge
value={accountStats.currentLeverage / accountStats.maxLeverage}
label='Lvg'
tooltip={
<Text size='sm'>
Current Leverage: {formatLeverage(accountStats.currentLeverage)}
<br />
Max Leverage: {formatLeverage(accountStats.maxLeverage)}
</Text>
}
/>
<Gauge
value={accountStats.risk}
label='Risk'
tooltip={
<Text size='sm'>
Current Risk:{' '}
{formatValue(accountStats.risk * 100, { minDecimals: 0, suffix: '%' })}
</Text>
}
/>
<BorrowCapacity
limit={80}
max={100}
balance={100 - accountStats.health * 100}
barHeight='16px'
hideValues={true}
showTitle={false}
className='w-[140px]'
/>
</div>
)}
</div>
<div className='flex w-full flex-wrap border-b border-white/20 p-6'>
<LabelValuePair
className='mb-2'
label='Total Position:'
value={{
format: 'number',
amount: convertFromGwei(
accountStats?.totalPosition ?? 0,
baseAsset.denom,
marketAssets,
),
prefix: '$',
}}
/>
<LabelValuePair
label='Total Liabilities:'
value={{
format: 'number',
amount: convertFromGwei(
accountStats?.totalDebt ?? 0,
baseAsset.denom,
marketAssets,
),
prefix: '$',
}}
/>
</div>
</div>
</div>
</div>
</Modal>
)
}

View File

@ -1,9 +1,8 @@
import { Suspense } from 'react'
import { Card } from 'components/Card'
import Card from 'components/Card'
import { getAccountDebts, getBorrowData } from 'utils/api'
import { getMarketAssets } from 'utils/assets'
import { BorrowTable } from 'components/Borrow/BorrowTable'
interface Props extends PageProps {
@ -46,7 +45,7 @@ async function Content(props: Props) {
if (props.type === 'active') {
return (
<Card
className='h-fit w-full'
className='h-fit w-full bg-white/5'
title={props.type === 'active' ? 'Borrowings' : 'Available to borrow'}
>
<BorrowTable data={assets} />
@ -71,7 +70,7 @@ function Fallback() {
export function AvailableBorrowings(props: PageProps) {
return (
<Card className='h-fit w-full' title={'Available to borrow'}>
<Card className='h-fit w-full bg-white/5' title={'Available to borrow'}>
<Suspense fallback={<Fallback />}>
{/* @ts-expect-error Server Component */}
<Content params={props.params} type='available' />

View File

@ -1,26 +1,57 @@
import Image from 'next/image'
import { useCallback, useState } from 'react'
import BigNumber from 'bignumber.js'
import { Modal } from 'components/Modal'
import TitleAndSubCell from 'components/TitleAndSubCell'
import { usePathname } from 'next/navigation'
import useStore from 'store'
import Image from 'next/image'
import { Text } from 'components/Text'
import { formatPercent, formatValue } from 'utils/formatters'
import Slider from 'components/Slider'
import AccountSummary from 'components/Account/AccountSummary'
import Card from 'components/Card'
import Divider from 'components/Divider'
import TokenInput from 'components/TokenInput'
import { Button } from 'components/Button'
import { ArrowRight } from 'components/Icons'
export default function BorrowModal() {
const modal = useStore((s) => s.borrowModal)
const [percentage, setPercentage] = useState(0)
const [value, setValue] = useState(0)
const onSliderChange = useCallback(
(percentage: number) => onPercentageChange(percentage),
[onPercentageChange],
)
const onInputChange = useCallback((value: number) => onValueChange(value), [onValueChange])
function setOpen(isOpen: boolean) {
useStore.setState({ borrowModal: null })
}
function onBorrowClick() {}
function onPercentageChange(percentage: number) {
setPercentage(percentage)
setValue(new BigNumber(percentage).div(100).times(liquidityAmount).toNumber())
}
function onValueChange(value: number) {
setValue(value)
setPercentage(new BigNumber(value).div(liquidityAmount).times(100).toNumber())
}
if (!modal) return null
const liquidityAmount: string = formatValue(modal.marketData.liquidity?.amount || '0', {
const liquidityAmount = Number(modal.marketData.liquidity?.amount || 0)
const liquidityAmountString: string = formatValue(liquidityAmount, {
abbreviated: true,
decimals: 6,
})
const liquidityValue: string = formatValue(modal.marketData.liquidity?.value || '0', {
const liquidityValue = Number(modal.marketData.liquidity?.value || 0)
const liquidityValueString: string = formatValue(liquidityValue, {
abbreviated: true,
decimals: 6,
})
@ -29,14 +60,16 @@ export default function BorrowModal() {
<Modal
open={true}
setOpen={setOpen}
title={
<span className='flex items-center gap-4'>
header={
<span className='flex items-center gap-4 px-4'>
<Image src={modal?.asset.logo} alt='token' width={24} height={24} />
<Text>Borrow {modal.asset.symbol}</Text>
</span>
}
headerClassName='gradient-header pl-2 pr-2.5 py-2.5 border-b-white/5 border-b'
contentClassName='flex flex-col'
>
<div className='flex gap-3'>
<div className='flex gap-3 border-b border-b-white/5 px-6 py-4 gradient-header'>
<TitleAndSubCell
title={formatPercent(modal.marketData.borrowRate || '0')}
sub={'Borrow rate'}
@ -45,11 +78,29 @@ export default function BorrowModal() {
<TitleAndSubCell title={'$0'} sub={'Borrowed'} />
<div className='h-100 w-[1px] bg-white/10'></div>
<TitleAndSubCell
title={`${liquidityAmount} (${liquidityValue})`}
title={`${liquidityAmountString} (${liquidityValueString})`}
sub={'Liquidity available'}
/>
</div>
<div className='flex'></div>
<div className='flex items-start gap-6 p-6'>
<Card className='w-full bg-white/5 p-4' contentClassName='gap-6 flex flex-col'>
<TokenInput
asset={modal.asset}
onChange={onInputChange}
value={value}
max={liquidityAmount}
/>
<Slider value={percentage} onChange={onSliderChange} />
<Divider />
<Button
onClick={onBorrowClick}
className='w-full'
text='Borrow'
rightIcon={<ArrowRight />}
/>
</Card>
<AccountSummary />
</div>
</Modal>
)
}

View File

@ -16,7 +16,8 @@ interface Props {
text?: string | ReactNode
variant?: 'solid' | 'transparent' | 'round'
onClick?: (e: React.MouseEvent<HTMLButtonElement>) => void
icon?: ReactElement
leftIcon?: ReactElement
rightIcon?: ReactElement
iconClassName?: string
hasSubmenu?: boolean
hasFocus?: boolean
@ -110,7 +111,8 @@ export const Button = React.forwardRef(function Button(
text,
variant = 'solid',
onClick,
icon,
leftIcon,
rightIcon,
iconClassName,
hasSubmenu,
hasFocus,
@ -164,7 +166,7 @@ export const Button = React.forwardRef(function Button(
ref={ref as LegacyRef<HTMLButtonElement>}
onClick={disabled ? () => {} : onClick}
>
{icon && !showProgressIndicator && (
{leftIcon && !showProgressIndicator && (
<span
className={classNames(
'flex items-center justify-center',
@ -172,11 +174,22 @@ export const Button = React.forwardRef(function Button(
iconClassName ?? 'h-4 w-4',
)}
>
{icon}
{leftIcon}
</span>
)}
{text && !children && !showProgressIndicator && <span>{text}</span>}
{children && !showProgressIndicator && children}
{rightIcon && !showProgressIndicator && (
<span
className={classNames(
'flex items-center justify-center',
(text || children) && 'ml-2',
iconClassName ?? 'h-4 w-4',
)}
>
{rightIcon}
</span>
)}
{hasSubmenu && !showProgressIndicator && (
<span className='ml-2 inline-block w-2.5'>
<ChevronDown />

View File

@ -10,12 +10,12 @@ interface Props {
contentClassName?: string
}
export const Card = (props: Props) => {
export default function Card(props: Props) {
return (
<section
className={classNames(
props.className,
'relative z-1 flex max-w-full flex-col flex-wrap items-start overflow-hidden rounded-base bg-white/5',
'relative z-1 flex max-w-full flex-wrap items-start overflow-hidden rounded-base',
'before:content-[" "] before:absolute before:inset-0 before:-z-1 before:rounded-base before:p-[1px] before:border-glas',
)}
>

View File

@ -1,6 +1,6 @@
import { Suspense } from 'react'
import { Card } from 'components/Card'
import Card from 'components/Card'
import Loading from 'components/Loading'
import { Text } from 'components/Text'
@ -20,7 +20,11 @@ function Fallback() {
export default function Overview(props: PageProps) {
return (
<Card className='h-fit w-full justify-center' title='Council' contentClassName='px-4 py-6'>
<Card
className='h-fit w-full justify-center bg-white/5'
title='Council'
contentClassName='px-4 py-6'
>
<Suspense fallback={<Fallback />}>
{/* @ts-expect-error Server Component */}
<Content params={props.params} />

View File

@ -0,0 +1,10 @@
interface Props {
orientation?: 'horizontal' | 'vertical'
}
export default function Divider(props: Props) {
if (props.orientation === 'vertical') {
return <div className='h-full w-[1px] bg-white/10'></div>
}
return <div className='h-[1px] w-full bg-white/10'></div>
}

View File

@ -1,6 +1,6 @@
import { Suspense } from 'react'
import { Card } from 'components/Card'
import Card from 'components/Card'
import Loading from 'components/Loading'
import { Text } from 'components/Text'
@ -22,7 +22,11 @@ function Fallback() {
export default function Overview(props: PageProps) {
return (
<Card className='h-fit w-full justify-center' title='Earn' contentClassName='px-4 py-6'>
<Card
className='h-fit w-full justify-center bg-white/5'
title='Earn'
contentClassName='px-4 py-6'
>
<Suspense fallback={<Fallback />}>
{/* @ts-expect-error Server Component */}
<Content params={props.params} />

View File

@ -0,0 +1,3 @@
<svg viewBox="0 0 13 12" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M1.8335 6.00065H11.1668M11.1668 6.00065L6.50016 1.33398M11.1668 6.00065L6.50016 10.6673" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

After

Width:  |  Height:  |  Size: 266 B

View File

@ -0,0 +1,3 @@
<svg viewBox="0 0 8 6" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M4.86824 5.48057C4.48435 6.15239 3.51565 6.15239 3.13176 5.48057L0 0L8 0L4.86824 5.48057Z" fill="currentColor"/>
</svg>

After

Width:  |  Height:  |  Size: 200 B

View File

@ -5,6 +5,7 @@ export { default as ArrowBack } from 'components/Icons/ArrowBack.svg'
export { default as ArrowDown } from 'components/Icons/ArrowDown.svg'
export { default as ArrowDownLine } from 'components/Icons/ArrowDownLine.svg'
export { default as ArrowLeftLine } from 'components/Icons/ArrowLeftLine.svg'
export { default as ArrowRight } from 'components/Icons/ArrowRight.svg'
export { default as ArrowRightLine } from 'components/Icons/ArrowRightLine.svg'
export { default as ArrowsLeftRight } from 'components/Icons/ArrowsLeftRight.svg'
export { default as ArrowsUpDown } from 'components/Icons/ArrowsUpDown.svg'
@ -35,6 +36,7 @@ export { default as Questionmark } from 'components/Icons/Questionmark.svg'
export { default as Reddit } from 'components/Icons/Reddit.svg'
export { default as Rubbish } from 'components/Icons/Rubbish.svg'
export { default as Search } from 'components/Icons/Search.svg'
export { default as SliderMark } from 'components/Icons/SliderMark.svg'
export { default as SmallClose } from 'components/Icons/SmallClose.svg'
export { default as SortAsc } from 'components/Icons/SortAsc.svg'
export { default as SortDesc } from 'components/Icons/SortDesc.svg'

View File

@ -4,12 +4,15 @@ import { ReactNode } from 'react'
import { Close } from 'components/Icons'
import { Text } from 'components/Text'
import { Button } from 'components/Button'
import Card from 'components/Card'
interface Props {
title: string | ReactNode
header: string | ReactNode
headerClassName?: string
children?: ReactNode | string
content?: ReactNode | string
className?: string
contentClassName?: string
open: boolean
setOpen?: (open: boolean) => void
}
@ -22,23 +25,26 @@ export const Modal = (props: Props) => {
return props.open ? (
<div className='fixed top-0 left-0 z-40 h-screen w-screen'>
<div className='relative flex h-full w-full items-center justify-center'>
<section
<Card
className={classNames(
'relative z-40 w-[790px] max-w-full rounded-base border border-white/20 bg-white/5 p-6 backdrop-blur-3xl',
'relative z-40 w-[790px] max-w-full bg-white/5 backdrop-blur-3xl',
props.className,
)}
>
<div className='flex justify-between pb-6'>
<Text>{props.title}</Text>
<div className={classNames('flex justify-between', props.headerClassName)}>
{props.header}
<Button
onClick={onClickAway}
icon={<Close />}
leftIcon={<Close />}
className='h-8 w-8'
iconClassName='h-2 w-2'
color='tertiary'
/>
</div>
<div>{props.children ? props.children : props.content}</div>
</section>
<div className={props.contentClassName}>
{props.children ? props.children : props.content}
</div>
</Card>
<div
className='fixed top-0 left-0 z-30 block h-full w-full bg-black/50 hover:cursor-pointer'
onClick={onClickAway}

View File

@ -1,14 +1,9 @@
'use client'
import { ConfirmModal } from 'components/Account/ConfirmModal'
import { FundAccountModal } from 'components/Account/FundAccountModal'
import BorrowModal from 'components/BorrowModal'
export const Modals = () => (
<>
<FundAccountModal />
{/* <WithdrawModal /> */}
<ConfirmModal />
<BorrowModal />
</>
)

View File

@ -0,0 +1,150 @@
'use client'
import classNames from 'classnames'
import React, { useEffect, useState } from 'react'
interface Props {
value: string
className: string
maxDecimals: number
minValue?: number
max?: number
maxLength?: number
allowNegative?: boolean
suffix?: string
style?: {}
onChange: (value: number) => void
onBlur?: () => void
onFocus?: () => void
onRef?: (ref: React.RefObject<HTMLInputElement>) => void
}
export default function NumberInput(props: Props) {
const inputRef = React.useRef<HTMLInputElement>(null)
const cursorRef = React.useRef(0)
const [inputValue, setInputValue] = useState({
formatted: props.value,
value: Number(props.value),
})
useEffect(() => {
setInputValue({
formatted: props.value,
value: Number(props.value),
})
}, [props.value])
useEffect(() => {
if (!props.onRef) return
props.onRef(inputRef)
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [inputRef, props.onRef])
const onInputFocus = () => {
inputRef.current?.select()
props.onFocus && props.onFocus()
}
const updateValues = (formatted: string, value: number) => {
const lastChar = formatted.charAt(formatted.length - 1)
if (lastChar === '.') {
cursorRef.current = (inputRef.current?.selectionEnd || 0) + 1
} else {
cursorRef.current = inputRef.current?.selectionEnd || 0
}
setInputValue({ formatted, value })
if (value !== inputValue.value) {
props.onChange(value)
}
}
useEffect(() => {
if (!inputRef.current) return
const cursor = cursorRef.current
const length = inputValue.formatted.length
if (cursor > length) {
inputRef.current.setSelectionRange(length, length)
return
}
inputRef.current.setSelectionRange(cursor, cursor)
}, [inputValue, inputRef])
const onInputChange = (value: string) => {
if (props.suffix) {
value = value.replace(props.suffix, '')
}
const numberCount = value.match(/[0-9]/g)?.length || 0
const decimals = value.split('.')[1]?.length || 0
const lastChar = value.charAt(value.length - 1)
const isNumber = !isNaN(Number(value))
const hasMultipleDots = (value.match(/[.,]/g)?.length || 0) > 1
const isSeparator = lastChar === '.' || lastChar === ','
const isNegative = value.indexOf('-') > -1
const isLowerThanMinimum = props.minValue !== undefined && Number(value) < props.minValue
const isHigherThanMaximum = props.max !== undefined && Number(value) > props.max
const isTooLong = props.maxLength !== undefined && numberCount > props.maxLength
const exceedsMaxDecimals = props.maxDecimals !== undefined && decimals > props.maxDecimals
if (isNegative && !props.allowNegative) return
if (isSeparator && value.length === 1) {
updateValues('0.', 0)
return
}
if (isSeparator && !hasMultipleDots) {
updateValues(value.replace(',', '.'), inputValue.value)
return
}
if (!isNumber || hasMultipleDots) return
if (exceedsMaxDecimals) {
value = value.substring(0, value.length - 1)
}
if (isTooLong) return
if (isLowerThanMinimum) {
updateValues(String(props.minValue), props.minValue!)
return
}
if (isHigherThanMaximum) {
updateValues(String(props.max), props.max!)
return
}
if (lastChar === '0' && Number(value) === Number(inputValue.value)) {
cursorRef.current = (inputRef.current?.selectionEnd || 0) + 1
setInputValue({ ...inputValue, formatted: value })
return
}
if (!value) {
updateValues(value, 0)
return
}
updateValues(String(Number(value)), Number(value))
}
return (
<input
ref={inputRef}
type='text'
value={`${inputValue.formatted}${props.suffix ? props.suffix : ''}`}
onFocus={onInputFocus}
onChange={(e) => onInputChange(e.target.value)}
onBlur={props.onBlur}
className={classNames(
'w-full cursor-pointer appearance-none border-none bg-transparent text-right outline-none',
props.className,
)}
style={props.style}
/>
)
}

View File

@ -1,7 +1,7 @@
import { Suspense } from 'react'
import classNames from 'classnames'
import { Card } from 'components/Card'
import Card from 'components/Card'
import Loading from 'components/Loading'
import { Text } from 'components/Text'
import { getCreditAccounts } from 'utils/api'
@ -16,7 +16,7 @@ async function Content(props: PageProps) {
<div className={classNames('grid grid-cols-1 gap-4', 'md:grid-cols-2', 'lg:grid-cols-3')}>
{creditAccounts.map((account: string, index: number) => (
<Card
className='h-fit w-full'
className='h-fit w-full bg-white/5'
title={`Account ${account}`}
key={index}
contentClassName='px-4 py-6'
@ -30,7 +30,11 @@ async function Content(props: PageProps) {
))}
</div>
) : (
<Card className='h-fit w-full justify-center' title='Portfolio' contentClassName='px-4 py-6'>
<Card
className='h-fit w-full justify-center bg-white/5'
title='Portfolio'
contentClassName='px-4 py-6'
>
<Text size='sm' className='w-full text-center'>
You need to be connected to view the porfolio page
</Text>
@ -45,7 +49,7 @@ function Fallback() {
{Array.from({ length: cardCount }, (_, i) => (
<Card
key={i}
className='h-fit w-full'
className='h-fit w-full bg-white/5'
title={
<>
Account <Loading className='ml-2 h-4 w-8' />

View File

@ -1,36 +1,160 @@
import * as RadixSlider from '@radix-ui/react-slider'
import { ChangeEvent, useRef, useState } from 'react'
import Draggable from 'react-draggable'
import { SliderMark } from 'components/Icons/index'
type Props = {
className?: string
value: number
onChange: (value: number[]) => void
onMaxClick: () => void
onChange: (value: number) => void
}
export const Slider = ({ className, value, onChange, onMaxClick }: Props) => {
export default function Slider(props: Props) {
const [showTooltip, setShowTooltip] = useState(false)
const [sliderRect, setSliderRect] = useState({ width: 0, left: 0, right: 0 })
const ref = useRef<HTMLDivElement>(null)
const nodeRef = useRef(null)
const [isDragging, setIsDragging] = useState(false)
function handleSliderRect() {
const leftCap = ref.current?.getBoundingClientRect().left ?? 0
const rightCap = ref.current?.getBoundingClientRect().right ?? 0
const newSliderWidth = ref.current?.getBoundingClientRect().width ?? 0
if (
sliderRect.width !== newSliderWidth ||
leftCap !== sliderRect.left ||
rightCap !== sliderRect.right
) {
setSliderRect({
left: leftCap,
right: rightCap,
width: newSliderWidth,
})
}
}
function handleDrag(e: any) {
if (!isDragging) {
setIsDragging(true)
}
const current: number = e.clientX
if (current < sliderRect.left) {
props.onChange(0)
return
}
if (current > sliderRect.right) {
props.onChange(100)
return
}
const value = Math.round(((current - sliderRect.left) / sliderRect.width) * 100)
if (value !== props.value) {
props.onChange(value)
}
}
function handleSliderClick(e: ChangeEvent<HTMLInputElement>) {
props.onChange(Number(e.target.value))
}
function handleShowTooltip() {
setShowTooltip(true)
}
function handleHideTooltip() {
setShowTooltip(false)
}
return (
<div className={`relative flex flex-1 items-center ${className || ''}`}>
<RadixSlider.Root
className='relative flex h-[20px] w-full cursor-pointer touch-none select-none items-center'
value={[value]}
min={0}
max={100}
step={1}
onValueChange={(value) => onChange(value)}
>
<RadixSlider.Track className='relative h-[6px] grow rounded-full bg-gray-400'>
<RadixSlider.Range className='absolute h-[100%] rounded-full bg-blue-600' />
</RadixSlider.Track>
<RadixSlider.Thumb className='flex h-[20px] w-[20px] items-center justify-center rounded-full bg-white !outline-none'>
<div className='relative top-5 text-xs'>{value.toFixed(0)}%</div>
</RadixSlider.Thumb>
</RadixSlider.Root>
<button
className='ml-4 rounded-base bg-blue-600 py-1 px-2 text-xs font-semibold text-white'
onClick={onMaxClick}
>
MAX
</button>
<div ref={ref} className='relative w-full' onMouseEnter={handleSliderRect}>
<input
type='range'
value={props.value}
onChange={handleSliderClick}
onMouseDown={handleShowTooltip}
className='absolute z-2 w-full cursor-pointer appearance-none bg-transparent [&::-webkit-slider-thumb]:h-3 [&::-webkit-slider-thumb]:w-3 [&::-webkit-slider-thumb]:appearance-none'
/>
<div className='absolute flex w-full items-center gap-1'>
<Mark onClick={props.onChange} value={0} sliderValue={props.value} />
<Track maxValue={23} sliderValue={props.value} />
<Mark onClick={props.onChange} value={25} sliderValue={props.value} />
<Track maxValue={48} sliderValue={props.value} />
<Mark onClick={props.onChange} value={50} sliderValue={props.value} />
<Track maxValue={73} sliderValue={props.value} />
<Mark onClick={props.onChange} value={75} sliderValue={props.value} />
<Track maxValue={98} sliderValue={props.value} />
<Mark onClick={props.onChange} value={100} sliderValue={props.value} />
</div>
<div onMouseEnter={handleShowTooltip} onMouseLeave={handleHideTooltip}>
<Draggable
nodeRef={nodeRef}
axis='x'
grid={[sliderRect.width / 100, 0]}
bounds={{ left: 0, right: sliderRect.width }}
positionOffset={{ x: (props.value / 100) * -12, y: 0 }}
onDrag={handleDrag}
onStop={() => setIsDragging(false)}
position={{ x: (sliderRect.width / 100) * props.value, y: 0 }}
>
<div ref={nodeRef} className='absolute z-20 leading-3'>
<div
className={
'z-20 h-3 w-3 rotate-45 cursor-pointer rounded-xs border-[2px] border-white bg-martian-red'
}
/>
{(showTooltip || isDragging) && (
<div className='absolute -top-8 left-1/2 -translate-x-1/2 rounded-xs bg-martian-red py-[2px] px-2 text-xs'>
<SliderMark className='absolute left-1/2 bottom-[-4px] -z-1 h-2 -translate-x-1/2 text-martian-red' />
{props.value}%
</div>
)}
</div>
</Draggable>
</div>
</div>
)
}
interface MarkProps {
value: number
sliderValue: number
onClick: (value: number) => void
}
function Mark(props: MarkProps) {
return (
<button
onClick={() => props.onClick(props.value)}
className={`z-20 h-3 w-3 rotate-45 rounded-xs border-[1px] border-white/20 hover:border-[2px] hover:border-white ${
props.sliderValue >= props.value ? 'bg-martian-red hover:border-white' : 'bg-grey-medium'
}`}
></button>
)
}
interface TrackProps {
maxValue: number
sliderValue: number
}
function Track(props: TrackProps) {
const minValue = props.maxValue - 21
let percentage = 0
if (props.sliderValue >= props.maxValue) percentage = 100
if (props.sliderValue > minValue && props.sliderValue < props.maxValue) {
percentage = ((props.sliderValue - minValue) / (props.maxValue - minValue)) * 100
}
return (
<div className='relative h-1 flex-grow overflow-hidden rounded-sm bg-transparent'>
<div className='absolute z-1 h-3 bg-martian-red ' style={{ width: `${percentage}%` }} />
<div className='absolute h-3 w-full bg-white/20' />
</div>
)
}

View File

@ -0,0 +1,39 @@
import Image from 'next/image'
import NumberInput from 'components/NumberInput'
import { Text } from 'components/Text'
interface Props {
value: number
max: number
asset: Asset
onChange: (value: number) => void
}
export default function TokenInput(props: Props) {
return (
<div className='flex w-full flex-col gap-2'>
<div className='box-content flex h-11 w-full rounded-sm border border-white/20 bg-white/5'>
<div className='flex min-w-fit items-center gap-2 border-r border-white/20 bg-white/5 p-3'>
<Image src={props.asset.logo} alt='token' width={20} height={20} />
<Text size='lg'>{props.asset.symbol}</Text>
</div>
<NumberInput
maxDecimals={props.asset.decimals}
onChange={props.onChange}
value={props.value.toString()}
max={props.max}
className='border-none p-3'
/>
</div>
<div className='flex justify-between'>
<Text size='xs' className='text-white/50' monospace>
1 OSMO = $0.9977
</Text>
<Text size='xs' monospace className='text-white/50'>
~ $0.00
</Text>
</div>
</div>
)
}

View File

@ -1,6 +1,6 @@
import { Suspense } from 'react'
import { Card } from 'components/Card'
import Card from 'components/Card'
import Loading from 'components/Loading'
import { Text } from 'components/Text'
@ -22,7 +22,7 @@ function Fallback() {
export default function OrderBook(props: PageProps) {
return (
<Card className='col-span-3' title='Order Book' contentClassName='px-4 py-6'>
<Card className='col-span-3 bg-white/5' title='Order Book' contentClassName='px-4 py-6'>
<Suspense fallback={<Fallback />}>
{/* @ts-expect-error Server Component */}
<Content params={props.params} />

View File

@ -1,6 +1,6 @@
import { Suspense } from 'react'
import { Card } from 'components/Card'
import Card from 'components/Card'
import Loading from 'components/Loading'
import { Text } from 'components/Text'
@ -28,7 +28,7 @@ function Fallback() {
export default function Trade(props: PageProps) {
return (
<Card className='h-full w-full' title='Trade Module' contentClassName='px-4 py-6'>
<Card className='h-full w-full bg-white/5' title='Trade Module' contentClassName='px-4 py-6'>
<Suspense fallback={<Fallback />}>
{/* @ts-expect-error Server Component */}
<Content params={props.params} />

View File

@ -1,350 +0,0 @@
'use client'
import BigNumber from 'bignumber.js'
import React, { useEffect, useMemo, useState } from 'react'
import { Button } from 'components/Button'
import { CircularProgress } from 'components/CircularProgress'
import { ArrowsUpDown } from 'components/Icons'
import { Slider } from 'components/Slider'
import { useCalculateMaxTradeAmount } from 'hooks/data/useCalculateMaxTradeAmount'
import { useTradeAsset } from 'hooks/mutations/useTradeAsset'
import { useAllBalances } from 'hooks/queries/useAllBalances'
import { useAllowedCoins } from 'hooks/queries/useAllowedCoins'
import { useCreditAccountPositions } from 'hooks/queries/useCreditAccountPositions'
import { useMarkets } from 'hooks/queries/useMarkets'
import { useTokenPrices } from 'hooks/queries/useTokenPrices'
import useStore from 'store'
import { getMarketAssets } from 'utils/assets'
import { getTokenDecimals, getTokenSymbol } from 'utils/tokens'
import Switch from 'components/Switch'
enum FundingMode {
Account = 'Account',
WalletAndAccount = 'WalletAndAccount',
}
export const TradeActionModule = () => {
const marketAssets = getMarketAssets()
const [selectedTokenIn, setSelectedTokenIn] = useState('')
const [selectedTokenOut, setSelectedTokenOut] = useState('')
const [amountIn, setAmountIn] = useState(0)
const [amountOut, setAmountOut] = useState(0)
const [slippageTolerance, setSlippageTolerance] = useState(1)
const [fundingMode, setFundingMode] = useState<FundingMode>(FundingMode.WalletAndAccount)
const [isMarginEnabled, setIsMarginEnabled] = React.useState(false)
const selectedAccount = useStore((s) => s.selectedAccount)
const { data: allowedCoinsData } = useAllowedCoins()
const { data: balancesData } = useAllBalances()
const { data: marketsData } = useMarkets()
const { data: tokenPrices } = useTokenPrices()
const { data: positionsData } = useCreditAccountPositions(selectedAccount ?? '')
const resetAmounts = () => {
setAmountIn(0)
setAmountOut(0)
}
useEffect(() => {
resetAmounts()
}, [selectedAccount])
const accountAmount = useMemo(() => {
return Number(positionsData?.coins?.find((coin) => coin.denom === selectedTokenIn)?.amount ?? 0)
}, [positionsData, selectedTokenIn])
const walletAmount = useMemo(() => {
return Number(balancesData?.find((balance) => balance.denom === selectedTokenIn)?.amount ?? 0)
}, [balancesData, selectedTokenIn])
const { swapAmount, borrowAmount, depositAmount } = useMemo(() => {
const swapAmount = amountIn
let borrowAmount = 0
let depositAmount = 0
if (fundingMode === FundingMode.WalletAndAccount) {
const walletAndAccountAmount = walletAmount + accountAmount
borrowAmount =
amountIn > walletAndAccountAmount
? BigNumber(amountIn).minus(walletAndAccountAmount).toNumber()
: 0
depositAmount = amountIn > walletAmount ? walletAmount : amountIn
}
if (fundingMode === FundingMode.Account) {
borrowAmount =
amountIn > accountAmount ? BigNumber(amountIn).minus(accountAmount).toNumber() : 0
}
return { swapAmount, borrowAmount, depositAmount }
}, [accountAmount, amountIn, fundingMode, walletAmount])
const { mutate, isLoading } = useTradeAsset(
swapAmount,
borrowAmount,
depositAmount,
selectedTokenIn,
selectedTokenOut,
slippageTolerance / 100,
{
onSuccess: () => {
useStore.setState({
toast: {
message: `${amountIn} ${getTokenSymbol(
selectedTokenIn,
marketAssets,
)} swapped for ${amountOut} ${getTokenSymbol(selectedTokenOut, marketAssets)}`,
},
})
resetAmounts()
},
},
)
useEffect(() => {
if (allowedCoinsData && allowedCoinsData.length > 0) {
// initialize selected token when allowedCoins fetch data is available
setSelectedTokenIn(allowedCoinsData[0])
if (allowedCoinsData.length > 1) {
setSelectedTokenOut(allowedCoinsData[1])
} else {
setSelectedTokenOut(allowedCoinsData[0])
}
}
}, [allowedCoinsData])
const handleSelectedTokenInChange = (e: React.ChangeEvent<HTMLSelectElement>) => {
setSelectedTokenIn(e.target.value)
resetAmounts()
}
const handleSelectedTokenOutChange = (e: React.ChangeEvent<HTMLSelectElement>) => {
setSelectedTokenOut(e.target.value)
resetAmounts()
}
const handleFundingModeChange = (e: React.ChangeEvent<HTMLSelectElement>) => {
setFundingMode(e.target.value as FundingMode)
resetAmounts()
}
// max amount that can be traded without considering wallet amount
// wallet amount should just be directly added to this amount in case user wants to include wallet as funding source
const maxTradeAmount = useCalculateMaxTradeAmount(
selectedTokenIn,
selectedTokenOut,
isMarginEnabled,
)
// if funding from wallet & account, add wallet amount to the max trade amount
const maxAmount = useMemo(() => {
if (fundingMode === FundingMode.WalletAndAccount) {
return walletAmount + maxTradeAmount
}
return maxTradeAmount
}, [fundingMode, maxTradeAmount, walletAmount])
const percentageValue = useMemo(() => {
if (isNaN(amountIn) || amountIn === 0) return 0
return amountIn / maxAmount > 1 ? 100 : (amountIn / maxAmount) * 100
}, [amountIn, maxAmount])
const borrowRate = Number(marketsData?.[selectedTokenOut]?.borrow_rate)
const handleAmountChange = (value: number, mode: 'in' | 'out') => {
const tokenInPrice = tokenPrices?.[selectedTokenIn] ?? 1
const tokenOutPrice = tokenPrices?.[selectedTokenOut] ?? 1
const priceRatio = BigNumber(tokenInPrice).div(tokenOutPrice)
if (mode === 'in') {
setAmountIn(value)
setAmountOut(BigNumber(value).times(priceRatio).decimalPlaces(0).toNumber())
} else {
setAmountOut(value)
setAmountIn(BigNumber(value).div(BigNumber(1).times(priceRatio)).decimalPlaces(0).toNumber())
}
}
const submitDisabled = selectedTokenIn === selectedTokenOut || !amountIn || amountIn > maxAmount
return (
<div>
{isLoading && (
<div className='fixed inset-0 z-40 grid place-items-center bg-black/50'>
<CircularProgress />
</div>
)}
<div className='border-b border-b-white/20 p-2'>
<div className='mb-2'>
<p className='mb-1'>From:</p>
<div className='flex gap-2'>
<select
className='h-8 w-20 text-black'
onChange={handleSelectedTokenInChange}
value={selectedTokenIn}
>
{allowedCoinsData?.map((entry) => (
<option key={entry} value={entry}>
{getTokenSymbol(entry, marketAssets)}
</option>
))}
</select>
<input
type='number'
className='h-8 flex-1 px-2 text-black outline-0'
value={amountIn / 10 ** getTokenDecimals(selectedTokenIn, marketAssets)}
min='0'
placeholder='0.00'
onChange={(e) => {
const valueAsNumber = e.target.valueAsNumber
const valueWithDecimals =
valueAsNumber * 10 ** getTokenDecimals(selectedTokenIn, marketAssets)
handleAmountChange(valueWithDecimals, 'in')
}}
/>
</div>
</div>
<div
className='mx-auto h-5 w-6 cursor-pointer text-white/70 hover:text-white'
onClick={() => {
setSelectedTokenIn(selectedTokenOut)
setSelectedTokenOut(selectedTokenIn)
resetAmounts()
}}
>
<ArrowsUpDown />
</div>
<div className='mb-5'>
<p className='mb-1'>To:</p>
<div className='flex gap-2'>
<select
className='h-8 w-20 text-black'
onChange={handleSelectedTokenOutChange}
value={selectedTokenOut}
>
{allowedCoinsData?.map((entry) => (
<option key={entry} value={entry}>
{getTokenSymbol(entry, marketAssets)}
</option>
))}
</select>
<input
type='number'
className='h-8 flex-1 px-2 text-black outline-0'
value={amountOut / 10 ** getTokenDecimals(selectedTokenOut, marketAssets)}
min='0'
placeholder='0.00'
onChange={(e) => {
const valueAsNumber = e.target.valueAsNumber
const valueWithDecimals =
valueAsNumber * 10 ** getTokenDecimals(selectedTokenOut, marketAssets)
handleAmountChange(valueWithDecimals, 'out')
}}
/>
</div>
</div>
<div className='mb-1'>
In Wallet:{' '}
{BigNumber(walletAmount)
.dividedBy(10 ** getTokenDecimals(selectedTokenIn, marketAssets))
.toNumber()
.toLocaleString(undefined, {
maximumFractionDigits: getTokenDecimals(selectedTokenIn, marketAssets),
})}{' '}
<span>{getTokenSymbol(selectedTokenIn, marketAssets)}</span>
</div>
<div className='mb-4'>
In Account:{' '}
{BigNumber(accountAmount)
.dividedBy(10 ** getTokenDecimals(selectedTokenIn, marketAssets))
.toNumber()
.toLocaleString(undefined, {
maximumFractionDigits: getTokenDecimals(selectedTokenIn, marketAssets),
})}{' '}
<span>{getTokenSymbol(selectedTokenIn, marketAssets)}</span>
</div>
<Slider
className='mb-6'
value={percentageValue}
onChange={(value) => {
const decimal = value[0] / 100
const tokenDecimals = getTokenDecimals(selectedTokenIn, marketAssets)
// limit decimal precision based on token contract decimals
const newAmount = Number((decimal * maxAmount).toFixed(0))
handleAmountChange(newAmount, 'in')
}}
onMaxClick={() => handleAmountChange(maxAmount, 'in')}
/>
</div>
<div className='border-b border-b-white/20 p-2'>
<div className='mb-4 flex items-center'>
<p className='mr-2'>Margin</p>
<Switch
name='marginEnabled'
checked={isMarginEnabled}
onChange={(value: boolean) => {
// reset amounts only if margin is turned off
if (!value) resetAmounts()
setIsMarginEnabled(value)
}}
/>
</div>
<div className='mb-1 flex justify-between'>
<p>Borrow</p>
<p>
{isMarginEnabled
? BigNumber(borrowAmount)
.dividedBy(10 ** getTokenDecimals(selectedTokenIn, marketAssets))
.toNumber()
.toLocaleString(undefined, {
maximumFractionDigits: getTokenDecimals(selectedTokenIn, marketAssets),
})
: '-'}
</p>
</div>
<div className='flex justify-between'>
<p>Borrow Rate</p>
<p>{isMarginEnabled ? `${(borrowRate * 100).toFixed(2)}%` : '-'}</p>
</div>
</div>
<div className='p-2'>
<div className='mb-6'>OTHER INFO PLACEHOLDER</div>
<div className='mb-2 flex justify-between'>
<p>Slippage Tolerance:</p>
<input
type='number'
step='0.1'
className='w-20 px-2 text-black'
onChange={(e) => setSlippageTolerance(e.target.valueAsNumber)}
value={slippageTolerance}
/>
</div>
<div className='flex justify-between'>
<p>Funded From</p>
<select value={fundingMode} className='text-black' onChange={handleFundingModeChange}>
<option value={FundingMode.Account}>Account</option>
<option value={FundingMode.WalletAndAccount}>Wallet & Account</option>
</select>
</div>
</div>
<Button className='w-full' onClick={() => mutate()} disabled={submitDisabled}>
Create Order
</Button>
</div>
)
}

View File

@ -1,6 +1,6 @@
import { Suspense } from 'react'
import { Card } from 'components/Card'
import Card from 'components/Card'
import Loading from 'components/Loading'
import { Text } from 'components/Text'
@ -14,7 +14,11 @@ function Fallback() {
export default function TradingView(props: PageProps) {
return (
<Card className='col-span-2 h-full' title='Trading View' contentClassName='px-4 py-6'>
<Card
className='col-span-2 h-full bg-white/5'
title='Trading View'
contentClassName='px-4 py-6'
>
<Suspense fallback={<Fallback />}>
{/* @ts-expect-error Server Component */}
<Content params={props.params} />

View File

@ -23,7 +23,7 @@ export default function ConnectButton(props: Props) {
color='tertiary'
disabled={props.disabled}
onClick={connect}
icon={<Wallet />}
leftIcon={<Wallet />}
>
{props.status === WalletConnectionStatus.Connecting ? (
<span className='flex justify-center'>

View File

@ -85,7 +85,7 @@ export default function ConnectedButton() {
<Button
variant='solid'
color='tertiary'
icon={<Osmo />}
leftIcon={<Osmo />}
onClick={() => {
setShowDetails(!showDetails)
}}
@ -136,7 +136,7 @@ export default function ConnectedButton() {
</Text>
<div className='flex w-full pt-1'>
<Button
icon={isCopied ? <Check /> : <Copy />}
leftIcon={isCopied ? <Check /> : <Copy />}
variant='transparent'
className='mr-10 flex w-auto py-2'
color='quaternary'
@ -144,7 +144,7 @@ export default function ConnectedButton() {
text={isCopied ? 'Copied' : 'Copy Address'}
/>
<Button
icon={<ExternalLink />}
leftIcon={<ExternalLink />}
variant='transparent'
className='flex w-auto py-2'
color='quaternary'

View File

@ -46,7 +46,7 @@ export const WalletConnectProvider: FC<Props> = ({ children }) => {
walletName: 'w-full text-lg',
walletDescription: 'w-full text-xs text-white/60 break-all',
}}
closeIcon={<Button icon={<Close />} iconClassName='h-2 w-2' color='tertiary' />}
closeIcon={<Button leftIcon={<Close />} iconClassName='h-2 w-2' color='tertiary' />}
renderLoader={() => (
<div>
<CircularProgress size={30} />

View File

@ -57,6 +57,6 @@ export const ASSETS: Asset[] = [
decimals: 6,
hasOraclePrice: true,
isMarket: IS_TESTNET,
isEnabled: false,
isEnabled: true,
},
]

View File

@ -2,7 +2,7 @@ import { gql, request as gqlRequest } from 'graphql-request'
import { NextApiRequest, NextApiResponse } from 'next'
import { ENV, ENV_MISSING_MESSAGE, VERCEL_BYPASS } from 'constants/env'
import { getContractQuery } from 'utils/query'
import { denomToKey, getContractQuery, keyToDenom } from 'utils/query'
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
if (!ENV.URL_API || !ENV.ADDRESS_RED_BANK || !ENV.URL_GQL) {
@ -13,9 +13,9 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
let query = ''
markets.forEach((asset: any) => {
markets.forEach((asset) => {
query += getContractQuery(
asset.denom,
denomToKey(asset.denom),
ENV.ADDRESS_RED_BANK || '',
`
{
@ -41,7 +41,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
if (result) {
const debts = Object.keys(result.debts).map((key) => {
return {
denom: key,
denom: keyToDenom(key),
amount: result.debts[key],
}
})

View File

@ -2,7 +2,7 @@ import { gql, request as gqlRequest } from 'graphql-request'
import { NextApiRequest, NextApiResponse } from 'next'
import { ENV, ENV_MISSING_MESSAGE, VERCEL_BYPASS } from 'constants/env'
import { getContractQuery } from 'utils/query'
import { denomToKey, getContractQuery, keyToDenom } from 'utils/query'
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
if (!ENV.URL_RPC || !ENV.ADDRESS_RED_BANK || !ENV.URL_GQL || !ENV.URL_API) {
@ -15,7 +15,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
markets.forEach((asset: any) => {
query += getContractQuery(
asset.denom,
denomToKey(asset.denom),
ENV.ADDRESS_RED_BANK || '',
`
{
@ -41,7 +41,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
if (result) {
const deposits = Object.keys(result.deposits).map((key) => {
return {
denom: key,
denom: keyToDenom(key),
amount: result.deposits[key],
}
})

View File

@ -3,6 +3,7 @@ import { NextApiRequest, NextApiResponse } from 'next'
import { ENV, ENV_MISSING_MESSAGE } from 'constants/env'
import { getMarketAssets } from 'utils/assets'
import { denomToKey } from 'utils/query'
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
if (!ENV.URL_GQL || !ENV.ADDRESS_RED_BANK || !ENV.ADDRESS_INCENTIVES) {
@ -13,8 +14,8 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
const marketQueries = marketAssets.map(
(asset: Asset) =>
`${asset.denom}: contractQuery(
contractAddress: "${ENV.ADDRESS_RED_BANK}"
`${denomToKey(asset.denom)}: contractQuery(
contractAddress: "${ENV.ADDRESS_RED_BANK}"
query: { market: { denom: "${asset.denom}" } }
)`,
)
@ -31,7 +32,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
)
const markets = marketAssets.map((asset) => {
const market = result.rbwasmkey[`${asset.denom}`]
const market = result.rbwasmkey[`${denomToKey(asset.denom)}`]
return market
})
return res.status(200).json(markets)

View File

@ -4,3 +4,11 @@ export function getContractQuery(key: string, contractAddress: string, query: st
${contractKey}contractQuery(contractAddress: "${contractAddress}", query: ${query})
`
}
export function denomToKey(denom: string) {
return denom.replace('ibc/', 'ibc_')
}
export function keyToDenom(key: string) {
return key.replace('ibc_', 'ibc/')
}

View File

@ -89,6 +89,7 @@ module.exports = {
input: '#282a33',
loss: '#f96363',
mars: '#a03b45',
'martian-red': '#FF645F',
osmo: '#9f1ab9',
'orb-primary': '#b12f25',
'orb-secondary': '#530781',
@ -239,6 +240,10 @@ module.exports = {
'linear-gradient(135deg,transparent 33.33%,#826d6b 33.33%,#826d6b 50%,transparent 50%,transparent 83.33%,#826d6b 83.33%,#826d6b 100%)',
backgroundSize: '5px 5px',
},
'.gradient-header': {
background:
'linear-gradient(90deg, rgba(255, 255, 255, 0.1) 0%, rgba(255, 255, 255, 0) 50%)',
},
'.gradient-limit': {
background:
'linear-gradient(to right,#15bfa9 20.9%,#5e4bb1 49.68%,#382685 82.55%,#c83333 100%)',

1964
yarn.lock

File diff suppressed because it is too large Load Diff