diff --git a/app/positions/components/ApyPreview.tsx b/app/positions/components/ApyPreview.tsx
index ab7eed06..221f003a 100644
--- a/app/positions/components/ApyPreview.tsx
+++ b/app/positions/components/ApyPreview.tsx
@@ -1,5 +1,5 @@
import React from 'react';
-import { formatReadable } from '@/utils/balance';
+import { MetricPreview } from './MetricPreview';
type ApyPreviewProps = {
currentApy: number;
@@ -7,26 +7,9 @@ type ApyPreviewProps = {
};
/**
- * Standardized APY preview component.
- * Shows current APY in a fixed position, and appends preview to the right when available.
- * This ensures perfect vertical alignment across all uses.
+ * APY preview component.
+ * Thin wrapper around MetricPreview for APY-specific usage.
*/
export function ApyPreview({ currentApy, previewApy }: ApyPreviewProps) {
- const formattedCurrent = formatReadable(currentApy * 100);
- const formattedPreview = previewApy ? formatReadable(previewApy * 100) : null;
- const hasPreview = Boolean(previewApy && formattedPreview);
-
- const currentClasses = `text-foreground${hasPreview ? ' line-through opacity-50' : ''}`;
-
- return (
-
- {formattedCurrent}%
- {hasPreview && (
- <>
- {' → '}
- {formattedPreview}%
- >
- )}
-
- );
+ return
| Market | -APY | +APY | +Util | Supplied Amount |
- | + {apyPreview ? ( + + + {formatReadable(position.market.state.supplyApy * 100)}% + + {' → '} + {formatReadable(apyPreview.supplyApy * 100)}% + + ) : ( + + {formatReadable(position.market.state.supplyApy * 100)}% + + )} + | ++ {apyPreview ? ( + + + {formatReadable(position.market.state.utilization * 100)}% + + {' → '} + {formatReadable(apyPreview.utilization * 100)}% + + ) : ( + + {formatReadable(position.market.state.utilization * 100)}% + + )} |
diff --git a/app/positions/components/MetricPreview.tsx b/app/positions/components/MetricPreview.tsx
new file mode 100644
index 00000000..ec47fa91
--- /dev/null
+++ b/app/positions/components/MetricPreview.tsx
@@ -0,0 +1,51 @@
+import React from 'react';
+import { Tooltip } from '@heroui/react';
+import { TooltipContent } from '@/components/TooltipContent';
+import { formatReadable } from '@/utils/balance';
+
+type MetricPreviewProps = {
+ currentValue: number;
+ previewValue?: number | null;
+ label: string;
+};
+
+/**
+ * Generic metric preview component.
+ * Shows preview value if available, otherwise shows current value.
+ * Displays tooltip on hover showing before/after values.
+ */
+export function MetricPreview({ currentValue, previewValue, label }: MetricPreviewProps) {
+ const formattedCurrent = formatReadable(currentValue * 100);
+ const formattedPreview = previewValue ? formatReadable(previewValue * 100) : null;
+ const hasPreview = Boolean(previewValue && formattedPreview);
+
+ // Show preview value if available, otherwise show current value
+ const displayValue = hasPreview ? formattedPreview : formattedCurrent;
+
+ if (!hasPreview) {
+ return (
+
+ {displayValue}%
+
+ );
+ }
+
+ return (
+
Add Rebalance Action
-
- {/* Column 1: From → To Market Section */}
-
+
-
-
- {/* Column 2: APY Preview */}
-
- From
-
-
-
- {selectedFromMarket ? (
-
-
- To
-
-
-
- {selectedToMarket && onClearToMarket && (
-
- )}
-
-
- Market APY
- {selectedToMarket ? (
-
-
- {/* Column 3: Amount Input + Button */}
-
-
-
- setAmount(e.target.value)}
- placeholder="0.0"
- className="h-full w-32 rounded-sm bg-transparent px-2 pr-8 text-right text-sm focus:border-primary focus:outline-none"
- />
-
-
-
-
-
-
+ {/* Column 1: From → To Market Section - 50% */}
+
+ );
+}
diff --git a/app/positions/components/RebalanceCart.tsx b/app/positions/components/RebalanceCart.tsx
index b72c1c47..e9af233c 100644
--- a/app/positions/components/RebalanceCart.tsx
+++ b/app/positions/components/RebalanceCart.tsx
@@ -1,12 +1,7 @@
import React from 'react';
-import { ArrowRightIcon, TrashIcon } from '@radix-ui/react-icons';
-import { formatUnits } from 'viem';
-import { MarketIdentity, MarketIdentityMode } from '@/components/MarketIdentity';
-import { TokenIcon } from '@/components/TokenIcon';
-import { previewMarketState } from '@/utils/morpho';
import { Market } from '@/utils/types';
import { GroupedPosition, RebalanceAction } from '@/utils/types';
-import { ApyPreview } from './ApyPreview';
+import { RebalanceActionRow } from './RebalanceActionRow';
type RebalanceCartProps = {
rebalanceActions: RebalanceAction[];
@@ -39,94 +34,19 @@ export function RebalanceCart({
)?.market;
const toMarket = eligibleMarkets.find((m) => m.uniqueKey === action.toMarket.uniqueKey);
- let apyPreview: ReturnType
+ {/* From Market */}
+
+
+ {/* Column 2: APY & Utilization Preview - 25% */}
+
+ From
+
+
+
+ {fromMarket ? (
+
+
+ To
+
+
+ {mode === 'input' ? (
+ <>
+
+ {toMarket && onClearToMarket && (
+
+ )}
+ >
+ ) : (
+
+
+ {toMarket ? (
+
+ )}
+
+ {/* Market APY */}
+
+
+ {/* Column 3: Amount Input/Display + Button - 25% */}
+
+ APY
+ {toMarket ? (
+
+
+ {/* Utilization Rate */}
+
+ Util
+ {toMarket ? (
+
+
+
+
+ {mode === 'input' ? (
+ <>
+ onAmountChange?.(e.target.value)}
+ placeholder="0.0"
+ className="h-full w-32 rounded-sm bg-transparent px-2 pr-8 text-right text-sm focus:border-primary focus:outline-none"
+ />
+
+
+ {mode === 'input' ? (
+
+ ) : (
+
+ )}
+
+
+ >
+ ) : (
+ <>
+
+ {displayAmount}
+
+
+
+ >
+ )}
+
- {/* Column 1: From → To Market Section */}
-
);
})}
diff --git a/app/positions/components/RebalanceProcessModal.tsx b/app/positions/components/RebalanceProcessModal.tsx
index e2e88908..3a58166d 100644
--- a/app/positions/components/RebalanceProcessModal.tsx
+++ b/app/positions/components/RebalanceProcessModal.tsx
@@ -103,26 +103,24 @@ export function RebalanceProcessModal({
return (
-
-
- {/* Column 2: APY Preview */}
-
- From
-
-
-
- {fromMarket ? (
-
-
- To
-
-
- {toMarket ? (
-
-
- Market APY
- {toMarket ? (
-
-
- {/* Column 3: Amount + Remove Button */}
-
-
+
-
- {formatUnits(action.amount, groupedPosition.loanAssetDecimals)}
-
-
-
-
-
-
-
-
+
{status === 'done' ? (
-
-
-
);
diff --git a/app/positions/components/UtilizationPreview.tsx b/app/positions/components/UtilizationPreview.tsx
new file mode 100644
index 00000000..84c16444
--- /dev/null
+++ b/app/positions/components/UtilizationPreview.tsx
@@ -0,0 +1,15 @@
+import React from 'react';
+import { MetricPreview } from './MetricPreview';
+
+type UtilizationPreviewProps = {
+ currentUtilization: number;
+ previewUtilization?: number | null;
+};
+
+/**
+ * Utilization preview component.
+ * Thin wrapper around MetricPreview for utilization-specific usage.
+ */
+export function UtilizationPreview({ currentUtilization, previewUtilization }: UtilizationPreviewProps) {
+ return {step.label}
- {status === 'current' && step.detail && (
- {step.detail}
- )}
+
+
{step.label}
+ {step.detail}
|
|---|