From 1018c55bb3b73a6e36e0fa3cdd180530e1c8214b Mon Sep 17 00:00:00 2001 From: null Date: Fri, 3 Jul 2026 19:06:12 -0500 Subject: [PATCH] refactor(bill-modal): extract DebtDetailsSection (BM2, 1/n) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit First BillModal decompose step: the collapsible Debt/Snowball fields (interest rate, current balance, minimum payment, snowball visibility) move to client/components/bill-modal/DebtDetailsSection.jsx as a presentational component; state stays in the parent (the save action reads it). Behavior- preserving — BillModal 1723 -> 1637 lines; build + client tests green. Co-Authored-By: Claude Opus 4.8 --- client/components/BillModal.jsx | 130 +++--------------- .../bill-modal/DebtDetailsSection.jsx | 130 ++++++++++++++++++ 2 files changed, 152 insertions(+), 108 deletions(-) create mode 100644 client/components/bill-modal/DebtDetailsSection.jsx 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. +

+
+ +
+ )} +
+ ); +}