diff --git a/src/components/wallet/LoanSimulator.tsx b/src/components/wallet/LoanSimulator.tsx index 839b187..3300e22 100644 --- a/src/components/wallet/LoanSimulator.tsx +++ b/src/components/wallet/LoanSimulator.tsx @@ -641,8 +641,73 @@ export default function LoanSimulator({ mode = "borrow", collateralType = "WBTC" {/* Input Field */}
{mode === "simulate" ? ( - /* Sandbox Mode - Collateral Type Selector + Collateral Input + Borrow Slider */ + /* Sandbox Mode - Borrow Slider + Collateral Type Selector + Collateral Input */
+ {/* Borrow Amount Slider */} +
+
+
+ + Borrow Amount (USDT) +
+ +
+
+ + {formatCurrency(parsedInput, 0, "$", false)} + +
+
+ setInputValue(e.target.value)} + disabled={maxBorrow <= 0} + className={cn( + "w-full h-2 rounded-full appearance-none cursor-pointer bg-white/10", + "[&::-webkit-slider-thumb]:appearance-none", + "[&::-webkit-slider-thumb]:w-5", + "[&::-webkit-slider-thumb]:h-5", + "[&::-webkit-slider-thumb]:rounded-full", + "[&::-webkit-slider-thumb]:bg-amber-500", + "[&::-webkit-slider-thumb]:shadow-lg", + "[&::-webkit-slider-thumb]:border-2", + "[&::-webkit-slider-thumb]:border-white/20", + "[&::-moz-range-thumb]:w-5", + "[&::-moz-range-thumb]:h-5", + "[&::-moz-range-thumb]:rounded-full", + "[&::-moz-range-thumb]:bg-amber-500", + "[&::-moz-range-thumb]:border-2", + "[&::-moz-range-thumb]:border-white/20", + "disabled:opacity-50" + )} + style={{ + background: maxBorrow > 0 + ? `linear-gradient(to right, rgb(245 158 11) 0%, rgb(245 158 11) ${(parsedInput / maxBorrow) * 100}%, rgba(255,255,255,0.1) ${(parsedInput / maxBorrow) * 100}%, rgba(255,255,255,0.1) 100%)` + : "rgba(255,255,255,0.1)" + }} + /> +
+ $0 + {formatCurrency(maxBorrow, 0, "$", false)} +
+
+ {maxBorrow <= 0 && parsedSandboxCollateral === 0 && ( +

+ Enter collateral amount to enable borrowing +

+ )} +
+ {/* Collateral Type Selector */}
- {/* Borrow Amount Slider */} -
-
-
- - Borrow Amount (USDT) -
- -
-
- - {formatCurrency(parsedInput, 0, "$", false)} - -
-
- setInputValue(e.target.value)} - disabled={maxBorrow <= 0} - className={cn( - "w-full h-2 rounded-full appearance-none cursor-pointer bg-white/10", - "[&::-webkit-slider-thumb]:appearance-none", - "[&::-webkit-slider-thumb]:w-5", - "[&::-webkit-slider-thumb]:h-5", - "[&::-webkit-slider-thumb]:rounded-full", - "[&::-webkit-slider-thumb]:bg-amber-500", - "[&::-webkit-slider-thumb]:shadow-lg", - "[&::-webkit-slider-thumb]:border-2", - "[&::-webkit-slider-thumb]:border-white/20", - "[&::-moz-range-thumb]:w-5", - "[&::-moz-range-thumb]:h-5", - "[&::-moz-range-thumb]:rounded-full", - "[&::-moz-range-thumb]:bg-amber-500", - "[&::-moz-range-thumb]:border-2", - "[&::-moz-range-thumb]:border-white/20", - "disabled:opacity-50" - )} - style={{ - background: maxBorrow > 0 - ? `linear-gradient(to right, rgb(245 158 11) 0%, rgb(245 158 11) ${(parsedInput / maxBorrow) * 100}%, rgba(255,255,255,0.1) ${(parsedInput / maxBorrow) * 100}%, rgba(255,255,255,0.1) 100%)` - : "rgba(255,255,255,0.1)" - }} - /> -
- $0 - {formatCurrency(maxBorrow, 0, "$", false)} -
-
- {maxBorrow <= 0 && parsedSandboxCollateral === 0 && ( -

- Enter collateral amount to enable borrowing -

- )} -
) : ( <> diff --git a/src/lib/utils.ts b/src/lib/utils.ts index faf252e..5296904 100644 --- a/src/lib/utils.ts +++ b/src/lib/utils.ts @@ -28,10 +28,10 @@ export function formatAmount( const abs = Math.abs(num); // Tiny positive numbers: show '< 0.0001' for values > 0 and < 0.0001 if (abs > 0 && abs < 0.0001) return `< 0.0001`; - // Cap maximum decimals to 4 to avoid very long fractional displays + // Cap decimals to valid range (0-4) and ensure min <= max const maxDecimals = Math.min(4, Math.max(0, decimals)); return num.toLocaleString("en-US", { - minimumFractionDigits: 2, + minimumFractionDigits: Math.min(2, maxDecimals), maximumFractionDigits: maxDecimals, }); } @@ -51,9 +51,10 @@ export function formatCurrency( return `${currencySymbol}${abbreviateNumber(num, 1)}`; } // For smaller numbers, show with requested decimals (no grouping abbreviation) + const safeDecimals = Math.max(0, decimals); return `${currencySymbol}${num.toLocaleString("en-US", { - minimumFractionDigits: decimals, - maximumFractionDigits: decimals, + minimumFractionDigits: safeDecimals, + maximumFractionDigits: safeDecimals, })}`; } @@ -64,9 +65,10 @@ export function formatPercent( if (value === null || value === undefined) return `0%`; const num = Number(value); if (!Number.isFinite(num)) return `0%`; + const safeDecimals = Math.max(0, decimals); return `${Number(value).toLocaleString("en-US", { - minimumFractionDigits: decimals, - maximumFractionDigits: decimals, + minimumFractionDigits: safeDecimals, + maximumFractionDigits: safeDecimals, })}%`; } diff --git a/test/lib/utils.test.ts b/test/lib/utils.test.ts index f17fbb5..e8ddee2 100644 --- a/test/lib/utils.test.ts +++ b/test/lib/utils.test.ts @@ -80,6 +80,21 @@ describe("formatAmount", () => { it("should respect custom decimals", () => { expect(formatAmount(1.123456, 2)).toBe("1.12"); }); + + it("should not throw RangeError when decimals is 0", () => { + expect(() => formatAmount(100000, 0)).not.toThrow(); + expect(formatAmount(100000, 0)).toBe("100,000"); + }); + + it("should not throw RangeError when decimals is 1", () => { + expect(() => formatAmount(1234.5, 1)).not.toThrow(); + expect(formatAmount(1234.5, 1)).toBe("1,234.5"); + }); + + it("should not throw RangeError when decimals is negative", () => { + expect(() => formatAmount(1234, -1)).not.toThrow(); + expect(formatAmount(1234, -1)).toBe("1,234"); + }); }); describe("formatCurrency", () => { @@ -110,6 +125,16 @@ describe("formatCurrency", () => { it("should not abbreviate when disabled", () => { expect(formatCurrency(5000, 2, "$", false)).toBe("$5,000.00"); }); + + it("should not throw RangeError when decimals is 0", () => { + expect(() => formatCurrency(100, 0)).not.toThrow(); + expect(formatCurrency(100, 0, "$", false)).toBe("$100"); + }); + + it("should not throw RangeError when decimals is negative", () => { + expect(() => formatCurrency(100, -1)).not.toThrow(); + expect(formatCurrency(100, -1, "$", false)).toBe("$100"); + }); }); describe("formatPercent", () => { @@ -132,6 +157,11 @@ describe("formatPercent", () => { it("should respect custom decimals", () => { expect(formatPercent(33.3333, 1)).toBe("33.3%"); }); + + it("should not throw RangeError when decimals is negative", () => { + expect(() => formatPercent(50, -1)).not.toThrow(); + expect(formatPercent(50, -1)).toBe("50%"); + }); }); describe("maskString", () => {