From cbb0700455410a373c33bed1c6926046e3507f2c Mon Sep 17 00:00:00 2001 From: Bob van der Helm <34470358+bobthebuidlr@users.noreply.github.com> Date: Wed, 1 Mar 2023 13:49:57 +0100 Subject: [PATCH] update layout for modal, implement borrow tables (#105) --- src/app/layout.tsx | 2 + .../accounts/[account]/borrow/loading.tsx | 5 - .../accounts/[account]/borrow/page.tsx | 5 +- src/components/Account/AccountDetails.tsx | 3 - src/components/Account/AccountStatus.tsx | 12 +- src/components/Account/ConfirmModal.tsx | 2 +- src/components/Account/FundAccountModal.tsx | 2 +- src/components/Account/RiskChart.tsx | 14 +- src/components/Account/WithdrawModal.tsx | 20 +- src/components/AmountAndValue.tsx | 27 ++ src/components/Borrow/AssetExpanded.tsx | 28 +- src/components/Borrow/AssetRow.tsx | 2 +- src/components/Borrow/BorrowTable.tsx | 58 +++- src/components/BorrowCapacity.tsx | 9 +- src/components/BorrowModal.tsx | 321 +----------------- src/components/Card.tsx | 8 +- src/components/FetchPrices.tsx | 15 + src/components/FormattedNumber.tsx | 106 +++--- src/components/Modal.tsx | 38 +-- src/components/Modals.tsx | 3 + src/components/RepayModal.tsx | 8 +- src/components/Text.tsx | 1 + src/components/TitleAndSubCell.tsx | 20 ++ src/components/Wallet/ConnectedButton.tsx | 2 +- src/hooks/data/useBalances.tsx | 36 -- src/pages/api/accounts/[id]/debts.ts | 2 +- src/store/slices/common.ts | 29 ++ src/styles/globals.scss | 30 +- src/types/interfaces/asset.d.ts | 5 +- src/utils/balances.ts | 43 --- src/utils/formatters.ts | 86 +++-- 31 files changed, 357 insertions(+), 585 deletions(-) delete mode 100644 src/app/wallets/[wallet]/accounts/[account]/borrow/loading.tsx create mode 100644 src/components/AmountAndValue.tsx create mode 100644 src/components/FetchPrices.tsx create mode 100644 src/components/TitleAndSubCell.tsx delete mode 100644 src/hooks/data/useBalances.tsx delete mode 100644 src/utils/balances.ts diff --git a/src/app/layout.tsx b/src/app/layout.tsx index fb5afbfc..9bec7c3c 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -1,4 +1,5 @@ import Background from 'components/Background' +import FetchPrices from 'components/FetchPrices' import { Modals } from 'components/Modals' import DesktopNavigation from 'components/Navigation/DesktopNavigation' import Toaster from 'components/Toaster' @@ -19,6 +20,7 @@ export default function RootLayout({ children }: { children: React.ReactNode }) +
{children}
diff --git a/src/app/wallets/[wallet]/accounts/[account]/borrow/loading.tsx b/src/app/wallets/[wallet]/accounts/[account]/borrow/loading.tsx deleted file mode 100644 index 955eca17..00000000 --- a/src/app/wallets/[wallet]/accounts/[account]/borrow/loading.tsx +++ /dev/null @@ -1,5 +0,0 @@ -import Loading from 'components/Loading' - -export default function page() { - return -} diff --git a/src/app/wallets/[wallet]/accounts/[account]/borrow/page.tsx b/src/app/wallets/[wallet]/accounts/[account]/borrow/page.tsx index c0b1ba88..add834c1 100644 --- a/src/app/wallets/[wallet]/accounts/[account]/borrow/page.tsx +++ b/src/app/wallets/[wallet]/accounts/[account]/borrow/page.tsx @@ -17,10 +17,7 @@ export default async function page({ params }: { params: PageParams }) { if (debt) { prev.active.push({ ...borrow, - debt: { - amount: '100000', - value: '12389478321', - }, + debt: debt.amount, }) } else { prev.available.push(borrow) diff --git a/src/components/Account/AccountDetails.tsx b/src/components/Account/AccountDetails.tsx index dd27b3d8..d77a9531 100644 --- a/src/components/Account/AccountDetails.tsx +++ b/src/components/Account/AccountDetails.tsx @@ -10,7 +10,6 @@ import { ArrowRightLine, ChevronDown, ChevronLeft } from 'components/Icons' import { LabelValuePair } from 'components/LabelValuePair' import { PositionsList } from 'components/PositionsList' import { useAccountStats } from 'hooks/data/useAccountStats' -import { useBalances } from 'hooks/data/useBalances' import { convertFromGwei } from 'utils/formatters' import { createRiskData } from 'utils/risk' import useStore from 'store' @@ -23,7 +22,6 @@ export const AccountDetails = () => { const marketAssets = getMarketAssets() const baseAsset = getBaseAsset() - const balances = useBalances() const accountStats = useAccountStats() const [showManageMenu, setShowManageMenu] = useState(false) @@ -118,7 +116,6 @@ export const AccountDetails = () => { /> {riskData && } - ) } diff --git a/src/components/Account/AccountStatus.tsx b/src/components/Account/AccountStatus.tsx index b29771db..11bd17b9 100644 --- a/src/components/Account/AccountStatus.tsx +++ b/src/components/Account/AccountStatus.tsx @@ -10,7 +10,7 @@ import { Text } from 'components/Text' import { useAccountStats } from 'hooks/data/useAccountStats' import { useCreditAccounts } from 'hooks/queries/useCreditAccounts' import { getBaseAsset } from 'utils/assets' -import { formatValue } from 'utils/formatters' +import { formatLeverage, formatValue } from 'utils/formatters' export const AccountStatus = () => { const baseAsset = getBaseAsset() @@ -41,7 +41,7 @@ export const AccountStatus = () => { .dividedBy(10 ** baseAsset.decimals) .toNumber()} animate - prefix='$: ' + options={{ prefix: '$: ' }} /> @@ -50,10 +50,9 @@ export const AccountStatus = () => { label='Lvg' tooltip={ - Current Leverage:{' '} - {formatValue(accountStats.currentLeverage, 0, 2, true, false, 'x')} + Current Leverage: {formatLeverage(accountStats.currentLeverage)}
- Max Leverage: {formatValue(accountStats.maxLeverage, 0, 2, true, false, 'x')} + Max Leverage: {formatLeverage(accountStats.maxLeverage)}
} /> @@ -63,7 +62,8 @@ export const AccountStatus = () => { label='Risk' tooltip={ - Current Risk: {formatValue(accountStats.risk * 100, 0, 2, true, false, '%')} + Current Risk:{' '} + {formatValue(accountStats.risk * 100, { minDecimals: 0, suffix: '%' })} } /> diff --git a/src/components/Account/ConfirmModal.tsx b/src/components/Account/ConfirmModal.tsx index e33003ca..ed5a8211 100644 --- a/src/components/Account/ConfirmModal.tsx +++ b/src/components/Account/ConfirmModal.tsx @@ -13,7 +13,7 @@ export const ConfirmModal = () => { const deleteOpen = useStore((s) => s.deleteAccountModal) return ( - +
{ const percentageValue = isNaN(amount) ? 0 : (amount * 100) / walletAmount return ( - +
{balanceIsLoading && (
diff --git a/src/components/Account/RiskChart.tsx b/src/components/Account/RiskChart.tsx index 13092e53..89007d4e 100644 --- a/src/components/Account/RiskChart.tsx +++ b/src/components/Account/RiskChart.tsx @@ -25,11 +25,13 @@ export const RiskChart = ({ data }: RiskChartProps) => {
@@ -77,7 +79,9 @@ export const RiskChart = ({ data }: RiskChartProps) => { return (
{moment(label).format('MM-DD-YYYY')} - Risk: {formatValue(risk, 0, 0, true, false, '%')} + + Risk: {formatValue(risk, { minDecimals: 0, maxDecimals: 0, suffix: '%' })} +
) } diff --git a/src/components/Account/WithdrawModal.tsx b/src/components/Account/WithdrawModal.tsx index 70bcdbf0..e1b5724e 100644 --- a/src/components/Account/WithdrawModal.tsx +++ b/src/components/Account/WithdrawModal.tsx @@ -5,7 +5,7 @@ import React, { useEffect, useMemo, useState } from 'react' import { toast } from 'react-toastify' import { BorrowCapacity } from 'components/BorrowCapacity' -import { convertFromGwei, formatValue } from 'utils/formatters' +import { convertFromGwei, formatLeverage, formatValue } from 'utils/formatters' import { getTokenDecimals, getTokenSymbol } from 'utils/tokens' import { CircularProgress } from 'components/CircularProgress' import { Button } from 'components/Button' @@ -17,7 +17,6 @@ import { LabelValuePair } from 'components/LabelValuePair' import { Modal } from 'components/Modal' import { PositionsList } from 'components/PositionsList' import { useAccountStats } from 'hooks/data/useAccountStats' -import { useBalances } from 'hooks/data/useBalances' import { useCalculateMaxWithdrawAmount } from 'hooks/data/useCalculateMaxWithdrawAmount' import { useWithdrawFunds } from 'hooks/mutations/useWithdrawFunds' import { useCreditAccountPositions } from 'hooks/queries/useCreditAccountPositions' @@ -46,7 +45,6 @@ export const WithdrawModal = () => { // EXTERNAL HOOKS // --------------- const { data: tokenPrices } = useTokenPrices() - const balances = useBalances() const selectedTokenSymbol = getTokenSymbol(selectedToken, marketAssets) const selectedTokenDecimals = getTokenDecimals(selectedToken, marketAssets) @@ -153,7 +151,7 @@ export const WithdrawModal = () => { } return ( - +
{isLoading && (
@@ -205,7 +203,7 @@ export const WithdrawModal = () => {
- Available: {formatValue(maxWithdrawAmount, 0, 4, true, false, false, false, false)} + Available: {formatValue(maxWithdrawAmount, { minDecimals: 0, maxDecimals: 4 })} { amount={BigNumber(accountStats.netWorth) .dividedBy(10 ** baseAsset.decimals) .toNumber()} - prefix='$: ' + options={{ prefix: '$: ' }} animate /> @@ -275,11 +273,9 @@ export const WithdrawModal = () => { label='Lvg' tooltip={ - Current Leverage:{' '} - {formatValue(accountStats.currentLeverage, 0, 2, true, false, 'x')} + Current Leverage: {formatLeverage(accountStats.currentLeverage)}
- Max Leverage:{' '} - {formatValue(accountStats.maxLeverage, 0, 2, true, false, 'x')} + Max Leverage: {formatLeverage(accountStats.maxLeverage)}
} /> @@ -288,7 +284,8 @@ export const WithdrawModal = () => { label='Risk' tooltip={ - Current Risk: {formatValue(accountStats.risk * 100, 0, 2, true, false, '%')} + Current Risk:{' '} + {formatValue(accountStats.risk * 100, { minDecimals: 0, suffix: '%' })} } /> @@ -331,7 +328,6 @@ export const WithdrawModal = () => { }} />
-
diff --git a/src/components/AmountAndValue.tsx b/src/components/AmountAndValue.tsx new file mode 100644 index 00000000..aad87fe3 --- /dev/null +++ b/src/components/AmountAndValue.tsx @@ -0,0 +1,27 @@ +import { FormattedNumber } from './FormattedNumber' +import TitleAndSubCell from './TitleAndSubCell' + +interface Props { + asset: Asset + amount: string +} + +export default function AmountAndValue(props: Props) { + return ( + + } + sub={ + + } + className='justify-end' + /> + ) +} diff --git a/src/components/Borrow/AssetExpanded.tsx b/src/components/Borrow/AssetExpanded.tsx index baae1cb0..213b281f 100644 --- a/src/components/Borrow/AssetExpanded.tsx +++ b/src/components/Borrow/AssetExpanded.tsx @@ -1,11 +1,14 @@ +'use client' + import React from 'react' import { Row } from '@tanstack/react-table' import { getMarketAssets } from 'utils/assets' import { Button } from 'components/Button' +import useStore from 'store' type AssetRowProps = { - row: Row + row: Row onBorrowClick: () => void onRepayClick: () => void resetExpanded: (defaultState?: boolean | undefined) => void @@ -14,9 +17,22 @@ type AssetRowProps = { export default function AssetExpanded(props: AssetRowProps) { const marketAssets = getMarketAssets() const asset = marketAssets.find((asset) => asset.denom === props.row.original.denom) + let isActive: boolean = false + + if ((props.row.original as BorrowAssetActive)?.debt) { + isActive = true + } if (!asset) return null + function borrowHandler() { + useStore.setState({ borrowModal: true }) + } + + function repayHandler() { + useStore.setState({ repayModal: true }) + } + return ( - +
-
diff --git a/src/components/Borrow/AssetRow.tsx b/src/components/Borrow/AssetRow.tsx index 49707db4..808d89ad 100644 --- a/src/components/Borrow/AssetRow.tsx +++ b/src/components/Borrow/AssetRow.tsx @@ -4,7 +4,7 @@ import { flexRender, Row } from '@tanstack/react-table' import { getMarketAssets } from 'utils/assets' type AssetRowProps = { - row: Row + row: Row resetExpanded: (defaultState?: boolean | undefined) => void } diff --git a/src/components/Borrow/BorrowTable.tsx b/src/components/Borrow/BorrowTable.tsx index 37fcd10a..46f4e4c7 100644 --- a/src/components/Borrow/BorrowTable.tsx +++ b/src/components/Borrow/BorrowTable.tsx @@ -15,6 +15,11 @@ import classNames from 'classnames' import { AssetRow } from 'components/Borrow/AssetRow' import { ChevronDown, ChevronUp } from 'components/Icons' import { getMarketAssets } from 'utils/assets' +import { Text } from 'components/Text' +import TitleAndSubCell from 'components/TitleAndSubCell' +import { FormattedNumber } from 'components/FormattedNumber' +import AmountAndValue from 'components/AmountAndValue' +import { formatPercent } from 'utils/formatters' import AssetExpanded from './AssetExpanded' @@ -26,7 +31,7 @@ export const BorrowTable = (props: Props) => { const [sorting, setSorting] = React.useState([]) const marketAssets = getMarketAssets() - const columns = React.useMemo[]>( + const columns = React.useMemo[]>( () => [ { header: 'Asset', @@ -37,12 +42,9 @@ export const BorrowTable = (props: Props) => { if (!asset) return null return ( -
+
token -
-
{asset.symbol}
-
{asset.name}
-
+
) }, @@ -50,17 +52,38 @@ export const BorrowTable = (props: Props) => { { accessorKey: 'borrowRate', header: 'Borrow Rate', - cell: ({ row }) =>
{(Number(row.original.borrowRate) * 100).toFixed(2)}%
, + cell: ({ row }) => ( + + {formatPercent(row.original.borrowRate)} + + ), }, + ...((props.data[0] as BorrowAssetActive)?.debt + ? [ + { + accessorKey: 'debt', + header: 'Borrowed', + cell: (info: any) => { + const borrowAsset = info.row.original as BorrowAssetActive + const asset = marketAssets.find((asset) => asset.denom === borrowAsset.denom) + + if (!asset) return null + + return + }, + }, + ] + : []), { accessorKey: 'liquidity', header: 'Liquidity Available', - cell: ({ row }) => ( -
-
{row.original.liquidity.amount}
-
${row.original.liquidity.value}
-
- ), + cell: ({ row }) => { + const asset = marketAssets.find((asset) => asset.denom === row.original.denom) + + if (!asset) return null + + return + }, }, { accessorKey: 'status', @@ -91,7 +114,7 @@ export const BorrowTable = (props: Props) => { return ( - + {table.getHeaderGroups().map((headerGroup) => ( {headerGroup.headers.map((header, index) => { @@ -100,7 +123,7 @@ export const BorrowTable = (props: Props) => { key={header.id} onClick={header.column.getToggleSortingHandler()} className={classNames( - 'px-4 py-2', + 'px-4 py-3', header.column.getCanSort() && 'cursor-pointer', header.id === 'symbol' ? 'text-left' : 'text-right', )} @@ -109,6 +132,7 @@ export const BorrowTable = (props: Props) => { className={classNames( 'flex', header.id === 'symbol' ? 'justify-start' : 'justify-end', + 'align-center', )} > {header.column.getCanSort() @@ -124,7 +148,9 @@ export const BorrowTable = (props: Props) => { ), }[header.column.getIsSorted() as string] ?? null : null} - {flexRender(header.column.columnDef.header, header.getContext())} + + {flexRender(header.column.columnDef.header, header.getContext())} + ) diff --git a/src/components/BorrowCapacity.tsx b/src/components/BorrowCapacity.tsx index 8d3c0ed3..3aba4e38 100644 --- a/src/components/BorrowCapacity.tsx +++ b/src/components/BorrowCapacity.tsx @@ -121,11 +121,12 @@ export const BorrowCapacity = ({ )} diff --git a/src/components/BorrowModal.tsx b/src/components/BorrowModal.tsx index ba375294..b491daac 100644 --- a/src/components/BorrowModal.tsx +++ b/src/components/BorrowModal.tsx @@ -1,314 +1,25 @@ -import { Dialog, Switch, Transition } from '@headlessui/react' -import BigNumber from 'bignumber.js' -import React, { useMemo, useState } from 'react' -import { NumericFormat } from 'react-number-format' - -import { Button } from 'components/Button' -import { CircularProgress } from 'components/CircularProgress' -import { ContainerSecondary } from 'components/ContainerSecondary' -import { Gauge } from 'components/Gauge' -import { PositionsList } from 'components/PositionsList' -import { ProgressBar } from 'components/ProgressBar' -import { Slider } from 'components/Slider' -import { Text } from 'components/Text' -import { Tooltip } from 'components/Tooltip' -import { useAccountStats } from 'hooks/data/useAccountStats' -import { useBalances } from 'hooks/data/useBalances' -import { useCalculateMaxBorrowAmount } from 'hooks/data/useCalculateMaxBorrowAmount' -import { useBorrowFunds } from 'hooks/mutations/useBorrowFunds' -import { useAllBalances } from 'hooks/queries/useAllBalances' -import { useMarkets } from 'hooks/queries/useMarkets' -import { useTokenPrices } from 'hooks/queries/useTokenPrices' import useStore from 'store' -import { getBaseAsset, getMarketAssets } from 'utils/assets' -import { formatCurrency, formatValue } from 'utils/formatters' -import { getTokenDecimals, getTokenSymbol } from 'utils/tokens' -type Props = { - show: boolean - onClose: () => void - tokenDenom: string -} +import { Modal } from './Modal' +import TitleAndSubCell from './TitleAndSubCell' -export const BorrowModal = ({ show, onClose, tokenDenom }: Props) => { - const [amount, setAmount] = useState(0) - const [isBorrowToCreditAccount, setIsBorrowToCreditAccount] = useState(false) +export default function BorrowModal() { + const open = useStore((s) => s.borrowModal) - const selectedAccount = useStore((s) => s.selectedAccount) - const marketAssets = getMarketAssets() - const baseAsset = getBaseAsset() - - const balances = useBalances() - - const { actions, borrowAmount } = useMemo(() => { - const borrowAmount = BigNumber(amount) - .times(10 ** getTokenDecimals(tokenDenom, marketAssets)) - .toNumber() - - const withdrawAmount = isBorrowToCreditAccount ? 0 : borrowAmount - - return { - borrowAmount, - withdrawAmount, - actions: [ - { - type: 'borrow', - amount: borrowAmount, - denom: tokenDenom, - }, - { - type: 'withdraw', - amount: withdrawAmount, - denom: tokenDenom, - }, - ] as AccountStatsAction[], - } - }, [amount, isBorrowToCreditAccount, tokenDenom, marketAssets]) - - const accountStats = useAccountStats(actions) - - const tokenSymbol = getTokenSymbol(tokenDenom, marketAssets) - - const { mutate, isLoading } = useBorrowFunds(borrowAmount, tokenDenom, !isBorrowToCreditAccount, { - onSuccess: () => { - onClose() - useStore.setState({ - toast: { message: `${amount} ${tokenSymbol} successfully Borrowed` }, - }) - }, - }) - - const { data: tokenPrices } = useTokenPrices() - const { data: balancesData } = useAllBalances() - const { data: marketsData } = useMarkets() - - const handleSubmit = () => { - mutate() - } - - const walletAmount = useMemo(() => { - return BigNumber(balancesData?.find((balance) => balance.denom === tokenDenom)?.amount ?? 0) - .div(10 ** getTokenDecimals(tokenDenom, marketAssets)) - .toNumber() - }, [balancesData, tokenDenom, marketAssets]) - - const tokenPrice = tokenPrices?.[tokenDenom] ?? 0 - const borrowRate = Number(marketsData?.[tokenDenom]?.borrow_rate) - - const maxValue = useCalculateMaxBorrowAmount(tokenDenom, isBorrowToCreditAccount) - - const percentageValue = useMemo(() => { - if (isNaN(amount) || maxValue === 0) return 0 - - return (amount * 100) / maxValue - }, [amount, maxValue]) - - const handleValueChange = (value: number) => { - if (value > maxValue) { - setAmount(maxValue) - return - } - - setAmount(value) - } - - const handleSliderValueChange = (value: number[]) => { - const decimal = value[0] / 100 - const tokenDecimals = getTokenDecimals(tokenDenom, marketAssets) - // limit decimal precision based on token contract decimals - const newAmount = Number((decimal * maxValue).toFixed(tokenDecimals)) - - setAmount(newAmount) - } - - const handleBorrowTargetChange = () => { - setIsBorrowToCreditAccount((c) => !c) - // reset amount due to max value calculations changing depending on borrow target - setAmount(0) + function setOpen(isOpen: boolean) { + useStore.setState({ borrowModal: isOpen }) } return ( - - - -
- - -
-
- - - {isLoading && ( -
- -
- )} -
- - Borrow {tokenSymbol} - -
- -

- In wallet: {walletAmount.toLocaleString()} {tokenSymbol} -

-

Borrow Rate: {(borrowRate * 100).toFixed(2)}%

- -
-

Amount

- handleValueChange(v.floatValue || 0)} - suffix={` ${tokenSymbol}`} - decimalScale={getTokenDecimals(tokenDenom, marketAssets)} - /> -
-
- 1 {tokenSymbol} = {formatCurrency(tokenPrice)} -
-
{formatCurrency(tokenPrice * amount)}
-
-
- setAmount(maxValue)} - /> -
- -
- Borrow to Credit Account{' '} - - - OFF = Borrow directly into your wallet by using your account Assets - as collateral. The borrowed asset will become a liability in your - account. - - - ON = Borrow into your Account. The borrowed asset will be available - in the account as an Asset and appear also as a liability in your - account. - - - } - /> -
- - - -
-
- -
- -
-

About

-

Account {selectedAccount}

-
- {accountStats && ( -
-

- {formatCurrency( - BigNumber(accountStats.netWorth) - .dividedBy(10 ** baseAsset.decimals) - .toNumber(), - )} -

- - Current Leverage:{' '} - {formatValue(accountStats.currentLeverage, 0, 2, true, false, 'x')} -
- Max Leverage:{' '} - {formatValue(accountStats.maxLeverage, 0, 2, true, false, 'x')} - - } - /> - - Current Risk:{' '} - {formatValue(accountStats.risk * 100, 0, 2, true, false, '%')} - - } - /> - -
- )} -
-
-
-
Total Position:
-
- {formatCurrency( - BigNumber(accountStats?.totalPosition ?? 0) - .dividedBy(10 ** baseAsset.decimals) - .toNumber(), - )} -
-
-
-
Total Liabilities:
-
- {formatCurrency( - BigNumber(accountStats?.totalDebt ?? 0) - .dividedBy(10 ** baseAsset.decimals) - .toNumber(), - )} -
-
-
- -
-
-
-
-
-
-
+ +
+ +
+ +
+ +
+
+
) } diff --git a/src/components/Card.tsx b/src/components/Card.tsx index fa011111..7953e087 100644 --- a/src/components/Card.tsx +++ b/src/components/Card.tsx @@ -1,6 +1,8 @@ import classNames from 'classnames' import { ReactNode } from 'react' +import { Text } from 'components/Text' + interface Props { title: string children: ReactNode @@ -15,8 +17,10 @@ export const Card = (props: Props) => { 'h-fit w-full max-w-full overflow-hidden rounded-md border-[1px] border-white/20', )} > -
{props.title}
-
{props.children}
+ + {props.title} + +
{props.children}
) } diff --git a/src/components/FetchPrices.tsx b/src/components/FetchPrices.tsx new file mode 100644 index 00000000..21b9973e --- /dev/null +++ b/src/components/FetchPrices.tsx @@ -0,0 +1,15 @@ +'use client' + +import useSWR from 'swr' + +import useStore from 'store' +import { getPrices } from 'utils/api' + +export default function FetchPrices() { + useSWR('prices', getPrices, { + refreshInterval: 30000, + onSuccess: (prices) => useStore.setState({ prices }), + }) + + return null +} diff --git a/src/components/FormattedNumber.tsx b/src/components/FormattedNumber.tsx index 78b14cc7..b9dae96d 100644 --- a/src/components/FormattedNumber.tsx +++ b/src/components/FormattedNumber.tsx @@ -5,64 +5,60 @@ import React, { useEffect, useRef } from 'react' import { animated, useSpring } from 'react-spring' import useStore from 'store' -import { formatValue } from 'utils/formatters' +import { FormatOptions, formatValue } from 'utils/formatters' -export const FormattedNumber = React.memo( - ({ - amount, - animate = false, - className, - minDecimals = 2, - maxDecimals = 2, - thousandSeparator = true, - prefix = false, - suffix = false, - rounded = false, - abbreviated = false, - }: FormattedNumberProps) => { - const enableAnimations = useStore((s) => s.enableAnimations) - const prevAmountRef = useRef(0) +interface Props { + amount: number | string + options?: FormatOptions + className?: string + animate?: boolean +} - useEffect(() => { - if (prevAmountRef.current !== Number(amount)) prevAmountRef.current = Number(amount) - }, [amount]) +export const FormattedNumber = React.memo((props: Props) => { + const enableAnimations = useStore((s) => s.enableAnimations) + const prevAmountRef = useRef(0) - const springAmount = useSpring({ - number: Number(amount), - from: { number: prevAmountRef.current }, - config: { duration: 1000 }, - }) + useEffect(() => { + if (prevAmountRef.current !== Number(props.amount)) prevAmountRef.current = Number(props.amount) + }, [props.amount]) - return (prevAmountRef.current === amount && amount === 0) || !animate || !enableAnimations ? ( - - {formatValue( - amount, - minDecimals, - maxDecimals, - thousandSeparator, - prefix, - suffix, - rounded, - abbreviated, - )} - - ) : ( - - {springAmount.number.to((num) => - formatValue( - num, - minDecimals, - maxDecimals, - thousandSeparator, - prefix, - suffix, - rounded, - abbreviated, - ), - )} - - ) - }, -) + const springAmount = useSpring({ + number: Number(props.amount), + from: { number: prevAmountRef.current }, + config: { duration: 1000 }, + }) + + return (prevAmountRef.current === props.amount && props.amount === 0) || + !props.animate || + !enableAnimations ? ( + + {formatValue(props.amount, { + minDecimals: props.options?.minDecimals, + maxDecimals: props.options?.maxDecimals, + thousandSeparator: props.options?.thousandSeparator, + prefix: props.options?.prefix, + suffix: props.options?.suffix, + rounded: props.options?.rounded, + abbreviated: props.options?.abbreviated, + decimals: props.options?.decimals, + })} + + ) : ( + + {springAmount.number.to((num) => + formatValue(num, { + minDecimals: props.options?.minDecimals, + maxDecimals: props.options?.maxDecimals, + thousandSeparator: props.options?.thousandSeparator, + prefix: props.options?.prefix, + suffix: props.options?.suffix, + rounded: props.options?.rounded, + abbreviated: props.options?.abbreviated, + decimals: props.options?.decimals, + }), + )} + + ) +}) FormattedNumber.displayName = 'FormattedNumber' diff --git a/src/components/Modal.tsx b/src/components/Modal.tsx index 9855f2b0..b7af9f8d 100644 --- a/src/components/Modal.tsx +++ b/src/components/Modal.tsx @@ -2,9 +2,12 @@ import classNames from 'classnames' import { ReactNode } from 'react' import { Close } from 'components/Icons' -import { Card } from 'components/Card' +import { Text } from 'components/Text' + +import { Button } from './Button' interface Props { + title: string children?: ReactNode | string content?: ReactNode | string className?: string @@ -12,31 +15,28 @@ interface Props { setOpen?: (open: boolean) => void } -export const Modal = ({ children, content, className, open, setOpen }: Props) => { +export const Modal = (props: Props) => { const onClickAway = () => { - if (setOpen) setOpen(false) + if (props.setOpen) props.setOpen(false) } - return open ? ( + return props.open ? (
- - {setOpen && ( - - - +
+ > +
+ {props.title} +
+
{props.children ? props.children : props.content}
+
diff --git a/src/components/Modals.tsx b/src/components/Modals.tsx index 7a0cb4af..b9e5878a 100644 --- a/src/components/Modals.tsx +++ b/src/components/Modals.tsx @@ -3,10 +3,13 @@ import { ConfirmModal } from 'components/Account/ConfirmModal' import { FundAccountModal } from 'components/Account/FundAccountModal' +import BorrowModal from './BorrowModal' + export const Modals = () => ( <> {/* */} + ) diff --git a/src/components/RepayModal.tsx b/src/components/RepayModal.tsx index 920752a1..93a91a45 100644 --- a/src/components/RepayModal.tsx +++ b/src/components/RepayModal.tsx @@ -13,7 +13,6 @@ import { useRepayFunds } from 'hooks/mutations/useRepayFunds' import { useAllBalances } from 'hooks/queries/useAllBalances' import { useCreditAccountPositions } from 'hooks/queries/useCreditAccountPositions' import { useTokenPrices } from 'hooks/queries/useTokenPrices' -import { formatCurrency } from 'utils/formatters' import { getTokenDecimals, getTokenSymbol } from 'utils/tokens' import { getMarketAssets } from 'utils/assets' import useStore from 'store' @@ -33,6 +32,7 @@ export const RepayModal = ({ show, onClose, tokenDenom }: Props) => { const selectedAccount = useStore((s) => s.selectedAccount) const { data: positionsData } = useCreditAccountPositions(selectedAccount ?? '') const marketAssets = getMarketAssets() + const formatCurrency = useStore((s) => s.formatCurrency) const tokenSymbol = getTokenSymbol(tokenDenom, marketAssets) @@ -155,9 +155,11 @@ export const RepayModal = ({ show, onClose, tokenDenom }: Props) => { />
- 1 {tokenSymbol} = {formatCurrency(tokenPrice)} + 1 {tokenSymbol} = {formatCurrency({ denom: tokenDenom, amount: '1' })} +
+
+ {formatCurrency({ denom: tokenDenom, amount: amount.toString() })}
-
{formatCurrency(tokenPrice * amount)}
diff --git a/src/components/Text.tsx b/src/components/Text.tsx index 5fb615f5..045099d6 100644 --- a/src/components/Text.tsx +++ b/src/components/Text.tsx @@ -29,6 +29,7 @@ export const Text = ({ + + {props.title} + + + {props.sub} + +
+ ) +} diff --git a/src/components/Wallet/ConnectedButton.tsx b/src/components/Wallet/ConnectedButton.tsx index 2da452e5..b4ce42a4 100644 --- a/src/components/Wallet/ConnectedButton.tsx +++ b/src/components/Wallet/ConnectedButton.tsx @@ -104,7 +104,7 @@ export default function ConnectedButton() { {isLoading ? ( ) : ( - `${formatValue(walletAmount, 2, 2, true, false, ` ${baseAsset.symbol}`)}` + `${formatValue(walletAmount, { suffix: baseAsset.symbol })}` )}
diff --git a/src/hooks/data/useBalances.tsx b/src/hooks/data/useBalances.tsx deleted file mode 100644 index 23eb2791..00000000 --- a/src/hooks/data/useBalances.tsx +++ /dev/null @@ -1,36 +0,0 @@ -import { useEffect, useState } from 'react' - -import { useCreditAccountPositions } from 'hooks/queries/useCreditAccountPositions' -import { useMarkets } from 'hooks/queries/useMarkets' -import { useTokenPrices } from 'hooks/queries/useTokenPrices' -import { formatBalances } from 'utils/balances' -import useStore from 'store' -import { getMarketAssets } from 'utils/assets' - -export const useBalances = () => { - const [balanceData, setBalanceData] = useState() - - const { data: marketsData } = useMarkets() - const { data: tokenPrices } = useTokenPrices() - const selectedAccount = useStore((s) => s.selectedAccount) - const marketAssets = getMarketAssets() - - const { data: positionsData, isLoading: isLoadingPositions } = useCreditAccountPositions( - selectedAccount ?? '', - ) - - useEffect(() => { - const balances = - positionsData?.coins && tokenPrices - ? formatBalances(positionsData.coins, tokenPrices, false, marketAssets) - : [] - const debtBalances = - positionsData?.debts && tokenPrices - ? formatBalances(positionsData.debts, tokenPrices, true, marketAssets, marketsData) - : [] - - setBalanceData([...balances, ...debtBalances]) - }, [positionsData, marketsData, tokenPrices, marketAssets]) - - return balanceData -} diff --git a/src/pages/api/accounts/[id]/debts.ts b/src/pages/api/accounts/[id]/debts.ts index 598dec1c..26e8a83d 100644 --- a/src/pages/api/accounts/[id]/debts.ts +++ b/src/pages/api/accounts/[id]/debts.ts @@ -12,7 +12,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse) const account = await (await fetch(`${URL_API}/accounts/${accountId}`)).json() if (account) { - return res.status(200).json(account.debts) + return res.status(200).json([{ denom: 'uosmo', amount: '123876' }]) } return res.status(404) diff --git a/src/store/slices/common.ts b/src/store/slices/common.ts index 28e15fc0..617f28f0 100644 --- a/src/store/slices/common.ts +++ b/src/store/slices/common.ts @@ -1,23 +1,52 @@ +import { Coin } from '@cosmjs/stargate' +import BigNumber from 'bignumber.js' import { GetState, SetState } from 'zustand' +import { getMarketAssets } from 'utils/assets' +import { formatValue } from 'utils/formatters' + export interface CommonSlice { + borrowModal: boolean createAccountModal: boolean deleteAccountModal: boolean enableAnimations: boolean + repayModal: boolean fundAccountModal: boolean + prices: Coin[] isOpen: boolean selectedAccount: string | null withdrawModal: boolean + formatCurrency: (coin: Coin) => string } export function createCommonSlice(set: SetState, get: GetState) { return { + borrowModal: false, createAccountModal: false, deleteAccountModal: false, + repayModal: false, enableAnimations: true, fundAccountModal: false, + prices: [], isOpen: true, selectedAccount: null, withdrawModal: false, + formatCurrency: (coin: Coin) => { + const price = get().prices.find((price) => price.denom === coin.denom) + const marketAsset = getMarketAssets().find((asset) => asset.denom === coin.denom) + + if (!price || !marketAsset) return '' + + return formatValue( + new BigNumber(coin.amount) + .times(price.amount) + .dividedBy(10 ** marketAsset.decimals) + .toNumber(), + { + minDecimals: 0, + prefix: '$', + }, + ) + }, } } diff --git a/src/styles/globals.scss b/src/styles/globals.scss index 8a5f7bc7..bfa7469c 100644 --- a/src/styles/globals.scss +++ b/src/styles/globals.scss @@ -66,20 +66,21 @@ a { } /* ORBS */ -@mixin orbs($count) { +@mixin orbs($count, $hue) { $text-shadow: (); @for $i from 0 through $count { $text-shadow: $text-shadow, - (-0.5+ (random()) * 3) + - em - (-0.5+ (random()) * 3) + - em - 7px - hsla((random() * 50)+210, 100%, 45%, 0.3); + (-0.5+ (random()) * 3) + em (-0.5+ (random()) * 3) + em 10px rgb(92, 5, 92); + // hsla((random() * 50)+$hue, 100%, 45%); } text-shadow: $text-shadow; } +@mixin newOrbs($count, $color) { + filter: blur(4px); + background: radial-gradient(circle at center, rgba($color, 0.25) 0%, rgba($color, 0) 20%); +} + .background { font-family: serif; font-size: 90px; @@ -98,18 +99,27 @@ a { width: 3em; height: 3em; content: '.'; + color: transparent; mix-blend-mode: screen; } + &:after { + top: 10%; + left: 10%; + @include newOrbs(1, rgb(177, 47, 37)); + } + &:before { - @include orbs(15); + top: 80%; + left: 80%; + @include newOrbs(1, rgb(83, 7, 129)); animation-duration: 300s; - animation-delay: -50s; + // animation-delay: -50s; animation: 180s -15s move infinite ease-in-out alternate; } &:after { - @include orbs(25); + // @include orbs(15, 260); animation-duration: 600s; animation: 180s 0s move infinite ease-in-out alternate; } diff --git a/src/types/interfaces/asset.d.ts b/src/types/interfaces/asset.d.ts index a298af47..0610b657 100644 --- a/src/types/interfaces/asset.d.ts +++ b/src/types/interfaces/asset.d.ts @@ -26,8 +26,5 @@ interface BorrowAsset { } interface BorrowAssetActive extends BorrowAsset { - debt: { - amount: string - value: string - } + debt: string } diff --git a/src/utils/balances.ts b/src/utils/balances.ts deleted file mode 100644 index 4e814b4d..00000000 --- a/src/utils/balances.ts +++ /dev/null @@ -1,43 +0,0 @@ -import { Coin } from '@cosmjs/stargate' - -import { convertFromGwei, getTokenTotalUSDValue } from 'utils/formatters' -import { getTokenSymbol } from 'utils/tokens' - -export const formatBalances = ( - positionData: Coin[], - tokenPrices: KeyValuePair, - debt: boolean, - marketAssets: Asset[], - marketsData?: MarketData, -): PositionsData[] => { - const balances: PositionsData[] = [] - - positionData.forEach((coin) => { - const dataEntry: PositionsData = { - asset: { - amount: getTokenSymbol(coin.denom, marketAssets), - type: debt ? 'debt' : undefined, - }, - value: { - amount: getTokenTotalUSDValue(coin.amount, coin.denom, marketAssets, tokenPrices), - format: 'number', - prefix: '$', - }, - size: { - amount: convertFromGwei(coin.amount, coin.denom, marketAssets), - format: 'number', - maxDecimals: 4, - minDecimals: 0, - }, - apy: { - amount: debt ? Number(marketsData?.[coin.denom].borrow_rate) * 100 : '-', - format: debt ? 'number' : 'string', - suffix: '%', - minDecimals: 0, - }, - } - balances.push(dataEntry) - }) - - return balances -} diff --git a/src/utils/formatters.ts b/src/utils/formatters.ts index 4c7d2b65..5034cfda 100644 --- a/src/utils/formatters.ts +++ b/src/utils/formatters.ts @@ -10,28 +10,6 @@ export function truncate(text = '', [h, t]: [number, number] = [6, 6]): string { return text.length > h + t ? [head, tail].join('...') : text } -export const formatCurrency = (value: string | number) => { - return Number(value).toLocaleString('en-US', { - style: 'currency', - currency: 'USD', - }) -} - -export const getTokenTotalUSDValue = ( - amount: string, - denom: string, - marketAssets: Asset[], - tokenPrices?: KeyValuePair, -) => { - if (!tokenPrices) return 0 - - return ( - BigNumber(amount) - .div(10 ** getTokenDecimals(denom, marketAssets)) - .toNumber() * tokenPrices[denom] - ) -} - export const convertFromGwei = (amount: string | number, denom: string, marketAssets: Asset[]) => { return BigNumber(amount) .div(10 ** getTokenDecimals(denom, marketAssets)) @@ -44,26 +22,34 @@ export const convertToGwei = (amount: string | number, denom: string, marketAsse .toNumber() } -export const formatValue = ( - amount: number | string, - minDecimals = 2, - maxDecimals = 2, - thousandSeparator = true, - prefix: boolean | string = false, - suffix: boolean | string = false, - rounded = false, - abbreviated = false, -): string => { +export interface FormatOptions { + decimals?: number + minDecimals?: number + maxDecimals?: number + thousandSeparator?: boolean + prefix?: string + suffix?: string + rounded?: boolean + abbreviated?: boolean +} + +export const formatValue = (amount: number | string, options?: FormatOptions): string => { let numberOfZeroDecimals: number | null = null + const minDecimals = options?.minDecimals ?? 2 + const maxDecimals = options?.maxDecimals ?? 2 + const thousandSeparator = options?.thousandSeparator ?? true + if (typeof amount === 'string') { const decimals = amount.split('.')[1] ?? null if (decimals && Number(decimals) === 0) { numberOfZeroDecimals = decimals.length } } - let convertedAmount: number | string = +amount || 0 + let convertedAmount: number | string = new BigNumber(amount) + .dividedBy(10 ** (options?.decimals ?? 0)) + .toNumber() - const amountSuffix = abbreviated + const amountSuffix = options?.abbreviated ? convertedAmount >= 1_000_000_000 ? 'B' : convertedAmount >= 1_000_000 @@ -73,19 +59,17 @@ export const formatValue = ( : false : '' - const amountPrefix = prefix - if (amountSuffix === 'B') { - convertedAmount = Number(amount) / 1_000_000_000 + convertedAmount = Number(convertedAmount) / 1_000_000_000 } if (amountSuffix === 'M') { - convertedAmount = Number(amount) / 1_000_000 + convertedAmount = Number(convertedAmount) / 1_000_000 } if (amountSuffix === 'K') { - convertedAmount = Number(amount) / 1_000 + convertedAmount = Number(convertedAmount) / 1_000 } - if (rounded) { + if (options?.rounded) { convertedAmount = convertedAmount.toFixed(maxDecimals) } else { const amountFractions = String(convertedAmount).split('.') @@ -112,8 +96,8 @@ export const formatValue = ( } let returnValue = '' - if (amountPrefix) { - returnValue += amountPrefix + if (options?.prefix) { + returnValue += options.prefix } returnValue += convertedAmount @@ -131,9 +115,23 @@ export const formatValue = ( returnValue += amountSuffix } - if (suffix) { - returnValue += suffix + if (options?.suffix) { + returnValue += options.suffix } return returnValue } + +export function formatLeverage(leverage: number) { + return formatValue(leverage, { + minDecimals: 0, + suffix: 'x', + }) +} + +export function formatPercent(percent: number | string) { + return formatValue(+percent * 100, { + minDecimals: 0, + suffix: '%', + }) +}