From 7505e2cd12af377a17e20ac43c2d75beac6debc6 Mon Sep 17 00:00:00 2001 From: anton Date: Wed, 28 Jan 2026 18:13:41 +0800 Subject: [PATCH 1/8] feat: improve borrow/repay UX (#334) - Add 'borrow' modal type to Zustand modal store - Create BorrowModalGlobal wrapper with oracle price fetching - Register BorrowModal in global modal registry - Add 'Borrow' action to MarketActionsDropdown - Add 'Manage Borrow' button to positions page for borrow positions - Show prominent 'Outstanding Debt' display in repay panel - Add bidirectional Target LTV % input to borrow form - Support defaultMode prop for opening modal in borrow/repay mode --- .../components/market-actions-dropdown.tsx | 11 ++- .../components/supplied-markets-detail.tsx | 17 ++++ src/modals/borrow/borrow-modal-global.tsx | 46 ++++++++++ src/modals/borrow/borrow-modal.tsx | 21 ++++- .../components/add-collateral-and-borrow.tsx | 88 ++++++++++++++++++- .../withdraw-collateral-and-repay.tsx | 21 +++++ src/modals/registry.tsx | 4 + src/stores/useModalStore.ts | 10 +++ 8 files changed, 213 insertions(+), 5 deletions(-) create mode 100644 src/modals/borrow/borrow-modal-global.tsx diff --git a/src/features/markets/components/market-actions-dropdown.tsx b/src/features/markets/components/market-actions-dropdown.tsx index e2cab73d..b15eec57 100644 --- a/src/features/markets/components/market-actions-dropdown.tsx +++ b/src/features/markets/components/market-actions-dropdown.tsx @@ -6,7 +6,7 @@ import { useState } from 'react'; import { AiOutlineStop } from 'react-icons/ai'; import { GoStarFill, GoStar, GoGraph } from 'react-icons/go'; import { IoEllipsisVertical } from 'react-icons/io5'; -import { TbArrowUp } from 'react-icons/tb'; +import { TbArrowUp, TbArrowDown } from 'react-icons/tb'; import { Button } from '@/components/ui/button'; import { DropdownMenu, DropdownMenuTrigger, DropdownMenuContent, DropdownMenuItem } from '@/components/ui/dropdown-menu'; import type { Market } from '@/utils/types'; @@ -81,6 +81,15 @@ export function MarketActionsDropdown({ market }: MarketActionsDropdownProps) { Supply + { + openModal('borrow', { market }); + }} + startContent={} + > + Borrow + + } diff --git a/src/features/positions/components/supplied-markets-detail.tsx b/src/features/positions/components/supplied-markets-detail.tsx index 56276edd..66f81439 100644 --- a/src/features/positions/components/supplied-markets-detail.tsx +++ b/src/features/positions/components/supplied-markets-detail.tsx @@ -22,6 +22,7 @@ function MarketRow({ position, totalSupply, rateLabel }: { position: MarketPosit const { open } = useModal(); const suppliedAmount = Number(formatBalance(position.state.supplyAssets, position.market.loanAsset.decimals)); const percentageOfPortfolio = totalSupply > 0 ? (suppliedAmount / totalSupply) * 100 : 0; + const hasBorrowPosition = BigInt(position.state.borrowAssets) > 0n || BigInt(position.state.collateral) > 0n; return (
+ {hasBorrowPosition && ( + + )}
+ + {/* Target LTV Input */} +
+
+

Target LTV %

+

Max: {lltvPercent.toFixed(2)}%

+
+
+ handleLtvInputChange(e.target.value)} + placeholder="0.00" + className="bg-hovered w-full rounded-sm border border-transparent px-3 py-2 font-zen text-sm outline-none transition-colors focus:border-primary" + /> +
+
{/* Action Button */} diff --git a/src/modals/borrow/components/withdraw-collateral-and-repay.tsx b/src/modals/borrow/components/withdraw-collateral-and-repay.tsx index 857de08c..df866448 100644 --- a/src/modals/borrow/components/withdraw-collateral-and-repay.tsx +++ b/src/modals/borrow/components/withdraw-collateral-and-repay.tsx @@ -247,6 +247,27 @@ export function WithdrawCollateralAndRepay({ /> + {/* Outstanding Debt Display */} + {currentPosition && BigInt(currentPosition.state.borrowAssets) > 0n && ( +
+
+ Outstanding Debt +
+ + + {formatBalance(BigInt(currentPosition.state.borrowAssets), market.loanAsset.decimals)} {market.loanAsset.symbol} + +
+
+
+ )} +
{/* Withdraw Input Section */}
diff --git a/src/modals/registry.tsx b/src/modals/registry.tsx index 342ab714..90787268 100644 --- a/src/modals/registry.tsx +++ b/src/modals/registry.tsx @@ -13,6 +13,9 @@ import { lazy } from 'react'; // Swap const SwapModal = lazy(() => import('@/features/swap/components/SwapModal').then((m) => ({ default: m.SwapModal }))); +// Borrow & Repay +const BorrowModalGlobal = lazy(() => import('@/modals/borrow/borrow-modal-global').then((m) => ({ default: m.BorrowModalGlobal }))); + // Supply & Withdraw const SupplyModalV2 = lazy(() => import('@/modals/supply/supply-modal').then((m) => ({ default: m.SupplyModalV2 }))); @@ -45,6 +48,7 @@ const VaultWithdrawModal = lazy(() => import('@/modals/vault/vault-withdraw-moda export const MODAL_REGISTRY: { [K in ModalType]: ComponentType; } = { + borrow: BorrowModalGlobal, bridgeSwap: SwapModal, supply: SupplyModalV2, rebalance: RebalanceModal, diff --git a/src/stores/useModalStore.ts b/src/stores/useModalStore.ts index 92b3e4e1..5bee05d7 100644 --- a/src/stores/useModalStore.ts +++ b/src/stores/useModalStore.ts @@ -16,6 +16,16 @@ export type ModalProps = { defaultTargetToken?: SwapToken; }; + // Borrow & Repay + borrow: { + market: Market; + position?: MarketPosition | null; + defaultMode?: 'borrow' | 'repay'; + isMarketPage?: boolean; + refetch?: () => void; + liquiditySourcing?: LiquiditySourcingResult; + }; + // Supply & Withdraw supply: { market: Market; From 85e8d1b3543aec54fbc5fcc5edff998b6772ed76 Mon Sep 17 00:00:00 2001 From: anton Date: Wed, 28 Jan 2026 19:55:51 +0800 Subject: [PATCH 2/8] fix: always show Borrow button on positions page for discoverability - Previously only showed 'Manage Borrow' when user had existing borrow position - Now always shows button: 'Borrow' for new users, 'Manage Borrow' for existing - Addresses core discoverability issue from #334 The original implementation only showed the borrow button when hasBorrowPosition was true, which defeated the purpose of making borrow/repay more discoverable for users who haven't borrowed yet. --- .../components/supplied-markets-detail.tsx | 30 +++++++++---------- 1 file changed, 14 insertions(+), 16 deletions(-) diff --git a/src/features/positions/components/supplied-markets-detail.tsx b/src/features/positions/components/supplied-markets-detail.tsx index 66f81439..7765c959 100644 --- a/src/features/positions/components/supplied-markets-detail.tsx +++ b/src/features/positions/components/supplied-markets-detail.tsx @@ -75,22 +75,20 @@ function MarketRow({ position, totalSupply, rateLabel }: { position: MarketPosit style={{ minWidth: '180px' }} >
- {hasBorrowPosition && ( - - )} +
- - {/* Target LTV Input */} -
-
-

Target LTV %

-

Max: {lltvPercent.toFixed(2)}%

-
-
- handleLtvInputChange(e.target.value)} - placeholder="0.00" - className="bg-hovered w-full rounded-sm border border-transparent px-3 py-2 font-zen text-sm outline-none transition-colors focus:border-primary" - /> -
-
{/* Action Button */} diff --git a/src/modals/borrow/components/withdraw-collateral-and-repay.tsx b/src/modals/borrow/components/withdraw-collateral-and-repay.tsx index df866448..3c9348e7 100644 --- a/src/modals/borrow/components/withdraw-collateral-and-repay.tsx +++ b/src/modals/borrow/components/withdraw-collateral-and-repay.tsx @@ -189,16 +189,16 @@ export function WithdrawCollateralAndRepay({
-

Total Borrowed

+

Outstanding Debt

-

+

{formatBalance(BigInt(currentPosition?.state.borrowAssets ?? 0), market.loanAsset.decimals)} {market.loanAsset.symbol}

@@ -247,27 +247,6 @@ export function WithdrawCollateralAndRepay({ />
- {/* Outstanding Debt Display */} - {currentPosition && BigInt(currentPosition.state.borrowAssets) > 0n && ( -
-
- Outstanding Debt -
- - - {formatBalance(BigInt(currentPosition.state.borrowAssets), market.loanAsset.decimals)} {market.loanAsset.symbol} - -
-
-
- )} -
{/* Withdraw Input Section */}
From 121e2f5734ce35b62da2f8a63f45467aee7f938a Mon Sep 17 00:00:00 2001 From: anton Date: Wed, 28 Jan 2026 20:10:10 +0800 Subject: [PATCH 4/8] feat: remove Source Liquidity from market header dropdown Per Anton's request - this will be a paid feature later. The underlying PA functionality still works automatically during withdraw/borrow when needed, just not exposed as a manual action. --- .../market-detail/components/market-header.tsx | 14 ++------------ src/features/market-detail/market-view.tsx | 12 ------------ 2 files changed, 2 insertions(+), 24 deletions(-) diff --git a/src/features/market-detail/components/market-header.tsx b/src/features/market-detail/components/market-header.tsx index b093262b..c205b0c4 100644 --- a/src/features/market-detail/components/market-header.tsx +++ b/src/features/market-detail/components/market-header.tsx @@ -8,7 +8,7 @@ import { ChevronDownIcon } from '@radix-ui/react-icons'; import { GrStatusGood } from 'react-icons/gr'; import { IoWarningOutline, IoEllipsisVertical } from 'react-icons/io5'; import { MdError } from 'react-icons/md'; -import { BsArrowUpCircle, BsArrowDownLeftCircle, BsFillLightningFill, BsArrowRepeat } from 'react-icons/bs'; +import { BsArrowUpCircle, BsArrowDownLeftCircle, BsFillLightningFill } from 'react-icons/bs'; import { FiExternalLink } from 'react-icons/fi'; import { LuCopy } from 'react-icons/lu'; import { Button } from '@/components/ui/button'; @@ -122,7 +122,6 @@ type MarketHeaderProps = { onSupplyClick: () => void; onBorrowClick: () => void; accrueInterest: () => void; - onPullLiquidity: () => void; }; export function MarketHeader({ @@ -135,11 +134,10 @@ export function MarketHeader({ onSupplyClick, onBorrowClick, accrueInterest, - onPullLiquidity, }: MarketHeaderProps) { const [isExpanded, setIsExpanded] = useState(false); const { short: rateLabel } = useRateLabel(); - const { isAprDisplay, showDeveloperOptions, usePublicAllocator } = useAppSettings(); + const { isAprDisplay, showDeveloperOptions } = useAppSettings(); const toast = useStyledToast(); const networkImg = getNetworkImg(network); @@ -355,14 +353,6 @@ export function MarketHeader({ > Borrow - {usePublicAllocator && ( - } - > - Source Liquidity - - )} {showDeveloperOptions && ( s.selectedTab); const setSelectedTab = useMarketDetailPreferences((s) => s.setSelectedTab); const [showBorrowModal, setShowBorrowModal] = useState(false); - const [showPullLiquidityModal, setShowPullLiquidityModal] = useState(false); const [isRefreshing, setIsRefreshing] = useState(false); const [showTransactionFiltersModal, setShowTransactionFiltersModal] = useState(false); const [showSupplierFiltersModal, setShowSupplierFiltersModal] = useState(false); @@ -310,7 +308,6 @@ function MarketContent() { onSupplyClick={handleSupplyClick} onBorrowClick={handleBorrowClick} accrueInterest={handleAccrueInterest} - onPullLiquidity={() => setShowPullLiquidityModal(true)} /> {showBorrowModal && ( @@ -325,15 +322,6 @@ function MarketContent() { /> )} - {showPullLiquidityModal && ( - - )} - {showTransactionFiltersModal && ( Date: Wed, 28 Jan 2026 20:13:16 +0800 Subject: [PATCH 5/8] feat: prominent Supply/Borrow buttons on market header - Supply and Borrow buttons now always visible (not hidden in dropdown) - Dropdown now contains: Star, Blacklist, Accrue Interest (dev), View on Morpho - Added star/blacklist functionality to market detail page - Reverted positions page change (supply users don't care about borrow) This addresses the actual user feedback: borrow was hidden on the MARKET page, not the positions page. --- .../components/market-header.tsx | 75 ++++++++++++++++--- .../components/supplied-markets-detail.tsx | 30 ++++---- 2 files changed, 82 insertions(+), 23 deletions(-) diff --git a/src/features/market-detail/components/market-header.tsx b/src/features/market-detail/components/market-header.tsx index c205b0c4..0a5fc9d9 100644 --- a/src/features/market-detail/components/market-header.tsx +++ b/src/features/market-detail/components/market-header.tsx @@ -9,9 +9,14 @@ import { GrStatusGood } from 'react-icons/gr'; import { IoWarningOutline, IoEllipsisVertical } from 'react-icons/io5'; import { MdError } from 'react-icons/md'; import { BsArrowUpCircle, BsArrowDownLeftCircle, BsFillLightningFill } from 'react-icons/bs'; +import { GoStarFill, GoStar } from 'react-icons/go'; +import { AiOutlineStop } from 'react-icons/ai'; import { FiExternalLink } from 'react-icons/fi'; import { LuCopy } from 'react-icons/lu'; import { Button } from '@/components/ui/button'; +import { useMarketPreferences } from '@/stores/useMarketPreferences'; +import { useBlacklistedMarkets } from '@/stores/useBlacklistedMarkets'; +import { BlacklistConfirmationModal } from '@/features/markets/components/blacklist-confirmation-modal'; import { DropdownMenu, DropdownMenuTrigger, DropdownMenuContent, DropdownMenuItem } from '@/components/ui/dropdown-menu'; import { TokenIcon } from '@/components/shared/token-icon'; import { Tooltip } from '@/components/ui/tooltip'; @@ -136,10 +141,34 @@ export function MarketHeader({ accrueInterest, }: MarketHeaderProps) { const [isExpanded, setIsExpanded] = useState(false); + const [isBlacklistModalOpen, setIsBlacklistModalOpen] = useState(false); const { short: rateLabel } = useRateLabel(); const { isAprDisplay, showDeveloperOptions } = useAppSettings(); + const { starredMarkets, starMarket, unstarMarket } = useMarketPreferences(); + const { isBlacklisted, addBlacklistedMarket } = useBlacklistedMarkets(); const toast = useStyledToast(); const networkImg = getNetworkImg(network); + const isStarred = starredMarkets.includes(market.uniqueKey); + + const handleToggleStar = () => { + if (isStarred) { + unstarMarket(market.uniqueKey); + toast.success('Market unstarred', 'Removed from favorites'); + } else { + starMarket(market.uniqueKey); + toast.success('Market starred', 'Added to favorites'); + } + }; + + const handleBlacklistClick = () => { + if (!isBlacklisted(market.uniqueKey)) { + setIsBlacklistModalOpen(true); + } + }; + + const handleConfirmBlacklist = () => { + addBlacklistedMarket(market.uniqueKey, market.morphoBlue.chain.id); + }; const handleCopyMarketId = async () => { try { @@ -321,7 +350,7 @@ export function MarketHeader({
- {/* Position Pill + Actions Dropdown */} + {/* Position Pill + Action Buttons + Dropdown */}
{userPosition && ( )} + {/* Prominent Supply & Borrow Buttons */} + + + {/* Advanced Options Dropdown */} } + onClick={handleToggleStar} + startContent={isStarred ? : } > - Supply + {isStarred ? 'Unstar' : 'Star'} } + onClick={handleBlacklistClick} + startContent={} + className={isBlacklisted(market.uniqueKey) ? 'opacity-50 cursor-not-allowed' : ''} + disabled={isBlacklisted(market.uniqueKey)} > - Borrow + {isBlacklisted(market.uniqueKey) ? 'Blacklisted' : 'Blacklist'} {showDeveloperOptions && (
+ ); } diff --git a/src/features/positions/components/supplied-markets-detail.tsx b/src/features/positions/components/supplied-markets-detail.tsx index 7765c959..66f81439 100644 --- a/src/features/positions/components/supplied-markets-detail.tsx +++ b/src/features/positions/components/supplied-markets-detail.tsx @@ -75,20 +75,22 @@ function MarketRow({ position, totalSupply, rateLabel }: { position: MarketPosit style={{ minWidth: '180px' }} >
- + {hasBorrowPosition && ( + + )} + ); + + const wrappedButton = showIndicator && indicator?.tooltip + ? {mainButton} + : mainButton; + + return ( +
+ {wrappedButton} + + + + + + + {dropdownItems.map((item) => ( + + {item.label} + + ))} + + +
+ ); +} diff --git a/src/features/market-detail/components/market-header.tsx b/src/features/market-detail/components/market-header.tsx index 0a5fc9d9..0b3f84b0 100644 --- a/src/features/market-detail/components/market-header.tsx +++ b/src/features/market-detail/components/market-header.tsx @@ -12,8 +12,9 @@ import { BsArrowUpCircle, BsArrowDownLeftCircle, BsFillLightningFill } from 'rea import { GoStarFill, GoStar } from 'react-icons/go'; import { AiOutlineStop } from 'react-icons/ai'; import { FiExternalLink } from 'react-icons/fi'; -import { LuCopy } from 'react-icons/lu'; +import { LuCopy, LuArrowDownToLine, LuRefreshCw } from 'react-icons/lu'; import { Button } from '@/components/ui/button'; +import { SplitActionButton } from '@/components/ui/split-action-button'; import { useMarketPreferences } from '@/stores/useMarketPreferences'; import { useBlacklistedMarkets } from '@/stores/useBlacklistedMarkets'; import { BlacklistConfirmationModal } from '@/features/markets/components/blacklist-confirmation-modal'; @@ -23,12 +24,12 @@ import { Tooltip } from '@/components/ui/tooltip'; import { TooltipContent } from '@/components/shared/tooltip-content'; import { AddressIdentity } from '@/components/shared/address-identity'; import { CampaignBadge } from '@/features/market-detail/components/campaign-badge'; -import { PositionPill } from '@/features/market-detail/components/position-pill'; import { OracleTypeInfo } from '@/features/markets/components/oracle/MarketOracle/OracleTypeInfo'; import { useRateLabel } from '@/hooks/useRateLabel'; import { useStyledToast } from '@/hooks/useStyledToast'; import { useAppSettings } from '@/stores/useAppSettings'; import { convertApyToApr } from '@/utils/rateMath'; +import { formatReadable } from '@/utils/balance'; import { getIRMTitle } from '@/utils/morpho'; import { getNetworkImg, getNetworkName, type SupportedNetworks } from '@/utils/networks'; import { getMarketURL } from '@/utils/external'; @@ -117,6 +118,127 @@ function RiskIcon({ level }: { level: RiskLevel }): React.ReactNode { } } +// Extracted action buttons component for cleaner code +type ActionButtonsProps = { + market: Market; + userPosition: MarketPosition | null; + onSupplyClick: () => void; + onWithdrawClick: () => void; + onBorrowClick: () => void; + onRepayClick: () => void; +}; + +function ActionButtons({ + market, + userPosition, + onSupplyClick, + onWithdrawClick, + onBorrowClick, + onRepayClick, +}: ActionButtonsProps): React.ReactNode { + // Compute position states once + const hasSupply = userPosition !== null && BigInt(userPosition.state.supplyShares) > 0n; + const hasBorrow = userPosition !== null && BigInt(userPosition.state.borrowShares) > 0n; + const hasCollateral = userPosition !== null && BigInt(userPosition.state.collateral) > 0n; + const hasBorrowPosition = hasBorrow || hasCollateral; + + const supplyTooltip = + hasSupply && userPosition ? ( +
+ +
+

Supplied

+

+ {formatReadable(Number(formatUnits(BigInt(userPosition.state.supplyAssets), market.loanAsset.decimals)))}{' '} + {market.loanAsset.symbol} +

+
+
+ ) : undefined; + + const borrowTooltip = + hasBorrowPosition && userPosition ? ( +
+ {hasCollateral && ( +
+ +
+

Collateral

+

+ {formatReadable(Number(formatUnits(BigInt(userPosition.state.collateral), market.collateralAsset.decimals)))}{' '} + {market.collateralAsset.symbol} +

+
+
+ )} + {hasBorrow && ( +
+ +
+

Borrowed

+

+ {formatReadable(Number(formatUnits(BigInt(userPosition.state.borrowAssets), market.loanAsset.decimals)))}{' '} + {market.loanAsset.symbol} +

+
+
+ )} +
+ ) : undefined; + + return ( + <> + } + onClick={onSupplyClick} + indicator={{ show: hasSupply, tooltip: supplyTooltip }} + dropdownItems={[ + { + label: 'Withdraw', + icon: , + onClick: onWithdrawClick, + disabled: !hasSupply, + }, + ]} + /> + + } + onClick={onBorrowClick} + indicator={{ show: hasBorrowPosition, tooltip: borrowTooltip }} + dropdownItems={[ + { + label: 'Repay', + icon: , + onClick: onRepayClick, + disabled: !hasBorrow, + }, + ]} + /> + + ); +} + type MarketHeaderProps = { market: Market; marketId: string; @@ -125,7 +247,9 @@ type MarketHeaderProps = { oraclePrice: string; allWarnings: WarningWithDetail[]; onSupplyClick: () => void; + onWithdrawClick: () => void; onBorrowClick: () => void; + onRepayClick: () => void; accrueInterest: () => void; }; @@ -137,7 +261,9 @@ export function MarketHeader({ oraclePrice, allWarnings, onSupplyClick, + onWithdrawClick, onBorrowClick, + onRepayClick, accrueInterest, }: MarketHeaderProps) { const [isExpanded, setIsExpanded] = useState(false); @@ -350,41 +476,24 @@ export function MarketHeader({
- {/* Position Pill + Action Buttons + Dropdown */} + {/* Action Buttons + Dropdown */}
- {userPosition && ( - - )} - {/* Prominent Supply & Borrow Buttons */} - - + + {/* Advanced Options Dropdown */} diff --git a/src/features/market-detail/components/position-pill.tsx b/src/features/market-detail/components/position-pill.tsx deleted file mode 100644 index 452dedaa..00000000 --- a/src/features/market-detail/components/position-pill.tsx +++ /dev/null @@ -1,127 +0,0 @@ -'use client'; - -import { formatUnits } from 'viem'; -import { Popover, PopoverTrigger, PopoverContent } from '@/components/ui/popover'; -import { TokenIcon } from '@/components/shared/token-icon'; -import { formatReadable } from '@/utils/balance'; -import type { MarketPosition } from '@/utils/types'; - -type PositionRowProps = { - tokenAddress: string; - chainId: number; - symbol: string; - label: string; - amount: number; - textColor?: string; -}; - -function PositionRow({ tokenAddress, chainId, symbol, label, amount, textColor }: PositionRowProps) { - if (amount <= 0) return null; - return ( -
-
- - {label} -
- - {formatReadable(amount)} {symbol} - -
- ); -} - -type PositionPillProps = { - position: MarketPosition; - onSupplyClick?: () => void; - onBorrowClick?: () => void; -}; - -export function PositionPill({ position, onSupplyClick, onBorrowClick }: PositionPillProps) { - const { market, state } = position; - - const supplyAmount = Number(formatUnits(BigInt(state.supplyAssets), market.loanAsset.decimals)); - const borrowAmount = Number(formatUnits(BigInt(state.borrowAssets), market.loanAsset.decimals)); - const collateralAmount = Number(formatUnits(BigInt(state.collateral), market.collateralAsset.decimals)); - - // Check if user has any position - const hasPosition = supplyAmount > 0 || borrowAmount > 0 || collateralAmount > 0; - - if (!hasPosition) { - return null; - } - - return ( - - - - - -
-

Your Position

- - - - - - {/* Action buttons */} - {(onSupplyClick ?? onBorrowClick) && ( -
- {onSupplyClick && ( - - )} - {onBorrowClick && ( - - )} -
- )} -
-
-
- ); -} diff --git a/src/features/market-detail/market-view.tsx b/src/features/market-detail/market-view.tsx index 2c1bbe3c..3b67b7d6 100644 --- a/src/features/market-detail/market-view.tsx +++ b/src/features/market-detail/market-view.tsx @@ -1,5 +1,3 @@ -// eslint-disable @typescript-eslint/prefer-nullish-coalescing - 'use client'; import { useState, useCallback, useMemo } from 'react'; @@ -7,7 +5,6 @@ import { useParams } from 'next/navigation'; import { parseUnits, formatUnits, type Address, encodeFunctionData } from 'viem'; import { useConnection, useSwitchChain } from 'wagmi'; import morphoAbi from '@/abis/morpho'; -import { BorrowModal } from '@/modals/borrow/borrow-modal'; import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'; import { Spinner } from '@/components/ui/spinner'; import Header from '@/components/layout/header/Header'; @@ -50,8 +47,6 @@ function MarketContent() { const { open: openModal } = useModal(); const selectedTab = useMarketDetailPreferences((s) => s.selectedTab); const setSelectedTab = useMarketDetailPreferences((s) => s.setSelectedTab); - const [showBorrowModal, setShowBorrowModal] = useState(false); - const [isRefreshing, setIsRefreshing] = useState(false); const [showTransactionFiltersModal, setShowTransactionFiltersModal] = useState(false); const [showSupplierFiltersModal, setShowSupplierFiltersModal] = useState(false); const [minSupplierShares, setMinSupplierShares] = useState(''); @@ -215,25 +210,11 @@ function MarketContent() { }); }, [suppliersData]); - // Unified refetch function for both market and user position - const handleRefreshAll = useCallback(async () => { - setIsRefreshing(true); - try { - await Promise.all([refetchMarket(), refetchUserPosition()]); - } catch (error) { - console.error('Failed to refresh data:', error); - } finally { - setIsRefreshing(false); - } + // Refetch function for both market and user position + const handleRefresh = useCallback(() => { + void Promise.all([refetchMarket(), refetchUserPosition()]); }, [refetchMarket, refetchUserPosition]); - // Non-async wrapper for components that expect void returns - const handleRefreshAllSync = useCallback(() => { - void handleRefreshAll().catch((error) => { - console.error('Failed to refresh data:', error); - }); - }, [handleRefreshAll]); - // 7. Early returns for loading/error states if (isMarketLoading) { return ( @@ -262,11 +243,33 @@ function MarketContent() { // Handlers for supply/borrow actions const handleSupplyClick = () => { - openModal('supply', { market, position: userPosition, isMarketPage: true, refetch: handleRefreshAllSync, liquiditySourcing }); + openModal('supply', { + market, + position: userPosition, + isMarketPage: true, + refetch: handleRefresh, + liquiditySourcing, + defaultMode: 'supply', + }); + }; + + const handleWithdrawClick = () => { + openModal('supply', { + market, + position: userPosition, + isMarketPage: true, + refetch: handleRefresh, + liquiditySourcing, + defaultMode: 'withdraw', + }); }; const handleBorrowClick = () => { - setShowBorrowModal(true); + openModal('borrow', { market, position: userPosition, refetch: handleRefresh, liquiditySourcing, defaultMode: 'borrow' }); + }; + + const handleRepayClick = () => { + openModal('borrow', { market, position: userPosition, refetch: handleRefresh, liquiditySourcing, defaultMode: 'repay' }); }; const handleAccrueInterest = async () => { @@ -306,22 +309,12 @@ function MarketContent() { oraclePrice={formattedOraclePrice} allWarnings={allWarnings} onSupplyClick={handleSupplyClick} + onWithdrawClick={handleWithdrawClick} onBorrowClick={handleBorrowClick} + onRepayClick={handleRepayClick} accrueInterest={handleAccrueInterest} /> - {showBorrowModal && ( - - )} - {showTransactionFiltersModal && ( Date: Thu, 29 Jan 2026 01:15:31 +0800 Subject: [PATCH 8/8] chore: remove redundant --- src/features/market-detail/market-view.tsx | 4 +-- .../components/supplied-markets-detail.tsx | 16 ----------- src/modals/borrow/borrow-modal-global.tsx | 27 +++++++++++++------ src/stores/useModalStore.ts | 1 - 4 files changed, 21 insertions(+), 27 deletions(-) diff --git a/src/features/market-detail/market-view.tsx b/src/features/market-detail/market-view.tsx index 3b67b7d6..aae5a4e8 100644 --- a/src/features/market-detail/market-view.tsx +++ b/src/features/market-detail/market-view.tsx @@ -265,11 +265,11 @@ function MarketContent() { }; const handleBorrowClick = () => { - openModal('borrow', { market, position: userPosition, refetch: handleRefresh, liquiditySourcing, defaultMode: 'borrow' }); + openModal('borrow', { market, refetch: handleRefresh, liquiditySourcing, defaultMode: 'borrow' }); }; const handleRepayClick = () => { - openModal('borrow', { market, position: userPosition, refetch: handleRefresh, liquiditySourcing, defaultMode: 'repay' }); + openModal('borrow', { market, refetch: handleRefresh, liquiditySourcing, defaultMode: 'repay' }); }; const handleAccrueInterest = async () => { diff --git a/src/features/positions/components/supplied-markets-detail.tsx b/src/features/positions/components/supplied-markets-detail.tsx index 6143248b..d59b35d9 100644 --- a/src/features/positions/components/supplied-markets-detail.tsx +++ b/src/features/positions/components/supplied-markets-detail.tsx @@ -22,7 +22,6 @@ function MarketRow({ position, totalSupply, rateLabel }: { position: MarketPosit const { open } = useModal(); const suppliedAmount = Number(formatBalance(position.state.supplyAssets, position.market.loanAsset.decimals)); const percentageOfPortfolio = totalSupply > 0 ? (suppliedAmount / totalSupply) * 100 : 0; - const hasBorrowPosition = BigInt(position.state.borrowAssets) > 0n || BigInt(position.state.collateral) > 0n; return (
- {hasBorrowPosition && ( - - )}