diff --git a/components/Modal/Action.tsx b/components/Modal/Action.tsx index 84e0d114..0fbeeefd 100644 --- a/components/Modal/Action.tsx +++ b/components/Modal/Action.tsx @@ -103,6 +103,7 @@ export default function Action({ maxBorrowAmount, healthFactor, collateralType } tokenId, amount, extraDecimals, + position: collateralType, }); } else { await repay({ @@ -110,6 +111,7 @@ export default function Action({ maxBorrowAmount, healthFactor, collateralType } amount, extraDecimals, isMax, + position: collateralType, }); } break; diff --git a/components/Modal/CollateralTypeSelector.tsx b/components/Modal/CollateralTypeSelector.tsx index 04b3b302..efd194f7 100644 --- a/components/Modal/CollateralTypeSelector.tsx +++ b/components/Modal/CollateralTypeSelector.tsx @@ -5,8 +5,10 @@ import { useAvailableAssets } from "../../hooks/hooks"; import { digitalProcess } from "../../utils/uiNumber"; import { UIAsset } from "../../interfaces"; import { DEFAULT_POSITION } from "../../utils/config"; +import { shrinkToken } from "../../store"; +import { Asset } from "../../redux/assetState"; -export default function CollateralTypeSelector({ +export function CollateralTypeSelectorBorrow({ maxBorrowAmountPositions, selectedCollateralType, setSelectedCollateralType, @@ -88,3 +90,85 @@ export default function CollateralTypeSelector({ ); } +export function CollateralTypeSelectorRepay({ + repayPositions, + selectedCollateralType, + setSelectedCollateralType, +}: { + repayPositions: Record; + selectedCollateralType: string; + setSelectedCollateralType: any; +}) { + const [show, setShow] = useState(false); + const assets = useAvailableAssets(); + const LPAssetMap = useMemo(() => { + return assets + .filter((asset: UIAsset) => asset.isLpToken) + .reduce((acc, cur) => ({ ...acc, [cur.tokenId]: cur }), {}); + }, [assets]); + function getName(position) { + if (position === DEFAULT_POSITION) return "Single token"; + const asset: UIAsset = LPAssetMap[position]; + const symbols = asset.tokens.reduce( + (acc, cur, index) => + acc + (cur.metadata?.symbol || "") + (index !== asset.tokens.length - 1 ? "-" : ""), + "", + ); + // return `LP token (${symbols})`; + return symbols; + } + return ( +
{ + setShow(false); + }} + > +
+ Collateral Type +
{ + setShow(!show); + }} + > + + {getName(selectedCollateralType)} + + +
+
+ {/* list */} +
+ {/* title */} +
+ Collateral type + Borrowed amount +
+ {[DEFAULT_POSITION, ...Object.keys(LPAssetMap)].map((position) => { + return ( +
{ + setSelectedCollateralType(position); + setShow(false); + }} + className="flex items-center justify-between text-sm text-white h-[46px] hover:bg-gray-950 px-3.5 cursor-pointer" + > +
+ {getName(position)} + {selectedCollateralType === position ? : null} +
+ {digitalProcess(repayPositions[position] || 0, 2)} +
+ ); + })} +
+
+ ); +} diff --git a/components/Modal/actionTemplate.ts b/components/Modal/actionTemplate.ts new file mode 100644 index 00000000..35286f97 --- /dev/null +++ b/components/Modal/actionTemplate.ts @@ -0,0 +1,10 @@ +import { TokenAction } from "../../redux/appSlice"; + +export const getRepayTemplate = (tokenId, position) => { + const action: TokenAction = "Repay"; + return { action, tokenId, amount: "0", position }; +}; +export const getBorrowTemplate = (tokenId, position) => { + const action: TokenAction = "Borrow"; + return { action, tokenId, amount: "0", position }; +}; diff --git a/components/Modal/index.tsx b/components/Modal/index.tsx index 8652a769..12e5f668 100644 --- a/components/Modal/index.tsx +++ b/components/Modal/index.tsx @@ -7,6 +7,7 @@ import { useAppSelector, useAppDispatch } from "../../redux/hooks"; import { hideModal, updateAmount } from "../../redux/appSlice"; import { getModalStatus, getAssetData, getSelectedValues } from "../../redux/appSelectors"; import { getWithdrawMaxAmount } from "../../redux/selectors/getWithdrawMaxAmount"; +import { getRepayPositions } from "../../redux/selectors/getRepayPositions"; import { getAccountId } from "../../redux/accountSelectors"; import { getBorrowMaxAmount } from "../../redux/selectors/getBorrowMaxAmount"; import { recomputeHealthFactor } from "../../redux/selectors/recomputeHealthFactor"; @@ -17,7 +18,6 @@ import { recomputeHealthFactorRepay } from "../../redux/selectors/recomputeHealt import { recomputeHealthFactorRepayFromDeposits } from "../../redux/selectors/recomputeHealthFactorRepayFromDeposits"; import { formatWithCommas_number } from "../../utils/uiNumber"; import { DEFAULT_POSITION } from "../../utils/config"; - import { Wrapper } from "./style"; import { getModalData } from "./utils"; import { @@ -34,7 +34,10 @@ import Controls from "./Controls"; import Action from "./Action"; import { fetchAssets, fetchRefPrices } from "../../redux/assetsSlice"; import { useDegenMode } from "../../hooks/hooks"; -import CollateralTypeSelector from "./CollateralTypeSelector"; +import { + CollateralTypeSelectorBorrow, + CollateralTypeSelectorRepay, +} from "./CollateralTypeSelector"; const Modal = () => { const isOpen = useAppSelector(getModalStatus); @@ -46,7 +49,7 @@ const Modal = () => { const theme = useTheme(); const [selectedCollateralType, setSelectedCollateralType] = useState(DEFAULT_POSITION); - const { action = "Deposit", tokenId } = asset; + const { action = "Deposit", tokenId, position } = asset; const healthFactor = useAppSelector( action === "Withdraw" @@ -64,7 +67,9 @@ const Modal = () => { // TODO 计算出每一类资产的最大可借出余额 const maxBorrowAmountPositions = useAppSelector(getBorrowMaxAmount(tokenId)); const maxWithdrawAmount = useAppSelector(getWithdrawMaxAmount(tokenId)); + const repayPositions = useAppSelector(getRepayPositions(tokenId)); const maxBorrowAmount = maxBorrowAmountPositions[selectedCollateralType]; + const repayAmount = repayPositions[selectedCollateralType]; const { symbol, apy, @@ -82,14 +87,17 @@ const Modal = () => { isRepayFromDeposits, healthFactor, amount, + borrowed: repayAmount, }); - const total = (price * +amount).toLocaleString(undefined, USD_FORMAT); const handleClose = () => dispatch(hideModal()); useEffect(() => { if (isOpen) { dispatch(fetchAssets()).then(() => dispatch(fetchRefPrices())); } }, [isOpen]); + useEffect(() => { + setSelectedCollateralType(position || DEFAULT_POSITION); + }, [position]); useEffect(() => { dispatch(updateAmount({ isMax: false, amount: "0" })); }, [selectedCollateralType]); @@ -112,13 +120,20 @@ const Modal = () => { {!accountId && } - {action === "Borrow" || action === "Repay" ? ( - ) : null} + {action === "Repay" ? ( + + ) : null} , + action: PayloadAction<{ + action: TokenAction; + amount: string; + tokenId: string; + position?: string; + }>, ) { state.selected = { ...state.selected, isMax: false, ...action.payload }; state.showModal = true; diff --git a/redux/selectors/getRepayPositions.ts b/redux/selectors/getRepayPositions.ts new file mode 100644 index 00000000..7441924e --- /dev/null +++ b/redux/selectors/getRepayPositions.ts @@ -0,0 +1,25 @@ +import { createSelector } from "@reduxjs/toolkit"; +import { RootState } from "../store"; +import { hasAssets } from "../utils"; +import { shrinkToken } from "../../store"; + +export const getRepayPositions = (tokenId: string) => + createSelector( + (state: RootState) => state.app, + (state: RootState) => state.assets, + (state: RootState) => state.account, + (app, assets, account) => { + if (!hasAssets(assets) || !tokenId) return {}; + const { positions } = account.portfolio; + const asset = assets.data[tokenId]; + return Object.keys(positions).reduce((acc, position) => { + return { + ...acc, + [position]: shrinkToken( + positions[position].borrowed[tokenId]?.balance || 0, + asset.metadata.decimals + asset.config.extra_decimals, + ), + }; + }, {}); + }, + ); diff --git a/screens/TokenDetail/index.tsx b/screens/TokenDetail/index.tsx index c9581a6e..d9b33d15 100644 --- a/screens/TokenDetail/index.tsx +++ b/screens/TokenDetail/index.tsx @@ -4,6 +4,7 @@ import { useEffect, useState, createContext, useContext, useMemo } from "react"; import { Modal as MUIModal } from "@mui/material"; import { twMerge } from "tailwind-merge"; import { LayoutBox } from "../../components/LayoutContainer/LayoutContainer"; +import { showModal } from "../../redux/appSlice"; import { ArrowLeft, SuppliedEmptyIcon, @@ -56,6 +57,8 @@ import { useTokenDetails } from "../../hooks/useTokenDetails"; import { IToken } from "../../interfaces/asset"; import LPTokenCell from "./LPTokenCell"; import AvailableBorrowCell from "./AvailableBorrowCell"; +import { useAppDispatch } from "../../redux/hooks"; +import { getRepayTemplate, getBorrowTemplate } from "../../components/Modal/actionTemplate"; const DetailData = createContext(null) as any; const TokenDetail = () => { @@ -739,7 +742,7 @@ function TokenUserInfo() { const isWrappedNear = tokenRow.symbol === "NEAR"; const { supplyBalance, maxBorrowAmountPositions } = useUserBalance(tokenId, isWrappedNear); const handleSupplyClick = useSupplyTrigger(tokenId); - const handleBorrowClick = useBorrowTrigger(tokenId); + const dispatch = useAppDispatch(); function getIcons() { return (
@@ -829,7 +832,9 @@ function TokenUserInfo() { { + dispatch(showModal(getBorrowTemplate(tokenId, DEFAULT_POSITION))); + }} > Borrow @@ -960,7 +965,7 @@ function YouBorrowed() { }, [[], 0], ) || [[], 0]; - const handleRepayClick = useRepayTrigger(tokenId); + const dispatch = useAppDispatch(); const is_empty = !borrowed && !Object.keys(borrowedLp).length; const borrowedList = { ...borrowedLp }; if (borrowed) { @@ -1063,7 +1068,12 @@ function YouBorrowed() { />