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:
parent
21d31c0f79
commit
740e982956
23390
package-lock.json
generated
Normal file
23390
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
@ -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
92
public/tokens/juno.svg
Normal 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 |
@ -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>
|
||||
)}
|
||||
|
12
src/components/Account/AccountSummary.tsx
Normal file
12
src/components/Account/AccountSummary.tsx
Normal 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>
|
||||
)
|
||||
}
|
@ -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>
|
||||
)
|
||||
}
|
@ -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 don’t
|
||||
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>
|
||||
)
|
||||
}
|
@ -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>
|
||||
)
|
||||
}
|
@ -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' />
|
||||
|
@ -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>
|
||||
)
|
||||
}
|
||||
|
@ -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 />
|
||||
|
@ -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',
|
||||
)}
|
||||
>
|
||||
|
@ -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} />
|
||||
|
10
src/components/Divider.tsx
Normal file
10
src/components/Divider.tsx
Normal 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>
|
||||
}
|
@ -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} />
|
||||
|
3
src/components/Icons/ArrowRight.svg
Normal file
3
src/components/Icons/ArrowRight.svg
Normal 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 |
3
src/components/Icons/SliderMark.svg
Normal file
3
src/components/Icons/SliderMark.svg
Normal 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 |
@ -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'
|
||||
|
@ -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}
|
||||
|
@ -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 />
|
||||
</>
|
||||
)
|
||||
|
150
src/components/NumberInput.tsx
Normal file
150
src/components/NumberInput.tsx
Normal 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}
|
||||
/>
|
||||
)
|
||||
}
|
@ -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' />
|
||||
|
@ -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>
|
||||
)
|
||||
}
|
||||
|
39
src/components/TokenInput.tsx
Normal file
39
src/components/TokenInput.tsx
Normal 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>
|
||||
)
|
||||
}
|
@ -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} />
|
||||
|
@ -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} />
|
||||
|
@ -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>
|
||||
)
|
||||
}
|
@ -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} />
|
||||
|
@ -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'>
|
||||
|
@ -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'
|
||||
|
@ -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} />
|
||||
|
@ -57,6 +57,6 @@ export const ASSETS: Asset[] = [
|
||||
decimals: 6,
|
||||
hasOraclePrice: true,
|
||||
isMarket: IS_TESTNET,
|
||||
isEnabled: false,
|
||||
isEnabled: true,
|
||||
},
|
||||
]
|
||||
|
@ -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],
|
||||
}
|
||||
})
|
||||
|
@ -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],
|
||||
}
|
||||
})
|
||||
|
@ -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)
|
||||
|
@ -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/')
|
||||
}
|
||||
|
@ -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%)',
|
||||
|
Loading…
Reference in New Issue
Block a user