diff --git a/client/components/BillModal.jsx b/client/components/BillModal.jsx
index 831e873..a918d95 100644
--- a/client/components/BillModal.jsx
+++ b/client/components/BillModal.jsx
@@ -1,5 +1,5 @@
import { useActionState, useEffect, useState } from 'react';
-import { ChevronDown, Copy, Layers, Link2, Link2Off, Loader2, Pencil, Plus, RefreshCw, Trash2 } from 'lucide-react';
+import { Copy, Layers, Link2, Link2Off, Loader2, Pencil, Plus, RefreshCw, Trash2 } from 'lucide-react';
import { formatCentsUSD, validateNonNegativeMoney } from '@/lib/money';
import { toast } from 'sonner';
import { Button } from '@/components/ui/button';
@@ -19,6 +19,7 @@ import {
import { api } from '@/api';
import { cn, fmt, fmtDate, todayStr } from '@/lib/utils';
import BillMerchantRules from '@/components/BillMerchantRules';
+import DebtDetailsSection from '@/components/bill-modal/DebtDetailsSection';
import {
BILLING_SCHEDULE_OPTIONS,
billingCycleForSchedule,
@@ -802,113 +803,26 @@ export default function BillModal({ bill, initialBill, categories, onClose, onSa
{/* Debt / Snowball Details — collapsible */}
-
-
-
-
-
-
-
- {showDebtSection && (
-
-
- {/* Interest Rate */}
-
-
-
{
- setInterestRate(e.target.value);
- setTimeout(() => setErrors(prev => ({ ...prev, interestRate: validateInterestRate(e.target.value) })), 300);
- }}
- onBlur={() => handleBlur('interestRate', interestRate, validateInterestRate)}
- />
- {errors.interestRate && (
-
{errors.interestRate}
- )}
-
Enter 29.99 for 29.99%.
-
-
- {/* Current Balance */}
-
-
-
{
- setCurrentBalance(e.target.value);
- setTimeout(() => setErrors(prev => ({ ...prev, currentBalance: validateCurrentBalance(e.target.value) })), 300);
- }}
- onBlur={() => handleBlur('currentBalance', currentBalance, validateCurrentBalance)}
- />
- {errors.currentBalance && (
-
{errors.currentBalance}
- )}
-
Outstanding debt balance.
-
-
- {/* Minimum Payment */}
-
-
-
{
- setMinimumPayment(e.target.value);
- setTimeout(() => setErrors(prev => ({ ...prev, minimumPayment: validateMinimumPayment(e.target.value) })), 300);
- }}
- onBlur={() => handleBlur('minimumPayment', minimumPayment, validateMinimumPayment)}
- />
- {errors.minimumPayment && (
-
{errors.minimumPayment}
- )}
-
Required minimum monthly payment.
-
-
- {/* Include in Snowball */}
-
-
-
- Uncheck to exempt an auto-detected snowball bill, or check to include this bill manually.
-
-
-
-
- )}
-
+
{/* Website */}
diff --git a/client/components/bill-modal/DebtDetailsSection.jsx b/client/components/bill-modal/DebtDetailsSection.jsx
new file mode 100644
index 0000000..ccf89ac
--- /dev/null
+++ b/client/components/bill-modal/DebtDetailsSection.jsx
@@ -0,0 +1,130 @@
+import { ChevronDown } from 'lucide-react';
+import { cn } from '@/lib/utils';
+import { Label } from '@/components/ui/label';
+import { Input } from '@/components/ui/input';
+
+// Collapsible Debt / Snowball fields (interest rate, current balance, minimum
+// payment, snowball visibility). State lives in the parent BillModal (the save
+// action reads these values); this is a presentational extraction.
+export default function DebtDetailsSection({
+ inp,
+ errors, setErrors,
+ showDebtSection, setShowDebtSection,
+ isSnowballCategory, showOnSnowball,
+ interestRate, setInterestRate,
+ currentBalance, setCurrentBalance,
+ minimumPayment, setMinimumPayment,
+ validateInterestRate, validateCurrentBalance, validateMinimumPayment,
+ handleBlur,
+ onSnowballVisibilityChange,
+}) {
+ return (
+
+
+
+
+
+
+
+ {showDebtSection && (
+
+
+ {/* Interest Rate */}
+
+
+
{
+ setInterestRate(e.target.value);
+ setTimeout(() => setErrors(prev => ({ ...prev, interestRate: validateInterestRate(e.target.value) })), 300);
+ }}
+ onBlur={() => handleBlur('interestRate', interestRate, validateInterestRate)}
+ />
+ {errors.interestRate && (
+
{errors.interestRate}
+ )}
+
Enter 29.99 for 29.99%.
+
+
+ {/* Current Balance */}
+
+
+
{
+ setCurrentBalance(e.target.value);
+ setTimeout(() => setErrors(prev => ({ ...prev, currentBalance: validateCurrentBalance(e.target.value) })), 300);
+ }}
+ onBlur={() => handleBlur('currentBalance', currentBalance, validateCurrentBalance)}
+ />
+ {errors.currentBalance && (
+
{errors.currentBalance}
+ )}
+
Outstanding debt balance.
+
+
+ {/* Minimum Payment */}
+
+
+
{
+ setMinimumPayment(e.target.value);
+ setTimeout(() => setErrors(prev => ({ ...prev, minimumPayment: validateMinimumPayment(e.target.value) })), 300);
+ }}
+ onBlur={() => handleBlur('minimumPayment', minimumPayment, validateMinimumPayment)}
+ />
+ {errors.minimumPayment && (
+
{errors.minimumPayment}
+ )}
+
Required minimum monthly payment.
+
+
+ {/* Include in Snowball */}
+
+
+
+ Uncheck to exempt an auto-detected snowball bill, or check to include this bill manually.
+
+
+
+
+ )}
+
+ );
+}