diff --git a/src/components/Modals/BorrowModal.tsx b/src/components/Modals/BorrowModal.tsx index 515c94dc..9a56c66f 100644 --- a/src/components/Modals/BorrowModal.tsx +++ b/src/components/Modals/BorrowModal.tsx @@ -35,18 +35,9 @@ interface Props { modal: BorrowModal } -function getDebtAmount(modal: BorrowModal) { - return BN((modal.marketData as BorrowMarketTableData)?.debt ?? 0).toString() -} - -function getAssetLogo(modal: BorrowModal) { - if (!modal.asset) return null - return -} - function RepayNotAvailable(props: { asset: Asset; repayFromWallet: boolean }) { return ( - +
@@ -90,7 +81,6 @@ function BorrowModal(props: Props) { const apy = modal.marketData.apy.borrow const isAutoLendEnabled = autoLendEnabledAccountIds.includes(account.id) const { computeMaxBorrowAmount } = useHealthComputer(account) - const totalDebt = BN(getDebtAmount(modal)) const accountDebt = account.debts.find(byDenom(asset.denom))?.amount ?? BN_ZERO const markets = useMarkets() @@ -237,7 +227,7 @@ function BorrowModal(props: Props) { onClose={onClose} header={ - {getAssetLogo(modal)} + {isRepay ? 'Repay' : 'Borrow'} {asset.symbol} @@ -251,14 +241,14 @@ function BorrowModal(props: Props) { title={formatPercent(modal.marketData.apy.borrow)} sub={'Borrow Rate APY'} /> - {totalDebt.isGreaterThan(0) && ( + {accountDebt.isGreaterThan(0) && ( <>
@@ -303,59 +293,57 @@ function BorrowModal(props: Props) {
-
- - {isRepay && maxRepayAmount.isZero() && ( - - )} - {isRepay ? ( - <> - + + {isRepay && maxRepayAmount.isZero() && ( + + )} + {isRepay ? ( + <> + +
Repay from Wallet Repay your debt directly from your wallet
-
- -
- - ) : ( - <> - + +
+ + ) : ( + <> + +
Receive funds to Wallet Your borrowed funds will directly go to your wallet
-
- -
- - )} -
+ +
+ + )}
+ + ) +} diff --git a/src/components/Modals/V1DepositAndWithdraw/Deposit.tsx b/src/components/Modals/v1/Deposit.tsx similarity index 94% rename from src/components/Modals/V1DepositAndWithdraw/Deposit.tsx rename to src/components/Modals/v1/Deposit.tsx index b2849729..cb9000f2 100644 --- a/src/components/Modals/V1DepositAndWithdraw/Deposit.tsx +++ b/src/components/Modals/v1/Deposit.tsx @@ -11,9 +11,8 @@ import { BNCoin } from 'types/classes/BNCoin' import { byDenom } from 'utils/array' import { defaultFee } from 'utils/constants' import { BN } from 'utils/helpers' - -import AssetAmountSelectActionModal from '../AssetAmountSelectActionModal' -import DetailsHeader from '../LendAndReclaim/DetailsHeader' +import AssetAmountSelectActionModal from 'components/Modals/AssetAmountSelectActionModal' +import DetailsHeader from 'components/Modals/LendAndReclaim/DetailsHeader' interface Props { account: Account diff --git a/src/components/Modals/v1/Repay.tsx b/src/components/Modals/v1/Repay.tsx new file mode 100644 index 00000000..8ab28264 --- /dev/null +++ b/src/components/Modals/v1/Repay.tsx @@ -0,0 +1,208 @@ +import BigNumber from 'bignumber.js' +import { useCallback, useEffect, useMemo, useState } from 'react' + +import Modal from 'components/Modals/Modal' +import AccountSummaryInModal from 'components/account/AccountSummary/AccountSummaryInModal' +import Button from 'components/common/Button' +import Card from 'components/common/Card' +import DisplayCurrency from 'components/common/DisplayCurrency' +import Divider from 'components/common/Divider' +import { FormattedNumber } from 'components/common/FormattedNumber' +import { ArrowRight, InfoCircle } from 'components/common/Icons' +import Text from 'components/common/Text' +import TitleAndSubCell from 'components/common/TitleAndSubCell' +import TokenInputWithSlider from 'components/common/TokenInput/TokenInputWithSlider' +import AssetImage from 'components/common/assets/AssetImage' +import { BN_ZERO } from 'constants/math' +import useBaseAsset from 'hooks/assets/useBasetAsset' +import useMarkets from 'hooks/markets/useMarkets' +import useCurrentWalletBalance from 'hooks/useCurrentWalletBalance' +import { useUpdatedAccount } from 'hooks/useUpdatedAccount' +import useStore from 'store' +import { BNCoin } from 'types/classes/BNCoin' +import { formatPercent } from 'utils/formatters' +import { BN } from 'utils/helpers' +import { getDebtAmountWithInterest } from 'utils/tokens' + +interface Props { + account: Account +} + +function RepayNotAvailable(props: { asset: Asset }) { + return ( + +
+ +
+ No funds for repay + {`Unfortunately you don't have any ${props.asset.symbol} in your Wallet to repay the debt.`} +
+
+
+ ) +} + +export default function Repay(props: Props) { + const { account } = props + const modal = useStore((s) => s.v1BorrowAndRepayModal) + const baseAsset = useBaseAsset() + const asset = modal?.data.asset ?? baseAsset + const [amount, setAmount] = useState(BN_ZERO) + const balance = useCurrentWalletBalance(asset.denom) + const v1Action = useStore((s) => s.v1Action) + const [max, setMax] = useState(BN_ZERO) + const { simulateRepay } = useUpdatedAccount(account) + const apy = modal?.data.apy.borrow ?? 0 + const accountDebt = modal?.data.accountDebtAmount ?? BN_ZERO + const markets = useMarkets() + + const accountDebtWithInterest = useMemo( + () => getDebtAmountWithInterest(accountDebt, apy), + [accountDebt, apy], + ) + + const overpayExeedsCap = useMemo(() => { + const marketAsset = markets.find((market) => market.asset.denom === asset.denom) + if (!marketAsset) return + const overpayAmount = accountDebtWithInterest.minus(accountDebt) + const marketCapAfterOverpay = marketAsset.cap.used.plus(overpayAmount) + + return marketAsset.cap.max.isLessThanOrEqualTo(marketCapAfterOverpay) + }, [markets, asset.denom, accountDebt, accountDebtWithInterest]) + + const maxRepayAmount = useMemo(() => { + const maxBalance = BN(balance?.amount ?? 0) + return BigNumber.min(maxBalance, overpayExeedsCap ? accountDebt : accountDebtWithInterest) + }, [accountDebtWithInterest, overpayExeedsCap, accountDebt, balance?.amount]) + + const close = useCallback(() => { + setAmount(BN_ZERO) + useStore.setState({ v1BorrowAndRepayModal: null }) + }, [setAmount]) + + const onConfirmClick = useCallback(() => { + v1Action('repay', BNCoin.fromDenomAndBigNumber(asset.denom, amount)) + close() + }, [v1Action, asset, amount, close]) + + const handleChange = useCallback( + (newAmount: BigNumber) => { + if (!amount.isEqualTo(newAmount)) setAmount(newAmount) + }, + [amount, setAmount], + ) + + const onDebounce = useCallback(() => { + const repayCoin = BNCoin.fromDenomAndBigNumber( + asset.denom, + amount.isGreaterThan(accountDebt) ? accountDebt : amount, + ) + simulateRepay(repayCoin, true) + }, [amount, accountDebt, asset, simulateRepay]) + + useEffect(() => { + if (maxRepayAmount.isEqualTo(max)) return + setMax(maxRepayAmount) + }, [max, maxRepayAmount]) + + useEffect(() => { + if (amount.isLessThanOrEqualTo(max)) return + handleChange(max) + setAmount(max) + }, [amount, max, handleChange]) + + if (!modal) return null + + return ( + + + + {'Repay'} {asset.symbol} + + + } + headerClassName='gradient-header pl-2 pr-2.5 py-2.5 border-b-white/5 border-b' + contentClassName='flex flex-col' + > +
+ + +
+
+
+ + +
+ + Total Borrowed + +
+
+
+
+ + +
+ + Liquidity available + +
+
+
+ + + + {maxRepayAmount.isZero() && } +
+ + ) +} diff --git a/src/components/Modals/v1/V1BorrowAndRepay.tsx b/src/components/Modals/v1/V1BorrowAndRepay.tsx new file mode 100644 index 00000000..141a052f --- /dev/null +++ b/src/components/Modals/v1/V1BorrowAndRepay.tsx @@ -0,0 +1,15 @@ +import useAccount from 'hooks/accounts/useAccount' +import useStore from 'store' +import Borrow from 'components/Modals/v1/Borrow' +import Repay from 'components/Modals/v1/Repay' + +export default function V1BorrowAndRepayModal() { + const address = useStore((s) => s.address) + const { data: account } = useAccount(address) + const modal = useStore((s) => s.v1BorrowAndRepayModal) + const isBorrow = modal?.type === 'borrow' + + if (!modal || !account) return null + if (isBorrow) return + return +} diff --git a/src/components/Modals/V1DepositAndWithdraw/index.tsx b/src/components/Modals/v1/V1DepositAndWithdraw.tsx similarity index 82% rename from src/components/Modals/V1DepositAndWithdraw/index.tsx rename to src/components/Modals/v1/V1DepositAndWithdraw.tsx index 698db31d..475f0c06 100644 --- a/src/components/Modals/V1DepositAndWithdraw/index.tsx +++ b/src/components/Modals/v1/V1DepositAndWithdraw.tsx @@ -1,8 +1,7 @@ import useAccount from 'hooks/accounts/useAccount' import useStore from 'store' - -import Deposit from './Deposit' -import Withdraw from './Withdraw' +import Deposit from 'components/Modals/v1/Deposit' +import Withdraw from 'components/Modals/v1/Withdraw' export default function V1DepositAndWithdraw() { const address = useStore((s) => s.address) diff --git a/src/components/Modals/V1DepositAndWithdraw/Withdraw.tsx b/src/components/Modals/v1/Withdraw.tsx similarity index 92% rename from src/components/Modals/V1DepositAndWithdraw/Withdraw.tsx rename to src/components/Modals/v1/Withdraw.tsx index 7cc27f62..f4d111ce 100644 --- a/src/components/Modals/V1DepositAndWithdraw/Withdraw.tsx +++ b/src/components/Modals/v1/Withdraw.tsx @@ -1,5 +1,7 @@ import { useCallback, useState } from 'react' +import AssetAmountSelectActionModal from 'components/Modals/AssetAmountSelectActionModal' +import DetailsHeader from 'components/Modals/LendAndReclaim/DetailsHeader' import { BN_ZERO } from 'constants/math' import useBaseAsset from 'hooks/assets/useBasetAsset' import useHealthComputer from 'hooks/useHealthComputer' @@ -7,9 +9,6 @@ import { useUpdatedAccount } from 'hooks/useUpdatedAccount' import useStore from 'store' import { BNCoin } from 'types/classes/BNCoin' -import AssetAmountSelectActionModal from '../AssetAmountSelectActionModal' -import DetailsHeader from '../LendAndReclaim/DetailsHeader' - interface Props { account: Account } diff --git a/src/components/borrow/Table/Columns/BorrowButton.tsx b/src/components/borrow/Table/Columns/BorrowButton.tsx index 8368bd83..421a277d 100644 --- a/src/components/borrow/Table/Columns/BorrowButton.tsx +++ b/src/components/borrow/Table/Columns/BorrowButton.tsx @@ -16,7 +16,6 @@ export const BORROW_BUTTON_META = { interface Props { data: LendingMarketTableData - v1?: boolean } export default function BorrowButton(props: Props) { const account = useCurrentAccount() diff --git a/src/components/borrow/Table/Columns/Manage.tsx b/src/components/borrow/Table/Columns/Manage.tsx index 59adbec6..8e5c72c6 100644 --- a/src/components/borrow/Table/Columns/Manage.tsx +++ b/src/components/borrow/Table/Columns/Manage.tsx @@ -12,7 +12,6 @@ export const MANAGE_META = { interface Props { data: BorrowMarketTableData - v1?: boolean } export default function Manage(props: Props) { diff --git a/src/components/earn/farm/Table/Columns/Manage.tsx b/src/components/earn/farm/Table/Columns/Manage.tsx index ab85bebc..0acd130b 100644 --- a/src/components/earn/farm/Table/Columns/Manage.tsx +++ b/src/components/earn/farm/Table/Columns/Manage.tsx @@ -1,17 +1,16 @@ import moment from 'moment/moment' -import React, { useCallback, useMemo, useState } from 'react' +import { useCallback, useMemo, useState } from 'react' +import DropDownButton from 'components/common/Button/DropDownButton' import { AccountArrowDown, LockLocked, LockUnlocked, Plus } from 'components/common/Icons' import Loading from 'components/common/Loading' +import { DEFAULT_SETTINGS } from 'constants/defaultSettings' +import { LocalStorageKeys } from 'constants/localStorageKeys' +import useLocalStorage from 'hooks/localStorage/useLocalStorage' +import useAccountId from 'hooks/useAccountId' +import useStore from 'store' import { VaultStatus } from 'types/enums/vault' -import { DEFAULT_SETTINGS } from '../../../../../constants/defaultSettings' -import { LocalStorageKeys } from '../../../../../constants/localStorageKeys' -import useLocalStorage from '../../../../../hooks/localStorage/useLocalStorage' -import useAccountId from '../../../../../hooks/useAccountId' -import useStore from '../../../../../store' -import DropDownButton from '../../../../common/Button/DropDownButton' - export const MANAGE_META = { accessorKey: 'details', enableSorting: false, header: '' } interface Props { @@ -104,7 +103,7 @@ export default function Manage(props: Props) { if (!address) return null return ( -
+
+ if (hasDebt) return - return + return } diff --git a/src/components/v1/Table/borrowings/Columns/BorrowButton.tsx b/src/components/v1/Table/borrowings/Columns/BorrowButton.tsx new file mode 100644 index 00000000..dc85b35d --- /dev/null +++ b/src/components/v1/Table/borrowings/Columns/BorrowButton.tsx @@ -0,0 +1,50 @@ +import ActionButton from 'components/common/Button/ActionButton' +import { Plus } from 'components/common/Icons' +import Text from 'components/common/Text' +import { Tooltip } from 'components/common/Tooltip' +import ConditionalWrapper from 'hocs/ConditionalWrapper' +import useAccount from 'hooks/accounts/useAccount' +import useStore from 'store' + +interface Props { + data: BorrowMarketTableData +} +export default function BorrowButton(props: Props) { + const address = useStore((s) => s.address) + const { data: account } = useAccount(address) + + const hasCollateral = account?.lends?.length ?? 0 > 0 + + return ( +
+ ( + {`You don’t have assets deposited in the Red Bank. Please deposit assets before you borrow.`} + } + contentClassName='max-w-[200px]' + className='ml-auto' + > + {children} + + )} + > + } + disabled={!hasCollateral} + color='tertiary' + onClick={(e) => { + useStore.setState({ + v1BorrowAndRepayModal: { type: 'borrow', data: props.data }, + }) + e.stopPropagation() + }} + text='Borrow' + /> + +
+ ) +} diff --git a/src/components/v1/Table/borrowings/Columns/Manage.tsx b/src/components/v1/Table/borrowings/Columns/Manage.tsx new file mode 100644 index 00000000..b05b896b --- /dev/null +++ b/src/components/v1/Table/borrowings/Columns/Manage.tsx @@ -0,0 +1,47 @@ +import { useMemo } from 'react' + +import DropDownButton from 'components/common/Button/DropDownButton' +import { HandCoins, Plus } from 'components/common/Icons' +import useWalletBalances from 'hooks/useWalletBalances' +import useStore from 'store' +import { byDenom } from 'utils/array' + +interface Props { + data: BorrowMarketTableData +} + +export default function Manage(props: Props) { + const address = useStore((s) => s.address) + const { data: balances } = useWalletBalances(address) + const hasBalance = !!balances.find(byDenom(props.data.asset.denom)) + + const ITEMS: DropDownItem[] = useMemo( + () => [ + { + icon: , + text: 'Borrow more', + onClick: () => + useStore.setState({ + v1BorrowAndRepayModal: { type: 'borrow', data: props.data }, + }), + }, + { + icon: , + text: 'Repay', + onClick: () => + useStore.setState({ + v1BorrowAndRepayModal: { type: 'repay', data: props.data }, + }), + disabled: !hasBalance, + disabledTooltip: `You don’t have any ${props.data.asset.symbol} in your Wallet.`, + }, + ], + [hasBalance, props.data], + ) + + return ( +
+ +
+ ) +} diff --git a/src/components/v1/Table/deposits/Columns/Manage.tsx b/src/components/v1/Table/deposits/Columns/Manage.tsx index f36616ba..728afbf1 100644 --- a/src/components/v1/Table/deposits/Columns/Manage.tsx +++ b/src/components/v1/Table/deposits/Columns/Manage.tsx @@ -12,7 +12,6 @@ interface Props { } export default function Manage(props: Props) { - const { openLend, openReclaim } = useLendAndReclaimModal() const address = useStore((s) => s.address) const { data: balances } = useWalletBalances(address) const hasBalance = !!balances.find(byDenom(props.data.asset.denom)) diff --git a/src/store/slices/modal.ts b/src/store/slices/modal.ts index f0984fd2..76277afe 100644 --- a/src/store/slices/modal.ts +++ b/src/store/slices/modal.ts @@ -20,5 +20,6 @@ export default function createModalSlice(set: SetState, get: GetStat walletAssetsModal: null, withdrawFromVaultsModal: null, v1DepositAndWithdrawModal: null, + v1BorrowAndRepayModal: null, } } diff --git a/src/types/interfaces/store/modals.d.ts b/src/types/interfaces/store/modals.d.ts index d2dd91e3..e0fc378f 100644 --- a/src/types/interfaces/store/modals.d.ts +++ b/src/types/interfaces/store/modals.d.ts @@ -7,7 +7,6 @@ interface ModalSlice { hlsManageModal: HlsManageModal | null borrowModal: BorrowModal | null fundAndWithdrawModal: 'fund' | 'withdraw' | null - v1DepositAndWithdrawModal: V1DepositAndWithdrawModal | null getStartedModal: boolean hlsInformationModal: boolean | null lendAndReclaimModal: LendAndReclaimModalConfig | null @@ -16,6 +15,8 @@ interface ModalSlice { vaultModal: VaultModal | null walletAssetsModal: WalletAssetModal | null withdrawFromVaultsModal: DepositedVault[] | null + v1DepositAndWithdrawModal: V1DepositAndWithdrawModal | null + v1BorrowAndRepayModal: V1BorrowAndRepayModal | null } interface AlertDialogButton { @@ -90,3 +91,8 @@ interface V1DepositAndWithdrawModal { type: 'deposit' | 'withdraw' data: LendingMarketTableData } + +interface V1BorrowAndRepayModal { + type: 'borrow' | 'repay' + data: BorrowMarketTableData +}