From ef272560320fd2d84de7f980ec0ef44ac12d37da Mon Sep 17 00:00:00 2001 From: Jen Breese-Kauth Date: Wed, 7 Jan 2026 16:20:00 -0800 Subject: [PATCH 01/11] IFDM-92: Initital commit for Mortgage Calculator --- .../mortgate-calculator-suite/page.tsx | 371 ++++++++++++++++++ 1 file changed, 371 insertions(+) create mode 100644 app/interactives/mortgate-calculator-suite/page.tsx diff --git a/app/interactives/mortgate-calculator-suite/page.tsx b/app/interactives/mortgate-calculator-suite/page.tsx new file mode 100644 index 0000000..308c0ff --- /dev/null +++ b/app/interactives/mortgate-calculator-suite/page.tsx @@ -0,0 +1,371 @@ +"use client" + +import { useState } from "react" +import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/app/ui/components/card" +import { Label } from "@/app/ui/components/label" +import { Input } from "@/app/ui/components/input" +import { Button } from "@/app/ui/components/button" +import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/app/ui/components/tabs" +import ThemeToggle from "@/app/lib/theme-toggle"; + +export default function MortgageCalculator() { + const [monthsRemaining, setMonthsRemaining] = useState("") + const [annualRate, setAnnualRate] = useState("") + const [monthlyPayment, setMonthlyPayment] = useState("") + const [currentBalance, setCurrentBalance] = useState(null) + + const [refCurrentBalance, setRefCurrentBalance] = useState("") + const [refCurrentRate, setRefCurrentRate] = useState("") + const [refCurrentMonths, setRefCurrentMonths] = useState("") + const [refNewRate, setRefNewRate] = useState("") + const [refNewMonths, setRefNewMonths] = useState("") + const [refClosingCosts, setRefClosingCosts] = useState("") + const [refinanceResults, setRefinanceResults] = useState<{ + currentMonthlyPayment: number + newMonthlyPayment: number + monthlySavings: number + totalCurrentCost: number + totalNewCost: number + totalSavings: number + breakEvenMonths: number + } | null>(null) + + const calculateBalance = () => { + const months = Number.parseFloat(monthsRemaining) + const rate = Number.parseFloat(annualRate) / 100 / 12 + const payment = Number.parseFloat(monthlyPayment) + + if (isNaN(months) || isNaN(rate) || isNaN(payment) || months <= 0 || rate < 0 || payment <= 0) { + alert("Please enter valid positive numbers") + return + } + + let balance: number + + if (rate === 0) { + balance = payment * months + } else { + balance = payment * ((1 - Math.pow(1 + rate, -months)) / rate) + } + + setCurrentBalance(balance) + } + + const calculateRefinance = () => { + const currentBal = Number.parseFloat(refCurrentBalance) + const currentR = Number.parseFloat(refCurrentRate) / 100 / 12 + const currentM = Number.parseFloat(refCurrentMonths) + const newR = Number.parseFloat(refNewRate) / 100 / 12 + const newM = Number.parseFloat(refNewMonths) + const closingCosts = Number.parseFloat(refClosingCosts) + + if ( + isNaN(currentBal) || + isNaN(currentR) || + isNaN(currentM) || + isNaN(newR) || + isNaN(newM) || + isNaN(closingCosts) || + currentBal <= 0 || + currentR < 0 || + currentM <= 0 || + newR < 0 || + newM <= 0 || + closingCosts < 0 + ) { + alert("Please enter valid numbers") + return + } + + // Calculate current monthly payment: PMT = P × [r(1 + r)^n] / [(1 + r)^n - 1] + let currentMonthlyPayment: number + if (currentR === 0) { + currentMonthlyPayment = currentBal / currentM + } else { + currentMonthlyPayment = + (currentBal * (currentR * Math.pow(1 + currentR, currentM))) / (Math.pow(1 + currentR, currentM) - 1) + } + + // Calculate new monthly payment + let newMonthlyPayment: number + if (newR === 0) { + newMonthlyPayment = currentBal / newM + } else { + newMonthlyPayment = (currentBal * (newR * Math.pow(1 + newR, newM))) / (Math.pow(1 + newR, newM) - 1) + } + + const monthlySavings = currentMonthlyPayment - newMonthlyPayment + const totalCurrentCost = currentMonthlyPayment * currentM + const totalNewCost = newMonthlyPayment * newM + closingCosts + const totalSavings = totalCurrentCost - totalNewCost + const breakEvenMonths = monthlySavings > 0 ? closingCosts / monthlySavings : 0 + + setRefinanceResults({ + currentMonthlyPayment, + newMonthlyPayment, + monthlySavings, + totalCurrentCost, + totalNewCost, + totalSavings, + breakEvenMonths, + }) + } + + return ( +
+
+ + {/* Header */} +

Debt Payoff Calculator

+ {/* Mode Selection */} +
+ + + + Current Balance + Refinance Analysis + + + + + + Current Mortgage Balance Calculator + + Calculate the present value of your remaining monthly mortgage payments (your mortgage balance). + + + +
+ + setMonthsRemaining(e.target.value)} + min="0" + step="1" + /> +
+ +
+ + setAnnualRate(e.target.value)} + min="0" + step="0.01" + /> +
+ +
+ + setMonthlyPayment(e.target.value)} + min="0" + step="0.01" + /> +
+ + + + {currentBalance !== null && ( +
+

Current Mortgage Balance

+

+ ${currentBalance.toLocaleString("en-US", { minimumFractionDigits: 2, maximumFractionDigits: 2 })} +

+
+ )} +
+
+
+ + + + + Refinance Analysis + Compare your current mortgage with refinancing options. + + +
+ + setRefCurrentBalance(e.target.value)} + min="0" + step="0.01" + /> +
+ +
+ + setRefCurrentRate(e.target.value)} + min="0" + step="0.01" + /> +
+ +
+ + setRefCurrentMonths(e.target.value)} + min="0" + step="1" + /> +
+ +
+ + setRefNewRate(e.target.value)} + min="0" + step="0.01" + /> +
+ +
+ + setRefNewMonths(e.target.value)} + min="0" + step="1" + /> +
+ +
+ + setRefClosingCosts(e.target.value)} + min="0" + step="0.01" + /> +
+ + + + {refinanceResults && ( +
+
+
+

Current Monthly Payment

+

+ $ + {refinanceResults.currentMonthlyPayment.toLocaleString("en-US", { + minimumFractionDigits: 2, + maximumFractionDigits: 2, + })} +

+
+ +
+

New Monthly Payment

+

+ $ + {refinanceResults.newMonthlyPayment.toLocaleString("en-US", { + minimumFractionDigits: 2, + maximumFractionDigits: 2, + })} +

+
+
+ +
+

Monthly Savings

+

+ $ + {refinanceResults.monthlySavings.toLocaleString("en-US", { + minimumFractionDigits: 2, + maximumFractionDigits: 2, + })} +

+
+ +
+
+

Total Current Cost

+

+ $ + {refinanceResults.totalCurrentCost.toLocaleString("en-US", { + minimumFractionDigits: 2, + maximumFractionDigits: 2, + })} +

+
+ +
+

Total New Cost

+

+ $ + {refinanceResults.totalNewCost.toLocaleString("en-US", { + minimumFractionDigits: 2, + maximumFractionDigits: 2, + })} +

+
+
+ +
+

Total Lifetime Savings

+

+ $ + {refinanceResults.totalSavings.toLocaleString("en-US", { + minimumFractionDigits: 2, + maximumFractionDigits: 2, + })} +

+
+ +
+

Break-Even Point

+

{refinanceResults.breakEvenMonths.toFixed(1)} months

+

+ Time needed to recover closing costs through monthly savings +

+
+
+ )} +
+
+
+
+
+
+
+ ) +} From 2e2dcd2ce9404609297cf7259b1754cc4b8d970f Mon Sep 17 00:00:00 2001 From: Jen Breese-Kauth Date: Thu, 8 Jan 2026 16:08:26 -0800 Subject: [PATCH 02/11] fixup --- .../mortgate-calculator-suite/page.tsx | 123 ++++++++++++------ 1 file changed, 84 insertions(+), 39 deletions(-) diff --git a/app/interactives/mortgate-calculator-suite/page.tsx b/app/interactives/mortgate-calculator-suite/page.tsx index 308c0ff..3dda047 100644 --- a/app/interactives/mortgate-calculator-suite/page.tsx +++ b/app/interactives/mortgate-calculator-suite/page.tsx @@ -15,11 +15,14 @@ export default function MortgageCalculator() { const [currentBalance, setCurrentBalance] = useState(null) const [refCurrentBalance, setRefCurrentBalance] = useState("") - const [refCurrentRate, setRefCurrentRate] = useState("") + const [refCurrentMonthlyPayment, setRefCurrentMonthlyPayment] = useState("") const [refCurrentMonths, setRefCurrentMonths] = useState("") + const [refCurrentRate, setRefCurrentRate] = useState("") // Added missing state for refinance current rate + const [refNewLoanAmount, setRefNewLoanAmount] = useState("") const [refNewRate, setRefNewRate] = useState("") const [refNewMonths, setRefNewMonths] = useState("") const [refClosingCosts, setRefClosingCosts] = useState("") + const [refYearsIn, setRefYearsIn] = useState("") const [refinanceResults, setRefinanceResults] = useState<{ currentMonthlyPayment: number newMonthlyPayment: number @@ -53,7 +56,7 @@ export default function MortgageCalculator() { const calculateRefinance = () => { const currentBal = Number.parseFloat(refCurrentBalance) - const currentR = Number.parseFloat(refCurrentRate) / 100 / 12 + const currentR = Number.parseFloat(refCurrentRate) / 100 / 12 // Fixed: Use refCurrentRate instead of refCurrentMonthlyPayment const currentM = Number.parseFloat(refCurrentMonths) const newR = Number.parseFloat(refNewRate) / 100 / 12 const newM = Number.parseFloat(refNewMonths) @@ -94,7 +97,7 @@ export default function MortgageCalculator() { newMonthlyPayment = (currentBal * (newR * Math.pow(1 + newR, newM))) / (Math.pow(1 + newR, newM) - 1) } - const monthlySavings = currentMonthlyPayment - newMonthlyPayment + const monthlySavings = currentMonthlyPayment - newMonthlyPayment // This is already correct: old monthly minus new monthly const totalCurrentCost = currentMonthlyPayment * currentM const totalNewCost = newMonthlyPayment * newM + closingCosts const totalSavings = totalCurrentCost - totalNewCost @@ -136,7 +139,7 @@ export default function MortgageCalculator() {
- +
- +
- +
- @@ -194,11 +200,14 @@ export default function MortgageCalculator() { Refinance Analysis - Compare your current mortgage with refinancing options. + Compare your current mortgage with refinancing options. + +

Current Loan Term

+
- +
- + + setRefCurrentMonths(e.target.value)} + min="0" + step="1" + /> +
+ +
+ setRefCurrentRate(e.target.value)} + value={refCurrentRate} // Fixed: Use refCurrentRate instead of annualRate + onChange={(e) => setRefCurrentRate(e.target.value)} // Fixed: Use setRefCurrentRate min="0" step="0.01" />
-
- +
+ setRefCurrentMonths(e.target.value)} + placeholder="Enter payment amount" + value={refCurrentMonthlyPayment} + onChange={(e) => setRefCurrentMonthlyPayment(e.target.value)} min="0" - step="1" + step="0.01" />
+

New Loan Terms

+
- + setRefNewRate(e.target.value)} + placeholder="Enter new loan amount" + value={refNewLoanAmount} + onChange={(e) => setRefNewLoanAmount(e.target.value)} min="0" step="0.01" />
-
- +
+
- + + setRefNewRate(e.target.value)} + min="0" + step="0.01" + /> +
+ +
+ +
+
- {refinanceResults && (
-
-

Current Monthly Payment

-

- $ - {refinanceResults.currentMonthlyPayment.toLocaleString("en-US", { - minimumFractionDigits: 2, - maximumFractionDigits: 2, - })} -

-

New Monthly Payment

@@ -368,4 +413,4 @@ export default function MortgageCalculator() {
) -} +} \ No newline at end of file From f0c1fdff52bd807238a15da3d5314f0a4c8612be Mon Sep 17 00:00:00 2001 From: Jen Breese-Kauth Date: Sun, 11 Jan 2026 17:50:51 -0800 Subject: [PATCH 03/11] IFDM-92: more work on calculator look --- .../mortgate-calculator-suite/page.tsx | 280 +++++++++--------- 1 file changed, 142 insertions(+), 138 deletions(-) diff --git a/app/interactives/mortgate-calculator-suite/page.tsx b/app/interactives/mortgate-calculator-suite/page.tsx index 3dda047..19aaffe 100644 --- a/app/interactives/mortgate-calculator-suite/page.tsx +++ b/app/interactives/mortgate-calculator-suite/page.tsx @@ -139,7 +139,7 @@ export default function MortgageCalculator() {
- +
- +
- +
- - - {currentBalance !== null && ( -
-

Current Mortgage Balance

-

+

+

Current Mortgage Balance

+

${currentBalance.toLocaleString("en-US", { minimumFractionDigits: 2, maximumFractionDigits: 2 })}

)} + @@ -204,143 +202,141 @@ export default function MortgageCalculator() { -

Current Loan Term

+
+
+

Current Loan Term

-
- - setRefCurrentBalance(e.target.value)} - min="0" - step="0.01" - /> -
- -
- - setRefCurrentMonths(e.target.value)} - min="0" - step="1" - /> -
- -
- - setRefCurrentRate(e.target.value)} // Fixed: Use setRefCurrentRate - min="0" - step="0.01" - /> -
- -
- - setRefCurrentMonthlyPayment(e.target.value)} - min="0" - step="0.01" - /> -
+
+ + setRefCurrentBalance(e.target.value)} + min="0" + step="0.01" + /> +
-

New Loan Terms

+
+ + setRefCurrentMonths(e.target.value)} + min="0" + step="1" + /> +
-
- - setRefNewLoanAmount(e.target.value)} - min="0" - step="0.01" - /> -
+
+ + setRefCurrentRate(e.target.value)} // Fixed: Use setRefCurrentRate + min="0" + step="0.01" + /> +
-
- - setRefNewMonths(e.target.value)} - min="0" - step="1" - /> -
+
+ + setRefCurrentMonthlyPayment(e.target.value)} + min="0" + step="0.01" + /> +
+
+
+

New Loan Terms

+ +
+ + setRefNewLoanAmount(e.target.value)} + min="0" + step="0.01" + /> +
-
- - setRefNewRate(e.target.value)} - min="0" - step="0.01" - /> -
+
+ + setRefNewMonths(e.target.value)} + min="0" + step="1" + /> +
-
+
+ + setRefNewRate(e.target.value)} + min="0" + step="0.01" + /> +
-
- - setRefClosingCosts(e.target.value)} - min="0" - step="0.01" - /> -
+
+ +
+ + setRefClosingCosts(e.target.value)} + min="0" + step="0.01" + /> +
-
- - setRefYearsIn(e.target.value)} - min="0" - step="0.01" - /> +
+ + setRefYearsIn(e.target.value)} + min="0" + step="0.01" + /> +
+
- - {refinanceResults && ( -
+
-
-

New Monthly Payment

-

+

+

New Monthly Payment

+

$ {refinanceResults.newMonthlyPayment.toLocaleString("en-US", { minimumFractionDigits: 2, @@ -405,6 +401,14 @@ export default function MortgageCalculator() {

)} + + + From fc0777520ea28486df5ed9e1c0123c5ca6645e7a Mon Sep 17 00:00:00 2001 From: Jen Breese-Kauth Date: Mon, 12 Jan 2026 11:59:30 -0800 Subject: [PATCH 04/11] fixup --- .../mortgate-calculator-suite/page.tsx | 102 ++++++------------ 1 file changed, 34 insertions(+), 68 deletions(-) diff --git a/app/interactives/mortgate-calculator-suite/page.tsx b/app/interactives/mortgate-calculator-suite/page.tsx index 19aaffe..44495db 100644 --- a/app/interactives/mortgate-calculator-suite/page.tsx +++ b/app/interactives/mortgate-calculator-suite/page.tsx @@ -97,11 +97,11 @@ export default function MortgageCalculator() { newMonthlyPayment = (currentBal * (newR * Math.pow(1 + newR, newM))) / (Math.pow(1 + newR, newM) - 1) } - const monthlySavings = currentMonthlyPayment - newMonthlyPayment // This is already correct: old monthly minus new monthly + const monthlySavings = newMonthlyPayment - currentMonthlyPayment // Difference: new minus current const totalCurrentCost = currentMonthlyPayment * currentM const totalNewCost = newMonthlyPayment * newM + closingCosts const totalSavings = totalCurrentCost - totalNewCost - const breakEvenMonths = monthlySavings > 0 ? closingCosts / monthlySavings : 0 + const breakEvenMonths = monthlySavings < 0 ? closingCosts / (-monthlySavings) : 0 setRefinanceResults({ currentMonthlyPayment, @@ -138,7 +138,7 @@ export default function MortgageCalculator() { -
+
-
+
-
+
)}
@@ -156,11 +158,12 @@ export default function MortgageCalculator() { setAnnualRate(e.target.value)} min="0" step="0.01" + className="text-lagunita font-bold" />
@@ -169,7 +172,7 @@ export default function MortgageCalculator() { setMonthlyPayment(e.target.value)} min="0" @@ -211,11 +214,12 @@ export default function MortgageCalculator() { setRefCurrentBalance(e.target.value)} min="0" step="0.01" + className="text-black font-bold" />
@@ -224,24 +228,26 @@ export default function MortgageCalculator() { setRefCurrentMonths(e.target.value)} min="0" step="1" + className="text-black font-bold" />
- + setRefCurrentRate(e.target.value)} // Fixed: Use setRefCurrentRate min="0" step="0.01" + className="text-lagunita font-bold" />
@@ -250,11 +256,12 @@ export default function MortgageCalculator() { setRefCurrentMonthlyPayment(e.target.value)} min="0" step="0.01" + className="text-black font-bold" />
@@ -266,11 +273,12 @@ export default function MortgageCalculator() { setRefNewLoanAmount(e.target.value)} min="0" step="0.01" + className="text-black font-bold" />
@@ -279,12 +287,14 @@ export default function MortgageCalculator() { setRefNewMonths(e.target.value)} min="0" step="1" + className="text-black font-bold" /> +

{refNewMonths || "0"} months = {(Number.parseFloat(refNewMonths || "0") / 12).toFixed(1)} years

@@ -292,11 +302,12 @@ export default function MortgageCalculator() { setRefNewRate(e.target.value)} min="0" step="0.01" + className="text-lagunita font-bold" />
@@ -307,11 +318,12 @@ export default function MortgageCalculator() { setRefClosingCosts(e.target.value)} min="0" step="0.01" + className="text-black font-bold" />
@@ -320,11 +332,12 @@ export default function MortgageCalculator() { setRefYearsIn(e.target.value)} min="0" step="0.01" + className="text-black font-bold" />
From 96d3f2906f6ca840a813eee7db190317e088f71a Mon Sep 17 00:00:00 2001 From: Jen Breese-Kauth Date: Mon, 12 Jan 2026 13:53:46 -0800 Subject: [PATCH 06/11] fixup --- app/interactives/mortgate-calculator-suite/page.tsx | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/app/interactives/mortgate-calculator-suite/page.tsx b/app/interactives/mortgate-calculator-suite/page.tsx index ce8f6ec..ccd38ab 100644 --- a/app/interactives/mortgate-calculator-suite/page.tsx +++ b/app/interactives/mortgate-calculator-suite/page.tsx @@ -201,7 +201,7 @@ export default function MortgageCalculator() { Refinance Analysis - Compare your current mortgage with refinancing options. + Analyze if refinancing makes financial sense for your situation. @@ -314,7 +314,7 @@ export default function MortgageCalculator() {
- + -
-

New monthly payment

+
+

New monthly payment

$ {refinanceResults.newMonthlyPayment.toLocaleString("en-US", { @@ -357,7 +357,7 @@ export default function MortgageCalculator() {

-
+

Difference in monthly payment

$ From 900ea004c6ac5a8b434f2b80ad5bed991f6a1413 Mon Sep 17 00:00:00 2001 From: Jen Breese-Kauth Date: Mon, 12 Jan 2026 15:48:24 -0800 Subject: [PATCH 07/11] fixup for padding --- app/interactives/mortgate-calculator-suite/page.tsx | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/app/interactives/mortgate-calculator-suite/page.tsx b/app/interactives/mortgate-calculator-suite/page.tsx index ccd38ab..1b18ccb 100644 --- a/app/interactives/mortgate-calculator-suite/page.tsx +++ b/app/interactives/mortgate-calculator-suite/page.tsx @@ -119,12 +119,12 @@ export default function MortgageCalculator() {

{/* Header */} -

Debt Payoff Calculator

+

Mortgage Calculator Suite

{/* Mode Selection */}
- + Current Balance Refinance Analysis @@ -206,7 +206,7 @@ export default function MortgageCalculator() {
-
+

Current Loan Term

@@ -265,7 +265,7 @@ export default function MortgageCalculator() { />
-
+

New Loan Terms

From 824049a697d9712c7bbb623109017dc74de6ee65 Mon Sep 17 00:00:00 2001 From: Jen Breese-Kauth Date: Tue, 13 Jan 2026 11:24:11 -0800 Subject: [PATCH 08/11] IFDM-92: fixup button --- .../mortgate-calculator-suite/page.tsx | 29 +++++++++++++++---- 1 file changed, 23 insertions(+), 6 deletions(-) diff --git a/app/interactives/mortgate-calculator-suite/page.tsx b/app/interactives/mortgate-calculator-suite/page.tsx index 1b18ccb..4ad75ff 100644 --- a/app/interactives/mortgate-calculator-suite/page.tsx +++ b/app/interactives/mortgate-calculator-suite/page.tsx @@ -381,12 +381,29 @@ export default function MortgageCalculator() {
)} - + {refinanceResults ? ( +
+ + +
+ ) : ( + + )} From c324bca4d93cba2044b848e968df1db380ee53af Mon Sep 17 00:00:00 2001 From: Jen Breese-Kauth Date: Tue, 13 Jan 2026 16:47:45 -0800 Subject: [PATCH 09/11] fixup --- app/interactives/mortgate-calculator-suite/page.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/interactives/mortgate-calculator-suite/page.tsx b/app/interactives/mortgate-calculator-suite/page.tsx index 4ad75ff..52464eb 100644 --- a/app/interactives/mortgate-calculator-suite/page.tsx +++ b/app/interactives/mortgate-calculator-suite/page.tsx @@ -369,10 +369,10 @@ export default function MortgageCalculator() {
-

Refinancing saves you

+

{refinanceResults.totalSavings >= 0 ? "Refinancing saves you" : "Refinancing costs you"}

$ - {refinanceResults.totalSavings.toLocaleString("en-US", { + {Math.abs(refinanceResults.totalSavings).toLocaleString("en-US", { minimumFractionDigits: 2, maximumFractionDigits: 2, })} From 5fb2e8c277251caaa89385d4c20545e31385f26d Mon Sep 17 00:00:00 2001 From: Jen Breese-Kauth Date: Mon, 19 Jan 2026 10:34:29 -0800 Subject: [PATCH 10/11] added in the up and down increment arrows --- .../mortgate-calculator-suite/page.tsx | 379 ++++++++++++++++-- 1 file changed, 350 insertions(+), 29 deletions(-) diff --git a/app/interactives/mortgate-calculator-suite/page.tsx b/app/interactives/mortgate-calculator-suite/page.tsx index 52464eb..6d377aa 100644 --- a/app/interactives/mortgate-calculator-suite/page.tsx +++ b/app/interactives/mortgate-calculator-suite/page.tsx @@ -7,6 +7,8 @@ import { Input } from "@/app/ui/components/input" import { Button } from "@/app/ui/components/button" import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/app/ui/components/tabs" import ThemeToggle from "@/app/lib/theme-toggle"; +import { BiSolidUpArrow, BiSolidDownArrow } from "react-icons/bi"; + export default function MortgageCalculator() { const [monthsRemaining, setMonthsRemaining] = useState("") @@ -97,7 +99,7 @@ export default function MortgageCalculator() { newMonthlyPayment = (currentBal * (newR * Math.pow(1 + newR, newM))) / (Math.pow(1 + newR, newM) - 1) } - const monthlySavings = newMonthlyPayment - currentMonthlyPayment // Difference: new minus current + const monthlySavings = currentMonthlyPayment - newMonthlyPayment const totalCurrentCost = currentMonthlyPayment * currentM const totalNewCost = newMonthlyPayment * newM + closingCosts const totalSavings = totalCurrentCost - totalNewCost @@ -138,7 +140,7 @@ export default function MortgageCalculator() { -

+
setMonthsRemaining(e.target.value)} min="0" step="1" - className="text-black font-bold" + className="text-black font-bold [appearance:textfield] [&::-webkit-outer-spin-button]:appearance-none [&::-webkit-inner-spin-button]:appearance-none" /> +
+ + +

{monthsRemaining || "0"} months = {(Number.parseFloat(monthsRemaining || "0") / 12).toFixed(1)} years

-
+
setAnnualRate(e.target.value)} min="0" - step="0.01" - className="text-lagunita font-bold" + step="0.1" + className="text-lagunita font-bold [appearance:textfield] [&::-webkit-outer-spin-button]:appearance-none [&::-webkit-inner-spin-button]:appearance-none" /> +
+ + +
-
+
setMonthlyPayment(e.target.value)} min="0" step="0.01" + className="[appearance:textfield] [&::-webkit-outer-spin-button]:appearance-none [&::-webkit-inner-spin-button]:appearance-none" /> +
+ + +
{currentBalance !== null && (
@@ -209,7 +294,7 @@ export default function MortgageCalculator() {

Current Loan Term

-
+
setRefCurrentBalance(e.target.value)} min="0" step="0.01" - className="text-black font-bold" + className="text-black font-bold [appearance:textfield] [&::-webkit-outer-spin-button]:appearance-none [&::-webkit-inner-spin-button]:appearance-none" /> +
+ + +
-
+
setRefCurrentMonths(e.target.value)} min="0" step="1" - className="text-black font-bold" + className="text-black font-bold [appearance:textfield] [&::-webkit-outer-spin-button]:appearance-none [&::-webkit-inner-spin-button]:appearance-none" /> +
+ + +
-
+
setRefCurrentRate(e.target.value)} // Fixed: Use setRefCurrentRate + value={refCurrentRate} + onChange={(e) => setRefCurrentRate(e.target.value)} min="0" step="0.01" - className="text-lagunita font-bold" + className="text-lagunita font-bold [appearance:textfield] [&::-webkit-outer-spin-button]:appearance-none [&::-webkit-inner-spin-button]:appearance-none" /> +
+ + +
-
+
setRefCurrentMonthlyPayment(e.target.value)} min="0" step="0.01" - className="text-black font-bold" + className="text-black font-bold [appearance:textfield] [&::-webkit-outer-spin-button]:appearance-none [&::-webkit-inner-spin-button]:appearance-none" /> +
+ + +

New Loan Terms

-
+
setRefNewLoanAmount(e.target.value)} min="0" step="0.01" - className="text-black font-bold" + className="text-black font-bold [appearance:textfield] [&::-webkit-outer-spin-button]:appearance-none [&::-webkit-inner-spin-button]:appearance-none" /> +
+ + +
-
+
setRefNewMonths(e.target.value)} min="0" step="1" - className="text-black font-bold" + className="text-black font-bold [appearance:textfield] [&::-webkit-outer-spin-button]:appearance-none [&::-webkit-inner-spin-button]:appearance-none" /> +
+ + +

{refNewMonths || "0"} months = {(Number.parseFloat(refNewMonths || "0") / 12).toFixed(1)} years

-
+
setRefNewRate(e.target.value)} min="0" - step="0.01" - className="text-lagunita font-bold" + step="0.1" + className="text-lagunita font-bold [appearance:textfield] [&::-webkit-outer-spin-button]:appearance-none [&::-webkit-inner-spin-button]:appearance-none" /> +
+ + +
-
+
setRefClosingCosts(e.target.value)} min="0" step="0.01" - className="text-black font-bold" + className="text-black font-bold [appearance:textfield] [&::-webkit-outer-spin-button]:appearance-none [&::-webkit-inner-spin-button]:appearance-none" /> +
+ + +
-
+
setRefYearsIn(e.target.value)} min="0" - step="0.01" - className="text-black font-bold" + step="1" + className="text-black font-bold [appearance:textfield] [&::-webkit-outer-spin-button]:appearance-none [&::-webkit-inner-spin-button]:appearance-none" /> +
+ + +
From d86ccd44e9abb5c89f9da3483dbb6496dc7ed2e4 Mon Sep 17 00:00:00 2001 From: Jen Breese-Kauth Date: Mon, 19 Jan 2026 11:26:38 -0800 Subject: [PATCH 11/11] fixup to the results display --- .../mortgate-calculator-suite/page.tsx | 123 +++++++++++++----- 1 file changed, 94 insertions(+), 29 deletions(-) diff --git a/app/interactives/mortgate-calculator-suite/page.tsx b/app/interactives/mortgate-calculator-suite/page.tsx index 6d377aa..03dde8c 100644 --- a/app/interactives/mortgate-calculator-suite/page.tsx +++ b/app/interactives/mortgate-calculator-suite/page.tsx @@ -9,6 +9,11 @@ import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/app/ui/components/ta import ThemeToggle from "@/app/lib/theme-toggle"; import { BiSolidUpArrow, BiSolidDownArrow } from "react-icons/bi"; +const formatCurrency = (amount: number) => + amount.toLocaleString("en-US", { + minimumFractionDigits: 2, + maximumFractionDigits: 2 + }) export default function MortgageCalculator() { const [monthsRemaining, setMonthsRemaining] = useState("") @@ -33,6 +38,7 @@ export default function MortgageCalculator() { totalNewCost: number totalSavings: number breakEvenMonths: number + breakEvenMessage: string } | null>(null) const calculateBalance = () => { @@ -91,19 +97,67 @@ export default function MortgageCalculator() { (currentBal * (currentR * Math.pow(1 + currentR, currentM))) / (Math.pow(1 + currentR, currentM) - 1) } + // Calculate new monthly payment + const newLoanAmount = Number.parseFloat(refNewLoanAmount) + + // Validate newLoanAmount in your validation section + if ( + isNaN(currentBal) || + isNaN(currentR) || + isNaN(currentM) || + isNaN(newLoanAmount) || // ✅ Add this + isNaN(newR) || + isNaN(newM) || + isNaN(closingCosts) || + currentBal <= 0 || + currentR < 0 || + currentM <= 0 || + newLoanAmount <= 0 || // ✅ Add this + newR < 0 || + newM <= 0 || + closingCosts < 0 + ) { + alert("Please enter valid numbers") + return + } + // Calculate new monthly payment let newMonthlyPayment: number if (newR === 0) { - newMonthlyPayment = currentBal / newM + newMonthlyPayment = newLoanAmount / newM // ✅ Use newLoanAmount } else { - newMonthlyPayment = (currentBal * (newR * Math.pow(1 + newR, newM))) / (Math.pow(1 + newR, newM) - 1) + newMonthlyPayment = (newLoanAmount * (newR * Math.pow(1 + newR, newM))) / (Math.pow(1 + newR, newM) - 1) // ✅ Use newLoanAmount } const monthlySavings = currentMonthlyPayment - newMonthlyPayment const totalCurrentCost = currentMonthlyPayment * currentM const totalNewCost = newMonthlyPayment * newM + closingCosts const totalSavings = totalCurrentCost - totalNewCost - const breakEvenMonths = monthlySavings < 0 ? closingCosts / (-monthlySavings) : 0 + + let breakEvenMonths: number + let breakEvenMessage: string + + if (monthlySavings > 0) { + // Case 1: Lower monthly payment + breakEvenMonths = closingCosts / monthlySavings + breakEvenMessage = `You'll recover closing costs in ${breakEvenMonths.toFixed(1)} months` + } else if (monthlySavings < 0) { + // Case 2: Higher monthly payment + // Only makes sense if total savings over loan term is positive + if (totalSavings > 0) { + breakEvenMonths = closingCosts / (-monthlySavings) + breakEvenMessage = `Despite higher monthly payments, you'll save overall if you stay ${breakEvenMonths.toFixed(1)}+ months` + } else { + breakEvenMonths = Infinity + breakEvenMessage = "This refinance costs more overall - not recommended" + } + } else { + // Case 3: Same monthly payment + breakEvenMonths = totalSavings > 0 ? 0 : Infinity + breakEvenMessage = totalSavings > 0 + ? "Same monthly payment, but you save on total interest" + : "No financial benefit" + } setRefinanceResults({ currentMonthlyPayment, @@ -113,6 +167,7 @@ export default function MortgageCalculator() { totalNewCost, totalSavings, breakEvenMonths, + breakEvenMessage, // Add this to state type }) } @@ -324,10 +379,8 @@ export default function MortgageCalculator() { tabIndex={-1} aria-label="Decrease amount" onClick={() => { - const current = Number.parseFloat(monthlyPayment || "0"); - if (current > 0) { - setMonthlyPayment((current - 1).toFixed(2)); - } + const current = Number.parseFloat(refCurrentBalance || "0"); + setRefCurrentBalance(Math.max(0,current - 1).toFixed(2)); }} className="hover:text-grey-med-dark focus:outline-none" > @@ -520,7 +573,7 @@ export default function MortgageCalculator() { const current = Number.parseInt(refNewMonths || "0"); setRefNewMonths((current + 1).toString()); }} - className="mt-[-6px] mb-[-5px] mt-6 hover:text-grey-med-dark focus:outline-none" + className="mt-[-6px] mb-[-5px] hover:text-grey-med-dark focus:outline-none" > @@ -666,37 +719,49 @@ export default function MortgageCalculator() { {refinanceResults && (
- +
-

New monthly payment

+

New monthly payment

- $ - {refinanceResults.newMonthlyPayment.toLocaleString("en-US", { - minimumFractionDigits: 2, - maximumFractionDigits: 2, - })} + ${formatCurrency(refinanceResults.newMonthlyPayment)}

-

Difference in monthly payment

-

- $ - {refinanceResults.monthlySavings.toLocaleString("en-US", { - minimumFractionDigits: 2, - maximumFractionDigits: 2, - })} +

+ {refinanceResults.monthlySavings >= 0 ? "Monthly savings" : "Monthly increase"} +

+

= 0 + ? "bg-lagunita-lighter text-black border-1 border-lagunita" + : "bg-berry-light border-berry text-black" + }`}> + {refinanceResults.monthlySavings >= 0 ? "+" : "-"}$ + {formatCurrency(Math.abs(refinanceResults.monthlySavings))}

+ {/*
+

Break-even analysis

+

+ {refinanceResults.breakEvenMessage} +

+ {refinanceResults.breakEvenMonths > 0 && refinanceResults.breakEvenMonths !== Infinity && ( +

+ ({refinanceResults.breakEvenMonths.toFixed(1)} months = {(refinanceResults.breakEvenMonths / 12).toFixed(1)} years) +

+ )} +
*/} +
-

{refinanceResults.totalSavings >= 0 ? "Refinancing saves you" : "Refinancing costs you"}

-

- $ - {Math.abs(refinanceResults.totalSavings).toLocaleString("en-US", { - minimumFractionDigits: 2, - maximumFractionDigits: 2, - })} +

+ {refinanceResults.totalSavings >= 0 ? "Refinancing saves you" : "Refinancing costs you"} +

+

= 0 ? "text-lagunita" : "text-berry" + }`}> + {refinanceResults.totalSavings >= 0 ? "" : "-"}$ + {formatCurrency(Math.abs(refinanceResults.totalSavings))}