- {steps
- .filter((step) => step.key !== 'idle')
- .map((step, index) => (
-
-
- {getStepStatus(step.key as RebalanceStepType) === 'done' && (
-
- )}
- {getStepStatus(step.key as RebalanceStepType) === 'current' && (
-
- )}
- {getStepStatus(step.key as RebalanceStepType) === 'undone' && (
+
+ }
+ onClose={() => onOpenChange(false)}
+ />
+
+ {steps
+ .filter((step) => step.key !== 'idle')
+ .map((step) => {
+ const status = getStepStatus(step.key as RebalanceStepType);
+ return (
+
+
+ {status === 'done' ? (
+
+ ) : status === 'current' ? (
+
+ ) : (
)}
-
-
{step.label}
- {currentStep === step.key && step.detail && (
-
- {step.detail}
-
+
+
{step.label}
+ {status === 'current' && step.detail && (
+
{step.detail}
)}
- {index < steps.length - 2 &&
}
- ))}
-
-
-
+ );
+ })}
+
+
);
}
diff --git a/app/positions/components/agent/Main.tsx b/app/positions/components/agent/Main.tsx
deleted file mode 100644
index 02203e8b..00000000
--- a/app/positions/components/agent/Main.tsx
+++ /dev/null
@@ -1,169 +0,0 @@
-import { Tooltip } from '@heroui/react';
-import { motion } from 'framer-motion';
-import Image from 'next/image';
-import Link from 'next/link';
-import { GrStatusGood } from 'react-icons/gr';
-import { Button } from '@/components/common';
-import { TokenIcon } from '@/components/TokenIcon';
-import { TooltipContent } from '@/components/TooltipContent';
-import { useMarkets } from '@/contexts/MarketsContext';
-import { getExplorerURL } from '@/utils/external';
-import { findAgent } from '@/utils/monarch-agent';
-import { getNetworkName } from '@/utils/networks';
-import { UserRebalancerInfo } from '@/utils/types';
-
-const img = require('../../../../src/imgs/agent/agent-detailed.png') as string;
-
-type MainProps = {
- onNext: () => void;
- userRebalancerInfos: UserRebalancerInfo[];
-};
-
-export function Main({ onNext, userRebalancerInfos }: MainProps) {
- const { markets } = useMarkets();
-
- const activeAgentInfos = userRebalancerInfos
- .map((info) => ({
- info,
- agent: findAgent(info.rebalancer),
- }))
- .filter((item) => item.agent !== undefined);
-
- if (activeAgentInfos.length === 0) {
- return (
-
-
No active agent found for the configured networks.
-
- Configure Agent
-
-
- );
- }
-
- return (
-
-
-
-
-
- {activeAgentInfos.map(({ info, agent }) => {
- if (!agent) return null;
-
- const networkName = getNetworkName(info.network);
-
- const authorizedMarkets = markets.filter(
- (market) =>
- market.morphoBlue.chain.id === info.network &&
- info.marketCaps.some(
- (cap) => cap.marketId.toLowerCase() === market.uniqueKey.toLowerCase(),
- ),
- );
-
- const loanAssetGroups = authorizedMarkets.reduce(
- (acc, market) => {
- const address = market.loanAsset.address.toLowerCase();
- if (!acc[address]) {
- acc[address] = {
- address,
- chainId: market.morphoBlue.chain.id,
- markets: [],
- symbol: market.loanAsset.symbol,
- };
- }
- acc[address].markets.push(market);
- return acc;
- },
- {} as Record<
- string,
- { address: string; chainId: number; symbol: string; markets: typeof authorizedMarkets }
- >,
- );
-
- const explorerUrl = getExplorerURL(agent.address, info.network);
-
- return (
-
-
-
-
{agent.name}
-
- {networkName}
-
-
- {agent.address.slice(0, 6) + '...' + agent.address.slice(-4)}
-
-
-
}
- title="Agent Active"
- detail={`Agent is active on ${networkName}`}
- />
- }
- >
-
-
-
-
-
-
-
Strategy
-
{agent.strategyDescription}
-
-
-
-
Monitoring Positions
-
- {Object.values(loanAssetGroups).map(
- ({ address, chainId, markets: marketsForLoanAsset, symbol }) => {
- return (
-
-
-
- {symbol ?? 'Unknown'} ({marketsForLoanAsset.length})
-
-
- );
- },
- )}
- {Object.values(loanAssetGroups).length === 0 && (
-
- No markets currently configured for this agent.
-
- )}
-
-
-
-
- );
- })}
-
-
- Update Settings
-
-
- );
-}
diff --git a/app/positions/components/agent/SetupAgent.tsx b/app/positions/components/agent/SetupAgent.tsx
deleted file mode 100644
index be40210f..00000000
--- a/app/positions/components/agent/SetupAgent.tsx
+++ /dev/null
@@ -1,516 +0,0 @@
-import { useState, useMemo, useCallback, useEffect } from 'react';
-import {
- Checkbox,
- Dropdown,
- DropdownTrigger,
- DropdownMenu,
- DropdownItem,
- SharedSelection,
-} from '@heroui/react';
-import { ChevronDownIcon, ChevronUpIcon } from '@radix-ui/react-icons';
-import { motion, AnimatePresence } from 'framer-motion';
-import Image from 'next/image';
-import { formatUnits, maxUint256 } from 'viem';
-import { AgentSetupProcessModal } from '@/components/AgentSetupProcessModal';
-import { Button } from '@/components/common/Button';
-import { MarketInfoBlockCompact } from '@/components/common/MarketInfoBlock';
-import { TokenIcon } from '@/components/TokenIcon';
-import { MarketCap, useAuthorizeAgent } from '@/hooks/useAuthorizeAgent';
-import { findAgent, KnownAgents } from '@/utils/monarch-agent';
-import {
- getNetworkName,
- getNetworkImg,
- SupportedNetworks,
- isAgentAvailable,
-} from '@/utils/networks';
-import { Market, MarketPosition, UserRebalancerInfo } from '@/utils/types';
-
-type MarketGroup = {
- network: SupportedNetworks;
- loanAsset: {
- address: string;
- symbol: string;
- };
-
- // setup already: this includes markets that have been authorized for agent
- authorizedMarkets: Market[];
- // have not setup yet, but currently acitve so should be consider priority
- activeMarkets: Market[];
- historicalMarkets: Market[];
- otherMarkets: Market[];
-};
-
-type SetupAgentProps = {
- positions: MarketPosition[];
- allMarkets: Market[];
- userRebalancerInfos: UserRebalancerInfo[];
- pendingCaps: MarketCap[];
- addToPendingCaps: (market: Market, cap: bigint) => void;
- removeFromPendingCaps: (market: Market) => void;
- onBack: () => void;
- onNext: () => void;
- account?: string;
-};
-
-// Helper component for market rows
-function MarketRow({
- market,
- isSelected,
- onToggle,
- isDisabled,
-}: {
- market: Market;
- isSelected: boolean;
- onToggle: (selected: boolean) => void;
- isDisabled: boolean;
-}) {
- return (
-
-
- !isDisabled && onToggle(selected)}
- size="sm"
- color="primary"
- className="mr-0"
- isDisabled={isDisabled}
- />
-
-
-
- );
-}
-
-export function SetupAgent({
- positions,
- allMarkets,
- userRebalancerInfos,
- pendingCaps,
- addToPendingCaps,
- removeFromPendingCaps,
- onBack,
- onNext,
-}: SetupAgentProps) {
- const [hasPreselected, setHasPreselected] = useState(false);
- const [expandedGroups, setExpandedGroups] = useState
([]);
- const [showAllMarkets, setShowAllMarkets] = useState(false);
- const [showProcessModal, setShowProcessModal] = useState(false);
-
- const defaultNetwork = useMemo(() => {
- const networks = Array.from(new Set(positions.map((p) => p.market.morphoBlue.chain.id)))
- .filter(isAgentAvailable)
- .sort();
- return networks[0] ?? SupportedNetworks.Base;
- }, [positions]);
-
- const [targetNetwork, setTargetNetwork] = useState(defaultNetwork);
-
- const currentUserRebalancerInfo = useMemo(() => {
- return userRebalancerInfos.find((info) => info.network === targetNetwork);
- }, [userRebalancerInfos, targetNetwork]);
-
- const availableNetworks = useMemo(() => {
- const networkSet = new Set();
- positions.forEach((p) => {
- if (isAgentAvailable(p.market.morphoBlue.chain.id)) {
- networkSet.add(p.market.morphoBlue.chain.id);
- }
- });
- userRebalancerInfos.forEach((info) => {
- if (isAgentAvailable(info.network)) {
- networkSet.add(info.network);
- }
- });
- return Array.from(networkSet).sort();
- }, [positions, userRebalancerInfos]);
-
- useEffect(() => {
- if (!availableNetworks.includes(targetNetwork)) {
- setTargetNetwork(availableNetworks[0] ?? defaultNetwork);
- setHasPreselected(false);
- }
- }, [availableNetworks, targetNetwork, defaultNetwork]);
-
- const isInPending = (market: Market) =>
- pendingCaps.some((cap) => cap.market.uniqueKey === market.uniqueKey && cap.amount > 0);
-
- const isInPendingRemove = (market: Market) =>
- pendingCaps.some(
- (cap) => cap.market.uniqueKey === market.uniqueKey && cap.amount === BigInt(0),
- );
-
- const groupedMarkets = useMemo(() => {
- const groups: Record = {};
- const activeLoanAssets = new Set();
-
- positions.forEach((position) => {
- if (BigInt(position.state.supplyShares) > 0) {
- activeLoanAssets.add(position.market.loanAsset.address.toLowerCase());
- }
- });
-
- allMarkets.forEach((market) => {
- if (market.morphoBlue.chain.id !== targetNetwork) return;
-
- const loanAssetKey = market.loanAsset.address.toLowerCase();
-
- if (!activeLoanAssets.has(loanAssetKey)) return;
-
- if (!groups[loanAssetKey]) {
- groups[loanAssetKey] = {
- network: market.morphoBlue.chain.id,
- loanAsset: market.loanAsset,
- authorizedMarkets: [],
- activeMarkets: [],
- historicalMarkets: [],
- otherMarkets: [],
- };
- }
-
- const authorized = currentUserRebalancerInfo?.marketCaps.find(
- (c) => c.marketId.toLowerCase() === market.uniqueKey.toLowerCase(),
- )?.cap;
-
- if (authorized) {
- groups[loanAssetKey].authorizedMarkets.push(market);
- return;
- }
-
- const position = positions.find((p) => p.market.uniqueKey === market.uniqueKey);
- if (position) {
- const supply = parseFloat(
- formatUnits(BigInt(position.state.supplyAssets), position.market.loanAsset.decimals),
- );
- if (supply > 0) {
- groups[loanAssetKey].activeMarkets.push(market);
- } else {
- groups[loanAssetKey].historicalMarkets.push(market);
- }
- } else {
- groups[loanAssetKey].otherMarkets.push(market);
- }
- });
-
- return Object.values(groups).sort((a, b) => {
- return a.loanAsset.symbol.localeCompare(b.loanAsset.symbol);
- });
- }, [allMarkets, positions, currentUserRebalancerInfo, targetNetwork]);
-
- useEffect(() => {
- let mounted = true;
- if (!hasPreselected && groupedMarkets.length > 0 && targetNetwork) {
- groupedMarkets.forEach((group) => {
- group.activeMarkets.forEach((market) => {
- if (!isInPending(market) && !isInPendingRemove(market)) {
- addToPendingCaps(market, maxUint256);
- }
- });
- });
- if (mounted) {
- setHasPreselected(true);
- }
- }
-
- return () => {
- mounted = false;
- };
- }, [
- hasPreselected,
- groupedMarkets,
- isInPending,
- isInPendingRemove,
- addToPendingCaps,
- targetNetwork,
- ]);
-
- const toggleGroup = (key: string) => {
- setExpandedGroups((prev) =>
- prev.includes(key) ? prev.filter((k) => k !== key) : [...prev, key],
- );
- };
-
- const hasSetupAgent =
- !!currentUserRebalancerInfo && findAgent(currentUserRebalancerInfo.rebalancer) !== undefined;
-
- const agent = useMemo(() => {
- return currentUserRebalancerInfo
- ? findAgent(currentUserRebalancerInfo.rebalancer)
- : findAgent(KnownAgents.MAX_APY ?? '');
- }, [currentUserRebalancerInfo]);
-
- const { executeBatchSetupAgent, currentStep } = useAuthorizeAgent(
- KnownAgents.MAX_APY,
- pendingCaps.filter((cap) => cap.market.morphoBlue.chain.id === targetNetwork),
- targetNetwork,
- onNext,
- );
-
- const handleExecute = useCallback(() => {
- setShowProcessModal(true);
- void executeBatchSetupAgent(() => setShowProcessModal(false));
- }, [executeBatchSetupAgent]);
-
- const handleNetworkChange = (keys: SharedSelection) => {
- const selectedKey = Array.from(keys)[0];
- const newNetwork = Number(selectedKey) as SupportedNetworks;
- if (newNetwork !== targetNetwork) {
- setTargetNetwork(newNetwork);
- setHasPreselected(false);
- setExpandedGroups([]);
- }
- };
-
- return (
-
- {!hasSetupAgent && agent && (
-
-
{agent.name}
-
{agent.strategyDescription}
-
- )}
-
-
- The agent can only reallocate funds among approved markets for the selected network.
-
- {availableNetworks.length > 1 && (
-
-
-
-
- {getNetworkName(targetNetwork) ?? 'Select Network'}
-
-
-
-
- {availableNetworks.map((networkId) => (
-
-
-
- {getNetworkName(networkId) ?? `Network ${networkId}`}
-
-
- ))}
-
-
- )}
-
-
-
- {groupedMarkets.length === 0 && (
-
- No active supplied markets found for {getNetworkName(targetNetwork)}.
-
- )}
- {groupedMarkets.map((group) => {
- const groupKey = `${group.loanAsset.address}-${group.network}`;
- const isExpanded = expandedGroups.includes(groupKey);
-
- const numMarketsToAdd = [
- ...group.activeMarkets,
- ...group.historicalMarkets,
- ...group.otherMarkets,
- ].filter(isInPending).length;
-
- const numMarketsToRemove = group.authorizedMarkets.filter(isInPendingRemove).length;
-
- return (
-
- {showProcessModal && (
-
setShowProcessModal(false)}
- />
- )}
-
- toggleGroup(groupKey)}
- className="flex w-full items-center justify-between px-4 py-3 transition-colors hover:bg-content2"
- >
-
-
- {group.loanAsset.symbol} Markets
-
- Authorized: {group.authorizedMarkets.length}{' '}
- {numMarketsToAdd > 0 && (
-
- (+ {numMarketsToAdd})
-
- )}
- {numMarketsToRemove > 0 && (
-
- (- {numMarketsToRemove})
-
- )}{' '}
- market
- {group.authorizedMarkets.length !== 1 ? 's' : ''}
-
-
-
-
- {isExpanded ? : }
-
-
-
-
- {isExpanded && (
-
-
- {group.authorizedMarkets.length > 0 && (
-
-
Authorized
- {group.authorizedMarkets.map((market) => (
-
- selected
- ? removeFromPendingCaps(market)
- : addToPendingCaps(market, BigInt(0))
- }
- isDisabled={false}
- />
- ))}
-
- )}
-
- {group.activeMarkets.length > 0 && (
-
-
Active Markets
- {group.activeMarkets.map((market) => (
-
- selected
- ? addToPendingCaps(market, maxUint256)
- : removeFromPendingCaps(market)
- }
- isDisabled={false}
- />
- ))}
-
- )}
-
- {group.historicalMarkets.length > 0 && (
-
-
Previously Used
- {group.historicalMarkets.map((market) => (
-
- selected
- ? addToPendingCaps(market, maxUint256)
- : removeFromPendingCaps(market)
- }
- isDisabled={false}
- />
- ))}
-
- )}
-
- {group.otherMarkets.length > 0 && !showAllMarkets && (
-
setShowAllMarkets(true)}
- className="w-full"
- >
- Show More Markets
-
- )}
-
- {showAllMarkets && group.otherMarkets.length > 0 && (
-
-
Other Available
- {group.otherMarkets.map((market) => (
-
- selected
- ? addToPendingCaps(market, maxUint256)
- : removeFromPendingCaps(market)
- }
- isDisabled={false}
- />
- ))}
-
- )}
-
-
- )}
-
-
- );
- })}
-
-
-
-
- Back
-
- cap.market.morphoBlue.chain.id === targetNetwork).length ===
- 0 || !targetNetwork
- }
- >
- Execute
-
-
-
- );
-}
diff --git a/app/positions/components/agent/SetupAgentModal.tsx b/app/positions/components/agent/SetupAgentModal.tsx
deleted file mode 100644
index 5c1820a6..00000000
--- a/app/positions/components/agent/SetupAgentModal.tsx
+++ /dev/null
@@ -1,225 +0,0 @@
-import { useState } from 'react';
-import { Modal, ModalContent, ModalHeader } from '@heroui/react';
-import { motion, AnimatePresence } from 'framer-motion';
-import { Address } from 'viem';
-import { useMarkets } from '@/contexts/MarketsContext';
-import { MarketCap } from '@/hooks/useAuthorizeAgent';
-import useUserPositions from '@/hooks/useUserPositions';
-import { findAgent } from '@/utils/monarch-agent';
-import { isAgentAvailable } from '@/utils/networks';
-import { Market, UserRebalancerInfo } from '@/utils/types';
-import { Main as MainContent } from './Main';
-import { SetupAgent } from './SetupAgent';
-import { Success as SuccessContent } from './Success';
-import { Welcome as WelcomeContent } from './Welcome';
-
-export enum SetupStep {
- Main = 'main',
- Setup = 'setup',
- Success = 'success',
-}
-
-const SETUP_STEPS = [
- {
- id: SetupStep.Main,
- title: 'Welcome to Monarch Agent',
- description: 'Bee-bee-bee, Monarch Agent is here!',
- },
- {
- id: SetupStep.Setup,
- title: 'Setup Markets',
- description: 'Choose which markets you want Monarch Agent to monitor',
- },
- {
- id: SetupStep.Success,
- title: 'Setup Complete',
- description: 'Your Monarch Agent is ready to go',
- },
-] as const;
-
-function StepIndicator({ currentStep }: { currentStep: SetupStep }) {
- return (
-
- {SETUP_STEPS.map((step, index) => {
- const isCurrent = step.id === currentStep;
- const isPast = SETUP_STEPS.findIndex((s) => s.id === currentStep) > index;
-
- return (
-
- );
- })}
-
- );
-}
-
-type SetupAgentModalProps = {
- account?: Address;
- isOpen: boolean;
- onClose: () => void;
- userRebalancerInfos: UserRebalancerInfo[];
-};
-
-export function SetupAgentModal({
- account,
- isOpen,
- onClose,
- userRebalancerInfos,
-}: SetupAgentModalProps) {
- const [currentStep, setCurrentStep] = useState(SetupStep.Main);
- const [pendingCaps, setPendingCaps] = useState([]);
-
- const { data: positions } = useUserPositions(account, true);
-
- // Use computed markets based on user setting
- const { allMarkets } = useMarkets();
-
- const currentStepIndex = SETUP_STEPS.findIndex((s) => s.id === currentStep);
-
- const handleNext = () => {
- setCurrentStep((prev) => {
- const currentIndex = SETUP_STEPS.findIndex((step) => step.id === prev);
- const nextStep = SETUP_STEPS[currentIndex + 1];
- return nextStep?.id || prev;
- });
- };
-
- const handleBack = () => {
- setCurrentStep((prev) => {
- const currentIndex = SETUP_STEPS.findIndex((step) => step.id === prev);
- const prevStep = SETUP_STEPS[currentIndex - 1];
- return prevStep?.id || prev;
- });
- };
-
- const handleReset = () => {
- setCurrentStep(SetupStep.Main);
- };
-
- const handleClose = () => {
- onClose();
- // Reset step after modal is closed
- setTimeout(() => {
- setCurrentStep(SetupStep.Main);
- }, 300);
- };
-
- const addToPendingCaps = (market: Market, cap: bigint) => {
- setPendingCaps((prev) => [
- ...prev,
- {
- market,
- amount: cap,
- },
- ]);
- };
-
- const removeFromPendingCaps = (market: Market) => {
- setPendingCaps((prev) => prev.filter((cap) => cap.market.uniqueKey !== market.uniqueKey));
- };
-
- const hasSetupAgent = userRebalancerInfos.some(
- (info) => findAgent(info.rebalancer) !== undefined,
- );
-
- return (
-
-
-
-
-
{SETUP_STEPS[currentStepIndex].title}
-
- {SETUP_STEPS[currentStepIndex].description}
-
-
-
-
-
-
-
- {/* Step Content */}
- {currentStep === SetupStep.Main && !hasSetupAgent && (
-
- )}
- {currentStep === SetupStep.Main && hasSetupAgent && (
-
- )}
- {currentStep === SetupStep.Setup && (
- isAgentAvailable(m.morphoBlue.chain.id))}
- userRebalancerInfos={userRebalancerInfos}
- pendingCaps={pendingCaps}
- addToPendingCaps={addToPendingCaps}
- removeFromPendingCaps={removeFromPendingCaps}
- onNext={handleNext}
- onBack={handleBack}
- account={account}
- />
- )}
- {currentStep === SetupStep.Success && (
-
- )}
-
-
-
-
- {/* Footer with Step Indicator */}
-
-
-
-
-
- );
-}
diff --git a/app/positions/components/agent/Success.tsx b/app/positions/components/agent/Success.tsx
deleted file mode 100644
index b5d85279..00000000
--- a/app/positions/components/agent/Success.tsx
+++ /dev/null
@@ -1,41 +0,0 @@
-import { motion } from 'framer-motion';
-import Image from 'next/image';
-import { Button } from '@/components/common';
-
-const img = require('../../../../src/imgs/agent/agent.png') as string;
-
-type SuccessProps = {
- onClose: () => void;
- onDone: () => void;
-};
-
-export function Success({ onClose, onDone }: SuccessProps) {
- const handleDone = () => {
- onDone();
- onClose();
- };
-
- return (
-
-
-
-
-
-
-
Setup Complete!
-
- Your Monarch Agent is now ready to manage your positions. You can always update your
- settings later.
-
-
-
-
- Done
-
-
- );
-}
diff --git a/app/positions/components/agent/Welcome.tsx b/app/positions/components/agent/Welcome.tsx
deleted file mode 100644
index 534e4be0..00000000
--- a/app/positions/components/agent/Welcome.tsx
+++ /dev/null
@@ -1,36 +0,0 @@
-import { motion } from 'framer-motion';
-import Image from 'next/image';
-import { Button } from '@/components/common';
-
-const img = require('../../../../src/imgs/agent/agent-detailed.png') as string;
-
-type WelcomeProps = {
- onNext: () => void;
-};
-
-export function Welcome({ onNext }: WelcomeProps) {
- return (
-
-
-
-
-
-
-
Automate Your Position Management
-
- Monarch Agent is a smart automation tool that helps you manage your positions across
- different markets. It can automatically reallocate your assets to optimize your returns
- while maintaining your risk preferences.
-
-
-
-
- Setup Agent
-
-
- );
-}
diff --git a/app/positions/components/onboarding/Modal.tsx b/app/positions/components/onboarding/OnboardingModal.tsx
similarity index 59%
rename from app/positions/components/onboarding/Modal.tsx
rename to app/positions/components/onboarding/OnboardingModal.tsx
index 05d456e2..5b2b21fc 100644
--- a/app/positions/components/onboarding/Modal.tsx
+++ b/app/positions/components/onboarding/OnboardingModal.tsx
@@ -1,6 +1,6 @@
-import { Modal, ModalContent, ModalHeader, Button } from '@heroui/react';
import { motion, AnimatePresence } from 'framer-motion';
-import { RxCross2 } from 'react-icons/rx';
+import { FaCompass } from 'react-icons/fa';
+import { Modal, ModalBody, ModalFooter, ModalHeader } from '@/components/common/Modal';
import { AssetSelection } from './AssetSelection';
import { MarketSelectionOnboarding } from './MarketSelectionOnboarding';
import { useOnboarding } from './OnboardingContext';
@@ -42,7 +42,7 @@ function StepIndicator({ currentStep }: { currentStep: string }) {
);
}
-export function OnboardingModal({ isOpen, onClose }: { isOpen: boolean; onClose: () => void }) {
+export function OnboardingModal({ isOpen, onOpenChange }: { isOpen: boolean; onOpenChange: (open: boolean) => void }) {
const { step } = useOnboarding();
const currentStepIndex = ONBOARDING_STEPS.findIndex((s) => s.id === step);
@@ -51,54 +51,39 @@ export function OnboardingModal({ isOpen, onClose }: { isOpen: boolean; onClose:
return (
-
- {/* Header */}
-
-
-
- {ONBOARDING_STEPS[currentStepIndex].title}
-
-
- {ONBOARDING_STEPS[currentStepIndex].description}
-
-
-
-
-
-
+ }
+ onClose={() => onOpenChange(false)}
+ />
- {/* Content */}
-
+
+
-
+ onOpenChange(false)} />
+
- {/* Footer with Step Indicator */}
-
-
-
-
+
+
+
);
}
diff --git a/app/rewards/components/RewardContent.tsx b/app/rewards/components/RewardContent.tsx
index f25802f9..fc713f2b 100644
--- a/app/rewards/components/RewardContent.tsx
+++ b/app/rewards/components/RewardContent.tsx
@@ -294,7 +294,7 @@ export default function Rewards() {
setShowProcessModal(false)}
+ onOpenChange={setShowProcessModal}
/>
)}
diff --git a/app/settings/page.tsx b/app/settings/page.tsx
index 0e848311..099f6137 100644
--- a/app/settings/page.tsx
+++ b/app/settings/page.tsx
@@ -55,7 +55,7 @@ export default function SettingsPage() {
return (
-
+
Settings
@@ -249,7 +249,7 @@ export default function SettingsPage() {
{/* Trusted Vaults Modal */}
setIsTrustedVaultsModalOpen(!isTrustedVaultsModalOpen)}
+ onOpenChange={setIsTrustedVaultsModalOpen}
userTrustedVaults={userTrustedVaults}
setUserTrustedVaults={setUserTrustedVaults}
/>
@@ -257,7 +257,7 @@ export default function SettingsPage() {
{/* Blacklisted Markets Modal */}
setIsBlacklistedMarketsModalOpen(!isBlacklistedMarketsModalOpen)}
+ onOpenChange={setIsBlacklistedMarketsModalOpen}
/>
);
diff --git a/docs/Styling.md b/docs/Styling.md
index 41980dc8..6eefe0aa 100644
--- a/docs/Styling.md
+++ b/docs/Styling.md
@@ -5,9 +5,237 @@
Use these shared components instead of raw HTML elements:
- `Button`: Import from `@/components/common/Button` for all clickable actions
-- `Modal`: For all modal dialogs
+- `Modal`: For **all** modal dialogs (always import from `@/components/common/Modal`)
- `Card`: For contained content sections
+## Modal Guidelines
+
+**IMPORTANT**: Always use our custom Modal components from `@/components/common/Modal`. Never import HeroUI modals directly. The shared wrapper applies Monarch typography, corner radius, background, blur, and z-index rules automatically.
+
+All modals MUST follow consistent styling standards for typography, spacing, and structure. There are two modal patterns based on use case.
+
+### Modal Types
+
+**1. Standard Modal** - For settings, management, and primary workflows
+- Large settings modals (Trusted Vaults, Blacklisted Markets, Market Settings)
+- Transaction modals (Rebalance, Market Selection)
+- Onboarding and setup flows
+
+**2. Compact Modal** - For filters, confirmations, and secondary dialogs
+- Filter modals (Supply Asset Filter)
+- Confirmation dialogs (Blacklist Confirmation)
+
+### Using Our Modal Components
+
+Import the primitives from our shared entry point (and nowhere else):
+
+```tsx
+import { Modal, ModalHeader, ModalBody, ModalFooter } from '@/components/common/Modal';
+import { Button } from '@/components/common/Button';
+```
+
+### Standard Modal Pattern
+
+Use this pattern for primary workflows, settings, and management interfaces:
+
+```tsx
+
+
+
+
+ {/* Modal content - spacing and font-zen applied automatically */}
+
+
+
+ Cancel
+ Confirm
+
+
+```
+
+**With Icon Support:**
+
+```tsx
+import { TokenIcon } from '@/components/TokenIcon';
+
+
+
+ }
+ />
+ {/* content */}
+
+```
+
+**With Actions in Header:**
+
+```tsx
+
+ Refresh
+
+ }
+/>
+```
+
+**Auto-Applied Standards:**
+- **Spacing**: Automatically applied based on variant (no extra padding needed)
+- **Typography**: `font-zen` and text scales handled for you
+- **Icon + actions slots**: Header provides `mainIcon`, `actions`, and an always-on close button
+- **Z-Index**: Managed through named layers (base, process, selection, settings)
+- **Backdrops**: Unified blur/opacity, so you get consistent overlays everywhere
+- **Portal**: All modals render to `document.body` to avoid stacking bugs
+
+### Compact Modal Pattern
+
+Use this pattern for filters, confirmations, and quick actions:
+
+```tsx
+
+
+
+
+ {/* Modal content - tighter spacing applied automatically */}
+
+
+
+ Cancel
+ Apply
+
+
+```
+
+**Auto-Applied Differences from Standard:**
+- **Smaller padding**: `px-6 pt-4` vs `px-10 pt-6`
+- **Smaller title**: `text-base` vs `text-lg`
+- **Tighter spacing**: `gap-4` vs `gap-5`
+
+### Z-Index Management
+
+Our Modal component manages z-index automatically through named layers. This prevents conflicts when multiple modals are open:
+
+```tsx
+// Z-Index Layers (from lowest to highest):
+zIndex="base" // z-50 - Standard modals (Supply, Borrow, Campaign)
+zIndex="process" // z-1100 - Process/transaction modals
+zIndex="selection" // z-2200 - Market/item selection modals
+zIndex="settings" // z-2300 - Settings modals (HIGHEST - always on top)
+```
+
+**Usage Example:**
+
+```tsx
+// Settings modal (should be on top of everything)
+
+
+ {/* ... */}
+
+
+// Market selection modal (opened from settings)
+
+
+ {/* ... */}
+
+
+// Base modal (standard use case)
+
+
+ {/* ... */}
+
+```
+
+
+### Typography Rules
+
+Typography is automatically handled by our Modal components. You don't need to specify font weights or sizes manually - just use the title/description props.
+
+**IMPORTANT**: Never manually add bold or semibold font weights in modal headings/labels; rely on the shared components.
+
+```tsx
+// ✅ Correct - let the component handle typography
+
+
+// ❌ Incorrect - don't override with manual styles
+Settings} />
+```
+
+Use color and size to create hierarchy, not font weight:
+- **Primary text**: `text-primary`
+- **Secondary text**: `text-secondary`
+- **Title size**: Automatically set based on variant
+- **Description size**: Automatically set to `text-sm`
+
+### Section Headers in Modal Body
+
+For section headers within modal content, use consistent styling:
+
+```tsx
+// ✅ Correct
+Section Title
+
+// ❌ Incorrect
+Section Title
+Section Title
+```
+
+### Custom Modals (Non-HeroUI)
+
+For custom modals using `framer-motion`, apply `font-zen` to the outer container:
+
+```tsx
+// ✅ Correct - font-zen on the modal overlay
+
+
+ {/* Modal content */}
+
+
+
+// With framer-motion
+
+
+
+ {/* Modal content */}
+
+
+
+```
+
## Component Guidelines
### Rounding
diff --git a/src/components/AgentSetupProcessModal.tsx b/src/components/AgentSetupProcessModal.tsx
deleted file mode 100644
index 3b98cd6f..00000000
--- a/src/components/AgentSetupProcessModal.tsx
+++ /dev/null
@@ -1,104 +0,0 @@
-import React from 'react';
-import { Cross1Icon } from '@radix-ui/react-icons';
-import { motion, AnimatePresence } from 'framer-motion';
-import { FaCheckCircle, FaCircle } from 'react-icons/fa';
-import { AuthorizeAgentStep } from '@/hooks/useAuthorizeAgent';
-
-type AgentSetupModalProps = {
- currentStep: AuthorizeAgentStep;
- onClose: () => void;
-};
-
-const steps = [
- {
- key: AuthorizeAgentStep.Authorize,
- label: 'Authorize Monarch Agent',
- detail:
- 'Sign a signature to authorize the Monarch Agent contract to reallocate your positions.',
- },
- {
- key: AuthorizeAgentStep.Execute,
- label: 'Execute Transaction',
- detail: 'Confirm transaction in wallet to complete the setup',
- },
-];
-
-export function AgentSetupProcessModal({
- currentStep,
- onClose,
-}: AgentSetupModalProps): JSX.Element {
- const getStepStatus = (stepKey: string) => {
- const currentIndex = steps.findIndex((step) => step.key === currentStep);
- const stepIndex = steps.findIndex((step) => step.key === stepKey);
-
- if (stepIndex < currentIndex) {
- return 'done';
- }
- if (stepKey === currentStep) {
- return 'current';
- }
- return 'undone';
- };
-
- return (
-
-
-
-
-
-
-
-
-
Setup Monarch Agent
-
Setup Rebalance market caps
-
- {/* Steps */}
-
- {steps.map((step) => {
- const status = getStepStatus(step.key);
- return (
-
-
- {status === 'done' ? (
-
- ) : status === 'current' ? (
-
- ) : (
-
- )}
-
-
-
{step.label}
-
{step.detail}
-
-
- );
- })}
-
-
-
-
-
- );
-}
diff --git a/src/components/Borrow/AddCollateralAndBorrow.tsx b/src/components/Borrow/AddCollateralAndBorrow.tsx
index 229b6e00..dff270c1 100644
--- a/src/components/Borrow/AddCollateralAndBorrow.tsx
+++ b/src/components/Borrow/AddCollateralAndBorrow.tsx
@@ -255,8 +255,8 @@ export function AddCollateralAndBorrow({
{/* Collateral Input Section */}
-
Add Collateral
-
+
Add Collateral
+
Balance:{' '}
{useEth
? formatBalance(ethBalance ? ethBalance : '0', 18)
@@ -270,7 +270,7 @@ export function AddCollateralAndBorrow({
{isWrappedNativeToken(market.collateralAsset.address, market.morphoBlue.chain.id) && (
-
Use {getNativeTokenSymbol(market.morphoBlue.chain.id)} instead
+
Use {getNativeTokenSymbol(market.morphoBlue.chain.id)} instead
-
Borrow
-
+
Borrow
+
Available:{' '}
{formatReadable(
formatBalance(market.state.liquidityAssets, market.loanAsset.decimals),
diff --git a/src/components/BorrowModal.tsx b/src/components/BorrowModal.tsx
index 48933678..cec97ed3 100644
--- a/src/components/BorrowModal.tsx
+++ b/src/components/BorrowModal.tsx
@@ -1,7 +1,8 @@
import React, { useState } from 'react';
-import { Cross1Icon } from '@radix-ui/react-icons';
-import { FaArrowRightArrowLeft } from 'react-icons/fa6';
+import { LuArrowRightLeft } from "react-icons/lu";
import { useAccount, useBalance } from 'wagmi';
+import { Button } from '@/components/common/Button';
+import { Modal, ModalHeader, ModalBody } from '@/components/common/Modal';
import { Market, MarketPosition } from '@/utils/types';
import { AddCollateralAndBorrow } from './Borrow/AddCollateralAndBorrow';
import { WithdrawCollateralAndRepay } from './Borrow/WithdrawCollateralAndRepay';
@@ -9,7 +10,7 @@ import { TokenIcon } from './TokenIcon';
type BorrowModalProps = {
market: Market;
- onClose: () => void;
+ onOpenChange: (open: boolean) => void;
oraclePrice: bigint;
refetch?: () => void;
isRefreshing?: boolean;
@@ -18,7 +19,7 @@ type BorrowModalProps = {
export function BorrowModal({
market,
- onClose,
+ onOpenChange,
oraclePrice,
refetch,
isRefreshing = false,
@@ -49,90 +50,77 @@ export function BorrowModal({
position &&
(BigInt(position.state.borrowAssets) > 0n || BigInt(position.state.collateral) > 0n);
- return (
-
-
-
-
-
-
-
-
-
-
-
-
-
- {market.loanAsset.symbol}
- / {market.collateralAsset.symbol}
-
-
- {mode === 'borrow' ? 'Borrow against collateral' : 'Repay borrowed assets'}
-
-
-
-
-
- {hasPosition && (
-
setMode(mode === 'borrow' ? 'repay' : 'borrow')}
- className="flex items-center gap-1.5 rounded-full bg-white/5 px-3 py-1.5 text-xs font-medium text-primary transition-colors hover:bg-white/10"
- >
-
- {mode === 'borrow' ? 'Repay' : 'Borrow'}
-
- )}
-
-
- {mode === 'borrow' ? (
-
- ) : (
-
- )}
-
+ const mainIcon = (
+
);
+
+ return (
+
+ onOpenChange(false)}
+ title={
+
+ {market.loanAsset.symbol}
+ / {market.collateralAsset.symbol}
+
+ }
+ description={mode === 'borrow' ? 'Borrow against collateral' : 'Repay borrowed assets'}
+ actions={
+ hasPosition ? (
+ setMode(mode === 'borrow' ? 'repay' : 'borrow')}
+ className="flex items-center gap-1.5"
+ >
+
+ {mode === 'borrow' ? 'Repay' : 'Borrow'}
+
+ ) : undefined
+ }
+ />
+
+ {mode === 'borrow' ? (
+
+ ) : (
+
+ )}
+
+
+ );
}
diff --git a/src/components/BorrowProcessModal.tsx b/src/components/BorrowProcessModal.tsx
index e21cdfda..45778992 100644
--- a/src/components/BorrowProcessModal.tsx
+++ b/src/components/BorrowProcessModal.tsx
@@ -1,7 +1,7 @@
import React, { useMemo } from 'react';
-import { Cross1Icon } from '@radix-ui/react-icons';
-import { motion, AnimatePresence } from 'framer-motion';
import { FaCheckCircle, FaCircle } from 'react-icons/fa';
+import { FiArrowDownCircle } from 'react-icons/fi';
+import { Modal, ModalBody, ModalHeader } from '@/components/common/Modal';
import { BorrowStepType } from '@/hooks/useBorrowTransaction';
import { Market } from '@/utils/types';
import { MarketInfoBlock } from './common/MarketInfoBlock';
@@ -103,69 +103,54 @@ export function BorrowProcessModal({
};
return (
-
-
-
-
-
-
+ {
+ if (!open) onClose();
+ }}
+ size="lg"
+ isDismissable={false}
+ backdrop="blur"
+ >
+ }
+ onClose={onClose}
+ />
+
+
-
-
Borrow {borrow.market.loanAsset.symbol}
-
Using {tokenSymbol} as collateral
-
- {/* Market details */}
-
-
-
-
- {/* Steps */}
-
- {steps.map((step) => {
- const status = getStepStatus(step.key);
- return (
-
-
- {status === 'done' ? (
-
- ) : status === 'current' ? (
-
- ) : (
-
- )}
-
-
-
{step.label}
-
{step.detail}
-
-
- );
- })}
-
-
-
-
-
+
+ {steps.map((step) => {
+ const status = getStepStatus(step.key);
+ return (
+
+
+ {status === 'done' ? (
+
+ ) : status === 'current' ? (
+
+ ) : (
+
+ )}
+
+
+
{step.label}
+
{step.detail}
+
+
+ );
+ })}
+
+
+
);
}
diff --git a/src/components/RepayProcessModal.tsx b/src/components/RepayProcessModal.tsx
index 3e94014a..11f9f563 100644
--- a/src/components/RepayProcessModal.tsx
+++ b/src/components/RepayProcessModal.tsx
@@ -1,7 +1,7 @@
import React, { useMemo } from 'react';
-import { Cross1Icon } from '@radix-ui/react-icons';
-import { motion, AnimatePresence } from 'framer-motion';
import { FaCheckCircle, FaCircle } from 'react-icons/fa';
+import { FiRepeat } from 'react-icons/fi';
+import { Modal, ModalBody, ModalHeader } from '@/components/common/Modal';
import { Market } from '@/utils/types';
import { MarketInfoBlock } from './common/MarketInfoBlock';
@@ -74,75 +74,58 @@ export function RepayProcessModal({
};
return (
-
-
-
-
-
-
+ {
+ if (!open) onClose();
+ }}
+ size="lg"
+ isDismissable={false}
+ backdrop="blur"
+ >
+ 0n ? 'Withdraw & Repay' : 'Repay'} ${tokenSymbol}`}
+ description={
+ withdrawAmount > 0n
+ ? 'Withdrawing collateral and repaying loan'
+ : 'Repaying loan to market'
+ }
+ mainIcon={ }
+ onClose={onClose}
+ />
+
+
-
-
- {withdrawAmount > 0n ? 'Withdraw & Repay' : 'Repay'} {tokenSymbol}
-
-
- {withdrawAmount > 0n
- ? 'Withdrawing collateral and repaying loan'
- : 'Repaying loan to market'}
-
-
- {/* Market details */}
-
-
-
-
- {/* Steps */}
-
- {steps.map((step) => {
- const status = getStepStatus(step.key);
- return (
-
-
- {status === 'done' ? (
-
- ) : status === 'current' ? (
-
- ) : (
-
- )}
-
-
-
{step.label}
-
{step.detail}
-
-
- );
- })}
-
-
-
-
-
+
+ {steps.map((step) => {
+ const status = getStepStatus(step.key);
+ return (
+
+
+ {status === 'done' ? (
+
+ ) : status === 'current' ? (
+
+ ) : (
+
+ )}
+
+
+
{step.label}
+
{step.detail}
+
+
+ );
+ })}
+
+
+
);
}
diff --git a/src/components/RiskNotificationModal.tsx b/src/components/RiskNotificationModal.tsx
index f71aeccf..be2fa7aa 100644
--- a/src/components/RiskNotificationModal.tsx
+++ b/src/components/RiskNotificationModal.tsx
@@ -1,17 +1,11 @@
'use client';
import { useState, useEffect } from 'react';
-import {
- Modal,
- ModalContent,
- ModalHeader,
- ModalBody,
- ModalFooter,
- Button,
- Checkbox,
-} from '@heroui/react';
+import { Button, Checkbox } from '@heroui/react';
import Link from 'next/link';
import { usePathname } from 'next/navigation';
+import { IoWarningOutline } from 'react-icons/io5';
+import { Modal, ModalHeader, ModalBody, ModalFooter } from '@/components/common/Modal';
export default function RiskNotificationModal() {
const [isOpen, setIsOpen] = useState(false);
@@ -37,12 +31,20 @@ export default function RiskNotificationModal() {
}
return (
-
{}} hideCloseButton size="3xl" scrollBehavior="inside">
-
-
- Welcome to Monarch
-
-
+
+ }
+ onClose={() => setIsOpen(false)}
+ />
+
Monarch enables direct lending to the Morpho Blue protocol. Before proceeding, it's
important to understand the key aspects of this approach. For a comprehensive overview,
@@ -83,17 +85,16 @@ export default function RiskNotificationModal() {
-
-
-
- Confirm and Proceed
-
-
-
+
+
+
+ Confirm and Proceed
+
+
);
}
diff --git a/src/components/SupplyModalV2.tsx b/src/components/SupplyModalV2.tsx
index cd1fd2ee..b84f2367 100644
--- a/src/components/SupplyModalV2.tsx
+++ b/src/components/SupplyModalV2.tsx
@@ -1,16 +1,15 @@
import React, { useState } from 'react';
-import { Cross1Icon } from '@radix-ui/react-icons';
-import { FaArrowRightArrowLeft } from 'react-icons/fa6';
+import { LuArrowRightLeft } from "react-icons/lu";
+import { Modal, ModalBody, ModalHeader } from '@/components/common/Modal';
import { Market, MarketPosition } from '@/utils/types';
import { MarketDetailsBlock } from './common/MarketDetailsBlock';
import { SupplyModalContent } from './SupplyModalContent';
import { TokenIcon } from './TokenIcon';
import { WithdrawModalContent } from './WithdrawModalContent';
-
type SupplyModalV2Props = {
market: Market;
position?: MarketPosition | null;
- onClose: () => void;
+ onOpenChange: (open: boolean) => void;
refetch?: () => void;
isMarketPage?: boolean;
defaultMode?: 'supply' | 'withdraw';
@@ -19,7 +18,7 @@ type SupplyModalV2Props = {
export function SupplyModalV2({
market,
position,
- onClose,
+ onOpenChange,
refetch,
isMarketPage,
defaultMode = 'supply',
@@ -31,87 +30,74 @@ export function SupplyModalV2({
const hasPosition = position && BigInt(position.state.supplyAssets) > 0n;
return (
-
-
-
-
-
-
-
-
-
-
-
-
- {mode === 'supply' ? 'Supply' : 'Withdraw'} {market.loanAsset.symbol}
-
-
-
- {mode === 'supply' ? 'Supply to earn interest' : 'Withdraw your supplied assets'}
-
-
-
- {hasPosition && (
-
setMode(mode === 'supply' ? 'withdraw' : 'supply')}
- className="flex items-center gap-1 rounded-full bg-white/5 px-2 py-1 text-xs font-medium text-primary transition-colors hover:bg-white/10"
- >
-
- {mode === 'supply' ? 'Withdraw' : 'Supply'}
-
- )}
-
-
- {/* Market Details Block - includes position overview and collapsible details */}
-
-
-
+
+ }
+ onClose={() => onOpenChange(false)}
+ actions={
+ hasPosition ? (
+
setMode(mode === 'supply' ? 'withdraw' : 'supply')}
+ className="flex items-center gap-1 text-sm font-medium text-primary transition hover:text-white"
+ >
+
+ {mode === 'supply' ? 'Withdraw' : 'Supply'}
+
+ ) : undefined
+ }
+ />
+
+
- {mode === 'supply' ? (
- {})}
- onAmountChange={setSupplyPreviewAmount}
- />
- ) : (
- {})}
- onAmountChange={setWithdrawPreviewAmount}
- />
- )}
-
-
-
+ {mode === 'supply' ? (
+
onOpenChange(false)}
+ refetch={refetch ?? (() => {})}
+ onAmountChange={setSupplyPreviewAmount}
+ />
+ ) : (
+ onOpenChange(false)}
+ refetch={refetch ?? (() => {})}
+ onAmountChange={setWithdrawPreviewAmount}
+ />
+ )}
+
+
);
}
diff --git a/src/components/SupplyProcessModal.tsx b/src/components/SupplyProcessModal.tsx
index 8ccb0a89..02055f8d 100644
--- a/src/components/SupplyProcessModal.tsx
+++ b/src/components/SupplyProcessModal.tsx
@@ -1,7 +1,7 @@
import React, { useMemo } from 'react';
-import { Cross1Icon } from '@radix-ui/react-icons';
-import { motion, AnimatePresence } from 'framer-motion';
import { FaCheckCircle, FaCircle } from 'react-icons/fa';
+import { FiUpload } from 'react-icons/fi';
+import { Modal, ModalBody, ModalHeader } from '@/components/common/Modal';
import { Market } from '@/utils/types';
import { MarketInfoBlock } from './common/MarketInfoBlock';
@@ -89,79 +89,64 @@ export function SupplyProcessModal({
const isMultiMarket = supplies.length > 1;
return (
-
-
-
-
-
-
+ {
+ if (!open) onClose();
+ }}
+ size="lg"
+ isDismissable={false}
+ backdrop="blur"
+ >
+ }
+ onClose={onClose}
+ />
+
+
+ {supplies.map((supply) => (
+
+ ))}
+
-
-
Supply {tokenSymbol}
-
- {isMultiMarket ? `Supplying to ${supplies.length} markets` : 'Supplying to market'}
-
-
- {/* Market details */}
-
- {supplies.map((supply) => {
- return (
-
- );
- })}
-
-
- {/* Steps */}
-
- {steps.map((step) => {
- const status = getStepStatus(step.key);
- return (
-
-
- {status === 'done' ? (
-
- ) : status === 'current' ? (
-
- ) : (
-
- )}
-
-
-
{step.label}
-
{step.detail}
-
-
- );
- })}
-
-
-
-
-
+
+ {steps.map((step) => {
+ const status = getStepStatus(step.key);
+ return (
+
+
+ {status === 'done' ? (
+
+ ) : status === 'current' ? (
+
+ ) : (
+
+ )}
+
+
+
{step.label}
+
{step.detail}
+
+
+ );
+ })}
+
+
+
);
}
diff --git a/src/components/WrapProcessModal.tsx b/src/components/WrapProcessModal.tsx
index 337d5466..45716c93 100644
--- a/src/components/WrapProcessModal.tsx
+++ b/src/components/WrapProcessModal.tsx
@@ -1,20 +1,21 @@
import React, { useMemo } from 'react';
-import { Cross1Icon } from '@radix-ui/react-icons';
import { motion, AnimatePresence } from 'framer-motion';
import { FaCheckCircle, FaCircle } from 'react-icons/fa';
+import { LuArrowRightLeft } from "react-icons/lu";
+import { Modal, ModalBody, ModalHeader } from '@/components/common/Modal';
import { WrapStep } from '@/hooks/useWrapLegacyMorpho';
import { formatBalance } from '@/utils/balance';
type WrapProcessModalProps = {
amount: bigint;
currentStep: WrapStep;
- onClose: () => void;
+ onOpenChange: (opened: boolean) => void;
};
export function WrapProcessModal({
amount,
currentStep,
- onClose,
+ onOpenChange,
}: WrapProcessModalProps): JSX.Element {
const steps = useMemo(
() => [
@@ -33,65 +34,47 @@ export function WrapProcessModal({
);
return (
-
-
-
-
- Wrapping {formatBalance(amount, 18)} MORPHO
-
-
-
-
-
+
+ }
+ />
+
+
+ {steps.map((step, index) => {
+ const isActive = currentStep === step.key;
+ const isPassed = steps.findIndex((s) => s.key === currentStep) > index;
-
-
- {steps.map((step, index) => {
- const isActive = currentStep === step.key;
- const isPassed = steps.findIndex((s) => s.key === currentStep) > index;
-
- return (
-
-
- {isPassed ? (
-
- ) : (
-
- )}
-
-
-
{step.label}
-
{step.detail}
-
-
- );
- })}
-
-
-
-
+ return (
+
+
+ {isPassed ? (
+
+ ) : (
+
+ )}
+
+
+
{step.label}
+
{step.detail}
+
+
+ );
+ })}
+
+
+
);
}
diff --git a/src/components/common/MarketDetailsBlock.tsx b/src/components/common/MarketDetailsBlock.tsx
index e44808ba..3cf23360 100644
--- a/src/components/common/MarketDetailsBlock.tsx
+++ b/src/components/common/MarketDetailsBlock.tsx
@@ -71,7 +71,7 @@ export function MarketDetailsBlock({
{/* Collapsible Market Details */}
!disableExpansion && setIsExpanded(!isExpanded)}
onKeyDown={(e) => {
if (!disableExpansion && (e.key === 'Enter' || e.key === ' ')) {
diff --git a/src/components/common/MarketSelectionModal.tsx b/src/components/common/MarketSelectionModal.tsx
index 8c6671a1..eb3f0bf2 100644
--- a/src/components/common/MarketSelectionModal.tsx
+++ b/src/components/common/MarketSelectionModal.tsx
@@ -1,8 +1,9 @@
import { useState, useMemo } from 'react';
-import { Modal, ModalBody, ModalContent, ModalFooter, ModalHeader } from '@heroui/react';
+import { FiSearch } from 'react-icons/fi';
import { Address } from 'viem';
import { Button } from '@/components/common/Button';
import { MarketsTableWithSameLoanAsset } from '@/components/common/MarketsTableWithSameLoanAsset';
+import { Modal, ModalHeader, ModalBody, ModalFooter } from '@/components/common/Modal';
import { Spinner } from '@/components/common/Spinner';
import { useMarkets } from '@/hooks/useMarkets';
import { SupportedNetworks } from '@/utils/networks';
@@ -16,7 +17,7 @@ type MarketSelectionModalProps = {
excludeMarketIds?: Set
;
multiSelect?: boolean;
isOpen?: boolean;
- onClose: () => void;
+ onOpenChange: (open: boolean) => void;
onSelect: (markets: Market[]) => void;
confirmButtonText?: string;
};
@@ -33,7 +34,7 @@ export function MarketSelectionModal({
excludeMarketIds,
multiSelect = true,
isOpen = true,
- onClose,
+ onOpenChange,
onSelect,
confirmButtonText,
}: MarketSelectionModalProps) {
@@ -68,7 +69,7 @@ export function MarketSelectionModal({
const market = availableMarkets.find((m) => m.uniqueKey === marketId);
if (market) {
onSelect([market]);
- onClose();
+ onOpenChange(false);
}
return;
}
@@ -90,7 +91,7 @@ export function MarketSelectionModal({
selectedMarkets.has(m.uniqueKey)
);
onSelect(marketsToReturn);
- onClose();
+ onOpenChange(false);
};
const selectedCount = selectedMarkets.size;
@@ -103,26 +104,20 @@ export function MarketSelectionModal({
return (
-
- <>
-
- {title}
- {description}
-
-
-
+ }
+ onClose={() => onOpenChange(false)}
+ />
+
{marketsLoading ? (
@@ -145,16 +140,15 @@ export function MarketSelectionModal({
showSelectColumn={multiSelect}
/>
)}
-
-
-
+
+
{multiSelect ? (
<>
{selectedCount} market{selectedCount !== 1 ? 's' : ''} selected
-
+ onOpenChange(false)}>
Cancel
) : (
-
+ onOpenChange(false)}>
Cancel
)}
-
- >
-
+
);
}
diff --git a/src/components/common/MarketsTableWithSameLoanAsset.tsx b/src/components/common/MarketsTableWithSameLoanAsset.tsx
index ac97f038..ac4f2380 100644
--- a/src/components/common/MarketsTableWithSameLoanAsset.tsx
+++ b/src/components/common/MarketsTableWithSameLoanAsset.tsx
@@ -31,6 +31,16 @@ import { MarketIdBadge } from '../MarketIdBadge';
import { MarketIdentity, MarketIdentityMode, MarketIdentityFocus } from '../MarketIdentity';
import { MarketIndicators } from '../MarketIndicators';
+const ZERO_DISPLAY_THRESHOLD = 1e-6;
+
+function formatAmountDisplay(value: bigint | string, decimals: number) {
+ const numericValue = formatBalance(value, decimals);
+ if (!Number.isFinite(numericValue) || Math.abs(numericValue) < ZERO_DISPLAY_THRESHOLD) {
+ return '-';
+ }
+ return formatReadable(numericValue);
+}
+
export type MarketWithSelection = {
market: Market;
isSelected: boolean;
@@ -480,21 +490,21 @@ function MarketRow({
{columnVisibility.totalSupply && (
- {formatReadable(formatBalance(market.state.supplyAssets, market.loanAsset.decimals))}
+ {formatAmountDisplay(market.state.supplyAssets, market.loanAsset.decimals)}
)}
{columnVisibility.totalBorrow && (
- {formatReadable(formatBalance(market.state.borrowAssets, market.loanAsset.decimals))}
+ {formatAmountDisplay(market.state.borrowAssets, market.loanAsset.decimals)}
)}
{columnVisibility.liquidity && (
- {formatReadable(formatBalance(market.state.liquidityAssets, market.loanAsset.decimals))}
+ {formatAmountDisplay(market.state.liquidityAssets, market.loanAsset.decimals)}
)}
@@ -1034,7 +1044,7 @@ export function MarketsTableWithSameLoanAsset({
{showSettingsModal && (
setShowSettingsModal(false)}
+ onOpenChange={setShowSettingsModal}
usdFilters={usdFilters}
setUsdFilters={setUsdFilters}
entriesPerPage={entriesPerPage}
@@ -1050,7 +1060,7 @@ export function MarketsTableWithSameLoanAsset({
{showTrustedVaultsModal && (
setShowTrustedVaultsModal(false)}
+ onOpenChange={setShowTrustedVaultsModal}
userTrustedVaults={userTrustedVaults}
setUserTrustedVaults={setUserTrustedVaults}
/>
diff --git a/src/components/common/Modal/Modal.tsx b/src/components/common/Modal/Modal.tsx
new file mode 100644
index 00000000..317c7f9c
--- /dev/null
+++ b/src/components/common/Modal/Modal.tsx
@@ -0,0 +1,78 @@
+import React, { useEffect, useState } from 'react';
+import { Modal as HeroModal, ModalContent } from '@heroui/react';
+
+export type ModalVariant = 'standard' | 'compact' | 'custom';
+export type ModalZIndex = 'base' | 'process' | 'selection' | 'settings' | 'custom';
+
+type ModalProps = {
+ isOpen: boolean;
+ onOpenChange: (open: boolean) => void;
+ children: React.ReactNode | ((onClose: () => void) => React.ReactNode);
+ zIndex?: ModalZIndex;
+ size?: 'sm' | 'md' | 'lg' | 'xl' | '2xl' | '3xl' | '4xl' | '5xl' | 'full';
+ isDismissable?: boolean;
+ hideCloseButton?: boolean;
+ scrollBehavior?: 'inside' | 'outside' | 'normal';
+ backdrop?: 'transparent' | 'opaque' | 'blur';
+ className?: string;
+};
+
+const Z_INDEX_MAP: Record = {
+ base: { wrapper: 'z-[2000]', backdrop: 'z-[1990]' },
+ process: { wrapper: 'z-[2600]', backdrop: 'z-[2590]' },
+ selection: { wrapper: 'z-[3000]', backdrop: 'z-[2990]' },
+ settings: { wrapper: 'z-[3200]', backdrop: 'z-[3190]' },
+ custom: { wrapper: '', backdrop: '' },
+};
+
+export function Modal({
+ isOpen,
+ onOpenChange,
+ children,
+ zIndex = 'base',
+ size = 'xl',
+ isDismissable = true,
+ hideCloseButton = true,
+ scrollBehavior = 'inside',
+ backdrop = 'blur',
+ className = '',
+}: ModalProps) {
+ const [portalContainer, setPortalContainer] = useState(null);
+ useEffect(() => {
+ setPortalContainer(document.body);
+ }, []);
+
+ const zIndexClasses = Z_INDEX_MAP[zIndex];
+ const backdropStyle =
+ backdrop === 'transparent'
+ ? 'bg-transparent'
+ : backdrop === 'opaque'
+ ? 'bg-black/70'
+ : 'bg-black/70 backdrop-blur-md';
+
+ return (
+
+
+ {/* eslint-disable-next-line @typescript-eslint/promise-function-async */}
+ {(closeModal) =>
+ typeof children === 'function' ? children(closeModal) : children}
+
+
+ );
+}
diff --git a/src/components/common/Modal/ModalBody.tsx b/src/components/common/Modal/ModalBody.tsx
new file mode 100644
index 00000000..a9558b51
--- /dev/null
+++ b/src/components/common/Modal/ModalBody.tsx
@@ -0,0 +1,29 @@
+import React from 'react';
+import { ModalBody as HeroModalBody } from '@heroui/react';
+import { twMerge } from 'tailwind-merge';
+
+export type ModalBodyVariant = 'standard' | 'compact';
+
+type ModalBodyProps = {
+ children: React.ReactNode;
+ variant?: ModalBodyVariant;
+ className?: string;
+};
+
+export function ModalBody({
+ children,
+ variant = 'standard',
+ className = '',
+}: ModalBodyProps) {
+ const isStandard = variant === 'standard';
+ const paddingClass = isStandard ? 'px-6 pb-6 pt-2' : 'px-6 pb-4 pt-2';
+ const gapClass = isStandard ? 'gap-5' : 'gap-4';
+
+ return (
+
+ {children}
+
+ );
+}
diff --git a/src/components/common/Modal/ModalFooter.tsx b/src/components/common/Modal/ModalFooter.tsx
new file mode 100644
index 00000000..5eb6a309
--- /dev/null
+++ b/src/components/common/Modal/ModalFooter.tsx
@@ -0,0 +1,15 @@
+import React from 'react';
+import { ModalFooter as HeroModalFooter } from '@heroui/react';
+
+type ModalFooterProps = {
+ children: React.ReactNode;
+ className?: string;
+};
+
+export function ModalFooter({ children, className = '' }: ModalFooterProps) {
+ return (
+
+ {children}
+
+ );
+}
diff --git a/src/components/common/Modal/ModalHeader.tsx b/src/components/common/Modal/ModalHeader.tsx
new file mode 100644
index 00000000..23415597
--- /dev/null
+++ b/src/components/common/Modal/ModalHeader.tsx
@@ -0,0 +1,106 @@
+import React from 'react';
+import { ModalHeader as HeroModalHeader } from '@heroui/react';
+import { Cross1Icon } from '@radix-ui/react-icons';
+import { twMerge } from 'tailwind-merge';
+
+export type ModalHeaderVariant = 'standard' | 'compact';
+
+export type ModalHeaderAction = {
+ icon: React.ReactNode;
+ onClick: () => void;
+ ariaLabel: string;
+};
+
+type ModalHeaderProps = {
+ title: string | React.ReactNode;
+ description?: string | React.ReactNode;
+ mainIcon?: React.ReactNode;
+ variant?: ModalHeaderVariant;
+ children?: React.ReactNode;
+ className?: string;
+ actions?: React.ReactNode;
+ onClose?: () => void;
+ showCloseButton?: boolean;
+ closeButtonAriaLabel?: string;
+ auxiliaryAction?: ModalHeaderAction;
+};
+
+export function ModalHeader({
+ title,
+ description,
+ mainIcon,
+ variant = 'standard',
+ children,
+ className = '',
+ actions,
+ onClose,
+ showCloseButton = true,
+ closeButtonAriaLabel = 'Close modal',
+ auxiliaryAction,
+}: ModalHeaderProps) {
+ const isStandard = variant === 'standard';
+ const paddingClass = isStandard ? 'px-6 pt-6 pb-4' : 'px-5 pt-4 pb-3';
+ const titleSizeClass = isStandard ? 'text-2xl' : 'text-lg';
+ const descriptionSizeClass = isStandard ? 'text-sm' : 'text-xs';
+ const showCloseIcon = Boolean(onClose) && showCloseButton;
+ const topRightControls = Boolean(actions || auxiliaryAction || showCloseIcon);
+ const controlPositionClass = isStandard ? 'top-6 right-6' : 'top-4 right-4';
+ const contentRightPadding = topRightControls ? (isStandard ? 'pr-14' : 'pr-10') : '';
+ const handleAuxiliaryClick = auxiliaryAction?.onClick;
+ const handleClose = onClose;
+ const iconButtonBaseClass =
+ 'flex h-8 w-8 items-center justify-center rounded-full text-secondary transition hover:text-primary focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-white/70';
+
+ // If children are provided, use them directly (for custom layouts)
+ if (children) {
+ return (
+
+ {children}
+
+ );
+ }
+
+ // Standard layout with title, description, and optional icon
+ return (
+
+
+
+ {mainIcon &&
{mainIcon}
}
+
{title}
+
+ {description && (
+
+ {description}
+
+ )}
+
+ {topRightControls && (
+
+ {actions &&
{actions}
}
+ {auxiliaryAction && handleAuxiliaryClick && (
+
+ {auxiliaryAction.icon}
+
+ )}
+ {showCloseIcon && handleClose && (
+
+
+
+ )}
+
+ )}
+
+ );
+}
diff --git a/src/components/common/Modal/index.ts b/src/components/common/Modal/index.ts
new file mode 100644
index 00000000..d03656b3
--- /dev/null
+++ b/src/components/common/Modal/index.ts
@@ -0,0 +1,10 @@
+export { Modal } from './Modal';
+export type { ModalVariant, ModalZIndex } from './Modal';
+
+export { ModalHeader } from './ModalHeader';
+export type { ModalHeaderVariant, ModalHeaderAction } from './ModalHeader';
+
+export { ModalBody } from './ModalBody';
+export type { ModalBodyVariant } from './ModalBody';
+
+export { ModalFooter } from './ModalFooter';
diff --git a/src/components/common/SuppliedAssetFilterCompactSwitch.tsx b/src/components/common/SuppliedAssetFilterCompactSwitch.tsx
index 4efc4e2d..2cbd9531 100644
--- a/src/components/common/SuppliedAssetFilterCompactSwitch.tsx
+++ b/src/components/common/SuppliedAssetFilterCompactSwitch.tsx
@@ -1,11 +1,12 @@
'use client';
import { useMemo } from 'react';
-import { Modal, ModalBody, ModalContent, ModalFooter, ModalHeader, Divider, Tooltip, useDisclosure } from '@heroui/react';
+import { Divider, Tooltip, useDisclosure } from '@heroui/react';
import { FiFilter } from 'react-icons/fi';
import { Button } from '@/components/common/Button';
import { FilterRow, FilterSection } from '@/components/common/FilterComponents';
import { IconSwitch } from '@/components/common/IconSwitch';
+import { Modal, ModalHeader, ModalBody, ModalFooter } from '@/components/common/Modal';
import { TooltipContent } from '@/components/TooltipContent';
import { MONARCH_PRIMARY } from '@/constants/chartColors';
import { formatReadable } from '@/utils/balance';
@@ -105,21 +106,18 @@ export function SuppliedAssetFilterCompactSwitch({
onOpenChange={onOpenChange}
size="md"
backdrop="opaque"
- classNames={{
- wrapper: 'z-[2400]',
- backdrop: 'z-[2390]',
- }}
+ zIndex="settings"
>
-
- {(close) => (
- <>
-
- Filters
-
- Quickly toggle the visibility filters that power the markets table.
-
-
-
+ {(close) => (
+ <>
+ }
+ onClose={close}
+ />
+
-
-
-
- Customize Filters
-
-
- Done
-
-
- >
- )}
-
+
+
+
+ Customize Filters
+
+
+ Done
+
+
+ >
+ )}
);
diff --git a/src/components/settings/BlacklistedMarketsModal.tsx b/src/components/settings/BlacklistedMarketsModal.tsx
index ccf15b54..d64c3e6f 100644
--- a/src/components/settings/BlacklistedMarketsModal.tsx
+++ b/src/components/settings/BlacklistedMarketsModal.tsx
@@ -1,17 +1,18 @@
'use client';
import React, { useMemo } from 'react';
-import { Modal, ModalContent, ModalHeader, ModalBody, ModalFooter, Divider } from '@heroui/react';
+import { Divider } from '@heroui/react';
import { FiPlus, FiX } from 'react-icons/fi';
-import { IoWarningOutline } from 'react-icons/io5';
+import { MdBlockFlipped } from "react-icons/md";
import { Button } from '@/components/common';
+import { Modal, ModalHeader, ModalBody, ModalFooter } from '@/components/common/Modal';
import { MarketIdentity, MarketIdentityMode, MarketIdentityFocus } from '@/components/MarketIdentity';
import { useMarkets } from '@/contexts/MarketsContext';
import type { Market } from '@/utils/types';
type BlacklistedMarketsModalProps = {
isOpen: boolean;
- onOpenChange: () => void;
+ onOpenChange: (opened: boolean) => void;
};
const ITEMS_PER_PAGE = 20;
@@ -87,39 +88,24 @@ export function BlacklistedMarketsModal({ isOpen, onOpenChange }: BlacklistedMar
onOpenChange={onOpenChange}
backdrop="blur"
size="3xl"
- classNames={{
- wrapper: 'z-[2300]',
- backdrop: 'z-[2290]',
- }}
+ zIndex="settings"
scrollBehavior="inside"
>
-
- {(onClose) => (
- <>
-
- Manage Blacklisted Markets
-
-
- {/* Info Section */}
-
-
- Block specific markets from appearing in your view. Blacklisted markets will be
- completely hidden from all market lists and filters.
-
-
-
-
- Some markets are blacklisted by default due to security concerns or issues.
- These cannot be removed from the blacklist.
-
-
-
-
+ {(onClose) => (
+ <>
+ }
+ onClose={onClose}
+ />
+
+
{/* Blacklisted Markets Section */}
{blacklistedMarkets.length > 0 && (
<>
-
-
+
+
Blacklisted Markets ({blacklistedMarkets.length})
@@ -171,9 +157,9 @@ export function BlacklistedMarketsModal({ isOpen, onOpenChange }: BlacklistedMar
)}
{/* Available Markets Section */}
-
+
-
Add Markets to Blacklist
+ Add Markets to Blacklist
{filteredAvailableMarkets.length > 0 && (
{filteredAvailableMarkets.length} result
@@ -191,7 +177,7 @@ export function BlacklistedMarketsModal({ isOpen, onOpenChange }: BlacklistedMar
{/* Available Markets List */}
-
+
{searchQuery.trim().length === 0 ? (
Start typing to search for markets to blacklist.
@@ -267,15 +253,14 @@ export function BlacklistedMarketsModal({ isOpen, onOpenChange }: BlacklistedMar
>
)}
-
-
-
- Close
-
-
- >
- )}
-
+
+
+
+ Close
+
+
+ >
+ )}
);
}
diff --git a/src/components/settings/CustomRpcSettings.tsx b/src/components/settings/CustomRpcSettings.tsx
index 3f8c0841..b86d706c 100644
--- a/src/components/settings/CustomRpcSettings.tsx
+++ b/src/components/settings/CustomRpcSettings.tsx
@@ -1,9 +1,9 @@
'use client';
import { useState } from 'react';
-import { Cross1Icon } from '@radix-ui/react-icons';
import Image from 'next/image';
import { Button } from '@/components/common/Button';
+import { Modal, ModalBody, ModalHeader } from '@/components/common/Modal';
import { Spinner } from '@/components/common/Spinner';
import { useStyledToast } from '@/hooks/useStyledToast';
import { SupportedNetworks, networks } from '@/utils/networks';
@@ -175,50 +175,43 @@ function RpcModal({ isOpen, onClose }: { isOpen: boolean; onClose: () => void })
setError('');
};
- if (!isOpen) return null;
+ if (!isOpen) {
+ return null;
+ }
return (
-
{
+ if (!open) handleClose();
+ }}
+ size="2xl"
+ scrollBehavior="inside"
>
-
-
-
-
-
-
-
-
-
Configure RPC Endpoints
-
- Set custom RPC URLs for blockchain networks
-
-
-
-
- Reset All
-
-
-
- {/* Network List */}
-
- {networks.map((network) => {
- const chainId = network.network;
- const isCustom = isUsingCustomRpc(chainId);
- const isSelected = selectedNetwork === chainId;
-
- return (
-
+ }
+ />
+
+
+ {networks.map((network) => {
+ const chainId = network.network;
+ const isCustom = isUsingCustomRpc(chainId);
+ const isSelected = selectedNetwork === chainId;
+
+ return (
+
handleNetworkSelect(chainId)}
@@ -246,77 +239,71 @@ function RpcModal({ isOpen, onClose }: { isOpen: boolean; onClose: () => void })
{isCustom &&
}
-
handleNetworkSelect(chainId)}
- className="rounded-sm px-3 py-1.5 text-xs font-medium text-primary transition-colors hover:bg-primary/5"
- >
+
{isCustom ? 'Edit' : 'Configure'}
-
+
);
})}
-
+
+
+ {selectedNetwork && (
+
+
+
+ n.network === selectedNetwork)?.logo || ''}
+ alt={networks.find((n) => n.network === selectedNetwork)?.name || ''}
+ width={20}
+ height={20}
+ className="rounded-full"
+ />
+
+ Configure {networks.find((n) => n.network === selectedNetwork)?.name} RPC
+
+
- {/* Edit Area */}
- {selectedNetwork && (
-
-
-
-
n.network === selectedNetwork)?.logo || ''}
- alt={networks.find((n) => n.network === selectedNetwork)?.name || ''}
- width={20}
- height={20}
- className="rounded-full"
+
+
+ handleInputChange(e.target.value)}
+ className={`bg-hovered h-10 w-full truncate rounded p-2 pr-16 text-sm focus:border-primary focus:outline-none ${
+ error ? 'border border-red-500 focus:border-red-500' : ''
+ }`}
/>
-
- Configure {networks.find((n) => n.network === selectedNetwork)?.name} RPC
-
+ void handleSave()}
+ isDisabled={isValidating}
+ className="absolute right-1 top-1/2 flex min-w-[60px] -translate-y-1/2 transform items-center justify-center"
+ >
+ {isValidating ? (
+
+ ) : (
+ Save
+ )}
+
+ {error &&
{error}
}
+
-
-
- handleInputChange(e.target.value)}
- className={`bg-hovered h-10 w-full truncate rounded p-2 pr-16 text-sm focus:border-primary focus:outline-none ${
- error ? 'border border-red-500 focus:border-red-500' : ''
- }`}
- />
- void handleSave()}
- isDisabled={isValidating}
- className="absolute right-1 top-1/2 flex min-w-[60px] -translate-y-1/2 transform items-center justify-center"
- >
- {isValidating ? (
-
- ) : (
- Save
- )}
-
-
- {error &&
{error}
}
+ {isUsingCustomRpc(selectedNetwork) && (
+
+
+ Reset to Default
+
-
- {isUsingCustomRpc(selectedNetwork) && (
-
-
- Reset to Default
-
-
- )}
-
+ )}
- )}
-
-
-
+
+ )}
+
+
);
}
diff --git a/src/components/settings/TrustedVaultsModal.tsx b/src/components/settings/TrustedVaultsModal.tsx
index d3240add..6d5a1162 100644
--- a/src/components/settings/TrustedVaultsModal.tsx
+++ b/src/components/settings/TrustedVaultsModal.tsx
@@ -1,21 +1,13 @@
'use client';
import React, { useMemo, useState } from 'react';
-import {
- Modal,
- ModalContent,
- ModalHeader,
- ModalBody,
- ModalFooter,
- Divider,
- Input,
- Spinner,
-} from '@heroui/react';
+import { Divider, Input, Spinner } from '@heroui/react';
import { FiChevronDown, FiChevronUp } from 'react-icons/fi';
import { GoShield, GoShieldCheck } from 'react-icons/go';
import { IoWarningOutline } from 'react-icons/io5';
import { Button } from '@/components/common';
import { IconSwitch } from '@/components/common/IconSwitch';
+import { Modal, ModalHeader, ModalBody, ModalFooter } from '@/components/common/Modal';
import { NetworkIcon } from '@/components/common/NetworkIcon';
import { VaultIdentity } from '@/components/vaults/VaultIdentity';
import {
@@ -27,7 +19,7 @@ import { useAllMorphoVaults } from '@/hooks/useAllMorphoVaults';
type TrustedVaultsModalProps = {
isOpen: boolean;
- onOpenChange: () => void;
+ onOpenChange: (isOpen: boolean) => void;
userTrustedVaults: TrustedVault[];
setUserTrustedVaults: React.Dispatch
>;
};
@@ -158,25 +150,20 @@ export default function TrustedVaultsModal({
onOpenChange={onOpenChange}
backdrop="blur"
size="3xl"
- classNames={{
- wrapper: 'z-[2300]',
- backdrop: 'z-[2290]',
- }}
+ zIndex="settings"
scrollBehavior="inside"
>
-
- {(onClose) => (
- <>
-
- Manage Trusted Vaults
-
-
+ {(onClose) => (
+ <>
+ }
+ onClose={onClose}
+ />
+
{/* Info Section */}
-
-
- Select which vaults you trust. Trusted vaults can be used to filter markets based on
- vault participation.
-
+
@@ -188,7 +175,7 @@ export default function TrustedVaultsModal({
{/* Search and Actions */}
-
+
-
-
+
+
Known Vaults ({sortedMonarchVaults.length})
{sortedMonarchVaults.length === 0 ? (
@@ -314,15 +301,14 @@ export default function TrustedVaultsModal({
)
)}
-
-
-
- Close
-
-
- >
- )}
-
+
+
+
+ Close
+
+
+ >
+ )}
);
}
diff --git a/src/hooks/useRebalance.ts b/src/hooks/useRebalance.ts
index bcee74b9..073ba41a 100644
--- a/src/hooks/useRebalance.ts
+++ b/src/hooks/useRebalance.ts
@@ -91,17 +91,21 @@ export const useRebalance = (groupedPosition: GroupedPosition, onRebalance?: ()
}, []);
// Transaction hook for the final multicall
+ const handleTransactionSuccess = useCallback(() => {
+ setRebalanceActions([]);
+ void refetchIsBundlerAuthorized();
+ if (onRebalance) {
+ onRebalance();
+ }
+ }, [refetchIsBundlerAuthorized, onRebalance]);
+
const { sendTransactionAsync, isConfirming: isExecuting } = useTransactionWithToast({
toastId: 'rebalance',
pendingText: 'Rebalancing positions',
successText: 'Positions rebalanced successfully',
errorText: 'Failed to rebalance positions',
chainId: groupedPosition.chainId,
- onSuccess: () => {
- setRebalanceActions([]); // Clear actions on success
- void refetchIsBundlerAuthorized(); // Refetch bundler auth status
- if (onRebalance) void onRebalance(); // Call external callback
- },
+ onSuccess: handleTransactionSuccess,
});
// Helper function to generate common withdraw/supply tx data