UI style updates (#106)
* feat: updates on the button styles * env: updated yarn.lock * fix: added account actions * fix: updated the orbs logic * fix: fixed the blur presets * feat: updated the button logic * fix: wallet modal style adjustments * fix: updated close icon * fix: fixed the close button * fix: fix types * fix: fixed the build * tidy: component cleanup * feat: added new AccountDetails component * refactor: propper usage of tailwind * refactor: imports * feat: added pages for all scenarios * fix: fix the loading component * fix: remove loading from default trade * fix: fixed the build * fix: fixed losing the provider on hotplug * tidy: remove unused code * fix: added error messages * add borrow page structure * env: enhanced debugging by restructuring the ENV object * fix: fixed the build * fix: fixed the wording on missing env variables * feat: added button hover (#112) * feat: added button hover * fix: added bg transition to primary buttons * feat: pages refactored (#111) * feat: pages refactored * fix: added loader for AccountNavigation * fix: fixed the wallet store management * fix: get rid of the walletSlice and refactor * fix: added gap to the borrow page * fix: fixed some dependencies * fix: added initClients back * fix: fixed according to feedback --------- Co-authored-by: bwvdhelm <34470358+bobthebuidlr@users.noreply.github.com>
@ -9,5 +9,7 @@ NEXT_PUBLIC_ACCOUNT_NFT=osmo1l8c3g6zy7kjhuh8d2kqyvxkw0myn4puxv0tzcdf9nwxd386r9l7
|
||||
NEXT_PUBLIC_ORACLE=osmo1dqz2u3c8rs5e7w5fnchsr2mpzzsxew69wtdy0aq4jsd76w7upmsstqe0s8
|
||||
NEXT_PUBLIC_RED_BANK=osmo1g30recyv8pfy3qd4qn3dn7plc0rn5z68y5gn32j39e96tjhthzxsw3uvvu
|
||||
NEXT_PUBLIC_CREDIT_MANAGER=osmo12hgn4jec4tftahm7spf7c2aqsqrxzzk50hkq60e89atumyu0zvys7vzxdc
|
||||
NEXT_PUBLIC_INCENTIVES=osmo1zxs8fry3m8j94pqg7h4muunyx86en27cl0xgk76fc839xg2qnn6qtpjs48
|
||||
NEXT_PUBLIC_ZAPPER=osmo1ua8dwc9v8qjh7n3qf8kg6xvrwjm5yu9xxln7yjvgmrvfzaxvzsuqfcdnjq
|
||||
NEXT_PUBLIC_SWAPPER=osmo1uj6r9tu440wwp2mhtagh48yvmeyeaqt2xa7kdnlhyrqcuthlj4ss7ghg6n
|
||||
NEXT_PUBLIC_API=http://localhost:3000/api
|
@ -39,7 +39,7 @@
|
||||
"recharts": "^2.2.0",
|
||||
"sass": "^1.58.3",
|
||||
"swr": "^2.0.3",
|
||||
"tailwindcss-border-gradient-radius": "^3.0.1",
|
||||
"tailwind-scrollbar-hide": "^1.1.7",
|
||||
"use-local-storage-state": "^18.1.1",
|
||||
"zustand": "^4.1.4"
|
||||
},
|
||||
|
@ -1,3 +0,0 @@
|
||||
export default function laoding() {
|
||||
return '...isLoading'
|
||||
}
|
@ -1,7 +1,5 @@
|
||||
import { getBorrowData } from 'utils/api'
|
||||
import BorrowPage from 'components/pages/borrow'
|
||||
|
||||
export default async function page() {
|
||||
const borrowData = await getBorrowData()
|
||||
|
||||
return `You are a guest`
|
||||
export default async function page({ params }: PageProps) {
|
||||
return <BorrowPage params={params} />
|
||||
}
|
||||
|
@ -1,3 +1,5 @@
|
||||
export default function page() {
|
||||
return `You are a guest`
|
||||
import CouncilPage from 'components/pages/council'
|
||||
|
||||
export default async function page({ params }: PageProps) {
|
||||
return <CouncilPage params={params} />
|
||||
}
|
||||
|
@ -1,3 +1,5 @@
|
||||
export default function page() {
|
||||
return `You are a guest`
|
||||
import EarnPage from 'components/pages/earn'
|
||||
|
||||
export default async function page({ params }: PageProps) {
|
||||
return <EarnPage params={params} />
|
||||
}
|
||||
|
@ -1,3 +1,6 @@
|
||||
import classNames from 'classnames'
|
||||
|
||||
import AccountDetails from 'components/Account/AccountDetails'
|
||||
import Background from 'components/Background'
|
||||
import FetchPrices from 'components/FetchPrices'
|
||||
import { Modals } from 'components/Modals'
|
||||
@ -9,22 +12,25 @@ import 'styles/globals.scss'
|
||||
|
||||
export default function RootLayout({ children }: { children: React.ReactNode }) {
|
||||
return (
|
||||
<html lang='en'>
|
||||
<html className='m-0 p-0' lang='en'>
|
||||
<head />
|
||||
|
||||
<body>
|
||||
<div className='relative min-h-screen w-full'>
|
||||
<body className='m-0 cursor-default bg-body p-0 font-sans text-white'>
|
||||
<WalletConnectProvider>
|
||||
<Background />
|
||||
<DesktopNavigation />
|
||||
</WalletConnectProvider>
|
||||
<FetchPrices />
|
||||
<main
|
||||
className={classNames(
|
||||
'relative flex justify-center py-6',
|
||||
'lg:mt-[65px] lg:h-[calc(100vh-65px)]',
|
||||
)}
|
||||
>
|
||||
<div className='flex max-w-content flex-grow flex-col flex-wrap'>{children}</div>
|
||||
<AccountDetails />
|
||||
</main>
|
||||
<Modals />
|
||||
<Toaster />
|
||||
<FetchPrices />
|
||||
<main className='relative flex lg:min-h-[calc(100vh-120px)]'>
|
||||
<div className='flex flex-grow flex-col flex-wrap'>{children}</div>
|
||||
</main>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
)
|
||||
|
@ -1,3 +1,5 @@
|
||||
export default function page() {
|
||||
return 'Connect to your wallet'
|
||||
import TradePage from 'components/pages/trade'
|
||||
|
||||
export default async function page({ params }: PageProps) {
|
||||
return <TradePage params={params} />
|
||||
}
|
||||
|
@ -1,3 +1,5 @@
|
||||
export default function page() {
|
||||
return `You are a guest`
|
||||
import PortfolioPage from 'components/pages/portfolio'
|
||||
|
||||
export default async function page({ params }: PageProps) {
|
||||
return <PortfolioPage params={params} />
|
||||
}
|
||||
|
@ -1,3 +1,5 @@
|
||||
export default function page() {
|
||||
return `You are a guest`
|
||||
import TradePage from 'components/pages/trade'
|
||||
|
||||
export default async function page({ params }: PageProps) {
|
||||
return <TradePage params={params} />
|
||||
}
|
||||
|
@ -1,45 +1,5 @@
|
||||
import { BorrowTable } from 'components/Borrow/BorrowTable'
|
||||
import { Card } from 'components/Card'
|
||||
import { getAccountDebts, getBorrowData } from 'utils/api'
|
||||
import { getMarketAssets } from 'utils/assets'
|
||||
import BorrowPage from 'components/pages/borrow'
|
||||
|
||||
export default async function page({ params }: { params: PageParams }) {
|
||||
const debtData = await getAccountDebts(params.account)
|
||||
const borrowData = await getBorrowData()
|
||||
|
||||
const marketAssets = getMarketAssets()
|
||||
|
||||
const { available, active } = marketAssets.reduce(
|
||||
(prev: { available: BorrowAsset[]; active: BorrowAssetActive[] }, curr) => {
|
||||
const borrow = borrowData.find((borrow) => borrow.denom === curr.denom)
|
||||
if (borrow) {
|
||||
const debt = debtData.find((debt) => debt.denom === curr.denom)
|
||||
if (debt) {
|
||||
prev.active.push({
|
||||
...borrow,
|
||||
debt: debt.amount,
|
||||
})
|
||||
} else {
|
||||
prev.available.push(borrow)
|
||||
}
|
||||
}
|
||||
return prev
|
||||
},
|
||||
{ available: [], active: [] },
|
||||
)
|
||||
|
||||
return (
|
||||
<div className='flex w-full flex-col'>
|
||||
{active.length > 0 && (
|
||||
<Card title='Borrowings'>
|
||||
<BorrowTable data={active} />
|
||||
</Card>
|
||||
)}
|
||||
{available.length > 0 && (
|
||||
<Card title='Available to borrow'>
|
||||
<BorrowTable data={available} />
|
||||
</Card>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
export default async function page({ params }: PageProps) {
|
||||
return <BorrowPage params={params} />
|
||||
}
|
||||
|
@ -1,3 +0,0 @@
|
||||
export default function loading() {
|
||||
return '...isLoading'
|
||||
}
|
@ -1,11 +1,5 @@
|
||||
import { Card } from 'components/Card'
|
||||
import CouncilPage from 'components/pages/council'
|
||||
|
||||
export default function page() {
|
||||
return (
|
||||
<div className='flex w-full'>
|
||||
<Card title='Council'>
|
||||
<></>
|
||||
</Card>
|
||||
</div>
|
||||
)
|
||||
export default async function page({ params }: PageProps) {
|
||||
return <CouncilPage params={params} />
|
||||
}
|
||||
|
@ -1,3 +0,0 @@
|
||||
export default function loading() {
|
||||
return '...isLoading'
|
||||
}
|
@ -1,12 +1,5 @@
|
||||
import { Card } from 'components/Card'
|
||||
import { Text } from 'components/Text'
|
||||
import EarnPage from 'components/pages/earn'
|
||||
|
||||
export default function page() {
|
||||
return (
|
||||
<div className='flex w-full gap-4'>
|
||||
<Card title='Yield'>
|
||||
<></>
|
||||
</Card>
|
||||
</div>
|
||||
)
|
||||
export default async function page({ params }: PageProps) {
|
||||
return <EarnPage params={params} />
|
||||
}
|
||||
|
@ -1,3 +1,5 @@
|
||||
export default function page() {
|
||||
return 'Trade page'
|
||||
import TradePage from 'components/pages/trade'
|
||||
|
||||
export default async function page({ params }: PageProps) {
|
||||
return <TradePage params={params} />
|
||||
}
|
||||
|
@ -1,5 +0,0 @@
|
||||
'use client'
|
||||
|
||||
export default function page({ params }: { params: PageParams }) {
|
||||
return 'error!'
|
||||
}
|
@ -1,15 +1,5 @@
|
||||
import { getCreditAccounts } from 'utils/api'
|
||||
import PortfolioPage from 'components/pages/portfolio'
|
||||
|
||||
export default async function page({ params }: { params: PageParams }) {
|
||||
const creditAccounts = await getCreditAccounts(params.wallet)
|
||||
|
||||
return (
|
||||
<div className='flex w-full items-start gap-4'>
|
||||
<ul>
|
||||
{creditAccounts.map((account: string, index: number) => (
|
||||
<li key={index}>{account}</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
)
|
||||
export default async function page({ params }: PageProps) {
|
||||
return <PortfolioPage params={params} />
|
||||
}
|
||||
|
@ -1,3 +0,0 @@
|
||||
export default function loading() {
|
||||
return '...isLoading'
|
||||
}
|
@ -1,21 +1,5 @@
|
||||
import { Card } from 'components/Card'
|
||||
import TradePage from 'components/pages/trade'
|
||||
|
||||
export default function page() {
|
||||
return (
|
||||
<div className='flex w-full flex-wrap'>
|
||||
<div className='mb-4 flex flex-grow gap-4'>
|
||||
<Card title='TradingView graph' className='flex-1'>
|
||||
<></>
|
||||
</Card>
|
||||
<div className='flex flex-col gap-4'>
|
||||
<Card title='Orderbook module'>
|
||||
<></>
|
||||
</Card>
|
||||
</div>
|
||||
</div>
|
||||
<Card title='Order history'>
|
||||
<></>
|
||||
</Card>
|
||||
</div>
|
||||
)
|
||||
export default async function page({ params }: PageProps) {
|
||||
return <TradePage params={params} />
|
||||
}
|
||||
|
@ -1,3 +0,0 @@
|
||||
export default function page() {
|
||||
return `You are a viewer or a user`
|
||||
}
|
@ -1,3 +0,0 @@
|
||||
export default function page() {
|
||||
return `You are a viewer or a user`
|
||||
}
|
@ -1,3 +1,5 @@
|
||||
export default function page() {
|
||||
return `You are a viewer or a user`
|
||||
import BorrowPage from 'components/pages/borrow'
|
||||
|
||||
export default async function page({ params }: PageProps) {
|
||||
return <BorrowPage params={params} />
|
||||
}
|
||||
|
@ -1,3 +0,0 @@
|
||||
export default function page() {
|
||||
return `You are a viewer or a user`
|
||||
}
|
@ -1,3 +0,0 @@
|
||||
export default function page() {
|
||||
return `You are a viewer or a user`
|
||||
}
|
@ -1,3 +1,5 @@
|
||||
export default function page() {
|
||||
return `You are a viewer or a user`
|
||||
import CouncilPage from 'components/pages/council'
|
||||
|
||||
export default async function page({ params }: PageProps) {
|
||||
return <CouncilPage params={params} />
|
||||
}
|
||||
|
@ -1,3 +1,5 @@
|
||||
export default function page() {
|
||||
return `You are a viewer or a user`
|
||||
import EarnPage from 'components/pages/earn'
|
||||
|
||||
export default async function page({ params }: PageProps) {
|
||||
return <EarnPage params={params} />
|
||||
}
|
||||
|
@ -1,21 +1,3 @@
|
||||
import { AccountNavigation } from 'components/Account/AccountNavigation'
|
||||
import { getCreditAccounts } from 'utils/api'
|
||||
|
||||
export default async function RootLayout({
|
||||
children,
|
||||
params,
|
||||
}: {
|
||||
children: React.ReactNode
|
||||
params: PageParams
|
||||
}) {
|
||||
const creditAccounts = await getCreditAccounts(params.wallet)
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className='relative hidden bg-header lg:block'>
|
||||
<AccountNavigation creditAccounts={creditAccounts} />
|
||||
</div>
|
||||
<main className='p-4'>{children}</main>
|
||||
</>
|
||||
)
|
||||
export default function RootLayout({ children }: { children: React.ReactNode }) {
|
||||
return children
|
||||
}
|
||||
|
@ -1,3 +1,5 @@
|
||||
export default function page() {
|
||||
return `You are a viewer or a user`
|
||||
import PortfolioPage from 'components/pages/portfolio'
|
||||
|
||||
export default async function page({ params }: PageProps) {
|
||||
return <PortfolioPage params={params} />
|
||||
}
|
||||
|
@ -1,3 +1,5 @@
|
||||
export default function page() {
|
||||
return `You are a viewer or a user`
|
||||
import TradePage from 'components/pages/trade'
|
||||
|
||||
export default async function page({ params }: PageProps) {
|
||||
return <TradePage params={params} />
|
||||
}
|
||||
|
@ -1,121 +1,40 @@
|
||||
'use client'
|
||||
import { Gauge } from 'components/Gauge'
|
||||
import { Heart } from 'components/Icons'
|
||||
import { Text } from 'components/Text'
|
||||
import useParams from 'hooks/useParams'
|
||||
|
||||
import classNames from 'classnames'
|
||||
import { useEffect, useState } from 'react'
|
||||
export default function AccountDetails() {
|
||||
const params = useParams()
|
||||
const hasAccount = params.account && !isNaN(Number(params.account))
|
||||
|
||||
import { AccountManageOverlay } from 'components/Account/AccountManageOverlay'
|
||||
import { RiskChart } from 'components/Account/RiskChart'
|
||||
import { Button } from 'components/Button'
|
||||
import { ArrowRightLine, ChevronDown, ChevronLeft } from 'components/Icons'
|
||||
import { LabelValuePair } from 'components/LabelValuePair'
|
||||
import { PositionsList } from 'components/PositionsList'
|
||||
import { useAccountStats } from 'hooks/data/useAccountStats'
|
||||
import { convertFromGwei } from 'utils/formatters'
|
||||
import { createRiskData } from 'utils/risk'
|
||||
import useStore from 'store'
|
||||
import { getBaseAsset, getMarketAssets } from 'utils/assets'
|
||||
|
||||
export const AccountDetails = () => {
|
||||
const enableAnimations = useStore((s) => s.enableAnimations)
|
||||
const selectedAccount = useStore((s) => s.selectedAccount)
|
||||
const isOpen = useStore((s) => s.isOpen)
|
||||
const marketAssets = getMarketAssets()
|
||||
const baseAsset = getBaseAsset()
|
||||
|
||||
const accountStats = useAccountStats()
|
||||
|
||||
const [showManageMenu, setShowManageMenu] = useState(false)
|
||||
const [riskData, setRiskData] = useState<RiskTimePair[]>()
|
||||
|
||||
useEffect(() => {
|
||||
setRiskData(createRiskData(accountStats?.risk ?? 0))
|
||||
}, [accountStats?.risk, selectedAccount])
|
||||
|
||||
return (
|
||||
<div
|
||||
className={classNames(
|
||||
'relative flex w-[400px] basis-[400px] flex-wrap content-start border-l border-white/20 bg-header',
|
||||
enableAnimations && 'transition-[margin] duration-1000 ease-in-out',
|
||||
isOpen ? 'mr-0' : '-mr-[400px]',
|
||||
)}
|
||||
>
|
||||
<Button
|
||||
onClick={() => {
|
||||
useStore.setState({ isOpen: true })
|
||||
}}
|
||||
variant='text'
|
||||
className={classNames(
|
||||
'absolute top-1/2 -left-[20px] w-[21px] -translate-y-1/2 bg-header p-0',
|
||||
'rounded-none rounded-tl-sm rounded-bl-sm',
|
||||
'border border-white/20',
|
||||
enableAnimations && 'transition-[opacity] delay-1000 duration-500 ease-in-out',
|
||||
isOpen ? 'pointer-events-none opacity-0' : 'opacity-100',
|
||||
)}
|
||||
>
|
||||
<span
|
||||
className={classNames(
|
||||
'flex h-20 px-1 py-6 text-white/40 hover:text-white',
|
||||
enableAnimations && 'transition-[color]',
|
||||
)}
|
||||
>
|
||||
<ChevronLeft />
|
||||
</span>
|
||||
</Button>
|
||||
<div className='relative flex w-full flex-wrap items-center border-b border-white/20'>
|
||||
<Button
|
||||
variant='text'
|
||||
className='flex flex-grow flex-nowrap items-center justify-center p-4 text-center text-white text-xl-caps'
|
||||
onClick={() => setShowManageMenu(!showManageMenu)}
|
||||
>
|
||||
Account {selectedAccount}
|
||||
<span className='ml-2 flex w-4'>
|
||||
<ChevronDown />
|
||||
</span>
|
||||
</Button>
|
||||
<div className='flex border-l border-white/20' onClick={() => {}}>
|
||||
<Button
|
||||
variant='text'
|
||||
className={classNames(
|
||||
'w-14 p-4 text-white/40 hover:cursor-pointer hover:text-white',
|
||||
enableAnimations && 'transition-[color]',
|
||||
)}
|
||||
onClick={() => {
|
||||
useStore.setState({ isOpen: false })
|
||||
}}
|
||||
>
|
||||
<ArrowRightLine />
|
||||
</Button>
|
||||
return hasAccount ? (
|
||||
<div className='fixed top-[89px] right-4 w-16 rounded-base border border-white/20 bg-white/5 backdrop-blur-sticky'>
|
||||
<div className='flex w-full flex-wrap justify-center py-4'>
|
||||
<Gauge tooltip='Health Factor' value={0.2} icon={<Heart />} />
|
||||
<Text size='2xs' className='mt-1 mb-0.5 w-full text-center text-white/50'>
|
||||
Health
|
||||
</Text>
|
||||
<Text size='xs' className='w-full text-center'>
|
||||
89%
|
||||
</Text>
|
||||
</div>
|
||||
<AccountManageOverlay
|
||||
className='top-[60px] left-[36px]'
|
||||
show={showManageMenu}
|
||||
setShow={setShowManageMenu}
|
||||
/>
|
||||
<div className='w-full border border-x-0 border-white/20 py-4'>
|
||||
<Text size='2xs' className='mb-0.5 w-full text-center text-white/50'>
|
||||
Leverage
|
||||
</Text>
|
||||
<Text size='xs' className='w-full text-center'>
|
||||
4.5x
|
||||
</Text>
|
||||
</div>
|
||||
<div className='flex w-full flex-wrap p-3'>
|
||||
<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 className='w-full py-4'>
|
||||
<Text size='2xs' className='mb-0.5 w-full text-center text-white/50'>
|
||||
Balance
|
||||
</Text>{' '}
|
||||
<Text size='xs' className='w-full text-center'>
|
||||
$300M
|
||||
</Text>
|
||||
</div>
|
||||
{riskData && <RiskChart data={riskData} />}
|
||||
</div>
|
||||
)
|
||||
) : null
|
||||
}
|
||||
|
@ -1,94 +0,0 @@
|
||||
'use client'
|
||||
|
||||
import { useRouter } from 'next/navigation'
|
||||
|
||||
import { Button } from 'components/Button'
|
||||
import { Add, ArrowDown, ArrowsLeftRight, ArrowUp, Rubbish } from 'components/Icons'
|
||||
import { Overlay } from 'components/Overlay/Overlay'
|
||||
import { OverlayAction } from 'components/Overlay/OverlayAction'
|
||||
import { Text } from 'components/Text'
|
||||
import useParams from 'hooks/useParams'
|
||||
import useStore from 'store'
|
||||
import { hardcodedFee } from 'utils/contants'
|
||||
|
||||
interface Props {
|
||||
className?: string
|
||||
setShow: (show: boolean) => void
|
||||
show: boolean
|
||||
}
|
||||
|
||||
export const AccountManageOverlay = ({ className, setShow, show }: Props) => {
|
||||
const router = useRouter()
|
||||
const params = useParams()
|
||||
const createCreditAccount = useStore((s) => s.createCreditAccount)
|
||||
const deleteCreditAccount = useStore((s) => s.deleteCreditAccount)
|
||||
|
||||
async function createAccount() {
|
||||
const newAccountId = await createCreditAccount({ fee: hardcodedFee })
|
||||
router.push(`/wallets/${params.wallet}/accounts/${newAccountId}`)
|
||||
}
|
||||
|
||||
async function deleteAccountHandler() {
|
||||
const isSuccess = await deleteCreditAccount({ fee: hardcodedFee, accountId: params.account })
|
||||
if (isSuccess) {
|
||||
router.push(`/wallets/${params.wallet}/accounts`)
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<Overlay className={className} show={show} setShow={setShow}>
|
||||
<div className='flex w-[274px] flex-wrap'>
|
||||
<Text size='sm' uppercase={true} className='w-full px-4 pt-4 text-center text-accent-dark'>
|
||||
Manage
|
||||
</Text>
|
||||
<div className='flex w-full justify-between border-b border-b-black/10 p-4'>
|
||||
<Button
|
||||
className='flex w-[115px] items-center justify-center pl-0 pr-2'
|
||||
onClick={() => {
|
||||
useStore.setState({ fundAccountModal: true })
|
||||
setShow(false)
|
||||
}}
|
||||
>
|
||||
<span className='mr-1 w-3'>
|
||||
<ArrowUp />
|
||||
</span>
|
||||
Fund
|
||||
</Button>
|
||||
<Button
|
||||
className='flex w-[115px] items-center justify-center pl-0 pr-2'
|
||||
color='secondary'
|
||||
onClick={() => {
|
||||
useStore.setState({ withdrawModal: true })
|
||||
setShow(false)
|
||||
}}
|
||||
>
|
||||
<span className='mr-1 w-3'>
|
||||
<ArrowDown />
|
||||
</span>
|
||||
Withdraw
|
||||
</Button>
|
||||
</div>
|
||||
<div className='flex w-full flex-wrap p-4'>
|
||||
<OverlayAction
|
||||
setShow={setShow}
|
||||
text='Create New Account'
|
||||
onClick={createAccount}
|
||||
icon={<Add />}
|
||||
/>
|
||||
<OverlayAction
|
||||
setShow={setShow}
|
||||
text='Close Account'
|
||||
onClick={deleteAccountHandler}
|
||||
icon={<Rubbish />}
|
||||
/>
|
||||
<OverlayAction
|
||||
setShow={setShow}
|
||||
text='Transfer Balance'
|
||||
onClick={() => alert('TODO')}
|
||||
icon={<ArrowsLeftRight />}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</Overlay>
|
||||
)
|
||||
}
|
@ -1,36 +1,38 @@
|
||||
'use client'
|
||||
|
||||
import classNames from 'classnames'
|
||||
import { useRouter } from 'next/navigation'
|
||||
import { useState } from 'react'
|
||||
|
||||
import { AccountManageOverlay } from 'components/Account/AccountManageOverlay'
|
||||
import { Button } from 'components/Button'
|
||||
import { ChevronDown } from 'components/Icons'
|
||||
import {
|
||||
Account,
|
||||
Add,
|
||||
ArrowDownLine,
|
||||
ArrowsLeftRight,
|
||||
ArrowUpLine,
|
||||
Rubbish,
|
||||
} from 'components/Icons'
|
||||
import Loading from 'components/Loading'
|
||||
import { Overlay } from 'components/Overlay/Overlay'
|
||||
import { Text } from 'components/Text'
|
||||
import useParams from 'hooks/useParams'
|
||||
import useStore from 'store'
|
||||
import { hardcodedFee } from 'utils/contants'
|
||||
|
||||
const MAX_VISIBLE_CREDIT_ACCOUNTS = 5
|
||||
|
||||
interface Props {
|
||||
creditAccounts: string[]
|
||||
}
|
||||
|
||||
export const AccountNavigation = (props: Props) => {
|
||||
export const AccountNavigation = () => {
|
||||
const router = useRouter()
|
||||
const params = useParams()
|
||||
const address = useStore((s) => s.client?.recentWallet.account?.address) || ''
|
||||
const selectedAccount = params.account
|
||||
|
||||
const createCreditAccount = useStore((s) => s.createCreditAccount)
|
||||
const deleteCreditAccount = useStore((s) => s.deleteCreditAccount)
|
||||
const creditAccounts = useStore((s) => s.creditAccounts)
|
||||
const address = useStore((s) => s.address)
|
||||
|
||||
const hasCreditAccounts = !!props.creditAccounts?.length
|
||||
const firstCreditAccounts = props.creditAccounts?.slice(0, MAX_VISIBLE_CREDIT_ACCOUNTS) ?? []
|
||||
const restCreditAccounts = props.creditAccounts?.slice(MAX_VISIBLE_CREDIT_ACCOUNTS) ?? []
|
||||
const hasCreditAccounts = !!creditAccounts?.length
|
||||
const accountSelected = !!selectedAccount && !isNaN(Number(selectedAccount))
|
||||
|
||||
const [showManageMenu, setShowManageMenu] = useState(false)
|
||||
const [showMoreMenu, setShowMoreMenu] = useState(false)
|
||||
const [showMenu, setShowMenu] = useState(false)
|
||||
|
||||
async function createAccountHandler() {
|
||||
const accountId = await createCreditAccount({ fee: hardcodedFee })
|
||||
@ -38,93 +40,128 @@ export const AccountNavigation = (props: Props) => {
|
||||
router.push(`/wallets/${params.wallet}/accounts/${accountId}`)
|
||||
}
|
||||
|
||||
return (
|
||||
<section
|
||||
role='navigation'
|
||||
className='flex h-11 w-full items-center gap-6 border-b border-white/20 px-6 text-sm text-white/40'
|
||||
>
|
||||
<>
|
||||
{hasCreditAccounts ? (
|
||||
<>
|
||||
{firstCreditAccounts.map((account) => (
|
||||
<Button
|
||||
key={account}
|
||||
className={classNames(
|
||||
'cursor-pointer whitespace-nowrap px-4 text-base hover:text-white',
|
||||
selectedAccount === account ? 'text-white' : 'text-white/40',
|
||||
)}
|
||||
variant='text'
|
||||
onClick={() => {
|
||||
router.push(`/wallets/${params.wallet}/accounts/${account}/${params.page}`)
|
||||
}}
|
||||
>
|
||||
Account {account}
|
||||
</Button>
|
||||
))}
|
||||
<div className='relative'>
|
||||
{restCreditAccounts.length > 0 && (
|
||||
<>
|
||||
<Button
|
||||
className='flex items-center px-3 py-3 text-base hover:text-white'
|
||||
variant='text'
|
||||
onClick={() => setShowMoreMenu(!showMoreMenu)}
|
||||
>
|
||||
More
|
||||
<span className='ml-1 inline-block w-3'>
|
||||
<ChevronDown />
|
||||
</span>
|
||||
</Button>
|
||||
<Overlay show={showMoreMenu} setShow={setShowMoreMenu}>
|
||||
<div className='flex w-[120px] flex-wrap p-4'>
|
||||
{restCreditAccounts.map((account) => (
|
||||
<Button
|
||||
key={account}
|
||||
variant='text'
|
||||
className={classNames(
|
||||
'w-full whitespace-nowrap py-2 text-sm',
|
||||
selectedAccount === account
|
||||
? 'text-secondary'
|
||||
: 'cursor-pointer text-accent-dark hover:text-secondary',
|
||||
)}
|
||||
onClick={() => {
|
||||
setShowMoreMenu(!showMoreMenu)
|
||||
router.push(`/wallets/${params.wallet}/accounts/${account}`)
|
||||
}}
|
||||
>
|
||||
Account {account}
|
||||
</Button>
|
||||
))}
|
||||
</div>
|
||||
</Overlay>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
<div className='relative'>
|
||||
<Button
|
||||
className={classNames(
|
||||
'flex items-center px-3 py-3 text-base hover:text-white',
|
||||
showManageMenu ? 'text-white' : 'text-white/40',
|
||||
)}
|
||||
onClick={() => setShowManageMenu(!showManageMenu)}
|
||||
variant='text'
|
||||
>
|
||||
Manage
|
||||
<span className='ml-1 inline-block w-3'>
|
||||
<ChevronDown />
|
||||
</span>
|
||||
</Button>
|
||||
async function deleteAccountHandler() {
|
||||
if (!accountSelected) return
|
||||
const isSuccess = await deleteCreditAccount({ fee: hardcodedFee, accountId: selectedAccount })
|
||||
if (isSuccess) {
|
||||
router.push(`/wallets/${params.wallet}/accounts`)
|
||||
}
|
||||
}
|
||||
|
||||
<AccountManageOverlay
|
||||
className='-left-[86px]'
|
||||
show={showManageMenu}
|
||||
setShow={setShowManageMenu}
|
||||
return !address ? null : (
|
||||
<>
|
||||
{creditAccounts === null ? (
|
||||
<Loading className='h-8 w-35' />
|
||||
) : (
|
||||
<>
|
||||
{' '}
|
||||
{hasCreditAccounts ? (
|
||||
<div className='relative'>
|
||||
<Button
|
||||
variant='solid'
|
||||
color='tertiary'
|
||||
className='flex flex-1 flex-nowrap'
|
||||
icon={<Account />}
|
||||
onClick={() => setShowMenu(!showMenu)}
|
||||
hasSubmenu
|
||||
>
|
||||
<span>{accountSelected ? `Account ${selectedAccount}` : 'Select Account'}</span>
|
||||
</Button>
|
||||
<Overlay className='l-0 mt-2 w-[274px]' show={showMenu} setShow={setShowMenu}>
|
||||
{accountSelected && (
|
||||
<div className='flex w-full flex-wrap'>
|
||||
<Text size='sm' uppercase={true} className='w-full justify-center px-4 pt-4'>
|
||||
Manage Account {selectedAccount}
|
||||
</Text>
|
||||
<div className='flex w-full justify-between p-4'>
|
||||
<Button
|
||||
className='flex w-[115px] items-center justify-center pl-0 pr-2'
|
||||
text='Fund'
|
||||
icon={<ArrowUpLine />}
|
||||
onClick={() => {
|
||||
useStore.setState({ fundAccountModal: true })
|
||||
setShowMenu(false)
|
||||
}}
|
||||
/>
|
||||
<Button
|
||||
className='flex w-[115px] items-center justify-center pl-0 pr-2'
|
||||
color='secondary'
|
||||
icon={<ArrowDownLine />}
|
||||
text='Withdraw'
|
||||
onClick={() => {
|
||||
useStore.setState({ withdrawModal: true })
|
||||
setShowMenu(false)
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
<div className='flex w-full flex-wrap border-t border-t-white/10 p-4'>
|
||||
<Button
|
||||
className='w-full whitespace-nowrap py-2'
|
||||
variant='transparent'
|
||||
color='quaternary'
|
||||
text='Create New Account'
|
||||
onClick={() => {
|
||||
setShowMenu(false)
|
||||
createAccountHandler()
|
||||
}}
|
||||
icon={<Add />}
|
||||
/>
|
||||
<Button
|
||||
className='w-full whitespace-nowrap py-2'
|
||||
variant='transparent'
|
||||
color='quaternary'
|
||||
text='Close Account'
|
||||
onClick={() => {
|
||||
setShowMenu(false)
|
||||
deleteAccountHandler()
|
||||
}}
|
||||
icon={<Rubbish />}
|
||||
/>
|
||||
<Button
|
||||
className='w-full whitespace-nowrap py-2'
|
||||
variant='transparent'
|
||||
color='quaternary'
|
||||
text='Transfer Balance'
|
||||
onClick={() => {
|
||||
setShowMenu(false)
|
||||
/* TODO: add Transfer Balance Function */
|
||||
}}
|
||||
icon={<ArrowsLeftRight />}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{creditAccounts.length > 1 && (
|
||||
<div className='flex w-full flex-wrap border-t border-t-white/10 p-4'>
|
||||
<Text size='sm' uppercase={true} className='w-full justify-center pb-2'>
|
||||
Select Account
|
||||
</Text>
|
||||
{creditAccounts.map((account) =>
|
||||
selectedAccount === account ? null : (
|
||||
<Button
|
||||
key={account}
|
||||
className='w-full whitespace-nowrap py-2'
|
||||
variant='transparent'
|
||||
color='quaternary'
|
||||
onClick={() => {
|
||||
router.push(`/wallets/${params.wallet}/accounts/${account}`)
|
||||
setShowMenu(!showMenu)
|
||||
}}
|
||||
text={`Account ${account}`}
|
||||
/>
|
||||
),
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</Overlay>
|
||||
</div>
|
||||
) : (
|
||||
<>{address ? <Button onClick={createAccountHandler}>Create Account</Button> : ''}</>
|
||||
<Button onClick={createAccountHandler} icon={<Add />} color='tertiary'>
|
||||
Create Account
|
||||
</Button>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
</section>
|
||||
)
|
||||
}
|
||||
|
@ -1,83 +0,0 @@
|
||||
'use client'
|
||||
|
||||
import BigNumber from 'bignumber.js'
|
||||
|
||||
import { BorrowCapacity } from 'components/BorrowCapacity'
|
||||
import { Button } from 'components/Button'
|
||||
import { FormattedNumber } from 'components/FormattedNumber'
|
||||
import { Gauge } from 'components/Gauge'
|
||||
import { Text } from 'components/Text'
|
||||
import { useAccountStats } from 'hooks/data/useAccountStats'
|
||||
import { useCreditAccounts } from 'hooks/queries/useCreditAccounts'
|
||||
import { getBaseAsset } from 'utils/assets'
|
||||
import { formatLeverage, formatValue } from 'utils/formatters'
|
||||
|
||||
export const AccountStatus = () => {
|
||||
const baseAsset = getBaseAsset()
|
||||
const accountStats = useAccountStats()
|
||||
const { data: creditAccountsList } = useCreditAccounts()
|
||||
|
||||
const createCreditAccount = () => {
|
||||
console.log('create credit account')
|
||||
}
|
||||
|
||||
const hasCreditAccounts = creditAccountsList && creditAccountsList.length > 0
|
||||
|
||||
if (!hasCreditAccounts) {
|
||||
return (
|
||||
<Button className='my-3 mr-6' onClick={() => createCreditAccount()}>
|
||||
Create Credit Account
|
||||
</Button>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<div className='flex w-[400px] items-center justify-between gap-3 border-l border-l-white/20 px-3 py-3'>
|
||||
{accountStats && (
|
||||
<>
|
||||
<Text size='sm' className='flex flex-grow text-white'>
|
||||
<FormattedNumber
|
||||
amount={BigNumber(accountStats.netWorth)
|
||||
.dividedBy(10 ** baseAsset.decimals)
|
||||
.toNumber()}
|
||||
animate
|
||||
options={{ prefix: '$: ' }}
|
||||
/>
|
||||
</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>
|
||||
)
|
||||
}
|
@ -66,7 +66,7 @@ export const FundAccountModal = () => {
|
||||
found = true
|
||||
}
|
||||
})
|
||||
}, [marketAssets, balancesData])
|
||||
}, [balancesData, marketAssets, selectedToken])
|
||||
|
||||
// ---------------
|
||||
// VARIABLES
|
||||
@ -130,7 +130,7 @@ export const FundAccountModal = () => {
|
||||
your osmosis wallet.
|
||||
</Text>
|
||||
<>
|
||||
<div className='mb-4 rounded-md border border-white/20'>
|
||||
<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:
|
||||
|
@ -2,20 +2,16 @@ import { Switch } from '@headlessui/react'
|
||||
import BigNumber from 'bignumber.js'
|
||||
import classNames from 'classnames'
|
||||
import React, { useEffect, useMemo, useState } from 'react'
|
||||
import { toast } from 'react-toastify'
|
||||
|
||||
import { BorrowCapacity } from 'components/BorrowCapacity'
|
||||
import { convertFromGwei, formatLeverage, formatValue } from 'utils/formatters'
|
||||
import { getTokenDecimals, getTokenSymbol } from 'utils/tokens'
|
||||
import { CircularProgress } from 'components/CircularProgress'
|
||||
import { Button } from 'components/Button'
|
||||
import { Text } from 'components/Text'
|
||||
import { Slider } from 'components/Slider'
|
||||
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 { PositionsList } from 'components/PositionsList'
|
||||
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'
|
||||
@ -23,6 +19,8 @@ import { useCreditAccountPositions } from 'hooks/queries/useCreditAccountPositio
|
||||
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'
|
||||
|
||||
export const WithdrawModal = () => {
|
||||
// ---------------
|
||||
@ -169,7 +167,7 @@ export const WithdrawModal = () => {
|
||||
<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-md border border-white/20'>
|
||||
<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:
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { FormattedNumber } from './FormattedNumber'
|
||||
import TitleAndSubCell from './TitleAndSubCell'
|
||||
import { FormattedNumber } from 'components/FormattedNumber'
|
||||
import TitleAndSubCell from 'components/TitleAndSubCell'
|
||||
|
||||
interface Props {
|
||||
asset: Asset
|
||||
|
@ -1,5 +1,44 @@
|
||||
'use client'
|
||||
import classNames from 'classnames'
|
||||
|
||||
export default function Background() {
|
||||
return <div className='background' />
|
||||
return (
|
||||
<div className='background pointer-events-none fixed inset-0 h-full w-full overflow-hidden bg-body'>
|
||||
<div
|
||||
className={classNames(
|
||||
'fixed',
|
||||
'h-[20vw] w-[20vw]',
|
||||
'min-h-[150px] min-w-[150px]',
|
||||
'max-h-[500px] max-w-[500px]',
|
||||
'top-[-10vw] left-[-10vw]',
|
||||
'bg-orb-primary blur-orb-primary ',
|
||||
'translate-x-0 translate-y-0 rounded-full opacity-20',
|
||||
'animate-[float_120s_ease_in_out_infinite_2s]',
|
||||
)}
|
||||
/>
|
||||
<div
|
||||
className={classNames(
|
||||
'fixed',
|
||||
'h-[40vw] w-[40vw]',
|
||||
'min-h-[400px] min-w-[400px]',
|
||||
'max-h-[1000px] max-w-[1000px]',
|
||||
'bottom-[-10vw] right-[-8vw]',
|
||||
'bg-orb-secondary blur-orb-secondary',
|
||||
'translate-x-0 translate-y-0 rounded-full opacity-30',
|
||||
'animate-[float_150s_bounce_out_infinite_1s]',
|
||||
)}
|
||||
/>
|
||||
<div
|
||||
className={classNames(
|
||||
'fixed',
|
||||
'h-[25vw] w-[25vw]',
|
||||
'min-h-[120px] min-w-[120px]',
|
||||
'max-h-[600px] max-w-[600px]',
|
||||
'top-[-10vw] right-[-4vw]',
|
||||
'bg-orb-tertiary blur-orb-tertiary ',
|
||||
'translate-x-0 translate-y-0 rounded-full opacity-20',
|
||||
'animate-[float_180s_ease_in_infinite]',
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
@ -8,20 +8,19 @@ import {
|
||||
SortingState,
|
||||
useReactTable,
|
||||
} from '@tanstack/react-table'
|
||||
import classNames from 'classnames'
|
||||
import Image from 'next/image'
|
||||
import React from 'react'
|
||||
import classNames from 'classnames'
|
||||
|
||||
import AmountAndValue from 'components/AmountAndValue'
|
||||
import { AssetRow } from 'components/Borrow/AssetRow'
|
||||
import { ChevronDown, ChevronUp } from 'components/Icons'
|
||||
import { getMarketAssets } from 'utils/assets'
|
||||
import { Text } from 'components/Text'
|
||||
import TitleAndSubCell from 'components/TitleAndSubCell'
|
||||
import { FormattedNumber } from 'components/FormattedNumber'
|
||||
import AmountAndValue from 'components/AmountAndValue'
|
||||
import { getMarketAssets } from 'utils/assets'
|
||||
import { formatPercent } from 'utils/formatters'
|
||||
|
||||
import AssetExpanded from './AssetExpanded'
|
||||
import AssetExpanded from 'components/Borrow/AssetExpanded'
|
||||
import Loading from 'components/Loading'
|
||||
|
||||
type Props = {
|
||||
data: BorrowAsset[] | BorrowAssetActive[]
|
||||
@ -52,11 +51,17 @@ export const BorrowTable = (props: Props) => {
|
||||
{
|
||||
accessorKey: 'borrowRate',
|
||||
header: 'Borrow Rate',
|
||||
cell: ({ row }) => (
|
||||
cell: ({ row }) => {
|
||||
if (row.original.borrowRate === null) {
|
||||
return <Loading />
|
||||
}
|
||||
|
||||
return (
|
||||
<Text className='justify-end' size='sm'>
|
||||
{formatPercent(row.original.borrowRate)}
|
||||
</Text>
|
||||
),
|
||||
)
|
||||
},
|
||||
},
|
||||
...((props.data[0] as BorrowAssetActive)?.debt
|
||||
? [
|
||||
@ -82,6 +87,10 @@ export const BorrowTable = (props: Props) => {
|
||||
|
||||
if (!asset) return null
|
||||
|
||||
if (row.original.liquidity === null) {
|
||||
return <Loading />
|
||||
}
|
||||
|
||||
return <AmountAndValue asset={asset} amount={row.original.liquidity.amount} />
|
||||
},
|
||||
},
|
||||
@ -97,7 +106,7 @@ export const BorrowTable = (props: Props) => {
|
||||
),
|
||||
},
|
||||
],
|
||||
[],
|
||||
[marketAssets, props.data],
|
||||
)
|
||||
|
||||
const table = useReactTable({
|
||||
|
69
src/components/Borrow/Borrowings.tsx
Normal file
@ -0,0 +1,69 @@
|
||||
import { Suspense } from 'react'
|
||||
|
||||
import { Card } from 'components/Card'
|
||||
import { getAccountDebts, getBorrowData } from 'utils/api'
|
||||
import { getMarketAssets } from 'utils/assets'
|
||||
|
||||
import { BorrowTable } from './BorrowTable'
|
||||
|
||||
async function Content(props: Props) {
|
||||
const debtData = await getAccountDebts(props.params?.account)
|
||||
const borrowData = await getBorrowData()
|
||||
|
||||
const marketAssets = getMarketAssets()
|
||||
|
||||
function getBorrowAssets() {
|
||||
return marketAssets.reduce(
|
||||
(prev: { available: BorrowAsset[]; active: BorrowAssetActive[] }, curr) => {
|
||||
const borrow = borrowData.find((borrow) => borrow.denom === curr.denom)
|
||||
if (borrow) {
|
||||
const debt = debtData.find((debt) => debt.denom === curr.denom)
|
||||
if (debt) {
|
||||
prev.active.push({
|
||||
...borrow,
|
||||
debt: debt.amount,
|
||||
})
|
||||
} else {
|
||||
prev.available.push(borrow)
|
||||
}
|
||||
}
|
||||
return prev
|
||||
},
|
||||
{ available: [], active: [] },
|
||||
)
|
||||
}
|
||||
|
||||
const { available, active } = getBorrowAssets()
|
||||
|
||||
return <BorrowTable data={props.type === 'active' ? active : available} />
|
||||
}
|
||||
|
||||
function Fallback() {
|
||||
const marketAssets = getMarketAssets()
|
||||
|
||||
const available: BorrowAsset[] = marketAssets.reduce((prev: BorrowAsset[], curr) => {
|
||||
prev.push({ ...curr, borrowRate: null, liquidity: null })
|
||||
|
||||
return prev
|
||||
}, [])
|
||||
|
||||
return <BorrowTable data={available} />
|
||||
}
|
||||
|
||||
export default function BorrowPage(props: Props) {
|
||||
return (
|
||||
<Card
|
||||
className='h-fit w-full'
|
||||
title={props.type === 'active' ? 'Borrowings' : 'Available to borrow'}
|
||||
>
|
||||
<Suspense fallback={<Fallback />}>
|
||||
{/* @ts-expect-error Server Component */}
|
||||
<Content params={props.params} type={props.type} />
|
||||
</Suspense>
|
||||
</Card>
|
||||
)
|
||||
}
|
||||
|
||||
interface Props extends PageProps {
|
||||
type: 'active' | 'available'
|
||||
}
|
@ -1,7 +1,6 @@
|
||||
import useStore from 'store'
|
||||
|
||||
import { Modal } from './Modal'
|
||||
import TitleAndSubCell from './TitleAndSubCell'
|
||||
import { Modal } from 'components/Modal'
|
||||
import TitleAndSubCell from 'components/TitleAndSubCell'
|
||||
|
||||
export default function BorrowModal() {
|
||||
const open = useStore((s) => s.borrowModal)
|
||||
|
@ -1,7 +1,8 @@
|
||||
import classNames from 'classnames'
|
||||
import React, { LegacyRef, ReactNode } from 'react'
|
||||
import React, { LegacyRef, ReactElement, ReactNode } from 'react'
|
||||
|
||||
import { CircularProgress } from 'components/CircularProgress'
|
||||
import { ChevronDown } from 'components/Icons'
|
||||
import useStore from 'store'
|
||||
|
||||
interface Props {
|
||||
@ -13,30 +14,39 @@ interface Props {
|
||||
showProgressIndicator?: boolean
|
||||
size?: 'small' | 'medium' | 'large'
|
||||
text?: string | ReactNode
|
||||
variant?: 'solid' | 'transparent' | 'round' | 'text'
|
||||
variant?: 'solid' | 'transparent' | 'round'
|
||||
onClick?: (e: React.MouseEvent<HTMLButtonElement>) => void
|
||||
icon?: ReactElement
|
||||
iconClassName?: string
|
||||
hasSubmenu?: boolean
|
||||
}
|
||||
|
||||
export const buttonColorClasses = {
|
||||
primary:
|
||||
'border-none text-white bg-primary hover:bg-primary-highlight active:bg-primary-highlight-10 focus:bg-primary-highlight',
|
||||
'border-none gradient-primary-to-secondary hover:bg-white/20 active:bg-white/40 focus:bg-white/20',
|
||||
secondary:
|
||||
'border-none text-white bg-secondary hover:bg-secondary-highlight active:bg-secondary-highlight-10 focus:bg-secondary-highlight',
|
||||
'border border-white/30 bg-transparent hover:bg-white/20 active:bg-white/40 focus:bg-white/20',
|
||||
tertiary:
|
||||
'border text-white bg-secondary-dark/60 border-white/60 hover:bg-secondary-dark hover:border-white active:bg-secondary-dark-10 active:border-white focus:bg-secondary-dark focus:border-white',
|
||||
'border border-transparent bg-white/10 hover:bg-white/20 active:bg-white/40 focus:bg-white/20',
|
||||
quaternary:
|
||||
'border bg-transparent text-white/60 border-transparent hover:text-white hover:border-white active:text-white active:border-white',
|
||||
'bg-transparent text-white/60 border-transparent hover:text-white hover:border-white active:text-white active:border-white',
|
||||
}
|
||||
|
||||
const buttonBorderClasses =
|
||||
'before:content-[" "] before:absolute before:inset-0 before:rounded-sm before:p-[1px] before:border-glas before:z-[-1]'
|
||||
|
||||
const buttonGradientClasses = [
|
||||
'before:content-[" "] before:absolute before:inset-0 before:rounded-sm before:z-[-1] before:opacity-0',
|
||||
'before:gradient-secondary-to-primary before:transition-opacity before:duration-500 before:ease-in',
|
||||
'hover:before:opacity-100',
|
||||
]
|
||||
|
||||
const buttonTransparentColorClasses = {
|
||||
primary:
|
||||
'border-none text-primary hover:text-primary-highlight active:text-primary-highlight focus:text-primary-highlight',
|
||||
secondary:
|
||||
'border-none text-secondary hover:text-secondary-highlight active:text-secondary-highlight focus:text-secondary-highlight',
|
||||
tertiary:
|
||||
'text-secondary-dark hover:text-secondary-dark-10 active:text-secondary-dark-10 focus:text-secondary-dark-10',
|
||||
primary: 'border-none hover:text-primary active:text-primary focus:text-primary',
|
||||
secondary: 'border-none hover:text-secondary active:text-secondary focus:text-secondary',
|
||||
tertiary: 'border-none hover:text-white/80 active:text-white/80 focus:text-white/80',
|
||||
quaternary:
|
||||
'border border-transparent text-white/60 hover:text-white hover:border-white active:text-white active:border-white',
|
||||
'border-none text-white/60 hover:text-white hover:border-white active:text-white active:border-white',
|
||||
}
|
||||
|
||||
const buttonRoundSizeClasses = {
|
||||
@ -46,16 +56,41 @@ const buttonRoundSizeClasses = {
|
||||
}
|
||||
|
||||
export const buttonSizeClasses = {
|
||||
small: 'text-sm px-5 py-1.5 min-h-[32px]',
|
||||
medium: 'text-base px-6 py-2.5 min-h-[40px]',
|
||||
large: 'text-lg px-6 py-2.5 min-h-[56px]',
|
||||
small: 'text-sm',
|
||||
medium: 'text-base',
|
||||
large: 'text-lg',
|
||||
}
|
||||
|
||||
export const buttonPaddingClasses = {
|
||||
small: 'px-2.5 py-1.5 min-h-[32px]',
|
||||
medium: 'px-3 py-2 min-h-[40px]',
|
||||
large: 'px-3.5 py-2.5 min-h-[56px]',
|
||||
}
|
||||
|
||||
export const buttonVariantClasses = {
|
||||
solid: 'text-white',
|
||||
transparent: 'bg-transparent p-0',
|
||||
solid: 'rounded-sm text-white shadow-button justify-center group',
|
||||
transparent: 'rounded-sm bg-transparent p-0 transition duration-200 ease-in',
|
||||
round: 'rounded-full p-0',
|
||||
text: 'border-none bg-transparent',
|
||||
}
|
||||
|
||||
function glowElement() {
|
||||
return (
|
||||
<svg
|
||||
className={classNames(
|
||||
'glow-container z-1 opacity-0 group-hover:animate-glow group-focus:animate-glow',
|
||||
'pointer-events-none absolute inset-0 h-full w-full',
|
||||
)}
|
||||
>
|
||||
<rect
|
||||
pathLength='100'
|
||||
strokeLinecap='round'
|
||||
width='100%'
|
||||
height='100%'
|
||||
rx='4'
|
||||
className='absolute glow-line group-hover:glow-hover group-focus:glow-hover'
|
||||
/>
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
|
||||
export const Button = React.forwardRef(function Button(
|
||||
@ -70,17 +105,22 @@ export const Button = React.forwardRef(function Button(
|
||||
text,
|
||||
variant = 'solid',
|
||||
onClick,
|
||||
icon,
|
||||
iconClassName,
|
||||
hasSubmenu,
|
||||
}: Props,
|
||||
ref,
|
||||
) {
|
||||
const buttonClasses = []
|
||||
const enableAnimations = useStore((s) => s.enableAnimations)
|
||||
const isDisabled = disabled || showProgressIndicator
|
||||
|
||||
switch (variant) {
|
||||
case 'round':
|
||||
buttonClasses.push(
|
||||
buttonSizeClasses[size],
|
||||
buttonRoundSizeClasses[size],
|
||||
buttonPaddingClasses[size],
|
||||
buttonColorClasses[color],
|
||||
)
|
||||
break
|
||||
@ -90,7 +130,11 @@ export const Button = React.forwardRef(function Button(
|
||||
break
|
||||
|
||||
case 'solid':
|
||||
buttonClasses.push(buttonSizeClasses[size], buttonColorClasses[color])
|
||||
buttonClasses.push(
|
||||
buttonSizeClasses[size],
|
||||
buttonPaddingClasses[size],
|
||||
buttonColorClasses[color],
|
||||
)
|
||||
break
|
||||
default:
|
||||
}
|
||||
@ -98,10 +142,14 @@ export const Button = React.forwardRef(function Button(
|
||||
return (
|
||||
<button
|
||||
className={classNames(
|
||||
'outline-nones cursor-pointer appearance-none break-normal rounded-3xl',
|
||||
'relative z-1 flex items-center',
|
||||
'cursor-pointer appearance-none break-normal outline-none',
|
||||
'text-white transition-all duration-500',
|
||||
enableAnimations && 'transition-color',
|
||||
buttonClasses,
|
||||
buttonVariantClasses[variant],
|
||||
variant === 'solid' && color === 'tertiary' && buttonBorderClasses,
|
||||
variant === 'solid' && color === 'primary' && buttonGradientClasses,
|
||||
disabled && 'pointer-events-none opacity-50',
|
||||
className,
|
||||
)}
|
||||
@ -109,8 +157,25 @@ export const Button = React.forwardRef(function Button(
|
||||
ref={ref as LegacyRef<HTMLButtonElement>}
|
||||
onClick={disabled ? () => {} : onClick}
|
||||
>
|
||||
{text && !children && !showProgressIndicator && <span>{text}</span>}
|
||||
{children && !showProgressIndicator && children}
|
||||
{icon && !isDisabled && (
|
||||
<span
|
||||
className={classNames(
|
||||
'flex items-center justify-center',
|
||||
(text || children) && 'mr-2',
|
||||
iconClassName ?? 'h-4 w-4',
|
||||
)}
|
||||
>
|
||||
{icon}
|
||||
</span>
|
||||
)}
|
||||
{text && !children && !isDisabled && <span>{text}</span>}
|
||||
{children && !isDisabled && children}
|
||||
{hasSubmenu && !isDisabled && (
|
||||
<span className='ml-2 inline-block w-2.5'>
|
||||
<ChevronDown />
|
||||
</span>
|
||||
)}
|
||||
{variant === 'solid' && !isDisabled && glowElement()}
|
||||
{showProgressIndicator && (
|
||||
<CircularProgress size={size === 'small' ? 10 : size === 'medium' ? 12 : 18} />
|
||||
)}
|
||||
|
@ -1,12 +1,13 @@
|
||||
import classNames from 'classnames'
|
||||
import { ReactNode } from 'react'
|
||||
import { ReactElement, ReactNode } from 'react'
|
||||
|
||||
import { Text } from 'components/Text'
|
||||
|
||||
interface Props {
|
||||
title: string
|
||||
title?: string | ReactElement
|
||||
children: ReactNode
|
||||
className?: string
|
||||
contentClassName?: string
|
||||
}
|
||||
|
||||
export const Card = (props: Props) => {
|
||||
@ -14,13 +15,16 @@ export const Card = (props: Props) => {
|
||||
<section
|
||||
className={classNames(
|
||||
props.className,
|
||||
'h-fit w-full max-w-full overflow-hidden rounded-md border-[1px] border-white/20',
|
||||
'relative z-1 flex max-w-full flex-col flex-wrap items-start overflow-hidden rounded-base border border-transparent bg-white/5',
|
||||
'before:content-[" "] before:absolute before:inset-0 before:z-[-1] before:rounded-base before:p-[1px] before:border-glas',
|
||||
)}
|
||||
>
|
||||
<Text size='lg' className='bg-white/10 p-4 font-semibold'>
|
||||
{props.title && (
|
||||
<Text size='lg' className='flex w-full items-center bg-white/10 p-4 font-semibold'>
|
||||
{props.title}
|
||||
</Text>
|
||||
<div>{props.children}</div>
|
||||
)}
|
||||
<div className={classNames('w-full', props.contentClassName)}>{props.children}</div>
|
||||
</section>
|
||||
)
|
||||
}
|
||||
|
@ -1,15 +0,0 @@
|
||||
import React from 'react'
|
||||
|
||||
export const ContainerSecondary = ({
|
||||
children,
|
||||
className,
|
||||
}: {
|
||||
children: React.ReactNode
|
||||
className?: string
|
||||
}) => {
|
||||
return (
|
||||
<div className={`rounded-md bg-[#D8DAEA] px-3 py-2 text-[#585A74] ${className}`}>
|
||||
{children}
|
||||
</div>
|
||||
)
|
||||
}
|
30
src/components/Council/Overview.tsx
Normal file
@ -0,0 +1,30 @@
|
||||
import { Suspense } from 'react'
|
||||
|
||||
import { Card } from 'components/Card'
|
||||
import Loading from 'components/Loading'
|
||||
import { Text } from 'components/Text'
|
||||
|
||||
async function Content(props: PageProps) {
|
||||
const wallet = props.params.wallet
|
||||
|
||||
return wallet ? (
|
||||
<Text size='sm'>{`Council page for ${wallet}`}</Text>
|
||||
) : (
|
||||
<Text size='sm'>Council view only</Text>
|
||||
)
|
||||
}
|
||||
|
||||
function Fallback() {
|
||||
return <Loading className='h-4 w-50' />
|
||||
}
|
||||
|
||||
export default function Overview(props: PageProps) {
|
||||
return (
|
||||
<Card className='h-fit w-full justify-center' title='Council' contentClassName='px-4 py-6'>
|
||||
<Suspense fallback={<Fallback />}>
|
||||
{/* @ts-expect-error Server Component */}
|
||||
<Content params={props.params} />
|
||||
</Suspense>
|
||||
</Card>
|
||||
)
|
||||
}
|
32
src/components/Earn/Overview.tsx
Normal file
@ -0,0 +1,32 @@
|
||||
import { Suspense } from 'react'
|
||||
|
||||
import { Card } from 'components/Card'
|
||||
import Loading from 'components/Loading'
|
||||
import { Text } from 'components/Text'
|
||||
|
||||
async function Content(props: PageProps) {
|
||||
const wallet = props.params.wallet
|
||||
|
||||
return wallet ? (
|
||||
<Text size='sm'>{`Earn page for ${wallet}`}</Text>
|
||||
) : (
|
||||
<Text size='sm' className='w-full text-center'>
|
||||
You need to be connected to use the earn page
|
||||
</Text>
|
||||
)
|
||||
}
|
||||
|
||||
function Fallback() {
|
||||
return <Loading className='h-4 w-50' />
|
||||
}
|
||||
|
||||
export default function Overview(props: PageProps) {
|
||||
return (
|
||||
<Card className='h-fit w-full justify-center' title='Earn' contentClassName='px-4 py-6'>
|
||||
<Suspense fallback={<Fallback />}>
|
||||
{/* @ts-expect-error Server Component */}
|
||||
<Content params={props.params} />
|
||||
</Suspense>
|
||||
</Card>
|
||||
)
|
||||
}
|
@ -1,5 +1,5 @@
|
||||
import classNames from 'classnames'
|
||||
import { ReactNode } from 'react'
|
||||
import { ReactElement, ReactNode } from 'react'
|
||||
|
||||
import { Tooltip } from 'components/Tooltip'
|
||||
import useStore from 'store'
|
||||
@ -11,81 +11,68 @@ interface Props {
|
||||
diameter?: number
|
||||
value: number
|
||||
label?: string
|
||||
icon?: ReactElement
|
||||
}
|
||||
|
||||
export const Gauge = ({
|
||||
background = '#15161A',
|
||||
background = '#FFFFFF22',
|
||||
diameter = 40,
|
||||
value = 0,
|
||||
label,
|
||||
tooltip,
|
||||
icon,
|
||||
}: Props) => {
|
||||
const enableAnimations = useStore((s) => s.enableAnimations)
|
||||
|
||||
const radius = 16
|
||||
const percentage = value * 100
|
||||
const percentageValue = percentage > 100 ? 100 : percentage < 0 ? 0 : percentage
|
||||
const semiCirclePercentage = percentageValue == -50 ? 0 : Math.abs(percentageValue / 2 - 50)
|
||||
const circlePercent = 100 - percentageValue
|
||||
|
||||
return (
|
||||
<Tooltip content={tooltip}>
|
||||
<div
|
||||
className={classNames(
|
||||
'relative overflow-hidden',
|
||||
`w-${diameter / 4} h-${diameter / 8 + 1}`,
|
||||
)}
|
||||
>
|
||||
<div className={classNames('relative', `w-${diameter / 4} h-${diameter / 4}`)}>
|
||||
<svg
|
||||
viewBox='2 -2 28 36'
|
||||
width={diameter}
|
||||
height={diameter}
|
||||
style={{ transform: 'rotate(180deg)' }}
|
||||
style={{ transform: 'rotate(-90deg)' }}
|
||||
className='absolute top-0 left-0'
|
||||
>
|
||||
<linearGradient id='gradient'>
|
||||
<stop stopColor='#C13338' offset='0%'></stop>
|
||||
<stop stopColor='#4F3D9F' offset='50%'></stop>
|
||||
<stop stopColor='#15BFA9' offset='100%'></stop>
|
||||
<stop stopColor='rgba(255, 160, 187)' offset='0%'></stop>
|
||||
<stop stopColor='rgba(186, 8, 189)' offset='50%'></stop>
|
||||
<stop stopColor='rgba(255, 160, 187)' offset='100%'></stop>
|
||||
</linearGradient>
|
||||
<circle
|
||||
fill='none'
|
||||
stroke={background}
|
||||
strokeWidth={4}
|
||||
strokeDasharray='50 100'
|
||||
strokeLinecap='round'
|
||||
r='16'
|
||||
cx='16'
|
||||
cy='16'
|
||||
strokeDashoffset='0'
|
||||
r={radius}
|
||||
cx={radius}
|
||||
cy={radius}
|
||||
shapeRendering='geometricPrecision'
|
||||
/>
|
||||
<circle
|
||||
r='16'
|
||||
cx='16'
|
||||
cy='16'
|
||||
fill='none'
|
||||
strokeLinecap='round'
|
||||
r={radius}
|
||||
cx={radius}
|
||||
cy={radius}
|
||||
fill='transparent'
|
||||
stroke='url(#gradient)'
|
||||
strokeDasharray='50 100'
|
||||
strokeWidth={5}
|
||||
strokeDashoffset={circlePercent}
|
||||
strokeDasharray='100'
|
||||
pathLength='100'
|
||||
style={{
|
||||
strokeDashoffset: semiCirclePercentage,
|
||||
transition: enableAnimations ? 'stroke-dashoffset 1s ease' : 'none',
|
||||
}}
|
||||
shapeRendering='geometricPrecision'
|
||||
strokeLinecap='round'
|
||||
/>
|
||||
</svg>
|
||||
{label && (
|
||||
<span
|
||||
className='text-xs'
|
||||
style={{
|
||||
width: '100%',
|
||||
left: '0',
|
||||
textAlign: 'center',
|
||||
bottom: '-2px',
|
||||
position: 'absolute',
|
||||
}}
|
||||
>
|
||||
{label}
|
||||
</span>
|
||||
{icon && (
|
||||
<div className='absolute inset-0 flex items-center justify-center p-2.5 opacity-30'>
|
||||
{icon}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</Tooltip>
|
||||
|
3
src/components/Icons/Account.svg
Normal file
@ -0,0 +1,3 @@
|
||||
<svg viewBox="0 0 15 12" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M14.1666 3.6665H0.833252M0.833252 6.33317H3.1977C3.55783 6.33317 3.73789 6.33317 3.91201 6.36421C4.06655 6.39176 4.21732 6.4374 4.36119 6.50021C4.52328 6.57097 4.6731 6.67085 4.97274 6.87061L5.36043 7.12906C5.66007 7.32883 5.80989 7.42871 5.97198 7.49947C6.11585 7.56227 6.26662 7.60792 6.42116 7.63547C6.59528 7.6665 6.77534 7.6665 7.13547 7.6665H7.86437C8.2245 7.6665 8.40456 7.6665 8.57868 7.63547C8.73322 7.60792 8.88399 7.56227 9.02786 7.49947C9.18995 7.42871 9.33977 7.32883 9.63941 7.12906L10.0271 6.87061C10.3267 6.67085 10.4766 6.57097 10.6386 6.50021C10.7825 6.4374 10.9333 6.39176 11.0878 6.36421C11.2619 6.33317 11.442 6.33317 11.8021 6.33317H14.1666M0.833252 2.79984L0.833252 9.19984C0.833252 9.94657 0.833252 10.3199 0.978577 10.6052C1.10641 10.856 1.31038 11.06 1.56126 11.1878C1.84648 11.3332 2.21985 11.3332 2.96659 11.3332L12.0333 11.3332C12.78 11.3332 13.1534 11.3332 13.4386 11.1878C13.6895 11.06 13.8934 10.856 14.0213 10.6052C14.1666 10.3199 14.1666 9.94657 14.1666 9.19984V2.79984C14.1666 2.0531 14.1666 1.67973 14.0213 1.39452C13.8934 1.14363 13.6895 0.939661 13.4386 0.81183C13.1534 0.666505 12.78 0.666505 12.0333 0.666505L2.96659 0.666504C2.21985 0.666504 1.84648 0.666504 1.56126 0.811828C1.31038 0.93966 1.10641 1.14363 0.978577 1.39452C0.833252 1.67973 0.833252 2.0531 0.833252 2.79984Z" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
After Width: | Height: | Size: 1.4 KiB |
@ -1,5 +1,3 @@
|
||||
<svg viewBox='0 0 12 12' xmlns='http://www.w3.org/2000/svg' fill="currentColor">
|
||||
<path
|
||||
d='M10.6667 5.33317H6.66675V1.33317C6.66675 1.15636 6.59651 0.98679 6.47149 0.861766C6.34646 0.736742 6.17689 0.666504 6.00008 0.666504C5.82327 0.666504 5.6537 0.736742 5.52868 0.861766C5.40365 0.98679 5.33341 1.15636 5.33341 1.33317V5.33317H1.33341C1.1566 5.33317 0.987035 5.40341 0.86201 5.52843C0.736986 5.65346 0.666748 5.82303 0.666748 5.99984C0.666748 6.17665 0.736986 6.34622 0.86201 6.47124C0.987035 6.59627 1.1566 6.6665 1.33341 6.6665H5.33341V10.6665C5.33341 10.8433 5.40365 11.0129 5.52868 11.1379C5.6537 11.2629 5.82327 11.3332 6.00008 11.3332C6.17689 11.3332 6.34646 11.2629 6.47149 11.1379C6.59651 11.0129 6.66675 10.8433 6.66675 10.6665V6.6665H10.6667C10.8436 6.6665 11.0131 6.59627 11.1382 6.47124C11.2632 6.34622 11.3334 6.17665 11.3334 5.99984C11.3334 5.82303 11.2632 5.65346 11.1382 5.52843C11.0131 5.40341 10.8436 5.33317 10.6667 5.33317Z'
|
||||
/>
|
||||
<svg viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M7.99992 5.33594V10.6693M5.33325 8.0026H10.6666M14.6666 8.0026C14.6666 11.6845 11.6818 14.6693 7.99992 14.6693C4.31802 14.6693 1.33325 11.6845 1.33325 8.0026C1.33325 4.32071 4.31802 1.33594 7.99992 1.33594C11.6818 1.33594 14.6666 4.32071 14.6666 8.0026Z" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
Before Width: | Height: | Size: 964 B After Width: | Height: | Size: 417 B |
3
src/components/Icons/ArrowDownLine.svg
Normal file
@ -0,0 +1,3 @@
|
||||
<svg viewBox="0 0 17 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M14.5 14H2.5M12.5 7.33333L8.5 11.3333M8.5 11.3333L4.5 7.33333M8.5 11.3333V2" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
After Width: | Height: | Size: 237 B |
3
src/components/Icons/ArrowUpLine.svg
Normal file
@ -0,0 +1,3 @@
|
||||
<svg viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M14 2H2M12 8.66667L8 4.66667M8 4.66667L4 8.66667M8 4.66667V14" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
After Width: | Height: | Size: 223 B |
Before Width: | Height: | Size: 96 KiB After Width: | Height: | Size: 178 B |
3
src/components/Icons/Heart.svg
Normal file
@ -0,0 +1,3 @@
|
||||
<svg viewBox="0 0 16 14" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M7.99541 2.42388C6.66251 0.8656 4.43981 0.446428 2.76978 1.87334C1.09974 3.30026 0.864625 5.68598 2.17611 7.3736C3.26652 8.77674 6.56649 11.7361 7.64805 12.6939C7.76905 12.801 7.82955 12.8546 7.90012 12.8757C7.96172 12.8941 8.02911 12.8941 8.09071 12.8757C8.16128 12.8546 8.22178 12.801 8.34278 12.6939C9.42433 11.7361 12.7243 8.77674 13.8147 7.3736C15.1262 5.68598 14.9198 3.28525 13.2211 1.87334C11.5223 0.461438 9.32832 0.8656 7.99541 2.42388Z" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
After Width: | Height: | Size: 648 B |
@ -1,12 +1,15 @@
|
||||
// @index(['./*.svg'], f => `export { default as ${f.name} } from 'components/Icons/${f.name}.svg'`)
|
||||
export { default as Account } from 'components/Icons/Account.svg'
|
||||
export { default as Add } from 'components/Icons/Add.svg'
|
||||
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 ArrowRightLine } from 'components/Icons/ArrowRightLine.svg'
|
||||
export { default as ArrowsLeftRight } from 'components/Icons/ArrowsLeftRight.svg'
|
||||
export { default as ArrowsUpDown } from 'components/Icons/ArrowsUpDown.svg'
|
||||
export { default as ArrowUp } from 'components/Icons/ArrowUp.svg'
|
||||
export { default as ArrowUpLine } from 'components/Icons/ArrowUpLine.svg'
|
||||
export { default as BurgerMenu } from 'components/Icons/BurgerMenu.svg'
|
||||
export { default as Check } from 'components/Icons/Check.svg'
|
||||
export { default as ChevronDown } from 'components/Icons/ChevronDown.svg'
|
||||
@ -22,6 +25,7 @@ export { default as Ellipsis } from 'components/Icons/Ellipsis.svg'
|
||||
export { default as ExternalLink } from 'components/Icons/ExternalLink.svg'
|
||||
export { default as Failed } from 'components/Icons/Failed.svg'
|
||||
export { default as Github } from 'components/Icons/Github.svg'
|
||||
export { default as Heart } from 'components/Icons/Heart.svg'
|
||||
export { default as Info } from 'components/Icons/Info.svg'
|
||||
export { default as Logo } from 'components/Icons/Logo.svg'
|
||||
export { default as MarsProtocol } from 'components/Icons/MarsProtocol.svg'
|
||||
|
@ -3,8 +3,6 @@ import classNames from 'classnames'
|
||||
interface Props {
|
||||
className?: string
|
||||
count?: number
|
||||
height?: number
|
||||
width?: number
|
||||
}
|
||||
|
||||
export default function Loading(props: Props) {
|
||||
@ -14,10 +12,8 @@ export default function Loading(props: Props) {
|
||||
<div
|
||||
role='status'
|
||||
className={classNames(
|
||||
'animate-pulse rounded-md bg-white/40',
|
||||
props.className,
|
||||
props.height ? `h-[${props.height}px]` : 'h-[300px]',
|
||||
props.width ? `w-[${props.width}px]` : 'w-full',
|
||||
'max-w-full animate-pulse rounded-sm bg-white/40',
|
||||
props.className ? props.className : 'h-4 w-full',
|
||||
)}
|
||||
key={i}
|
||||
/>
|
||||
|
@ -3,8 +3,7 @@ import { ReactNode } from 'react'
|
||||
|
||||
import { Close } from 'components/Icons'
|
||||
import { Text } from 'components/Text'
|
||||
|
||||
import { Button } from './Button'
|
||||
import { Button } from 'components/Button'
|
||||
|
||||
interface Props {
|
||||
title: string
|
||||
@ -21,17 +20,22 @@ export const Modal = (props: Props) => {
|
||||
}
|
||||
|
||||
return props.open ? (
|
||||
<div className='fixed top-0 left-0 z-20 h-screen w-screen'>
|
||||
<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
|
||||
className={classNames(
|
||||
'relative z-40 w-[790px] max-w-full rounded-md border-[1px] border-white/20 bg-white/5 p-6 backdrop-blur-3xl ',
|
||||
'relative z-40 w-[790px] max-w-full rounded-base border border-white/20 bg-white/5 p-6 backdrop-blur-3xl',
|
||||
props.className,
|
||||
)}
|
||||
>
|
||||
<div className='flex justify-between pb-6'>
|
||||
<Text>{props.title}</Text>
|
||||
<Button onClick={onClickAway} text='X' color='tertiary' />
|
||||
<Button
|
||||
onClick={onClickAway}
|
||||
icon={<Close />}
|
||||
iconClassName='h-2 w-2'
|
||||
color='tertiary'
|
||||
/>
|
||||
</div>
|
||||
<div>{props.children ? props.children : props.content}</div>
|
||||
</section>
|
||||
|
@ -2,8 +2,7 @@
|
||||
|
||||
import { ConfirmModal } from 'components/Account/ConfirmModal'
|
||||
import { FundAccountModal } from 'components/Account/FundAccountModal'
|
||||
|
||||
import BorrowModal from './BorrowModal'
|
||||
import BorrowModal from 'components/BorrowModal'
|
||||
|
||||
export const Modals = () => (
|
||||
<>
|
||||
|
@ -2,7 +2,9 @@
|
||||
|
||||
import Link from 'next/link'
|
||||
import { usePathname } from 'next/navigation'
|
||||
import classNames from 'classnames'
|
||||
|
||||
import { AccountNavigation } from 'components/Account/AccountNavigation'
|
||||
import { Logo } from 'components/Icons'
|
||||
import { NavLink } from 'components/Navigation/NavLink'
|
||||
import Wallet from 'components/Wallet/Wallet'
|
||||
@ -20,8 +22,14 @@ export default function DesktopNavigation() {
|
||||
const pathname = usePathname() || ''
|
||||
|
||||
return (
|
||||
<div className='relative hidden bg-header lg:block'>
|
||||
<div className='flex items-center justify-between border-b border-white/20 px-6 py-3'>
|
||||
<header
|
||||
className={classNames(
|
||||
'fixed top-0 left-0 z-30 hidden w-full',
|
||||
'before:content-[" "] before:absolute before:inset-0 before:z-[-1] before:h-full before:w-full before:rounded-sm before:backdrop-blur-sticky',
|
||||
'lg:block',
|
||||
)}
|
||||
>
|
||||
<div className='flex items-center justify-between border-b border-white/20 py-3 pl-6 pr-4'>
|
||||
<div className='flex flex-grow items-center'>
|
||||
<Link href={getRoute(pathname, { page: 'trade' })}>
|
||||
<span className='block h-10 w-10'>
|
||||
@ -36,8 +44,11 @@ export default function DesktopNavigation() {
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
<div className='flex gap-4'>
|
||||
<AccountNavigation />
|
||||
<Wallet />
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
)
|
||||
}
|
||||
|
@ -1,9 +1,7 @@
|
||||
'use client'
|
||||
|
||||
import classNames from 'classnames'
|
||||
import Link from 'next/link'
|
||||
import { usePathname } from 'next/navigation'
|
||||
import { ReactNode } from 'react'
|
||||
import classNames from 'classnames'
|
||||
|
||||
interface Props {
|
||||
href: string
|
||||
@ -18,7 +16,7 @@ export const NavLink = ({ href, children }: Props) => {
|
||||
<Link
|
||||
href={href}
|
||||
className={classNames(
|
||||
'text-lg-caps hover:text-white active:text-white',
|
||||
'text-sm font-semibold hover:text-white active:text-white',
|
||||
isActive ? 'pointer-events-none text-white' : 'text-white/60',
|
||||
)}
|
||||
>
|
||||
|
@ -18,7 +18,7 @@ export const Overlay = ({ children, content, className, show, setShow }: Props)
|
||||
<>
|
||||
<div
|
||||
className={classNames(
|
||||
'max-w-screen absolute z-50 rounded-lg shadow-overlay gradient-popover',
|
||||
'max-w-screen absolute z-50 rounded-sm border border-white/40 shadow-overlay backdrop-blur-sm gradient-popover',
|
||||
className,
|
||||
)}
|
||||
>
|
||||
|
@ -1,31 +0,0 @@
|
||||
import classNames from 'classnames'
|
||||
import { ReactNode } from 'react'
|
||||
|
||||
import { Button } from 'components/Button'
|
||||
|
||||
interface Props {
|
||||
className?: string
|
||||
icon?: ReactNode
|
||||
onClick: () => void
|
||||
setShow: (show: boolean) => void
|
||||
text: string | ReactNode
|
||||
}
|
||||
|
||||
export const OverlayAction = ({ className, icon, onClick, setShow, text }: Props) => {
|
||||
return (
|
||||
<Button
|
||||
className={classNames(
|
||||
'flex items-center whitespace-nowrap py-2 text-left text-sm text-accent-dark hover:text-secondary',
|
||||
className,
|
||||
)}
|
||||
variant='text'
|
||||
onClick={() => {
|
||||
setShow(false)
|
||||
onClick()
|
||||
}}
|
||||
>
|
||||
{icon && <span className='mt-[1px] mr-2 flex w-4'>{icon}</span>}
|
||||
{text}
|
||||
</Button>
|
||||
)
|
||||
}
|
70
src/components/Portfolio/AccountOverview.tsx
Normal file
@ -0,0 +1,70 @@
|
||||
import { Suspense } from 'react'
|
||||
import classNames from 'classnames'
|
||||
|
||||
import { Card } from 'components/Card'
|
||||
import Loading from 'components/Loading'
|
||||
import { Text } from 'components/Text'
|
||||
import { getCreditAccounts } from 'utils/api'
|
||||
|
||||
async function Content(props: PageProps) {
|
||||
const wallet = props.params.wallet
|
||||
const currentAccount = props.params.account
|
||||
const hasAccount = !isNaN(Number(currentAccount))
|
||||
const creditAccounts = await getCreditAccounts(wallet)
|
||||
|
||||
return wallet ? (
|
||||
<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'
|
||||
title={`Account ${account}`}
|
||||
key={index}
|
||||
contentClassName='px-4 py-6'
|
||||
>
|
||||
{hasAccount && currentAccount === account ? (
|
||||
<Text size='sm'>Current Account</Text>
|
||||
) : (
|
||||
<Text size='sm'>Account details</Text>
|
||||
)}
|
||||
</Card>
|
||||
))}
|
||||
</div>
|
||||
) : (
|
||||
<Card className='h-fit w-full justify-center' 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>
|
||||
</Card>
|
||||
)
|
||||
}
|
||||
|
||||
function Fallback() {
|
||||
const cardCount = 3
|
||||
return (
|
||||
<div className={classNames('grid grid-cols-1 gap-4', 'md:grid-cols-2', 'lg:grid-cols-3')}>
|
||||
{Array.from({ length: cardCount }, (_, i) => (
|
||||
<Card
|
||||
key={i}
|
||||
className='h-fit w-full'
|
||||
title={
|
||||
<>
|
||||
Account <Loading className='ml-2 h-4 w-8' />
|
||||
</>
|
||||
}
|
||||
contentClassName='px-4 py-6'
|
||||
>
|
||||
<Loading className='h-4 w-50' />
|
||||
</Card>
|
||||
))}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default function AccountOverview(props: PageProps) {
|
||||
return (
|
||||
<Suspense fallback={<Fallback />}>
|
||||
{/* @ts-expect-error Server Component */}
|
||||
<Content params={props.params} />
|
||||
</Suspense>
|
||||
)
|
||||
}
|
@ -3,19 +3,18 @@ import BigNumber from 'bignumber.js'
|
||||
import Image from 'next/image'
|
||||
import React, { useMemo, useState } from 'react'
|
||||
import { NumericFormat } from 'react-number-format'
|
||||
import { toast } from 'react-toastify'
|
||||
|
||||
import { Button } from 'components/Button'
|
||||
import { Card } from 'components/Card'
|
||||
import { CircularProgress } from 'components/CircularProgress'
|
||||
import { ContainerSecondary } from 'components/ContainerSecondary'
|
||||
import { Slider } from 'components/Slider'
|
||||
import { useRepayFunds } from 'hooks/mutations/useRepayFunds'
|
||||
import { useAllBalances } from 'hooks/queries/useAllBalances'
|
||||
import { useCreditAccountPositions } from 'hooks/queries/useCreditAccountPositions'
|
||||
import { useTokenPrices } from 'hooks/queries/useTokenPrices'
|
||||
import { getTokenDecimals, getTokenSymbol } from 'utils/tokens'
|
||||
import { getMarketAssets } from 'utils/assets'
|
||||
import useStore from 'store'
|
||||
import { getMarketAssets } from 'utils/assets'
|
||||
import { getTokenDecimals, getTokenSymbol } from 'utils/tokens'
|
||||
|
||||
// 0.001% buffer / slippage to avoid repay action from not fully repaying the debt amount
|
||||
const REPAY_BUFFER = 1.00001
|
||||
@ -137,7 +136,7 @@ export const RepayModal = ({ show, onClose, tokenDenom }: Props) => {
|
||||
Repay {tokenSymbol}
|
||||
</Dialog.Title>
|
||||
<div className='mb-4 flex flex-col gap-2 text-sm'>
|
||||
<ContainerSecondary>
|
||||
<Card>
|
||||
<p className='mb-7'>
|
||||
In wallet: {walletAmount.toLocaleString()} {tokenSymbol}
|
||||
</p>
|
||||
@ -176,7 +175,7 @@ export const RepayModal = ({ show, onClose, tokenDenom }: Props) => {
|
||||
}}
|
||||
onMaxClick={() => setAmount(maxValue)}
|
||||
/>
|
||||
</ContainerSecondary>
|
||||
</Card>
|
||||
</div>
|
||||
<Button
|
||||
className='mt-auto w-full'
|
||||
|
@ -1,5 +1,4 @@
|
||||
import * as RadixSlider from '@radix-ui/react-slider'
|
||||
import React from 'react'
|
||||
|
||||
type Props = {
|
||||
className?: string
|
||||
@ -27,7 +26,7 @@ export const Slider = ({ className, value, onChange, onMaxClick }: Props) => {
|
||||
</RadixSlider.Thumb>
|
||||
</RadixSlider.Root>
|
||||
<button
|
||||
className='ml-4 rounded-md bg-blue-600 py-1 px-2 text-xs font-semibold text-white'
|
||||
className='ml-4 rounded-base bg-blue-600 py-1 px-2 text-xs font-semibold text-white'
|
||||
onClick={onMaxClick}
|
||||
>
|
||||
MAX
|
||||
|
@ -29,7 +29,6 @@ export const Text = ({
|
||||
<HtmlElement
|
||||
className={classNames(
|
||||
className,
|
||||
'flex items-center',
|
||||
uppercase ? `text-${sizeClass}-caps` : `text-${sizeClass}`,
|
||||
monospace && 'number',
|
||||
)}
|
||||
|
@ -13,11 +13,9 @@ interface Props extends React.HTMLProps<HTMLAnchorElement> {
|
||||
|
||||
const colorClasses = {
|
||||
primary:
|
||||
'text-primary hover:text-primary-highlight active:text-primary-highlight-10 focus:text-primary-highlight',
|
||||
secondary:
|
||||
'text-secondary hover:text-secondary-highlight active:text-secondary-highlight-10 focus:text-secondary-highlight',
|
||||
tertiary:
|
||||
'text-secondary-dark/60 hover:text-secondary-dark active:text-secondary-dark-10 focus:text-secondary-dark',
|
||||
'text-primary hover:text-secondary active:text-secondary/90 focus:text-text-secondary/90',
|
||||
secondary: 'text-secondary hover:text-primary active:text-primary/90 focus:text-text-primary/90',
|
||||
tertiary: 'text-white/80 hover:text-white active:text-white/90 focus:text-text-white/90',
|
||||
quaternary: 'hover:text-white active:text-white',
|
||||
}
|
||||
const textSizeClasses = {
|
||||
|
@ -2,6 +2,7 @@
|
||||
import { useRouter } from 'next/navigation'
|
||||
import { toast as createToast, Slide, ToastContainer } from 'react-toastify'
|
||||
|
||||
import { Check, Warning } from 'components/Icons'
|
||||
import useStore from 'store'
|
||||
|
||||
export default function Toaster() {
|
||||
@ -11,9 +12,23 @@ export default function Toaster() {
|
||||
|
||||
if (toast) {
|
||||
if (toast.isError) {
|
||||
createToast.error(toast.message)
|
||||
createToast.error(toast.message, {
|
||||
progressClassName: 'bg-loss',
|
||||
icon: (
|
||||
<span className='h-4 w-4'>
|
||||
<Warning />
|
||||
</span>
|
||||
),
|
||||
})
|
||||
} else {
|
||||
createToast.success(toast.message)
|
||||
createToast.success(toast.message, {
|
||||
progressClassName: 'bg-profit',
|
||||
icon: (
|
||||
<span className='h-4 w-4'>
|
||||
<Check />
|
||||
</span>
|
||||
),
|
||||
})
|
||||
}
|
||||
useStore.setState({ toast: null })
|
||||
router.refresh()
|
||||
@ -21,11 +36,14 @@ export default function Toaster() {
|
||||
|
||||
return (
|
||||
<ToastContainer
|
||||
autoClose={3000}
|
||||
autoClose={5000}
|
||||
closeButton={false}
|
||||
position='bottom-right'
|
||||
newestOnTop
|
||||
transition={enableAnimations ? Slide : undefined}
|
||||
className='w-[280px] p-0'
|
||||
toastClassName='z-50 text-xs rounded-sm border border-white/40 shadow-overlay backdrop-blur-sm gradient-popover px-2 py-4'
|
||||
bodyClassName='p-0 text-white m-0'
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
@ -33,7 +33,7 @@ export const Tooltip = ({
|
||||
render={(attrs) => {
|
||||
return (
|
||||
<div
|
||||
className='max-w-[320px] rounded-lg px-4 py-2 shadow-tooltip gradient-tooltip'
|
||||
className='max-w-[320px] rounded-lg px-4 py-2 text-xs shadow-tooltip gradient-tooltip'
|
||||
{...attrs}
|
||||
>
|
||||
{content}
|
||||
|
32
src/components/Trade/OrderBook.tsx
Normal file
@ -0,0 +1,32 @@
|
||||
import { Suspense } from 'react'
|
||||
|
||||
import { Card } from 'components/Card'
|
||||
import Loading from 'components/Loading'
|
||||
import { Text } from 'components/Text'
|
||||
|
||||
async function Content(props: PageProps) {
|
||||
const wallet = props.params.wallet
|
||||
|
||||
return wallet ? (
|
||||
<Text size='sm'>{`Order book for ${wallet}`}</Text>
|
||||
) : (
|
||||
<Text size='sm' className='w-full text-center'>
|
||||
You need to be connected to see the order book
|
||||
</Text>
|
||||
)
|
||||
}
|
||||
|
||||
function Fallback() {
|
||||
return <Loading className='h-4 w-50' />
|
||||
}
|
||||
|
||||
export default function OrderBook(props: PageProps) {
|
||||
return (
|
||||
<Card className='col-span-3' title='Order Book' contentClassName='px-4 py-6'>
|
||||
<Suspense fallback={<Fallback />}>
|
||||
{/* @ts-expect-error Server Component */}
|
||||
<Content params={props.params} />
|
||||
</Suspense>
|
||||
</Card>
|
||||
)
|
||||
}
|
38
src/components/Trade/Trade.tsx
Normal file
@ -0,0 +1,38 @@
|
||||
import { Suspense } from 'react'
|
||||
|
||||
import { Card } from 'components/Card'
|
||||
import Loading from 'components/Loading'
|
||||
import { Text } from 'components/Text'
|
||||
|
||||
async function Content(props: PageProps) {
|
||||
const wallet = props.params.wallet
|
||||
const currentAccount = props.params.account
|
||||
const hasAccount = !isNaN(Number(currentAccount))
|
||||
|
||||
return wallet ? (
|
||||
<>
|
||||
{hasAccount ? (
|
||||
<Text size='sm'>{`Trade with Account ${currentAccount}`}</Text>
|
||||
) : (
|
||||
<Text size='sm'>Select an Account to trade</Text>
|
||||
)}
|
||||
</>
|
||||
) : (
|
||||
<Text size='sm'>You need to be connected to trade</Text>
|
||||
)
|
||||
}
|
||||
|
||||
function Fallback() {
|
||||
return <Loading className='h-4 w-50' />
|
||||
}
|
||||
|
||||
export default function Trade(props: PageProps) {
|
||||
return (
|
||||
<Card className='h-full w-full' title='Trade Module' contentClassName='px-4 py-6'>
|
||||
<Suspense fallback={<Fallback />}>
|
||||
{/* @ts-expect-error Server Component */}
|
||||
<Content params={props.params} />
|
||||
</Suspense>
|
||||
</Card>
|
||||
)
|
||||
}
|
24
src/components/Trade/TradingView.tsx
Normal file
@ -0,0 +1,24 @@
|
||||
import { Suspense } from 'react'
|
||||
|
||||
import { Card } from 'components/Card'
|
||||
import Loading from 'components/Loading'
|
||||
import { Text } from 'components/Text'
|
||||
|
||||
async function Content(props: PageProps) {
|
||||
return <Text size='sm'>Chart view</Text>
|
||||
}
|
||||
|
||||
function Fallback() {
|
||||
return <Loading className='h-4 w-50' />
|
||||
}
|
||||
|
||||
export default function TradingView(props: PageProps) {
|
||||
return (
|
||||
<Card className='col-span-2 h-full' title='Trading View' contentClassName='px-4 py-6'>
|
||||
<Suspense fallback={<Fallback />}>
|
||||
{/* @ts-expect-error Server Component */}
|
||||
<Content params={props.params} />
|
||||
</Suspense>
|
||||
</Card>
|
||||
)
|
||||
}
|
@ -1,6 +1,9 @@
|
||||
'use client'
|
||||
|
||||
import { useWalletManager, WalletConnectionStatus } from '@marsprotocol/wallet-connector'
|
||||
import { ReactNode } from 'react'
|
||||
|
||||
import { Button } from 'components/Button'
|
||||
import { CircularProgress } from 'components/CircularProgress'
|
||||
import { Wallet } from 'components/Icons'
|
||||
|
||||
@ -15,24 +18,21 @@ export default function ConnectButton(props: Props) {
|
||||
|
||||
return (
|
||||
<div className='relative'>
|
||||
<button
|
||||
<Button
|
||||
variant='solid'
|
||||
color='tertiary'
|
||||
disabled={props.disabled}
|
||||
className='flex h-[31px] min-w-[186px] flex-1 flex-nowrap content-center items-center justify-center rounded-2xl border border-white/60 bg-black/10 px-4 pt-0.5 text-white text-2xs-caps hover:border-white hover:bg-white/60'
|
||||
onClick={connect}
|
||||
icon={<Wallet />}
|
||||
>
|
||||
{props.status === WalletConnectionStatus.Connecting ? (
|
||||
<span className='flex justify-center'>
|
||||
<CircularProgress size={16} />
|
||||
</span>
|
||||
) : (
|
||||
<>
|
||||
<span className='flex h-4 w-4 items-center justify-center'>
|
||||
<Wallet />
|
||||
</span>
|
||||
<span className='ml-2 mt-0.5'>{props.textOverride || 'Connect Wallet'}</span>
|
||||
</>
|
||||
<span>{props.textOverride || 'Connect Wallet'}</span>
|
||||
)}
|
||||
</button>
|
||||
</Button>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
@ -1,3 +1,5 @@
|
||||
'use client'
|
||||
|
||||
import { Coin } from '@cosmjs/stargate'
|
||||
import {
|
||||
ChainInfoID,
|
||||
@ -18,9 +20,9 @@ import { Check, Copy, ExternalLink, Osmo } from 'components/Icons'
|
||||
import { Overlay } from 'components/Overlay/Overlay'
|
||||
import { Text } from 'components/Text'
|
||||
import useStore from 'store'
|
||||
import { getWalletBalances } from 'utils/api'
|
||||
import { getBaseAsset } from 'utils/assets'
|
||||
import { formatValue, truncate } from 'utils/formatters'
|
||||
import { getWalletBalances } from 'utils/api'
|
||||
|
||||
export default function ConnectedButton() {
|
||||
// ---------------
|
||||
@ -30,7 +32,6 @@ export default function ConnectedButton() {
|
||||
const { disconnect: terminate } = useWalletManager()
|
||||
const address = useStore((s) => s.client?.recentWallet.account?.address)
|
||||
const network = useStore((s) => s.client?.recentWallet.network)
|
||||
const name = useStore((s) => s.name)
|
||||
const baseAsset = getBaseAsset()
|
||||
|
||||
const { data, isLoading } = useSWR(address, getWalletBalances)
|
||||
@ -73,7 +74,7 @@ export default function ConnectedButton() {
|
||||
<div className={'relative'}>
|
||||
{network?.chainId !== ChainInfoID.Osmosis1 && (
|
||||
<Text
|
||||
className='absolute -right-2 -top-2.5 rounded-lg bg-secondary-highlight p-0.5 px-2'
|
||||
className='absolute -right-2 -top-2.5 z-10 rounded-lg p-0.5 px-2 gradient-primary-to-secondary'
|
||||
size='3xs'
|
||||
uppercase
|
||||
>
|
||||
@ -81,24 +82,19 @@ export default function ConnectedButton() {
|
||||
</Text>
|
||||
)}
|
||||
|
||||
<button
|
||||
className={classNames(
|
||||
'flex h-[31px] flex-1 flex-nowrap content-center items-center justify-center rounded-2xl border border-white/60 bg-secondary-dark/70 px-4 py-0 text-sm text-white ',
|
||||
'hover:border-white hover:bg-secondary-dark',
|
||||
'active:border-white active:bg-secondary-dark-10',
|
||||
)}
|
||||
<Button
|
||||
variant='solid'
|
||||
color='tertiary'
|
||||
icon={<Osmo />}
|
||||
onClick={() => {
|
||||
setShowDetails(!showDetails)
|
||||
}}
|
||||
>
|
||||
<span className='flex h-4 w-4 items-center justify-center'>
|
||||
<Osmo />
|
||||
</span>
|
||||
<span className='ml-2'>{name ? name : truncate(address, [2, 4])}</span>
|
||||
<span>{truncate(address, [2, 4])}</span>
|
||||
<div
|
||||
className={classNames(
|
||||
'number relative ml-2 flex h-full items-center pl-2',
|
||||
'before:content-[" "] before:absolute before:top-1.5 before:bottom-1.5 before:left-0 before:h-[calc(100%-12px)] before:border-l before:border-white',
|
||||
'relative ml-2 flex h-full items-center pl-2 number',
|
||||
'before:content-[" "] before:absolute before:top-0.5 before:bottom-1.5 before:left-0 before:h-[calc(100%-4px)] before:border-l before:border-white/20',
|
||||
)}
|
||||
>
|
||||
{isLoading ? (
|
||||
@ -107,18 +103,18 @@ export default function ConnectedButton() {
|
||||
`${formatValue(walletAmount, { suffix: baseAsset.symbol })}`
|
||||
)}
|
||||
</div>
|
||||
</button>
|
||||
</Button>
|
||||
<Overlay className='right-0 mt-2' show={showDetails} setShow={setShowDetails}>
|
||||
<div className='flex w-[420px] flex-wrap p-6'>
|
||||
<div className='flex w-[440px] flex-wrap p-6'>
|
||||
<div className='flex-0 mb-4 flex w-full flex-nowrap items-start'>
|
||||
<div className='flex w-auto flex-1'>
|
||||
<div className='mr-2 flex h-[31px] items-end pb-0.5 text-secondary-dark text-base-caps'>
|
||||
<div className='mr-2 flex h-[31px] items-end pb-0.5 text-base-caps'>
|
||||
{baseAsset.denom}
|
||||
</div>
|
||||
<div className='flex-0 flex flex-wrap justify-end'>
|
||||
<FormattedNumber
|
||||
animate
|
||||
className='flex items-end text-2xl text-secondary-dark'
|
||||
className='flex items-end text-2xl '
|
||||
amount={walletAmount}
|
||||
/>
|
||||
</div>
|
||||
@ -128,44 +124,44 @@ export default function ConnectedButton() {
|
||||
</div>
|
||||
</div>
|
||||
<div className='flex w-full flex-wrap'>
|
||||
<Text uppercase className='mb-1 break-all text-secondary-dark/80'>
|
||||
{name ? `‘${name}’` : 'Your Address'}
|
||||
<Text uppercase className='/80 mb-1 break-all'>
|
||||
{'Your Address'}
|
||||
</Text>
|
||||
|
||||
<Text
|
||||
size='sm'
|
||||
className='mb-1 hidden break-all font-bold text-secondary-dark md:block'
|
||||
>
|
||||
<Text size='sm' className='mb-1 hidden break-all font-bold md:block'>
|
||||
{address}
|
||||
</Text>
|
||||
<Text size='sm' className='mb-1 break-all font-bold text-secondary-dark md:hidden'>
|
||||
<Text size='sm' className='mb-1 break-all font-bold md:hidden'>
|
||||
{truncate(address, [14, 14])}
|
||||
</Text>
|
||||
<div className='flex w-full pt-1'>
|
||||
<button
|
||||
className='mr-10 flex w-auto appearance-none items-center border-none py-2 text-secondary-dark opacity-70 hover:opacity-100'
|
||||
<Button
|
||||
icon={<Copy />}
|
||||
variant='transparent'
|
||||
className='mr-10 flex w-auto py-2'
|
||||
color='quaternary'
|
||||
onClick={setCopied}
|
||||
>
|
||||
<span className='mr-1 w-4'>
|
||||
<Copy />
|
||||
</span>
|
||||
{isCopied ? (
|
||||
<Text size='sm'>
|
||||
Copied <Check />
|
||||
Copied{' '}
|
||||
<span className='ml-1 w-4'>
|
||||
<Check />
|
||||
</span>
|
||||
</Text>
|
||||
) : (
|
||||
<Text size='sm'>Copy Address</Text>
|
||||
)}
|
||||
</button>
|
||||
<button
|
||||
className='flex w-auto appearance-none items-center border-none py-2 text-secondary-dark opacity-70 hover:opacity-100'
|
||||
</Button>
|
||||
<Button
|
||||
icon={<ExternalLink />}
|
||||
variant='transparent'
|
||||
className='flex w-auto py-2'
|
||||
color='quaternary'
|
||||
onClick={viewOnFinder}
|
||||
>
|
||||
<span className='mr-1 w-4'>
|
||||
<ExternalLink />
|
||||
</span>
|
||||
<Text size='sm'>View on {explorerName}</Text>
|
||||
</button>
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -6,42 +6,46 @@ import {
|
||||
useWalletManager,
|
||||
WalletConnectionStatus,
|
||||
} from '@marsprotocol/wallet-connector'
|
||||
import { useEffect, useState } from 'react'
|
||||
import { useRouter } from 'next/navigation'
|
||||
import { useEffect } from 'react'
|
||||
|
||||
import ConnectButton from 'components/Wallet/ConnectButton'
|
||||
import ConnectedButton from 'components/Wallet/ConnectedButton'
|
||||
import useParams from 'hooks/useParams'
|
||||
import useStore from 'store'
|
||||
import { getCreditAccounts } from 'utils/api'
|
||||
|
||||
export default function Wallet() {
|
||||
const router = useRouter()
|
||||
const params = useParams()
|
||||
|
||||
const { status } = useWalletManager()
|
||||
const [isConnected, setIsConnected] = useState(false)
|
||||
const { recentWallet, simulate, sign, broadcast } = useWallet()
|
||||
const client = useStore((s) => s.client)
|
||||
const address = useStore((s) => s.address)
|
||||
|
||||
useEffect(() => {
|
||||
const connectedStatus = status === WalletConnectionStatus.Connected
|
||||
if (connectedStatus === isConnected) return
|
||||
setIsConnected(connectedStatus)
|
||||
}, [status, isConnected])
|
||||
const isConnected = status === WalletConnectionStatus.Connected
|
||||
|
||||
useEffect(() => {
|
||||
if (!isConnected && !params.wallet) {
|
||||
router.push('/')
|
||||
return
|
||||
useStore.setState({ status })
|
||||
useStore.setState(
|
||||
isConnected
|
||||
? {
|
||||
address: recentWallet?.account.address,
|
||||
}
|
||||
: { address: undefined, creditAccounts: null, client: undefined },
|
||||
)
|
||||
|
||||
if (!isConnected || !recentWallet) return
|
||||
|
||||
const fetchCreditAccounts = async () => {
|
||||
if (!recentWallet?.account.address) return
|
||||
const walletCreditAccounts = await getCreditAccounts(recentWallet?.account.address)
|
||||
useStore.setState({ creditAccounts: walletCreditAccounts })
|
||||
}
|
||||
|
||||
const address = client?.recentWallet.account.address
|
||||
if (!address || address === params.wallet) return
|
||||
fetchCreditAccounts()
|
||||
|
||||
router.push(`/wallets/${client.recentWallet.account.address}`)
|
||||
}, [client, params, isConnected])
|
||||
|
||||
useEffect(() => {
|
||||
if (!recentWallet) return
|
||||
if (!client) {
|
||||
const getCosmWasmClient = async () => {
|
||||
const cosmClient = await getClient(recentWallet.network.rpc)
|
||||
@ -57,9 +61,11 @@ export default function Wallet() {
|
||||
}
|
||||
|
||||
getCosmWasmClient()
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
}, [simulate, sign, recentWallet, broadcast])
|
||||
return isConnected ? <ConnectedButton /> : <ConnectButton status={status} />
|
||||
if (!address || address === params.wallet) return
|
||||
router.push(`/wallets/${address}`)
|
||||
}, [address, broadcast, client, params, recentWallet, router, simulate, sign, status])
|
||||
|
||||
return address ? <ConnectedButton /> : <ConnectButton status={status} />
|
||||
}
|
||||
|
@ -3,25 +3,27 @@
|
||||
import { WalletManagerProvider } from '@marsprotocol/wallet-connector'
|
||||
import { FC } from 'react'
|
||||
|
||||
import { Button } from 'components/Button'
|
||||
import { CircularProgress } from 'components/CircularProgress'
|
||||
import { CHAIN_ID, ENV_MISSING_MESSAGE, URL_REST, URL_RPC, WALLETS } from 'constants/env'
|
||||
import { Close } from 'components/Icons'
|
||||
import { ENV, ENV_MISSING_MESSAGE } from 'constants/env'
|
||||
|
||||
type Props = {
|
||||
children?: React.ReactNode
|
||||
}
|
||||
|
||||
export const WalletConnectProvider: FC<Props> = ({ children }) => {
|
||||
if (!CHAIN_ID || !URL_REST || !URL_RPC || !WALLETS) {
|
||||
if (!ENV.CHAIN_ID || !ENV.URL_REST || !ENV.URL_RPC || !ENV.WALLETS) {
|
||||
console.error(ENV_MISSING_MESSAGE)
|
||||
return null
|
||||
}
|
||||
|
||||
const chainInfoOverrides = {
|
||||
rpc: URL_RPC,
|
||||
rest: URL_REST,
|
||||
chainID: CHAIN_ID,
|
||||
rpc: ENV.URL_RPC,
|
||||
rest: ENV.URL_REST,
|
||||
chainID: ENV.CHAIN_ID,
|
||||
}
|
||||
const enabledWallets: string[] = WALLETS
|
||||
const enabledWallets: string[] = ENV.WALLETS
|
||||
|
||||
return (
|
||||
<WalletManagerProvider
|
||||
@ -30,6 +32,21 @@ export const WalletConnectProvider: FC<Props> = ({ children }) => {
|
||||
defaultChainId={chainInfoOverrides.chainID}
|
||||
enabledWallets={enabledWallets}
|
||||
persistent
|
||||
classNames={{
|
||||
modalContent:
|
||||
'relative z-50 w-[460px] max-w-full rounded-base border border-white/20 bg-white/5 p-6 pb-4 backdrop-blur-3xl flex flex-wrap',
|
||||
modalOverlay:
|
||||
'fixed inset-0 bg-black/60 w-full h-full z-40 flex items-center justify-center cursor-pointer m-0 backdrop-blur-sm',
|
||||
modalHeader: 'text-lg text-white mb-4 flex-grow',
|
||||
modalCloseButton: 'inline-block',
|
||||
walletList: 'w-full',
|
||||
wallet:
|
||||
'flex bg-transparent p-2 w-full rounded-sm cursor-pointer transition duration-500 ease-in mb-2 hover:bg-primary',
|
||||
walletImage: 'w-10 h-10',
|
||||
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' />}
|
||||
renderLoader={() => (
|
||||
<div>
|
||||
<CircularProgress size={30} />
|
||||
|
14
src/components/pages/borrow.tsx
Normal file
@ -0,0 +1,14 @@
|
||||
import Borrowings from 'components/Borrow/Borrowings'
|
||||
|
||||
interface Props {
|
||||
params: PageParams
|
||||
}
|
||||
|
||||
export default function BorrowPage(props: Props) {
|
||||
return (
|
||||
<div className='flex w-full flex-col gap-4'>
|
||||
<Borrowings params={props.params} type='active' />
|
||||
<Borrowings params={props.params} type='available' />
|
||||
</div>
|
||||
)
|
||||
}
|
9
src/components/pages/council.tsx
Normal file
@ -0,0 +1,9 @@
|
||||
import Overview from 'components/Council/Overview'
|
||||
|
||||
interface Props {
|
||||
params: PageParams
|
||||
}
|
||||
|
||||
export default function CouncilPage(props: Props) {
|
||||
return <Overview params={props.params} />
|
||||
}
|
9
src/components/pages/earn.tsx
Normal file
@ -0,0 +1,9 @@
|
||||
import Overview from 'components/Earn/Overview'
|
||||
|
||||
interface Props {
|
||||
params: PageParams
|
||||
}
|
||||
|
||||
export default function EarnPage(props: Props) {
|
||||
return <Overview params={props.params} />
|
||||
}
|
9
src/components/pages/portfolio.tsx
Normal file
@ -0,0 +1,9 @@
|
||||
import AccountOverview from 'components/Portfolio/AccountOverview'
|
||||
|
||||
interface Props {
|
||||
params: PageParams
|
||||
}
|
||||
|
||||
export default function PortfolioPage(props: Props) {
|
||||
return <AccountOverview params={props.params} />
|
||||
}
|
17
src/components/pages/trade.tsx
Normal file
@ -0,0 +1,17 @@
|
||||
import OrderBook from 'components/Trade/OrderBook'
|
||||
import Trade from 'components/Trade/Trade'
|
||||
import TradingView from 'components/Trade/TradingView'
|
||||
|
||||
interface Props {
|
||||
params: PageParams
|
||||
}
|
||||
|
||||
export default function TradePage(props: Props) {
|
||||
return (
|
||||
<div className='grid grid-flow-row grid-cols-3 grid-rows-2 gap-4'>
|
||||
<TradingView params={props.params} />
|
||||
<Trade params={props.params} />
|
||||
<OrderBook params={props.params} />
|
||||
</div>
|
||||
)
|
||||
}
|
@ -1,18 +1,46 @@
|
||||
export const ADDRESS_ACCOUNT_NFT = process.env.NEXT_PUBLIC_ACCOUNT_NFT
|
||||
export const ADDRESS_CREDIT_MANAGER = process.env.NEXT_PUBLIC_CREDIT_MANAGER
|
||||
export const ADDRESS_INCENTIVES = process.env.NEXT_PUBLIC_INCENTIVES
|
||||
export const ADDRESS_ORACLE = process.env.NEXT_PUBLIC_ORACLE
|
||||
export const ADDRESS_RED_BANK = process.env.NEXT_PUBLIC_RED_BANK
|
||||
export const ADDRESS_SWAPPER = process.env.NEXT_PUBLIC_SWAPPER
|
||||
export const ADDRESS_ZAPPER = process.env.NEXT_PUBLIC_ZAPPER
|
||||
interface EnvironmentVariables {
|
||||
ADDRESS_ACCOUNT_NFT: string | undefined
|
||||
ADDRESS_CREDIT_MANAGER: string | undefined
|
||||
ADDRESS_INCENTIVES: string | undefined
|
||||
ADDRESS_ORACLE: string | undefined
|
||||
ADDRESS_RED_BANK: string | undefined
|
||||
ADDRESS_SWAPPER: string | undefined
|
||||
ADDRESS_ZAPPER: string | undefined
|
||||
CHAIN_ID: string | undefined
|
||||
NETWORK: string | undefined
|
||||
URL_GQL: string | undefined
|
||||
URL_REST: string | undefined
|
||||
URL_RPC: string | undefined
|
||||
URL_API: string | undefined
|
||||
WALLETS: string[] | undefined
|
||||
}
|
||||
|
||||
export const CHAIN_ID = process.env.NEXT_PUBLIC_CHAIN_ID
|
||||
export const NETWORK = process.env.NEXT_PUBLIC_NETWORK
|
||||
export const IS_TESTNET = NETWORK !== 'mainnet'
|
||||
export const URL_GQL = process.env.NEXT_PUBLIC_GQL
|
||||
export const URL_REST = process.env.NEXT_PUBLIC_REST
|
||||
export const URL_RPC = process.env.NEXT_PUBLIC_RPC
|
||||
export const URL_API = process.env.NEXT_PUBLIC_API
|
||||
export const WALLETS = process.env.NEXT_PUBLIC_WALLETS?.split(',') ?? []
|
||||
export const ENV: EnvironmentVariables = {
|
||||
ADDRESS_ACCOUNT_NFT: process.env.NEXT_PUBLIC_ACCOUNT_NFT,
|
||||
ADDRESS_CREDIT_MANAGER: process.env.NEXT_PUBLIC_CREDIT_MANAGER,
|
||||
ADDRESS_INCENTIVES: process.env.NEXT_PUBLIC_INCENTIVES,
|
||||
ADDRESS_ORACLE: process.env.NEXT_PUBLIC_ORACLE,
|
||||
ADDRESS_RED_BANK: process.env.NEXT_PUBLIC_RED_BANK,
|
||||
ADDRESS_SWAPPER: process.env.NEXT_PUBLIC_SWAPPER,
|
||||
ADDRESS_ZAPPER: process.env.NEXT_PUBLIC_ZAPPER,
|
||||
CHAIN_ID: process.env.NEXT_PUBLIC_CHAIN_ID,
|
||||
NETWORK: process.env.NEXT_PUBLIC_NETWORK,
|
||||
URL_GQL: process.env.NEXT_PUBLIC_GQL,
|
||||
URL_REST: process.env.NEXT_PUBLIC_REST,
|
||||
URL_RPC: process.env.NEXT_PUBLIC_RPC,
|
||||
URL_API: process.env.NEXT_PUBLIC_API,
|
||||
WALLETS: process.env.NEXT_PUBLIC_WALLETS?.split(','),
|
||||
}
|
||||
|
||||
export const ENV_MISSING_MESSAGE = 'Environment variable missing'
|
||||
export const IS_TESTNET = ENV.NETWORK !== 'mainnet'
|
||||
|
||||
export const ENV_MISSING_MESSAGE = () => {
|
||||
const missing: string[] = []
|
||||
Object.keys(ENV).forEach((key) => {
|
||||
if (!ENV[key as keyof EnvironmentVariables]) {
|
||||
missing.push(key)
|
||||
}
|
||||
})
|
||||
|
||||
return `Environment variable(s) missing for: ${missing.join(', ')}`
|
||||
}
|
||||
|
@ -3,7 +3,7 @@ import { useQuery } from '@tanstack/react-query'
|
||||
import request, { gql } from 'graphql-request'
|
||||
import { useMemo } from 'react'
|
||||
|
||||
import { URL_GQL } from 'constants/env'
|
||||
import { ENV } from 'constants/env'
|
||||
import useStore from 'store'
|
||||
import { queryKeys } from 'types/query-keys-factory'
|
||||
|
||||
@ -20,7 +20,7 @@ export const useAllBalances = () => {
|
||||
queryKeys.allBalances(address ?? ''),
|
||||
async () => {
|
||||
return await request(
|
||||
URL_GQL!,
|
||||
ENV.URL_GQL!,
|
||||
gql`
|
||||
query UserBalanceQuery {
|
||||
balance: bank {
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { useQuery } from '@tanstack/react-query'
|
||||
|
||||
import { ADDRESS_CREDIT_MANAGER } from 'constants/env'
|
||||
import { ENV } from 'constants/env'
|
||||
import useStore from 'store'
|
||||
import { queryKeys } from 'types/query-keys-factory'
|
||||
|
||||
@ -12,7 +12,7 @@ const queryMsg = {
|
||||
|
||||
export const useAllowedCoins = () => {
|
||||
const client = useStore((s) => s.signingClient)
|
||||
const creditManagerAddress = ADDRESS_CREDIT_MANAGER
|
||||
const creditManagerAddress = ENV.ADDRESS_CREDIT_MANAGER
|
||||
|
||||
const result = useQuery<Result>(
|
||||
queryKeys.allowedCoins(),
|
||||
|
@ -2,7 +2,7 @@ import { Coin } from '@cosmjs/stargate'
|
||||
import { useQuery } from '@tanstack/react-query'
|
||||
import { useMemo } from 'react'
|
||||
|
||||
import { ADDRESS_CREDIT_MANAGER } from 'constants/env'
|
||||
import { ENV } from 'constants/env'
|
||||
import useStore from 'store'
|
||||
import { queryKeys } from 'types/query-keys-factory'
|
||||
|
||||
@ -27,7 +27,7 @@ interface Result {
|
||||
export const useCreditAccountPositions = (accountId: string) => {
|
||||
const address = useStore((s) => s.address)
|
||||
const client = useStore((s) => s.signingClient)
|
||||
const creditManagerAddress = ADDRESS_CREDIT_MANAGER
|
||||
const creditManagerAddress = ENV.ADDRESS_CREDIT_MANAGER
|
||||
|
||||
const result = useQuery<Result>(
|
||||
queryKeys.creditAccountsPositions(accountId),
|
||||
|
@ -3,7 +3,7 @@
|
||||
import { useQuery } from '@tanstack/react-query'
|
||||
import { useMemo } from 'react'
|
||||
|
||||
import { ADDRESS_ACCOUNT_NFT } from 'constants/env'
|
||||
import { ENV } from 'constants/env'
|
||||
import useStore from 'store'
|
||||
import { queryKeys } from 'types/query-keys-factory'
|
||||
|
||||
@ -15,7 +15,7 @@ export const useCreditAccounts = () => {
|
||||
const address = useStore((s) => s.address)
|
||||
const client = useStore((s) => s.signingClient)
|
||||
const selectedAccount = useStore((s) => s.selectedAccount)
|
||||
const accountNftAddress = ADDRESS_ACCOUNT_NFT
|
||||
const accountNftAddress = ENV.ADDRESS_ACCOUNT_NFT
|
||||
const setSelectedAccount = (account: string) => {
|
||||
useStore.setState({ selectedAccount: account })
|
||||
}
|
||||
|
@ -3,7 +3,7 @@ import { useQuery } from '@tanstack/react-query'
|
||||
import request, { gql } from 'graphql-request'
|
||||
import { useMemo } from 'react'
|
||||
|
||||
import { ADDRESS_RED_BANK, URL_GQL } from 'constants/env'
|
||||
import { ENV } from 'constants/env'
|
||||
import { queryKeys } from 'types/query-keys-factory'
|
||||
|
||||
interface Result {
|
||||
@ -13,12 +13,12 @@ interface Result {
|
||||
}
|
||||
|
||||
export const useRedbankBalances = () => {
|
||||
const redBankAddress = ADDRESS_RED_BANK
|
||||
const redBankAddress = ENV.ADDRESS_RED_BANK
|
||||
const result = useQuery<Result>(
|
||||
queryKeys.redbankBalances(),
|
||||
async () => {
|
||||
return await request(
|
||||
URL_GQL!,
|
||||
ENV.URL_GQL!,
|
||||
gql`
|
||||
query RedbankBalances {
|
||||
bank {
|
||||
|
@ -2,11 +2,15 @@ import { useQuery } from '@tanstack/react-query'
|
||||
import { gql, request } from 'graphql-request'
|
||||
import { useMemo } from 'react'
|
||||
|
||||
import { ADDRESS_ORACLE, URL_GQL } from 'constants/env'
|
||||
import { ENV } from 'constants/env'
|
||||
import { queryKeys } from 'types/query-keys-factory'
|
||||
import { getMarketAssets } from 'utils/assets'
|
||||
|
||||
const fetchTokenPrices = async (hiveUrl: string, marketAssets: Asset[], oracleAddress: string) => {
|
||||
const fetchTokenPrices = async (
|
||||
hiveUrl: string,
|
||||
marketAssets: Asset[],
|
||||
oracleAddress: string,
|
||||
): Promise<TokenPricesResult> => {
|
||||
return request(
|
||||
hiveUrl,
|
||||
gql`
|
||||
@ -32,7 +36,7 @@ export const useTokenPrices = () => {
|
||||
const marketAssets = getMarketAssets()
|
||||
const result = useQuery<TokenPricesResult>(
|
||||
queryKeys.tokenPrices(),
|
||||
async () => await fetchTokenPrices(URL_GQL!, marketAssets, ADDRESS_ORACLE || ''),
|
||||
async () => await fetchTokenPrices(ENV.URL_GQL!, marketAssets, ENV.ADDRESS_ORACLE || ''),
|
||||
{
|
||||
refetchInterval: 30000,
|
||||
staleTime: Infinity,
|
||||
|
@ -1,15 +1,15 @@
|
||||
import { NextApiRequest, NextApiResponse } from 'next'
|
||||
|
||||
import { ADDRESS_CREDIT_MANAGER, ENV_MISSING_MESSAGE, URL_API, URL_RPC } from 'constants/env'
|
||||
import { ENV, ENV_MISSING_MESSAGE } from 'constants/env'
|
||||
|
||||
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||
if (!URL_RPC || !ADDRESS_CREDIT_MANAGER || !URL_API) {
|
||||
if (!ENV.URL_RPC || !ENV.ADDRESS_CREDIT_MANAGER || !ENV.URL_API) {
|
||||
return res.status(404).json(ENV_MISSING_MESSAGE)
|
||||
}
|
||||
|
||||
const accountId = req.query.id
|
||||
|
||||
const account = await (await fetch(`${URL_API}/accounts/${accountId}`)).json()
|
||||
const account = await (await fetch(`${ENV.URL_API}/accounts/${accountId}`)).json()
|
||||
|
||||
if (account) {
|
||||
return res.status(200).json([{ denom: 'uosmo', amount: '123876' }])
|
||||
|
@ -1,15 +1,15 @@
|
||||
import { NextApiRequest, NextApiResponse } from 'next'
|
||||
|
||||
import { ADDRESS_CREDIT_MANAGER, ENV_MISSING_MESSAGE, URL_API, URL_RPC } from 'constants/env'
|
||||
import { ENV, ENV_MISSING_MESSAGE } from 'constants/env'
|
||||
|
||||
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||
if (!URL_RPC || !ADDRESS_CREDIT_MANAGER || !URL_API) {
|
||||
if (!ENV.URL_RPC || !ENV.ADDRESS_CREDIT_MANAGER || !ENV.URL_API) {
|
||||
return res.status(404).json(ENV_MISSING_MESSAGE)
|
||||
}
|
||||
|
||||
const accountId = req.query.id
|
||||
|
||||
const account = await (await fetch(`${URL_API}/accounts/${accountId}`)).json()
|
||||
const account = await (await fetch(`${ENV.URL_API}/accounts/${accountId}`)).json()
|
||||
|
||||
if (account) {
|
||||
return res.status(200).json(account.deposits)
|
||||
|
@ -1,18 +1,18 @@
|
||||
import { CosmWasmClient } from '@cosmjs/cosmwasm-stargate'
|
||||
import { NextApiRequest, NextApiResponse } from 'next'
|
||||
|
||||
import { ADDRESS_CREDIT_MANAGER, ENV_MISSING_MESSAGE, URL_RPC } from 'constants/env'
|
||||
import { ENV, ENV_MISSING_MESSAGE } from 'constants/env'
|
||||
|
||||
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||
if (!URL_RPC || !ADDRESS_CREDIT_MANAGER) {
|
||||
if (!ENV.URL_RPC || !ENV.ADDRESS_CREDIT_MANAGER) {
|
||||
return res.status(404).json(ENV_MISSING_MESSAGE)
|
||||
}
|
||||
|
||||
const accountId = req.query.id
|
||||
|
||||
const client = await CosmWasmClient.connect(URL_RPC)
|
||||
const client = await CosmWasmClient.connect(ENV.URL_RPC)
|
||||
|
||||
const data = await client.queryContractSmart(ADDRESS_CREDIT_MANAGER, {
|
||||
const data = await client.queryContractSmart(ENV.ADDRESS_CREDIT_MANAGER, {
|
||||
positions: {
|
||||
account_id: accountId,
|
||||
},
|
||||
|
@ -2,20 +2,20 @@ import { Coin } from '@cosmjs/stargate'
|
||||
import { gql, request as gqlRequest } from 'graphql-request'
|
||||
import { NextApiRequest, NextApiResponse } from 'next'
|
||||
|
||||
import { ADDRESS_RED_BANK, ENV_MISSING_MESSAGE, URL_GQL } from 'constants/env'
|
||||
import { ENV, ENV_MISSING_MESSAGE } from 'constants/env'
|
||||
|
||||
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||
if (!URL_GQL || !ADDRESS_RED_BANK) {
|
||||
if (!ENV.URL_GQL || !ENV.ADDRESS_RED_BANK) {
|
||||
return res.status(404).json(ENV_MISSING_MESSAGE)
|
||||
}
|
||||
|
||||
const result = await gqlRequest<Result>(
|
||||
URL_GQL,
|
||||
ENV.URL_GQL,
|
||||
gql`
|
||||
query RedbankBalances {
|
||||
bank {
|
||||
balance(
|
||||
address: "${ADDRESS_RED_BANK}"
|
||||
address: "${ENV.ADDRESS_RED_BANK}"
|
||||
) {
|
||||
amount
|
||||
denom
|
||||
|
@ -1,19 +1,19 @@
|
||||
import { NextApiRequest, NextApiResponse } from 'next'
|
||||
import { Coin } from '@cosmjs/stargate'
|
||||
import BigNumber from 'bignumber.js'
|
||||
import { NextApiRequest, NextApiResponse } from 'next'
|
||||
|
||||
import { ENV_MISSING_MESSAGE, URL_API } from 'constants/env'
|
||||
import { ENV, ENV_MISSING_MESSAGE } from 'constants/env'
|
||||
import { getMarketAssets } from 'utils/assets'
|
||||
|
||||
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||
if (!URL_API) {
|
||||
if (!ENV.URL_API) {
|
||||
return res.status(404).json(ENV_MISSING_MESSAGE)
|
||||
}
|
||||
|
||||
const marketAssets = getMarketAssets()
|
||||
const $liquidity = fetch(`${URL_API}/markets/liquidity`)
|
||||
const $markets = fetch(`${URL_API}/markets`)
|
||||
const $prices = fetch(`${URL_API}/prices`)
|
||||
const $liquidity = fetch(`${ENV.URL_API}/markets/liquidity`)
|
||||
const $markets = fetch(`${ENV.URL_API}/markets`)
|
||||
const $prices = fetch(`${ENV.URL_API}/prices`)
|
||||
|
||||
const borrow: BorrowAsset[] = await Promise.all([$liquidity, $markets, $prices]).then(
|
||||
async ([$liquidity, $markets, $prices]) => {
|
||||
|
@ -1,22 +1,22 @@
|
||||
import { NextApiRequest, NextApiResponse } from 'next'
|
||||
import { gql, request as gqlRequest } from 'graphql-request'
|
||||
|
||||
import { ADDRESS_RED_BANK, ENV_MISSING_MESSAGE, URL_API, URL_GQL } from 'constants/env'
|
||||
import { ENV, ENV_MISSING_MESSAGE } from 'constants/env'
|
||||
import { getContractQuery } from 'utils/query'
|
||||
|
||||
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||
if (!URL_API || !ADDRESS_RED_BANK || !URL_GQL) {
|
||||
if (!ENV.URL_API || !ENV.ADDRESS_RED_BANK || !ENV.URL_GQL) {
|
||||
return res.status(404).json(ENV_MISSING_MESSAGE)
|
||||
}
|
||||
|
||||
const markets: Market[] = await (await fetch(`${URL_API}/markets`)).json()
|
||||
const markets: Market[] = await (await fetch(`${ENV.URL_API}/markets`)).json()
|
||||
|
||||
let query = ''
|
||||
|
||||
markets.forEach((asset: any) => {
|
||||
query += getContractQuery(
|
||||
asset.denom,
|
||||
ADDRESS_RED_BANK || '',
|
||||
ENV.ADDRESS_RED_BANK || '',
|
||||
`
|
||||
{
|
||||
underlying_debt_amount: {
|
||||
@ -28,7 +28,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
|
||||
})
|
||||
|
||||
const result = await gqlRequest<DebtsQuery>(
|
||||
URL_GQL,
|
||||
ENV.URL_GQL,
|
||||
gql`
|
||||
query RedbankBalances {
|
||||
debts: wasm {
|
||||
|
@ -1,22 +1,22 @@
|
||||
import { NextApiRequest, NextApiResponse } from 'next'
|
||||
import { gql, request as gqlRequest } from 'graphql-request'
|
||||
|
||||
import { ADDRESS_RED_BANK, ENV_MISSING_MESSAGE, URL_API, URL_GQL, URL_RPC } from 'constants/env'
|
||||
import { ENV, ENV_MISSING_MESSAGE } from 'constants/env'
|
||||
import { getContractQuery } from 'utils/query'
|
||||
|
||||
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||
if (!URL_RPC || !ADDRESS_RED_BANK || !URL_GQL || !URL_API) {
|
||||
if (!ENV.URL_RPC || !ENV.ADDRESS_RED_BANK || !ENV.URL_GQL || !ENV.URL_API) {
|
||||
return res.status(404).json(ENV_MISSING_MESSAGE)
|
||||
}
|
||||
|
||||
const markets = await (await fetch(`${URL_API}/markets`)).json()
|
||||
const markets = await (await fetch(`${ENV.URL_API}/markets`)).json()
|
||||
|
||||
let query = ''
|
||||
|
||||
markets.forEach((asset: any) => {
|
||||
query += getContractQuery(
|
||||
asset.denom,
|
||||
ADDRESS_RED_BANK || '',
|
||||
ENV.ADDRESS_RED_BANK || '',
|
||||
`
|
||||
{
|
||||
underlying_liquidity_amount: {
|
||||
@ -28,7 +28,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
|
||||
})
|
||||
|
||||
const result = await gqlRequest<DepositsQuery>(
|
||||
URL_GQL,
|
||||
ENV.URL_GQL,
|
||||
gql`
|
||||
query RedbankBalances {
|
||||
deposits: wasm {
|
||||
|
@ -1,11 +1,11 @@
|
||||
import { gql, request as gqlRequest } from 'graphql-request'
|
||||
import { NextApiRequest, NextApiResponse } from 'next'
|
||||
|
||||
import { ADDRESS_INCENTIVES, ADDRESS_RED_BANK, ENV_MISSING_MESSAGE, URL_GQL } from 'constants/env'
|
||||
import { ENV, ENV_MISSING_MESSAGE } from 'constants/env'
|
||||
import { getMarketAssets } from 'utils/assets'
|
||||
|
||||
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||
if (!URL_GQL || !ADDRESS_RED_BANK || !ADDRESS_INCENTIVES) {
|
||||
if (!ENV.URL_GQL || !ENV.ADDRESS_RED_BANK || !ENV.ADDRESS_INCENTIVES) {
|
||||
return res.status(404).json(ENV_MISSING_MESSAGE)
|
||||
}
|
||||
|
||||
@ -14,13 +14,13 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
|
||||
const marketQueries = marketAssets.map(
|
||||
(asset: Asset) =>
|
||||
`${asset.denom}: contractQuery(
|
||||
contractAddress: "${ADDRESS_RED_BANK}"
|
||||
contractAddress: "${ENV.ADDRESS_RED_BANK}"
|
||||
query: { market: { denom: "${asset.denom}" } }
|
||||
)`,
|
||||
)
|
||||
|
||||
const result = await gqlRequest<RedBankData>(
|
||||
URL_GQL,
|
||||
ENV.URL_GQL,
|
||||
gql`
|
||||
query RedbankQuery {
|
||||
rbwasmkey: wasm {
|
||||
|
@ -1,15 +1,15 @@
|
||||
import { NextApiRequest, NextApiResponse } from 'next'
|
||||
import { Coin } from '@cosmjs/stargate'
|
||||
|
||||
import { ENV_MISSING_MESSAGE, URL_API } from 'constants/env'
|
||||
import { ENV, ENV_MISSING_MESSAGE } from 'constants/env'
|
||||
|
||||
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||
if (!URL_API) {
|
||||
if (!ENV.URL_API) {
|
||||
return res.status(404).json(ENV_MISSING_MESSAGE)
|
||||
}
|
||||
|
||||
const $deposits = fetch(`${URL_API}/markets/deposits`)
|
||||
const $debts = fetch(`${URL_API}/markets/debts`)
|
||||
const $deposits = fetch(`${ENV.URL_API}/markets/deposits`)
|
||||
const $debts = fetch(`${ENV.URL_API}/markets/debts`)
|
||||
|
||||
const liquidity: Coin[] = await Promise.all([$deposits, $debts]).then(
|
||||
async ([$deposits, $debts]) => {
|
||||
|
@ -2,24 +2,24 @@ import { gql, request as gqlRequest } from 'graphql-request'
|
||||
import { NextApiRequest, NextApiResponse } from 'next'
|
||||
import { Coin } from '@cosmjs/stargate'
|
||||
|
||||
import { ADDRESS_ORACLE, ENV_MISSING_MESSAGE, URL_GQL } from 'constants/env'
|
||||
import { ENV, ENV_MISSING_MESSAGE } from 'constants/env'
|
||||
import { getMarketAssets } from 'utils/assets'
|
||||
|
||||
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||
if (!URL_GQL || !ADDRESS_ORACLE) {
|
||||
if (!ENV.URL_GQL || !ENV.ADDRESS_ORACLE) {
|
||||
return res.status(404).json(ENV_MISSING_MESSAGE)
|
||||
}
|
||||
|
||||
const marketAssets = getMarketAssets()
|
||||
|
||||
const result = await gqlRequest<TokenPricesResult>(
|
||||
URL_GQL,
|
||||
ENV.URL_GQL,
|
||||
gql`
|
||||
query PriceOracle {
|
||||
prices: wasm {
|
||||
${marketAssets.map((asset) => {
|
||||
return `${asset.symbol}: contractQuery(
|
||||
contractAddress: "${ADDRESS_ORACLE}"
|
||||
contractAddress: "${ENV.ADDRESS_ORACLE}"
|
||||
query: {
|
||||
price: {
|
||||
denom: "${asset.denom}"
|
||||
|
@ -1,15 +1,15 @@
|
||||
import { CosmWasmClient } from '@cosmjs/cosmwasm-stargate'
|
||||
import { NextApiRequest, NextApiResponse } from 'next'
|
||||
|
||||
import { ADDRESS_CREDIT_MANAGER, ENV_MISSING_MESSAGE, URL_RPC } from 'constants/env'
|
||||
import { ENV, ENV_MISSING_MESSAGE } from 'constants/env'
|
||||
|
||||
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||
if (!URL_RPC || !ADDRESS_CREDIT_MANAGER) {
|
||||
if (!ENV.URL_RPC || !ENV.ADDRESS_CREDIT_MANAGER) {
|
||||
return res.status(404).json(ENV_MISSING_MESSAGE)
|
||||
}
|
||||
const client = await CosmWasmClient.connect(URL_RPC)
|
||||
const client = await CosmWasmClient.connect(ENV.URL_RPC)
|
||||
|
||||
const data = await client.queryContractSmart(ADDRESS_CREDIT_MANAGER, {
|
||||
const data = await client.queryContractSmart(ENV.ADDRESS_CREDIT_MANAGER, {
|
||||
vaults_info: { limit: 5, start_after: undefined },
|
||||
})
|
||||
|
||||
|
@ -1,18 +1,18 @@
|
||||
import { CosmWasmClient } from '@cosmjs/cosmwasm-stargate'
|
||||
import { NextApiRequest, NextApiResponse } from 'next'
|
||||
|
||||
import { ADDRESS_ACCOUNT_NFT, ENV_MISSING_MESSAGE, URL_RPC } from 'constants/env'
|
||||
import { ENV, ENV_MISSING_MESSAGE } from 'constants/env'
|
||||
|
||||
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||
if (!URL_RPC || !ADDRESS_ACCOUNT_NFT) {
|
||||
if (!ENV.URL_RPC || !ENV.ADDRESS_ACCOUNT_NFT) {
|
||||
return res.status(404).json(ENV_MISSING_MESSAGE)
|
||||
}
|
||||
|
||||
const address = req.query.address
|
||||
|
||||
const client = await CosmWasmClient.connect(URL_RPC)
|
||||
const client = await CosmWasmClient.connect(ENV.URL_RPC)
|
||||
|
||||
const data = await client.queryContractSmart(ADDRESS_ACCOUNT_NFT, {
|
||||
const data = await client.queryContractSmart(ENV.ADDRESS_ACCOUNT_NFT, {
|
||||
tokens: {
|
||||
owner: address,
|
||||
},
|
||||
|
@ -1,16 +1,16 @@
|
||||
import { NextApiRequest, NextApiResponse } from 'next'
|
||||
|
||||
import { ENV_MISSING_MESSAGE, URL_REST } from 'constants/env'
|
||||
import { ENV, ENV_MISSING_MESSAGE } from 'constants/env'
|
||||
|
||||
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||
if (!URL_REST) {
|
||||
if (!ENV.URL_REST) {
|
||||
return res.status(404).json(ENV_MISSING_MESSAGE)
|
||||
}
|
||||
|
||||
const address = req.query.address
|
||||
const uri = '/cosmos/bank/v1beta1/balances/'
|
||||
|
||||
const response = await fetch(`${URL_REST}${uri}${address}`)
|
||||
const response = await fetch(`${ENV.URL_REST}${uri}${address}`)
|
||||
|
||||
if (response.ok) {
|
||||
const data = await response.json()
|
||||
|