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>
This commit is contained in:
Linkie Link 2023-03-08 10:44:39 +01:00 committed by GitHub
parent cbb0700455
commit 21268e5536
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
110 changed files with 2602 additions and 2483 deletions

View File

@ -9,5 +9,7 @@ NEXT_PUBLIC_ACCOUNT_NFT=osmo1l8c3g6zy7kjhuh8d2kqyvxkw0myn4puxv0tzcdf9nwxd386r9l7
NEXT_PUBLIC_ORACLE=osmo1dqz2u3c8rs5e7w5fnchsr2mpzzsxew69wtdy0aq4jsd76w7upmsstqe0s8 NEXT_PUBLIC_ORACLE=osmo1dqz2u3c8rs5e7w5fnchsr2mpzzsxew69wtdy0aq4jsd76w7upmsstqe0s8
NEXT_PUBLIC_RED_BANK=osmo1g30recyv8pfy3qd4qn3dn7plc0rn5z68y5gn32j39e96tjhthzxsw3uvvu NEXT_PUBLIC_RED_BANK=osmo1g30recyv8pfy3qd4qn3dn7plc0rn5z68y5gn32j39e96tjhthzxsw3uvvu
NEXT_PUBLIC_CREDIT_MANAGER=osmo12hgn4jec4tftahm7spf7c2aqsqrxzzk50hkq60e89atumyu0zvys7vzxdc NEXT_PUBLIC_CREDIT_MANAGER=osmo12hgn4jec4tftahm7spf7c2aqsqrxzzk50hkq60e89atumyu0zvys7vzxdc
NEXT_PUBLIC_INCENTIVES=osmo1zxs8fry3m8j94pqg7h4muunyx86en27cl0xgk76fc839xg2qnn6qtpjs48
NEXT_PUBLIC_ZAPPER=osmo1ua8dwc9v8qjh7n3qf8kg6xvrwjm5yu9xxln7yjvgmrvfzaxvzsuqfcdnjq NEXT_PUBLIC_ZAPPER=osmo1ua8dwc9v8qjh7n3qf8kg6xvrwjm5yu9xxln7yjvgmrvfzaxvzsuqfcdnjq
NEXT_PUBLIC_SWAPPER=osmo1uj6r9tu440wwp2mhtagh48yvmeyeaqt2xa7kdnlhyrqcuthlj4ss7ghg6n NEXT_PUBLIC_SWAPPER=osmo1uj6r9tu440wwp2mhtagh48yvmeyeaqt2xa7kdnlhyrqcuthlj4ss7ghg6n
NEXT_PUBLIC_API=http://localhost:3000/api

View File

@ -39,7 +39,7 @@
"recharts": "^2.2.0", "recharts": "^2.2.0",
"sass": "^1.58.3", "sass": "^1.58.3",
"swr": "^2.0.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", "use-local-storage-state": "^18.1.1",
"zustand": "^4.1.4" "zustand": "^4.1.4"
}, },

View File

@ -1,3 +0,0 @@
export default function laoding() {
return '...isLoading'
}

View File

@ -1,7 +1,5 @@
import { getBorrowData } from 'utils/api' import BorrowPage from 'components/pages/borrow'
export default async function page() { export default async function page({ params }: PageProps) {
const borrowData = await getBorrowData() return <BorrowPage params={params} />
return `You are a guest`
} }

View File

@ -1,3 +1,5 @@
export default function page() { import CouncilPage from 'components/pages/council'
return `You are a guest`
export default async function page({ params }: PageProps) {
return <CouncilPage params={params} />
} }

View File

@ -1,3 +1,5 @@
export default function page() { import EarnPage from 'components/pages/earn'
return `You are a guest`
export default async function page({ params }: PageProps) {
return <EarnPage params={params} />
} }

View File

@ -1,3 +1,6 @@
import classNames from 'classnames'
import AccountDetails from 'components/Account/AccountDetails'
import Background from 'components/Background' import Background from 'components/Background'
import FetchPrices from 'components/FetchPrices' import FetchPrices from 'components/FetchPrices'
import { Modals } from 'components/Modals' import { Modals } from 'components/Modals'
@ -9,22 +12,25 @@ import 'styles/globals.scss'
export default function RootLayout({ children }: { children: React.ReactNode }) { export default function RootLayout({ children }: { children: React.ReactNode }) {
return ( return (
<html lang='en'> <html className='m-0 p-0' lang='en'>
<head /> <head />
<body className='m-0 cursor-default bg-body p-0 font-sans text-white'>
<body>
<div className='relative min-h-screen w-full'>
<WalletConnectProvider> <WalletConnectProvider>
<Background /> <Background />
<DesktopNavigation /> <DesktopNavigation />
</WalletConnectProvider> </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 /> <Modals />
<Toaster /> <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> </body>
</html> </html>
) )

View File

@ -1,3 +1,5 @@
export default function page() { import TradePage from 'components/pages/trade'
return 'Connect to your wallet'
export default async function page({ params }: PageProps) {
return <TradePage params={params} />
} }

View File

@ -1,3 +1,5 @@
export default function page() { import PortfolioPage from 'components/pages/portfolio'
return `You are a guest`
export default async function page({ params }: PageProps) {
return <PortfolioPage params={params} />
} }

View File

@ -1,3 +1,5 @@
export default function page() { import TradePage from 'components/pages/trade'
return `You are a guest`
export default async function page({ params }: PageProps) {
return <TradePage params={params} />
} }

View File

@ -1,45 +1,5 @@
import { BorrowTable } from 'components/Borrow/BorrowTable' import BorrowPage from 'components/pages/borrow'
import { Card } from 'components/Card'
import { getAccountDebts, getBorrowData } from 'utils/api'
import { getMarketAssets } from 'utils/assets'
export default async function page({ params }: { params: PageParams }) { export default async function page({ params }: PageProps) {
const debtData = await getAccountDebts(params.account) return <BorrowPage params={params} />
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>
)
} }

View File

@ -1,3 +0,0 @@
export default function loading() {
return '...isLoading'
}

View File

@ -1,11 +1,5 @@
import { Card } from 'components/Card' import CouncilPage from 'components/pages/council'
export default function page() { export default async function page({ params }: PageProps) {
return ( return <CouncilPage params={params} />
<div className='flex w-full'>
<Card title='Council'>
<></>
</Card>
</div>
)
} }

View File

@ -1,3 +0,0 @@
export default function loading() {
return '...isLoading'
}

View File

@ -1,12 +1,5 @@
import { Card } from 'components/Card' import EarnPage from 'components/pages/earn'
import { Text } from 'components/Text'
export default function page() { export default async function page({ params }: PageProps) {
return ( return <EarnPage params={params} />
<div className='flex w-full gap-4'>
<Card title='Yield'>
<></>
</Card>
</div>
)
} }

View File

@ -1,3 +1,5 @@
export default function page() { import TradePage from 'components/pages/trade'
return 'Trade page'
export default async function page({ params }: PageProps) {
return <TradePage params={params} />
} }

View File

@ -1,5 +0,0 @@
'use client'
export default function page({ params }: { params: PageParams }) {
return 'error!'
}

View File

@ -1,15 +1,5 @@
import { getCreditAccounts } from 'utils/api' import PortfolioPage from 'components/pages/portfolio'
export default async function page({ params }: { params: PageParams }) { export default async function page({ params }: PageProps) {
const creditAccounts = await getCreditAccounts(params.wallet) return <PortfolioPage params={params} />
return (
<div className='flex w-full items-start gap-4'>
<ul>
{creditAccounts.map((account: string, index: number) => (
<li key={index}>{account}</li>
))}
</ul>
</div>
)
} }

View File

@ -1,3 +0,0 @@
export default function loading() {
return '...isLoading'
}

View File

@ -1,21 +1,5 @@
import { Card } from 'components/Card' import TradePage from 'components/pages/trade'
export default function page() { export default async function page({ params }: PageProps) {
return ( return <TradePage params={params} />
<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>
)
} }

View File

@ -1,3 +0,0 @@
export default function page() {
return `You are a viewer or a user`
}

View File

@ -1,3 +0,0 @@
export default function page() {
return `You are a viewer or a user`
}

View File

@ -1,3 +1,5 @@
export default function page() { import BorrowPage from 'components/pages/borrow'
return `You are a viewer or a user`
export default async function page({ params }: PageProps) {
return <BorrowPage params={params} />
} }

View File

@ -1,3 +0,0 @@
export default function page() {
return `You are a viewer or a user`
}

View File

@ -1,3 +0,0 @@
export default function page() {
return `You are a viewer or a user`
}

View File

@ -1,3 +1,5 @@
export default function page() { import CouncilPage from 'components/pages/council'
return `You are a viewer or a user`
export default async function page({ params }: PageProps) {
return <CouncilPage params={params} />
} }

View File

@ -1,3 +1,5 @@
export default function page() { import EarnPage from 'components/pages/earn'
return `You are a viewer or a user`
export default async function page({ params }: PageProps) {
return <EarnPage params={params} />
} }

View File

@ -1,21 +1,3 @@
import { AccountNavigation } from 'components/Account/AccountNavigation' export default function RootLayout({ children }: { children: React.ReactNode }) {
import { getCreditAccounts } from 'utils/api' return children
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>
</>
)
} }

View File

@ -1,3 +1,5 @@
export default function page() { import PortfolioPage from 'components/pages/portfolio'
return `You are a viewer or a user`
export default async function page({ params }: PageProps) {
return <PortfolioPage params={params} />
} }

View File

@ -1,3 +1,5 @@
export default function page() { import TradePage from 'components/pages/trade'
return `You are a viewer or a user`
export default async function page({ params }: PageProps) {
return <TradePage params={params} />
} }

View File

@ -1,121 +1,40 @@
'use client' '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' export default function AccountDetails() {
import { useEffect, useState } from 'react' const params = useParams()
const hasAccount = params.account && !isNaN(Number(params.account))
import { AccountManageOverlay } from 'components/Account/AccountManageOverlay' return hasAccount ? (
import { RiskChart } from 'components/Account/RiskChart' <div className='fixed top-[89px] right-4 w-16 rounded-base border border-white/20 bg-white/5 backdrop-blur-sticky'>
import { Button } from 'components/Button' <div className='flex w-full flex-wrap justify-center py-4'>
import { ArrowRightLine, ChevronDown, ChevronLeft } from 'components/Icons' <Gauge tooltip='Health Factor' value={0.2} icon={<Heart />} />
import { LabelValuePair } from 'components/LabelValuePair' <Text size='2xs' className='mt-1 mb-0.5 w-full text-center text-white/50'>
import { PositionsList } from 'components/PositionsList' Health
import { useAccountStats } from 'hooks/data/useAccountStats' </Text>
import { convertFromGwei } from 'utils/formatters' <Text size='xs' className='w-full text-center'>
import { createRiskData } from 'utils/risk' 89%
import useStore from 'store' </Text>
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>
</div> </div>
<AccountManageOverlay <div className='w-full border border-x-0 border-white/20 py-4'>
className='top-[60px] left-[36px]' <Text size='2xs' className='mb-0.5 w-full text-center text-white/50'>
show={showManageMenu} Leverage
setShow={setShowManageMenu} </Text>
/> <Text size='xs' className='w-full text-center'>
4.5x
</Text>
</div> </div>
<div className='flex w-full flex-wrap p-3'> <div className='w-full py-4'>
<LabelValuePair <Text size='2xs' className='mb-0.5 w-full text-center text-white/50'>
className='mb-2' Balance
label='Total Position:' </Text>{' '}
value={{ <Text size='xs' className='w-full text-center'>
format: 'number', $300M
amount: convertFromGwei( </Text>
accountStats?.totalPosition ?? 0,
baseAsset.denom,
marketAssets,
),
prefix: '$',
}}
/>
<LabelValuePair
label='Total Liabilities:'
value={{
format: 'number',
amount: convertFromGwei(accountStats?.totalDebt ?? 0, baseAsset.denom, marketAssets),
prefix: '$',
}}
/>
</div> </div>
{riskData && <RiskChart data={riskData} />}
</div> </div>
) ) : null
} }

View File

@ -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>
)
}

View File

@ -1,36 +1,38 @@
'use client' 'use client'
import classNames from 'classnames'
import { useRouter } from 'next/navigation' import { useRouter } from 'next/navigation'
import { useState } from 'react' import { useState } from 'react'
import { AccountManageOverlay } from 'components/Account/AccountManageOverlay'
import { Button } from 'components/Button' 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 { Overlay } from 'components/Overlay/Overlay'
import { Text } from 'components/Text'
import useParams from 'hooks/useParams' import useParams from 'hooks/useParams'
import useStore from 'store' import useStore from 'store'
import { hardcodedFee } from 'utils/contants' import { hardcodedFee } from 'utils/contants'
const MAX_VISIBLE_CREDIT_ACCOUNTS = 5 export const AccountNavigation = () => {
interface Props {
creditAccounts: string[]
}
export const AccountNavigation = (props: Props) => {
const router = useRouter() const router = useRouter()
const params = useParams() const params = useParams()
const address = useStore((s) => s.client?.recentWallet.account?.address) || ''
const selectedAccount = params.account const selectedAccount = params.account
const createCreditAccount = useStore((s) => s.createCreditAccount) 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 hasCreditAccounts = !!creditAccounts?.length
const firstCreditAccounts = props.creditAccounts?.slice(0, MAX_VISIBLE_CREDIT_ACCOUNTS) ?? [] const accountSelected = !!selectedAccount && !isNaN(Number(selectedAccount))
const restCreditAccounts = props.creditAccounts?.slice(MAX_VISIBLE_CREDIT_ACCOUNTS) ?? []
const [showManageMenu, setShowManageMenu] = useState(false) const [showMenu, setShowMenu] = useState(false)
const [showMoreMenu, setShowMoreMenu] = useState(false)
async function createAccountHandler() { async function createAccountHandler() {
const accountId = await createCreditAccount({ fee: hardcodedFee }) const accountId = await createCreditAccount({ fee: hardcodedFee })
@ -38,93 +40,128 @@ export const AccountNavigation = (props: Props) => {
router.push(`/wallets/${params.wallet}/accounts/${accountId}`) router.push(`/wallets/${params.wallet}/accounts/${accountId}`)
} }
return ( async function deleteAccountHandler() {
<section if (!accountSelected) return
role='navigation' const isSuccess = await deleteCreditAccount({ fee: hardcodedFee, accountId: selectedAccount })
className='flex h-11 w-full items-center gap-6 border-b border-white/20 px-6 text-sm text-white/40' if (isSuccess) {
> router.push(`/wallets/${params.wallet}/accounts`)
<> }
{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>
<AccountManageOverlay return !address ? null : (
className='-left-[86px]' <>
show={showManageMenu} {creditAccounts === null ? (
setShow={setShowManageMenu} <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>
</> <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>
) )
} }

View File

@ -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>
)
}

View File

@ -66,7 +66,7 @@ export const FundAccountModal = () => {
found = true found = true
} }
}) })
}, [marketAssets, balancesData]) }, [balancesData, marketAssets, selectedToken])
// --------------- // ---------------
// VARIABLES // VARIABLES
@ -130,7 +130,7 @@ export const FundAccountModal = () => {
your osmosis wallet. your osmosis wallet.
</Text> </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'> <div className='mb-1 flex justify-between border-b border-white/20 p-2'>
<Text size='sm' className='text-white'> <Text size='sm' className='text-white'>
Asset: Asset:

View File

@ -2,20 +2,16 @@ import { Switch } from '@headlessui/react'
import BigNumber from 'bignumber.js' import BigNumber from 'bignumber.js'
import classNames from 'classnames' import classNames from 'classnames'
import React, { useEffect, useMemo, useState } from 'react' import React, { useEffect, useMemo, useState } from 'react'
import { toast } from 'react-toastify'
import { BorrowCapacity } from 'components/BorrowCapacity' 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 { Button } from 'components/Button'
import { Text } from 'components/Text' import { CircularProgress } from 'components/CircularProgress'
import { Slider } from 'components/Slider'
import { FormattedNumber } from 'components/FormattedNumber' import { FormattedNumber } from 'components/FormattedNumber'
import { Gauge } from 'components/Gauge' import { Gauge } from 'components/Gauge'
import { LabelValuePair } from 'components/LabelValuePair' import { LabelValuePair } from 'components/LabelValuePair'
import { Modal } from 'components/Modal' 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 { useAccountStats } from 'hooks/data/useAccountStats'
import { useCalculateMaxWithdrawAmount } from 'hooks/data/useCalculateMaxWithdrawAmount' import { useCalculateMaxWithdrawAmount } from 'hooks/data/useCalculateMaxWithdrawAmount'
import { useWithdrawFunds } from 'hooks/mutations/useWithdrawFunds' import { useWithdrawFunds } from 'hooks/mutations/useWithdrawFunds'
@ -23,6 +19,8 @@ import { useCreditAccountPositions } from 'hooks/queries/useCreditAccountPositio
import { useTokenPrices } from 'hooks/queries/useTokenPrices' import { useTokenPrices } from 'hooks/queries/useTokenPrices'
import useStore from 'store' import useStore from 'store'
import { getBaseAsset, getMarketAssets } from 'utils/assets' import { getBaseAsset, getMarketAssets } from 'utils/assets'
import { convertFromGwei, formatLeverage, formatValue } from 'utils/formatters'
import { getTokenDecimals, getTokenSymbol } from 'utils/tokens'
export const WithdrawModal = () => { export const WithdrawModal = () => {
// --------------- // ---------------
@ -169,7 +167,7 @@ export const WithdrawModal = () => {
<div className='flex w-full'> <div className='flex w-full'>
<div className='flex flex-1 flex-col border-r border-white/20'> <div className='flex flex-1 flex-col border-r border-white/20'>
<div className='border-b border-white/20 p-6'> <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'> <div className='mb-1 flex justify-between border-b border-white/20 p-2'>
<Text size='sm' className='text-white'> <Text size='sm' className='text-white'>
Asset: Asset:

View File

@ -1,5 +1,5 @@
import { FormattedNumber } from './FormattedNumber' import { FormattedNumber } from 'components/FormattedNumber'
import TitleAndSubCell from './TitleAndSubCell' import TitleAndSubCell from 'components/TitleAndSubCell'
interface Props { interface Props {
asset: Asset asset: Asset

View File

@ -1,5 +1,44 @@
'use client' import classNames from 'classnames'
export default function Background() { 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>
)
} }

View File

@ -8,20 +8,19 @@ import {
SortingState, SortingState,
useReactTable, useReactTable,
} from '@tanstack/react-table' } from '@tanstack/react-table'
import classNames from 'classnames'
import Image from 'next/image' import Image from 'next/image'
import React from 'react' import React from 'react'
import classNames from 'classnames'
import AmountAndValue from 'components/AmountAndValue'
import { AssetRow } from 'components/Borrow/AssetRow' import { AssetRow } from 'components/Borrow/AssetRow'
import { ChevronDown, ChevronUp } from 'components/Icons' import { ChevronDown, ChevronUp } from 'components/Icons'
import { getMarketAssets } from 'utils/assets'
import { Text } from 'components/Text' import { Text } from 'components/Text'
import TitleAndSubCell from 'components/TitleAndSubCell' import TitleAndSubCell from 'components/TitleAndSubCell'
import { FormattedNumber } from 'components/FormattedNumber' import { getMarketAssets } from 'utils/assets'
import AmountAndValue from 'components/AmountAndValue'
import { formatPercent } from 'utils/formatters' import { formatPercent } from 'utils/formatters'
import AssetExpanded from 'components/Borrow/AssetExpanded'
import AssetExpanded from './AssetExpanded' import Loading from 'components/Loading'
type Props = { type Props = {
data: BorrowAsset[] | BorrowAssetActive[] data: BorrowAsset[] | BorrowAssetActive[]
@ -52,11 +51,17 @@ export const BorrowTable = (props: Props) => {
{ {
accessorKey: 'borrowRate', accessorKey: 'borrowRate',
header: 'Borrow Rate', header: 'Borrow Rate',
cell: ({ row }) => ( cell: ({ row }) => {
if (row.original.borrowRate === null) {
return <Loading />
}
return (
<Text className='justify-end' size='sm'> <Text className='justify-end' size='sm'>
{formatPercent(row.original.borrowRate)} {formatPercent(row.original.borrowRate)}
</Text> </Text>
), )
},
}, },
...((props.data[0] as BorrowAssetActive)?.debt ...((props.data[0] as BorrowAssetActive)?.debt
? [ ? [
@ -82,6 +87,10 @@ export const BorrowTable = (props: Props) => {
if (!asset) return null if (!asset) return null
if (row.original.liquidity === null) {
return <Loading />
}
return <AmountAndValue asset={asset} amount={row.original.liquidity.amount} /> return <AmountAndValue asset={asset} amount={row.original.liquidity.amount} />
}, },
}, },
@ -97,7 +106,7 @@ export const BorrowTable = (props: Props) => {
), ),
}, },
], ],
[], [marketAssets, props.data],
) )
const table = useReactTable({ const table = useReactTable({

View 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'
}

View File

@ -1,7 +1,6 @@
import useStore from 'store' import useStore from 'store'
import { Modal } from 'components/Modal'
import { Modal } from './Modal' import TitleAndSubCell from 'components/TitleAndSubCell'
import TitleAndSubCell from './TitleAndSubCell'
export default function BorrowModal() { export default function BorrowModal() {
const open = useStore((s) => s.borrowModal) const open = useStore((s) => s.borrowModal)

View File

@ -1,7 +1,8 @@
import classNames from 'classnames' import classNames from 'classnames'
import React, { LegacyRef, ReactNode } from 'react' import React, { LegacyRef, ReactElement, ReactNode } from 'react'
import { CircularProgress } from 'components/CircularProgress' import { CircularProgress } from 'components/CircularProgress'
import { ChevronDown } from 'components/Icons'
import useStore from 'store' import useStore from 'store'
interface Props { interface Props {
@ -13,30 +14,39 @@ interface Props {
showProgressIndicator?: boolean showProgressIndicator?: boolean
size?: 'small' | 'medium' | 'large' size?: 'small' | 'medium' | 'large'
text?: string | ReactNode text?: string | ReactNode
variant?: 'solid' | 'transparent' | 'round' | 'text' variant?: 'solid' | 'transparent' | 'round'
onClick?: (e: React.MouseEvent<HTMLButtonElement>) => void onClick?: (e: React.MouseEvent<HTMLButtonElement>) => void
icon?: ReactElement
iconClassName?: string
hasSubmenu?: boolean
} }
export const buttonColorClasses = { export const buttonColorClasses = {
primary: 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: 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: 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: 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 = { const buttonTransparentColorClasses = {
primary: primary: 'border-none hover:text-primary active:text-primary focus:text-primary',
'border-none text-primary hover:text-primary-highlight active:text-primary-highlight focus:text-primary-highlight', secondary: 'border-none hover:text-secondary active:text-secondary focus:text-secondary',
secondary: tertiary: 'border-none hover:text-white/80 active:text-white/80 focus:text-white/80',
'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',
quaternary: 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 = { const buttonRoundSizeClasses = {
@ -46,16 +56,41 @@ const buttonRoundSizeClasses = {
} }
export const buttonSizeClasses = { export const buttonSizeClasses = {
small: 'text-sm px-5 py-1.5 min-h-[32px]', small: 'text-sm',
medium: 'text-base px-6 py-2.5 min-h-[40px]', medium: 'text-base',
large: 'text-lg px-6 py-2.5 min-h-[56px]', 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 = { export const buttonVariantClasses = {
solid: 'text-white', solid: 'rounded-sm text-white shadow-button justify-center group',
transparent: 'bg-transparent p-0', transparent: 'rounded-sm bg-transparent p-0 transition duration-200 ease-in',
round: 'rounded-full p-0', 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( export const Button = React.forwardRef(function Button(
@ -70,17 +105,22 @@ export const Button = React.forwardRef(function Button(
text, text,
variant = 'solid', variant = 'solid',
onClick, onClick,
icon,
iconClassName,
hasSubmenu,
}: Props, }: Props,
ref, ref,
) { ) {
const buttonClasses = [] const buttonClasses = []
const enableAnimations = useStore((s) => s.enableAnimations) const enableAnimations = useStore((s) => s.enableAnimations)
const isDisabled = disabled || showProgressIndicator
switch (variant) { switch (variant) {
case 'round': case 'round':
buttonClasses.push( buttonClasses.push(
buttonSizeClasses[size], buttonSizeClasses[size],
buttonRoundSizeClasses[size], buttonRoundSizeClasses[size],
buttonPaddingClasses[size],
buttonColorClasses[color], buttonColorClasses[color],
) )
break break
@ -90,7 +130,11 @@ export const Button = React.forwardRef(function Button(
break break
case 'solid': case 'solid':
buttonClasses.push(buttonSizeClasses[size], buttonColorClasses[color]) buttonClasses.push(
buttonSizeClasses[size],
buttonPaddingClasses[size],
buttonColorClasses[color],
)
break break
default: default:
} }
@ -98,10 +142,14 @@ export const Button = React.forwardRef(function Button(
return ( return (
<button <button
className={classNames( 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', enableAnimations && 'transition-color',
buttonClasses, buttonClasses,
buttonVariantClasses[variant], buttonVariantClasses[variant],
variant === 'solid' && color === 'tertiary' && buttonBorderClasses,
variant === 'solid' && color === 'primary' && buttonGradientClasses,
disabled && 'pointer-events-none opacity-50', disabled && 'pointer-events-none opacity-50',
className, className,
)} )}
@ -109,8 +157,25 @@ export const Button = React.forwardRef(function Button(
ref={ref as LegacyRef<HTMLButtonElement>} ref={ref as LegacyRef<HTMLButtonElement>}
onClick={disabled ? () => {} : onClick} onClick={disabled ? () => {} : onClick}
> >
{text && !children && !showProgressIndicator && <span>{text}</span>} {icon && !isDisabled && (
{children && !showProgressIndicator && children} <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 && ( {showProgressIndicator && (
<CircularProgress size={size === 'small' ? 10 : size === 'medium' ? 12 : 18} /> <CircularProgress size={size === 'small' ? 10 : size === 'medium' ? 12 : 18} />
)} )}

View File

@ -1,12 +1,13 @@
import classNames from 'classnames' import classNames from 'classnames'
import { ReactNode } from 'react' import { ReactElement, ReactNode } from 'react'
import { Text } from 'components/Text' import { Text } from 'components/Text'
interface Props { interface Props {
title: string title?: string | ReactElement
children: ReactNode children: ReactNode
className?: string className?: string
contentClassName?: string
} }
export const Card = (props: Props) => { export const Card = (props: Props) => {
@ -14,13 +15,16 @@ export const Card = (props: Props) => {
<section <section
className={classNames( className={classNames(
props.className, 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} {props.title}
</Text> </Text>
<div>{props.children}</div> )}
<div className={classNames('w-full', props.contentClassName)}>{props.children}</div>
</section> </section>
) )
} }

View File

@ -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>
)
}

View 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>
)
}

View 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>
)
}

View File

@ -1,5 +1,5 @@
import classNames from 'classnames' import classNames from 'classnames'
import { ReactNode } from 'react' import { ReactElement, ReactNode } from 'react'
import { Tooltip } from 'components/Tooltip' import { Tooltip } from 'components/Tooltip'
import useStore from 'store' import useStore from 'store'
@ -11,81 +11,68 @@ interface Props {
diameter?: number diameter?: number
value: number value: number
label?: string label?: string
icon?: ReactElement
} }
export const Gauge = ({ export const Gauge = ({
background = '#15161A', background = '#FFFFFF22',
diameter = 40, diameter = 40,
value = 0, value = 0,
label,
tooltip, tooltip,
icon,
}: Props) => { }: Props) => {
const enableAnimations = useStore((s) => s.enableAnimations) const enableAnimations = useStore((s) => s.enableAnimations)
const radius = 16
const percentage = value * 100 const percentage = value * 100
const percentageValue = percentage > 100 ? 100 : percentage < 0 ? 0 : percentage const percentageValue = percentage > 100 ? 100 : percentage < 0 ? 0 : percentage
const semiCirclePercentage = percentageValue == -50 ? 0 : Math.abs(percentageValue / 2 - 50) const circlePercent = 100 - percentageValue
return ( return (
<Tooltip content={tooltip}> <Tooltip content={tooltip}>
<div <div className={classNames('relative', `w-${diameter / 4} h-${diameter / 4}`)}>
className={classNames(
'relative overflow-hidden',
`w-${diameter / 4} h-${diameter / 8 + 1}`,
)}
>
<svg <svg
viewBox='2 -2 28 36' viewBox='2 -2 28 36'
width={diameter} width={diameter}
height={diameter} height={diameter}
style={{ transform: 'rotate(180deg)' }} style={{ transform: 'rotate(-90deg)' }}
className='absolute top-0 left-0' className='absolute top-0 left-0'
> >
<linearGradient id='gradient'> <linearGradient id='gradient'>
<stop stopColor='#C13338' offset='0%'></stop> <stop stopColor='rgba(255, 160, 187)' offset='0%'></stop>
<stop stopColor='#4F3D9F' offset='50%'></stop> <stop stopColor='rgba(186, 8, 189)' offset='50%'></stop>
<stop stopColor='#15BFA9' offset='100%'></stop> <stop stopColor='rgba(255, 160, 187)' offset='100%'></stop>
</linearGradient> </linearGradient>
<circle <circle
fill='none' fill='none'
stroke={background} stroke={background}
strokeWidth={4} strokeWidth={4}
strokeDasharray='50 100' strokeDashoffset='0'
strokeLinecap='round' r={radius}
r='16' cx={radius}
cx='16' cy={radius}
cy='16'
shapeRendering='geometricPrecision' shapeRendering='geometricPrecision'
/> />
<circle <circle
r='16' r={radius}
cx='16' cx={radius}
cy='16' cy={radius}
fill='none' fill='transparent'
strokeLinecap='round'
stroke='url(#gradient)' stroke='url(#gradient)'
strokeDasharray='50 100'
strokeWidth={5} strokeWidth={5}
strokeDashoffset={circlePercent}
strokeDasharray='100'
pathLength='100'
style={{ style={{
strokeDashoffset: semiCirclePercentage,
transition: enableAnimations ? 'stroke-dashoffset 1s ease' : 'none', transition: enableAnimations ? 'stroke-dashoffset 1s ease' : 'none',
}} }}
shapeRendering='geometricPrecision' shapeRendering='geometricPrecision'
strokeLinecap='round'
/> />
</svg> </svg>
{label && ( {icon && (
<span <div className='absolute inset-0 flex items-center justify-center p-2.5 opacity-30'>
className='text-xs' {icon}
style={{ </div>
width: '100%',
left: '0',
textAlign: 'center',
bottom: '-2px',
position: 'absolute',
}}
>
{label}
</span>
)} )}
</div> </div>
</Tooltip> </Tooltip>

View 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

View File

@ -1,5 +1,3 @@
<svg viewBox='0 0 12 12' xmlns='http://www.w3.org/2000/svg' fill="currentColor"> <svg viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path <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"/>
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> </svg>

Before

Width:  |  Height:  |  Size: 964 B

After

Width:  |  Height:  |  Size: 417 B

View 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

View 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

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 96 KiB

After

Width:  |  Height:  |  Size: 178 B

View 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

View File

@ -1,12 +1,15 @@
// @index(['./*.svg'], f => `export { default as ${f.name} } from 'components/Icons/${f.name}.svg'`) // @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 Add } from 'components/Icons/Add.svg'
export { default as ArrowBack } from 'components/Icons/ArrowBack.svg' export { default as ArrowBack } from 'components/Icons/ArrowBack.svg'
export { default as ArrowDown } from 'components/Icons/ArrowDown.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 ArrowLeftLine } from 'components/Icons/ArrowLeftLine.svg'
export { default as ArrowRightLine } from 'components/Icons/ArrowRightLine.svg' export { default as ArrowRightLine } from 'components/Icons/ArrowRightLine.svg'
export { default as ArrowsLeftRight } from 'components/Icons/ArrowsLeftRight.svg' export { default as ArrowsLeftRight } from 'components/Icons/ArrowsLeftRight.svg'
export { default as ArrowsUpDown } from 'components/Icons/ArrowsUpDown.svg' export { default as ArrowsUpDown } from 'components/Icons/ArrowsUpDown.svg'
export { default as ArrowUp } from 'components/Icons/ArrowUp.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 BurgerMenu } from 'components/Icons/BurgerMenu.svg'
export { default as Check } from 'components/Icons/Check.svg' export { default as Check } from 'components/Icons/Check.svg'
export { default as ChevronDown } from 'components/Icons/ChevronDown.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 ExternalLink } from 'components/Icons/ExternalLink.svg'
export { default as Failed } from 'components/Icons/Failed.svg' export { default as Failed } from 'components/Icons/Failed.svg'
export { default as Github } from 'components/Icons/Github.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 Info } from 'components/Icons/Info.svg'
export { default as Logo } from 'components/Icons/Logo.svg' export { default as Logo } from 'components/Icons/Logo.svg'
export { default as MarsProtocol } from 'components/Icons/MarsProtocol.svg' export { default as MarsProtocol } from 'components/Icons/MarsProtocol.svg'

View File

@ -3,8 +3,6 @@ import classNames from 'classnames'
interface Props { interface Props {
className?: string className?: string
count?: number count?: number
height?: number
width?: number
} }
export default function Loading(props: Props) { export default function Loading(props: Props) {
@ -14,10 +12,8 @@ export default function Loading(props: Props) {
<div <div
role='status' role='status'
className={classNames( className={classNames(
'animate-pulse rounded-md bg-white/40', 'max-w-full animate-pulse rounded-sm bg-white/40',
props.className, props.className ? props.className : 'h-4 w-full',
props.height ? `h-[${props.height}px]` : 'h-[300px]',
props.width ? `w-[${props.width}px]` : 'w-full',
)} )}
key={i} key={i}
/> />

View File

@ -3,8 +3,7 @@ import { ReactNode } from 'react'
import { Close } from 'components/Icons' import { Close } from 'components/Icons'
import { Text } from 'components/Text' import { Text } from 'components/Text'
import { Button } from 'components/Button'
import { Button } from './Button'
interface Props { interface Props {
title: string title: string
@ -21,17 +20,22 @@ export const Modal = (props: Props) => {
} }
return props.open ? ( 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'> <div className='relative flex h-full w-full items-center justify-center'>
<section <section
className={classNames( 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, props.className,
)} )}
> >
<div className='flex justify-between pb-6'> <div className='flex justify-between pb-6'>
<Text>{props.title}</Text> <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>
<div>{props.children ? props.children : props.content}</div> <div>{props.children ? props.children : props.content}</div>
</section> </section>

View File

@ -2,8 +2,7 @@
import { ConfirmModal } from 'components/Account/ConfirmModal' import { ConfirmModal } from 'components/Account/ConfirmModal'
import { FundAccountModal } from 'components/Account/FundAccountModal' import { FundAccountModal } from 'components/Account/FundAccountModal'
import BorrowModal from 'components/BorrowModal'
import BorrowModal from './BorrowModal'
export const Modals = () => ( export const Modals = () => (
<> <>

View File

@ -2,7 +2,9 @@
import Link from 'next/link' import Link from 'next/link'
import { usePathname } from 'next/navigation' import { usePathname } from 'next/navigation'
import classNames from 'classnames'
import { AccountNavigation } from 'components/Account/AccountNavigation'
import { Logo } from 'components/Icons' import { Logo } from 'components/Icons'
import { NavLink } from 'components/Navigation/NavLink' import { NavLink } from 'components/Navigation/NavLink'
import Wallet from 'components/Wallet/Wallet' import Wallet from 'components/Wallet/Wallet'
@ -20,8 +22,14 @@ export default function DesktopNavigation() {
const pathname = usePathname() || '' const pathname = usePathname() || ''
return ( return (
<div className='relative hidden bg-header lg:block'> <header
<div className='flex items-center justify-between border-b border-white/20 px-6 py-3'> 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'> <div className='flex flex-grow items-center'>
<Link href={getRoute(pathname, { page: 'trade' })}> <Link href={getRoute(pathname, { page: 'trade' })}>
<span className='block h-10 w-10'> <span className='block h-10 w-10'>
@ -36,8 +44,11 @@ export default function DesktopNavigation() {
))} ))}
</div> </div>
</div> </div>
<div className='flex gap-4'>
<AccountNavigation />
<Wallet /> <Wallet />
</div> </div>
</div> </div>
</header>
) )
} }

View File

@ -1,9 +1,7 @@
'use client' import classNames from 'classnames'
import Link from 'next/link' import Link from 'next/link'
import { usePathname } from 'next/navigation' import { usePathname } from 'next/navigation'
import { ReactNode } from 'react' import { ReactNode } from 'react'
import classNames from 'classnames'
interface Props { interface Props {
href: string href: string
@ -18,7 +16,7 @@ export const NavLink = ({ href, children }: Props) => {
<Link <Link
href={href} href={href}
className={classNames( 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', isActive ? 'pointer-events-none text-white' : 'text-white/60',
)} )}
> >

View File

@ -18,7 +18,7 @@ export const Overlay = ({ children, content, className, show, setShow }: Props)
<> <>
<div <div
className={classNames( 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, className,
)} )}
> >

View File

@ -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>
)
}

View 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>
)
}

View File

@ -3,19 +3,18 @@ import BigNumber from 'bignumber.js'
import Image from 'next/image' import Image from 'next/image'
import React, { useMemo, useState } from 'react' import React, { useMemo, useState } from 'react'
import { NumericFormat } from 'react-number-format' import { NumericFormat } from 'react-number-format'
import { toast } from 'react-toastify'
import { Button } from 'components/Button' import { Button } from 'components/Button'
import { Card } from 'components/Card'
import { CircularProgress } from 'components/CircularProgress' import { CircularProgress } from 'components/CircularProgress'
import { ContainerSecondary } from 'components/ContainerSecondary'
import { Slider } from 'components/Slider' import { Slider } from 'components/Slider'
import { useRepayFunds } from 'hooks/mutations/useRepayFunds' import { useRepayFunds } from 'hooks/mutations/useRepayFunds'
import { useAllBalances } from 'hooks/queries/useAllBalances' import { useAllBalances } from 'hooks/queries/useAllBalances'
import { useCreditAccountPositions } from 'hooks/queries/useCreditAccountPositions' import { useCreditAccountPositions } from 'hooks/queries/useCreditAccountPositions'
import { useTokenPrices } from 'hooks/queries/useTokenPrices' import { useTokenPrices } from 'hooks/queries/useTokenPrices'
import { getTokenDecimals, getTokenSymbol } from 'utils/tokens'
import { getMarketAssets } from 'utils/assets'
import useStore from 'store' 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 // 0.001% buffer / slippage to avoid repay action from not fully repaying the debt amount
const REPAY_BUFFER = 1.00001 const REPAY_BUFFER = 1.00001
@ -137,7 +136,7 @@ export const RepayModal = ({ show, onClose, tokenDenom }: Props) => {
Repay {tokenSymbol} Repay {tokenSymbol}
</Dialog.Title> </Dialog.Title>
<div className='mb-4 flex flex-col gap-2 text-sm'> <div className='mb-4 flex flex-col gap-2 text-sm'>
<ContainerSecondary> <Card>
<p className='mb-7'> <p className='mb-7'>
In wallet: {walletAmount.toLocaleString()} {tokenSymbol} In wallet: {walletAmount.toLocaleString()} {tokenSymbol}
</p> </p>
@ -176,7 +175,7 @@ export const RepayModal = ({ show, onClose, tokenDenom }: Props) => {
}} }}
onMaxClick={() => setAmount(maxValue)} onMaxClick={() => setAmount(maxValue)}
/> />
</ContainerSecondary> </Card>
</div> </div>
<Button <Button
className='mt-auto w-full' className='mt-auto w-full'

View File

@ -1,5 +1,4 @@
import * as RadixSlider from '@radix-ui/react-slider' import * as RadixSlider from '@radix-ui/react-slider'
import React from 'react'
type Props = { type Props = {
className?: string className?: string
@ -27,7 +26,7 @@ export const Slider = ({ className, value, onChange, onMaxClick }: Props) => {
</RadixSlider.Thumb> </RadixSlider.Thumb>
</RadixSlider.Root> </RadixSlider.Root>
<button <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} onClick={onMaxClick}
> >
MAX MAX

View File

@ -29,7 +29,6 @@ export const Text = ({
<HtmlElement <HtmlElement
className={classNames( className={classNames(
className, className,
'flex items-center',
uppercase ? `text-${sizeClass}-caps` : `text-${sizeClass}`, uppercase ? `text-${sizeClass}-caps` : `text-${sizeClass}`,
monospace && 'number', monospace && 'number',
)} )}

View File

@ -13,11 +13,9 @@ interface Props extends React.HTMLProps<HTMLAnchorElement> {
const colorClasses = { const colorClasses = {
primary: primary:
'text-primary hover:text-primary-highlight active:text-primary-highlight-10 focus:text-primary-highlight', 'text-primary hover:text-secondary active:text-secondary/90 focus:text-text-secondary/90',
secondary: secondary: 'text-secondary hover:text-primary active:text-primary/90 focus:text-text-primary/90',
'text-secondary hover:text-secondary-highlight active:text-secondary-highlight-10 focus:text-secondary-highlight', tertiary: 'text-white/80 hover:text-white active:text-white/90 focus:text-text-white/90',
tertiary:
'text-secondary-dark/60 hover:text-secondary-dark active:text-secondary-dark-10 focus:text-secondary-dark',
quaternary: 'hover:text-white active:text-white', quaternary: 'hover:text-white active:text-white',
} }
const textSizeClasses = { const textSizeClasses = {

View File

@ -2,6 +2,7 @@
import { useRouter } from 'next/navigation' import { useRouter } from 'next/navigation'
import { toast as createToast, Slide, ToastContainer } from 'react-toastify' import { toast as createToast, Slide, ToastContainer } from 'react-toastify'
import { Check, Warning } from 'components/Icons'
import useStore from 'store' import useStore from 'store'
export default function Toaster() { export default function Toaster() {
@ -11,9 +12,23 @@ export default function Toaster() {
if (toast) { if (toast) {
if (toast.isError) { if (toast.isError) {
createToast.error(toast.message) createToast.error(toast.message, {
progressClassName: 'bg-loss',
icon: (
<span className='h-4 w-4'>
<Warning />
</span>
),
})
} else { } 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 }) useStore.setState({ toast: null })
router.refresh() router.refresh()
@ -21,11 +36,14 @@ export default function Toaster() {
return ( return (
<ToastContainer <ToastContainer
autoClose={3000} autoClose={5000}
closeButton={false} closeButton={false}
position='bottom-right' position='bottom-right'
newestOnTop newestOnTop
transition={enableAnimations ? Slide : undefined} 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'
/> />
) )
} }

View File

@ -33,7 +33,7 @@ export const Tooltip = ({
render={(attrs) => { render={(attrs) => {
return ( return (
<div <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} {...attrs}
> >
{content} {content}

View 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>
)
}

View 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>
)
}

View 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>
)
}

View File

@ -1,6 +1,9 @@
'use client'
import { useWalletManager, WalletConnectionStatus } from '@marsprotocol/wallet-connector' import { useWalletManager, WalletConnectionStatus } from '@marsprotocol/wallet-connector'
import { ReactNode } from 'react' import { ReactNode } from 'react'
import { Button } from 'components/Button'
import { CircularProgress } from 'components/CircularProgress' import { CircularProgress } from 'components/CircularProgress'
import { Wallet } from 'components/Icons' import { Wallet } from 'components/Icons'
@ -15,24 +18,21 @@ export default function ConnectButton(props: Props) {
return ( return (
<div className='relative'> <div className='relative'>
<button <Button
variant='solid'
color='tertiary'
disabled={props.disabled} 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} onClick={connect}
icon={<Wallet />}
> >
{props.status === WalletConnectionStatus.Connecting ? ( {props.status === WalletConnectionStatus.Connecting ? (
<span className='flex justify-center'> <span className='flex justify-center'>
<CircularProgress size={16} /> <CircularProgress size={16} />
</span> </span>
) : ( ) : (
<> <span>{props.textOverride || 'Connect Wallet'}</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>
</>
)} )}
</button> </Button>
</div> </div>
) )
} }

View File

@ -1,3 +1,5 @@
'use client'
import { Coin } from '@cosmjs/stargate' import { Coin } from '@cosmjs/stargate'
import { import {
ChainInfoID, ChainInfoID,
@ -18,9 +20,9 @@ import { Check, Copy, ExternalLink, Osmo } from 'components/Icons'
import { Overlay } from 'components/Overlay/Overlay' import { Overlay } from 'components/Overlay/Overlay'
import { Text } from 'components/Text' import { Text } from 'components/Text'
import useStore from 'store' import useStore from 'store'
import { getWalletBalances } from 'utils/api'
import { getBaseAsset } from 'utils/assets' import { getBaseAsset } from 'utils/assets'
import { formatValue, truncate } from 'utils/formatters' import { formatValue, truncate } from 'utils/formatters'
import { getWalletBalances } from 'utils/api'
export default function ConnectedButton() { export default function ConnectedButton() {
// --------------- // ---------------
@ -30,7 +32,6 @@ export default function ConnectedButton() {
const { disconnect: terminate } = useWalletManager() const { disconnect: terminate } = useWalletManager()
const address = useStore((s) => s.client?.recentWallet.account?.address) const address = useStore((s) => s.client?.recentWallet.account?.address)
const network = useStore((s) => s.client?.recentWallet.network) const network = useStore((s) => s.client?.recentWallet.network)
const name = useStore((s) => s.name)
const baseAsset = getBaseAsset() const baseAsset = getBaseAsset()
const { data, isLoading } = useSWR(address, getWalletBalances) const { data, isLoading } = useSWR(address, getWalletBalances)
@ -73,7 +74,7 @@ export default function ConnectedButton() {
<div className={'relative'}> <div className={'relative'}>
{network?.chainId !== ChainInfoID.Osmosis1 && ( {network?.chainId !== ChainInfoID.Osmosis1 && (
<Text <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' size='3xs'
uppercase uppercase
> >
@ -81,24 +82,19 @@ export default function ConnectedButton() {
</Text> </Text>
)} )}
<button <Button
className={classNames( variant='solid'
'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 ', color='tertiary'
'hover:border-white hover:bg-secondary-dark', icon={<Osmo />}
'active:border-white active:bg-secondary-dark-10',
)}
onClick={() => { onClick={() => {
setShowDetails(!showDetails) setShowDetails(!showDetails)
}} }}
> >
<span className='flex h-4 w-4 items-center justify-center'> <span>{truncate(address, [2, 4])}</span>
<Osmo />
</span>
<span className='ml-2'>{name ? name : truncate(address, [2, 4])}</span>
<div <div
className={classNames( className={classNames(
'number relative ml-2 flex h-full items-center pl-2', 'relative ml-2 flex h-full items-center pl-2 number',
'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', '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 ? ( {isLoading ? (
@ -107,18 +103,18 @@ export default function ConnectedButton() {
`${formatValue(walletAmount, { suffix: baseAsset.symbol })}` `${formatValue(walletAmount, { suffix: baseAsset.symbol })}`
)} )}
</div> </div>
</button> </Button>
<Overlay className='right-0 mt-2' show={showDetails} setShow={setShowDetails}> <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-0 mb-4 flex w-full flex-nowrap items-start'>
<div className='flex w-auto flex-1'> <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} {baseAsset.denom}
</div> </div>
<div className='flex-0 flex flex-wrap justify-end'> <div className='flex-0 flex flex-wrap justify-end'>
<FormattedNumber <FormattedNumber
animate animate
className='flex items-end text-2xl text-secondary-dark' className='flex items-end text-2xl '
amount={walletAmount} amount={walletAmount}
/> />
</div> </div>
@ -128,44 +124,44 @@ export default function ConnectedButton() {
</div> </div>
</div> </div>
<div className='flex w-full flex-wrap'> <div className='flex w-full flex-wrap'>
<Text uppercase className='mb-1 break-all text-secondary-dark/80'> <Text uppercase className='/80 mb-1 break-all'>
{name ? `${name}` : 'Your Address'} {'Your Address'}
</Text> </Text>
<Text <Text size='sm' className='mb-1 hidden break-all font-bold md:block'>
size='sm'
className='mb-1 hidden break-all font-bold text-secondary-dark md:block'
>
{address} {address}
</Text> </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])} {truncate(address, [14, 14])}
</Text> </Text>
<div className='flex w-full pt-1'> <div className='flex w-full pt-1'>
<button <Button
className='mr-10 flex w-auto appearance-none items-center border-none py-2 text-secondary-dark opacity-70 hover:opacity-100' icon={<Copy />}
variant='transparent'
className='mr-10 flex w-auto py-2'
color='quaternary'
onClick={setCopied} onClick={setCopied}
> >
<span className='mr-1 w-4'>
<Copy />
</span>
{isCopied ? ( {isCopied ? (
<Text size='sm'> <Text size='sm'>
Copied <Check /> Copied{' '}
<span className='ml-1 w-4'>
<Check />
</span>
</Text> </Text>
) : ( ) : (
<Text size='sm'>Copy Address</Text> <Text size='sm'>Copy Address</Text>
)} )}
</button> </Button>
<button <Button
className='flex w-auto appearance-none items-center border-none py-2 text-secondary-dark opacity-70 hover:opacity-100' icon={<ExternalLink />}
variant='transparent'
className='flex w-auto py-2'
color='quaternary'
onClick={viewOnFinder} onClick={viewOnFinder}
> >
<span className='mr-1 w-4'>
<ExternalLink />
</span>
<Text size='sm'>View on {explorerName}</Text> <Text size='sm'>View on {explorerName}</Text>
</button> </Button>
</div> </div>
</div> </div>
</div> </div>

View File

@ -6,42 +6,46 @@ import {
useWalletManager, useWalletManager,
WalletConnectionStatus, WalletConnectionStatus,
} from '@marsprotocol/wallet-connector' } from '@marsprotocol/wallet-connector'
import { useEffect, useState } from 'react'
import { useRouter } from 'next/navigation' import { useRouter } from 'next/navigation'
import { useEffect } from 'react'
import ConnectButton from 'components/Wallet/ConnectButton' import ConnectButton from 'components/Wallet/ConnectButton'
import ConnectedButton from 'components/Wallet/ConnectedButton' import ConnectedButton from 'components/Wallet/ConnectedButton'
import useParams from 'hooks/useParams' import useParams from 'hooks/useParams'
import useStore from 'store' import useStore from 'store'
import { getCreditAccounts } from 'utils/api'
export default function Wallet() { export default function Wallet() {
const router = useRouter() const router = useRouter()
const params = useParams() const params = useParams()
const { status } = useWalletManager() const { status } = useWalletManager()
const [isConnected, setIsConnected] = useState(false)
const { recentWallet, simulate, sign, broadcast } = useWallet() const { recentWallet, simulate, sign, broadcast } = useWallet()
const client = useStore((s) => s.client) const client = useStore((s) => s.client)
const address = useStore((s) => s.address)
useEffect(() => { useEffect(() => {
const connectedStatus = status === WalletConnectionStatus.Connected const isConnected = status === WalletConnectionStatus.Connected
if (connectedStatus === isConnected) return
setIsConnected(connectedStatus)
}, [status, isConnected])
useEffect(() => { useStore.setState({ status })
if (!isConnected && !params.wallet) { useStore.setState(
router.push('/') isConnected
return ? {
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 fetchCreditAccounts()
if (!address || address === params.wallet) return
router.push(`/wallets/${client.recentWallet.account.address}`)
}, [client, params, isConnected])
useEffect(() => {
if (!recentWallet) return
if (!client) { if (!client) {
const getCosmWasmClient = async () => { const getCosmWasmClient = async () => {
const cosmClient = await getClient(recentWallet.network.rpc) const cosmClient = await getClient(recentWallet.network.rpc)
@ -57,9 +61,11 @@ export default function Wallet() {
} }
getCosmWasmClient() getCosmWasmClient()
}
return if (!address || address === params.wallet) return
} router.push(`/wallets/${address}`)
}, [simulate, sign, recentWallet, broadcast]) }, [address, broadcast, client, params, recentWallet, router, simulate, sign, status])
return isConnected ? <ConnectedButton /> : <ConnectButton status={status} />
return address ? <ConnectedButton /> : <ConnectButton status={status} />
} }

View File

@ -3,25 +3,27 @@
import { WalletManagerProvider } from '@marsprotocol/wallet-connector' import { WalletManagerProvider } from '@marsprotocol/wallet-connector'
import { FC } from 'react' import { FC } from 'react'
import { Button } from 'components/Button'
import { CircularProgress } from 'components/CircularProgress' 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 = { type Props = {
children?: React.ReactNode children?: React.ReactNode
} }
export const WalletConnectProvider: FC<Props> = ({ children }) => { 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) console.error(ENV_MISSING_MESSAGE)
return null return null
} }
const chainInfoOverrides = { const chainInfoOverrides = {
rpc: URL_RPC, rpc: ENV.URL_RPC,
rest: URL_REST, rest: ENV.URL_REST,
chainID: CHAIN_ID, chainID: ENV.CHAIN_ID,
} }
const enabledWallets: string[] = WALLETS const enabledWallets: string[] = ENV.WALLETS
return ( return (
<WalletManagerProvider <WalletManagerProvider
@ -30,6 +32,21 @@ export const WalletConnectProvider: FC<Props> = ({ children }) => {
defaultChainId={chainInfoOverrides.chainID} defaultChainId={chainInfoOverrides.chainID}
enabledWallets={enabledWallets} enabledWallets={enabledWallets}
persistent 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={() => ( renderLoader={() => (
<div> <div>
<CircularProgress size={30} /> <CircularProgress size={30} />

View 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>
)
}

View 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} />
}

View 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} />
}

View 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} />
}

View 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>
)
}

View File

@ -1,18 +1,46 @@
export const ADDRESS_ACCOUNT_NFT = process.env.NEXT_PUBLIC_ACCOUNT_NFT interface EnvironmentVariables {
export const ADDRESS_CREDIT_MANAGER = process.env.NEXT_PUBLIC_CREDIT_MANAGER ADDRESS_ACCOUNT_NFT: string | undefined
export const ADDRESS_INCENTIVES = process.env.NEXT_PUBLIC_INCENTIVES ADDRESS_CREDIT_MANAGER: string | undefined
export const ADDRESS_ORACLE = process.env.NEXT_PUBLIC_ORACLE ADDRESS_INCENTIVES: string | undefined
export const ADDRESS_RED_BANK = process.env.NEXT_PUBLIC_RED_BANK ADDRESS_ORACLE: string | undefined
export const ADDRESS_SWAPPER = process.env.NEXT_PUBLIC_SWAPPER ADDRESS_RED_BANK: string | undefined
export const ADDRESS_ZAPPER = process.env.NEXT_PUBLIC_ZAPPER 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 ENV: EnvironmentVariables = {
export const NETWORK = process.env.NEXT_PUBLIC_NETWORK ADDRESS_ACCOUNT_NFT: process.env.NEXT_PUBLIC_ACCOUNT_NFT,
export const IS_TESTNET = NETWORK !== 'mainnet' ADDRESS_CREDIT_MANAGER: process.env.NEXT_PUBLIC_CREDIT_MANAGER,
export const URL_GQL = process.env.NEXT_PUBLIC_GQL ADDRESS_INCENTIVES: process.env.NEXT_PUBLIC_INCENTIVES,
export const URL_REST = process.env.NEXT_PUBLIC_REST ADDRESS_ORACLE: process.env.NEXT_PUBLIC_ORACLE,
export const URL_RPC = process.env.NEXT_PUBLIC_RPC ADDRESS_RED_BANK: process.env.NEXT_PUBLIC_RED_BANK,
export const URL_API = process.env.NEXT_PUBLIC_API ADDRESS_SWAPPER: process.env.NEXT_PUBLIC_SWAPPER,
export const WALLETS = process.env.NEXT_PUBLIC_WALLETS?.split(',') ?? [] 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(', ')}`
}

View File

@ -3,7 +3,7 @@ import { useQuery } from '@tanstack/react-query'
import request, { gql } from 'graphql-request' import request, { gql } from 'graphql-request'
import { useMemo } from 'react' import { useMemo } from 'react'
import { URL_GQL } from 'constants/env' import { ENV } from 'constants/env'
import useStore from 'store' import useStore from 'store'
import { queryKeys } from 'types/query-keys-factory' import { queryKeys } from 'types/query-keys-factory'
@ -20,7 +20,7 @@ export const useAllBalances = () => {
queryKeys.allBalances(address ?? ''), queryKeys.allBalances(address ?? ''),
async () => { async () => {
return await request( return await request(
URL_GQL!, ENV.URL_GQL!,
gql` gql`
query UserBalanceQuery { query UserBalanceQuery {
balance: bank { balance: bank {

View File

@ -1,6 +1,6 @@
import { useQuery } from '@tanstack/react-query' import { useQuery } from '@tanstack/react-query'
import { ADDRESS_CREDIT_MANAGER } from 'constants/env' import { ENV } from 'constants/env'
import useStore from 'store' import useStore from 'store'
import { queryKeys } from 'types/query-keys-factory' import { queryKeys } from 'types/query-keys-factory'
@ -12,7 +12,7 @@ const queryMsg = {
export const useAllowedCoins = () => { export const useAllowedCoins = () => {
const client = useStore((s) => s.signingClient) const client = useStore((s) => s.signingClient)
const creditManagerAddress = ADDRESS_CREDIT_MANAGER const creditManagerAddress = ENV.ADDRESS_CREDIT_MANAGER
const result = useQuery<Result>( const result = useQuery<Result>(
queryKeys.allowedCoins(), queryKeys.allowedCoins(),

View File

@ -2,7 +2,7 @@ import { Coin } from '@cosmjs/stargate'
import { useQuery } from '@tanstack/react-query' import { useQuery } from '@tanstack/react-query'
import { useMemo } from 'react' import { useMemo } from 'react'
import { ADDRESS_CREDIT_MANAGER } from 'constants/env' import { ENV } from 'constants/env'
import useStore from 'store' import useStore from 'store'
import { queryKeys } from 'types/query-keys-factory' import { queryKeys } from 'types/query-keys-factory'
@ -27,7 +27,7 @@ interface Result {
export const useCreditAccountPositions = (accountId: string) => { export const useCreditAccountPositions = (accountId: string) => {
const address = useStore((s) => s.address) const address = useStore((s) => s.address)
const client = useStore((s) => s.signingClient) const client = useStore((s) => s.signingClient)
const creditManagerAddress = ADDRESS_CREDIT_MANAGER const creditManagerAddress = ENV.ADDRESS_CREDIT_MANAGER
const result = useQuery<Result>( const result = useQuery<Result>(
queryKeys.creditAccountsPositions(accountId), queryKeys.creditAccountsPositions(accountId),

View File

@ -3,7 +3,7 @@
import { useQuery } from '@tanstack/react-query' import { useQuery } from '@tanstack/react-query'
import { useMemo } from 'react' import { useMemo } from 'react'
import { ADDRESS_ACCOUNT_NFT } from 'constants/env' import { ENV } from 'constants/env'
import useStore from 'store' import useStore from 'store'
import { queryKeys } from 'types/query-keys-factory' import { queryKeys } from 'types/query-keys-factory'
@ -15,7 +15,7 @@ export const useCreditAccounts = () => {
const address = useStore((s) => s.address) const address = useStore((s) => s.address)
const client = useStore((s) => s.signingClient) const client = useStore((s) => s.signingClient)
const selectedAccount = useStore((s) => s.selectedAccount) const selectedAccount = useStore((s) => s.selectedAccount)
const accountNftAddress = ADDRESS_ACCOUNT_NFT const accountNftAddress = ENV.ADDRESS_ACCOUNT_NFT
const setSelectedAccount = (account: string) => { const setSelectedAccount = (account: string) => {
useStore.setState({ selectedAccount: account }) useStore.setState({ selectedAccount: account })
} }

View File

@ -3,7 +3,7 @@ import { useQuery } from '@tanstack/react-query'
import request, { gql } from 'graphql-request' import request, { gql } from 'graphql-request'
import { useMemo } from 'react' 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' import { queryKeys } from 'types/query-keys-factory'
interface Result { interface Result {
@ -13,12 +13,12 @@ interface Result {
} }
export const useRedbankBalances = () => { export const useRedbankBalances = () => {
const redBankAddress = ADDRESS_RED_BANK const redBankAddress = ENV.ADDRESS_RED_BANK
const result = useQuery<Result>( const result = useQuery<Result>(
queryKeys.redbankBalances(), queryKeys.redbankBalances(),
async () => { async () => {
return await request( return await request(
URL_GQL!, ENV.URL_GQL!,
gql` gql`
query RedbankBalances { query RedbankBalances {
bank { bank {

View File

@ -2,11 +2,15 @@ import { useQuery } from '@tanstack/react-query'
import { gql, request } from 'graphql-request' import { gql, request } from 'graphql-request'
import { useMemo } from 'react' 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 { queryKeys } from 'types/query-keys-factory'
import { getMarketAssets } from 'utils/assets' 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( return request(
hiveUrl, hiveUrl,
gql` gql`
@ -32,7 +36,7 @@ export const useTokenPrices = () => {
const marketAssets = getMarketAssets() const marketAssets = getMarketAssets()
const result = useQuery<TokenPricesResult>( const result = useQuery<TokenPricesResult>(
queryKeys.tokenPrices(), queryKeys.tokenPrices(),
async () => await fetchTokenPrices(URL_GQL!, marketAssets, ADDRESS_ORACLE || ''), async () => await fetchTokenPrices(ENV.URL_GQL!, marketAssets, ENV.ADDRESS_ORACLE || ''),
{ {
refetchInterval: 30000, refetchInterval: 30000,
staleTime: Infinity, staleTime: Infinity,

View File

@ -1,15 +1,15 @@
import { NextApiRequest, NextApiResponse } from 'next' 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) { 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) return res.status(404).json(ENV_MISSING_MESSAGE)
} }
const accountId = req.query.id 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) { if (account) {
return res.status(200).json([{ denom: 'uosmo', amount: '123876' }]) return res.status(200).json([{ denom: 'uosmo', amount: '123876' }])

View File

@ -1,15 +1,15 @@
import { NextApiRequest, NextApiResponse } from 'next' 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) { 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) return res.status(404).json(ENV_MISSING_MESSAGE)
} }
const accountId = req.query.id 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) { if (account) {
return res.status(200).json(account.deposits) return res.status(200).json(account.deposits)

View File

@ -1,18 +1,18 @@
import { CosmWasmClient } from '@cosmjs/cosmwasm-stargate' import { CosmWasmClient } from '@cosmjs/cosmwasm-stargate'
import { NextApiRequest, NextApiResponse } from 'next' 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) { 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) return res.status(404).json(ENV_MISSING_MESSAGE)
} }
const accountId = req.query.id 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: { positions: {
account_id: accountId, account_id: accountId,
}, },

View File

@ -2,20 +2,20 @@ import { Coin } from '@cosmjs/stargate'
import { gql, request as gqlRequest } from 'graphql-request' import { gql, request as gqlRequest } from 'graphql-request'
import { NextApiRequest, NextApiResponse } from 'next' 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) { 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) return res.status(404).json(ENV_MISSING_MESSAGE)
} }
const result = await gqlRequest<Result>( const result = await gqlRequest<Result>(
URL_GQL, ENV.URL_GQL,
gql` gql`
query RedbankBalances { query RedbankBalances {
bank { bank {
balance( balance(
address: "${ADDRESS_RED_BANK}" address: "${ENV.ADDRESS_RED_BANK}"
) { ) {
amount amount
denom denom

View File

@ -1,19 +1,19 @@
import { NextApiRequest, NextApiResponse } from 'next'
import { Coin } from '@cosmjs/stargate' import { Coin } from '@cosmjs/stargate'
import BigNumber from 'bignumber.js' 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' import { getMarketAssets } from 'utils/assets'
export default async function handler(req: NextApiRequest, res: NextApiResponse) { export default async function handler(req: NextApiRequest, res: NextApiResponse) {
if (!URL_API) { if (!ENV.URL_API) {
return res.status(404).json(ENV_MISSING_MESSAGE) return res.status(404).json(ENV_MISSING_MESSAGE)
} }
const marketAssets = getMarketAssets() const marketAssets = getMarketAssets()
const $liquidity = fetch(`${URL_API}/markets/liquidity`) const $liquidity = fetch(`${ENV.URL_API}/markets/liquidity`)
const $markets = fetch(`${URL_API}/markets`) const $markets = fetch(`${ENV.URL_API}/markets`)
const $prices = fetch(`${URL_API}/prices`) const $prices = fetch(`${ENV.URL_API}/prices`)
const borrow: BorrowAsset[] = await Promise.all([$liquidity, $markets, $prices]).then( const borrow: BorrowAsset[] = await Promise.all([$liquidity, $markets, $prices]).then(
async ([$liquidity, $markets, $prices]) => { async ([$liquidity, $markets, $prices]) => {

View File

@ -1,22 +1,22 @@
import { NextApiRequest, NextApiResponse } from 'next' import { NextApiRequest, NextApiResponse } from 'next'
import { gql, request as gqlRequest } from 'graphql-request' 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' import { getContractQuery } from 'utils/query'
export default async function handler(req: NextApiRequest, res: NextApiResponse) { 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) 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 = '' let query = ''
markets.forEach((asset: any) => { markets.forEach((asset: any) => {
query += getContractQuery( query += getContractQuery(
asset.denom, asset.denom,
ADDRESS_RED_BANK || '', ENV.ADDRESS_RED_BANK || '',
` `
{ {
underlying_debt_amount: { underlying_debt_amount: {
@ -28,7 +28,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
}) })
const result = await gqlRequest<DebtsQuery>( const result = await gqlRequest<DebtsQuery>(
URL_GQL, ENV.URL_GQL,
gql` gql`
query RedbankBalances { query RedbankBalances {
debts: wasm { debts: wasm {

View File

@ -1,22 +1,22 @@
import { NextApiRequest, NextApiResponse } from 'next' import { NextApiRequest, NextApiResponse } from 'next'
import { gql, request as gqlRequest } from 'graphql-request' 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' import { getContractQuery } from 'utils/query'
export default async function handler(req: NextApiRequest, res: NextApiResponse) { 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) 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 = '' let query = ''
markets.forEach((asset: any) => { markets.forEach((asset: any) => {
query += getContractQuery( query += getContractQuery(
asset.denom, asset.denom,
ADDRESS_RED_BANK || '', ENV.ADDRESS_RED_BANK || '',
` `
{ {
underlying_liquidity_amount: { underlying_liquidity_amount: {
@ -28,7 +28,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
}) })
const result = await gqlRequest<DepositsQuery>( const result = await gqlRequest<DepositsQuery>(
URL_GQL, ENV.URL_GQL,
gql` gql`
query RedbankBalances { query RedbankBalances {
deposits: wasm { deposits: wasm {

View File

@ -1,11 +1,11 @@
import { gql, request as gqlRequest } from 'graphql-request' import { gql, request as gqlRequest } from 'graphql-request'
import { NextApiRequest, NextApiResponse } from 'next' 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' import { getMarketAssets } from 'utils/assets'
export default async function handler(req: NextApiRequest, res: NextApiResponse) { 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) 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( const marketQueries = marketAssets.map(
(asset: Asset) => (asset: Asset) =>
`${asset.denom}: contractQuery( `${asset.denom}: contractQuery(
contractAddress: "${ADDRESS_RED_BANK}" contractAddress: "${ENV.ADDRESS_RED_BANK}"
query: { market: { denom: "${asset.denom}" } } query: { market: { denom: "${asset.denom}" } }
)`, )`,
) )
const result = await gqlRequest<RedBankData>( const result = await gqlRequest<RedBankData>(
URL_GQL, ENV.URL_GQL,
gql` gql`
query RedbankQuery { query RedbankQuery {
rbwasmkey: wasm { rbwasmkey: wasm {

View File

@ -1,15 +1,15 @@
import { NextApiRequest, NextApiResponse } from 'next' import { NextApiRequest, NextApiResponse } from 'next'
import { Coin } from '@cosmjs/stargate' 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) { export default async function handler(req: NextApiRequest, res: NextApiResponse) {
if (!URL_API) { if (!ENV.URL_API) {
return res.status(404).json(ENV_MISSING_MESSAGE) return res.status(404).json(ENV_MISSING_MESSAGE)
} }
const $deposits = fetch(`${URL_API}/markets/deposits`) const $deposits = fetch(`${ENV.URL_API}/markets/deposits`)
const $debts = fetch(`${URL_API}/markets/debts`) const $debts = fetch(`${ENV.URL_API}/markets/debts`)
const liquidity: Coin[] = await Promise.all([$deposits, $debts]).then( const liquidity: Coin[] = await Promise.all([$deposits, $debts]).then(
async ([$deposits, $debts]) => { async ([$deposits, $debts]) => {

View File

@ -2,24 +2,24 @@ import { gql, request as gqlRequest } from 'graphql-request'
import { NextApiRequest, NextApiResponse } from 'next' import { NextApiRequest, NextApiResponse } from 'next'
import { Coin } from '@cosmjs/stargate' 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' import { getMarketAssets } from 'utils/assets'
export default async function handler(req: NextApiRequest, res: NextApiResponse) { 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) return res.status(404).json(ENV_MISSING_MESSAGE)
} }
const marketAssets = getMarketAssets() const marketAssets = getMarketAssets()
const result = await gqlRequest<TokenPricesResult>( const result = await gqlRequest<TokenPricesResult>(
URL_GQL, ENV.URL_GQL,
gql` gql`
query PriceOracle { query PriceOracle {
prices: wasm { prices: wasm {
${marketAssets.map((asset) => { ${marketAssets.map((asset) => {
return `${asset.symbol}: contractQuery( return `${asset.symbol}: contractQuery(
contractAddress: "${ADDRESS_ORACLE}" contractAddress: "${ENV.ADDRESS_ORACLE}"
query: { query: {
price: { price: {
denom: "${asset.denom}" denom: "${asset.denom}"

View File

@ -1,15 +1,15 @@
import { CosmWasmClient } from '@cosmjs/cosmwasm-stargate' import { CosmWasmClient } from '@cosmjs/cosmwasm-stargate'
import { NextApiRequest, NextApiResponse } from 'next' 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) { 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) 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 }, vaults_info: { limit: 5, start_after: undefined },
}) })

View File

@ -1,18 +1,18 @@
import { CosmWasmClient } from '@cosmjs/cosmwasm-stargate' import { CosmWasmClient } from '@cosmjs/cosmwasm-stargate'
import { NextApiRequest, NextApiResponse } from 'next' 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) { 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) return res.status(404).json(ENV_MISSING_MESSAGE)
} }
const address = req.query.address 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: { tokens: {
owner: address, owner: address,
}, },

View File

@ -1,16 +1,16 @@
import { NextApiRequest, NextApiResponse } from 'next' 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) { export default async function handler(req: NextApiRequest, res: NextApiResponse) {
if (!URL_REST) { if (!ENV.URL_REST) {
return res.status(404).json(ENV_MISSING_MESSAGE) return res.status(404).json(ENV_MISSING_MESSAGE)
} }
const address = req.query.address const address = req.query.address
const uri = '/cosmos/bank/v1beta1/balances/' 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) { if (response.ok) {
const data = await response.json() const data = await response.json()

Some files were not shown because too many files have changed in this diff Show More