From 1478c0e625a5b5dd2c7bcd153438b261e3bcf8d8 Mon Sep 17 00:00:00 2001 From: Jen Breese-Kauth Date: Fri, 5 Jun 2026 16:06:19 -0700 Subject: [PATCH] Client feedback changes --- .../present-value-calculator-v2/page.tsx | 264 +++++++++++------- 1 file changed, 163 insertions(+), 101 deletions(-) diff --git a/app/interactives/present-value-calculator-v2/page.tsx b/app/interactives/present-value-calculator-v2/page.tsx index a0b0362..d539a8f 100644 --- a/app/interactives/present-value-calculator-v2/page.tsx +++ b/app/interactives/present-value-calculator-v2/page.tsx @@ -13,6 +13,8 @@ import { SelectValue, } from "@/app/ui/components/select" import ThemeToggle from "@/app/lib/theme-toggle" +import { FaCircleInfo } from "react-icons/fa6" + type CompoundingFrequency = "annually" | "semi-annually" | "quarterly" | "monthly" | "biweekly" | "weekly" | "daily" @@ -28,19 +30,19 @@ const frequencyMap: Record("") + const [interestRate, setInterestRate] = useState("") + const [timePeriod, setTimePeriod] = useState("") const [compoundingFrequency, setCompoundingFrequency] = useState("annually") // Payment Series State - const [paymentAmount, setPaymentAmount] = useState(0) - const [paymentInterestRate, setPaymentInterestRate] = useState(0) - const [numberOfPayments, setNumberOfPayments] = useState(0) + const [paymentAmount, setPaymentAmount] = useState("") + const [paymentInterestRate, setPaymentInterestRate] = useState("") + const [numberOfPayments, setNumberOfPayments] = useState("") const [paymentFrequency, setPaymentFrequency] = useState("annually") - const [finalAmount, setFinalAmount] = useState(0) + const [finalAmount, setFinalAmount] = useState("") // Error states const [futureValueError, setFutureValueError] = useState(""); @@ -48,17 +50,17 @@ export default function PresentValueCalculator() { const [timePeriodError, setTimePeriodError] = useState(""); const [paymentAmountError, setPaymentAmountError] = useState(""); const [finalAmountError, setFinalAmountError] = useState(""); - const [paymentInterestRateError, setPaymentInterestRateError] = - useState(""); + const [paymentInterestRateError, setPaymentInterestRateError] = useState(""); const [numberOfPaymentsError, setNumberOfPaymentsError] = useState(""); const singleCalculations = useMemo(() => { - const rate = interestRate / 100 + const fv = parseFloat(futureValue) || 0 + const rate = (parseFloat(interestRate) || 0) / 100 const n = frequencyMap[compoundingFrequency].periods - const totalPeriods = timePeriod // CHANGED: use input directly as total periods + const totalPeriods = parseFloat(timePeriod) || 0 const periodRate = rate / n - const presentValue = futureValue / Math.pow(1 + periodRate, totalPeriods) - const discountAmount = futureValue - presentValue + const presentValue = fv / Math.pow(1 + periodRate, totalPeriods) + const discountAmount = fv - presentValue return { presentValue, @@ -68,25 +70,23 @@ export default function PresentValueCalculator() { }, [futureValue, interestRate, timePeriod, compoundingFrequency]) const paymentCalculations = useMemo(() => { - const rate = paymentInterestRate / 100; + const rate = (parseFloat(paymentInterestRate) || 0) / 100; const n = frequencyMap[paymentFrequency].periods; + const periods = parseFloat(numberOfPayments) || 0; + const pa = parseFloat(paymentAmount) || 0; + const fa = parseFloat(finalAmount) || 0; const periodRate = rate / n; let pvPayments: number; if (periodRate === 0) { - pvPayments = paymentAmount * numberOfPayments; + pvPayments = pa * periods; } else { - pvPayments = - paymentAmount * - ((1 - Math.pow(1 + periodRate, -numberOfPayments)) / periodRate); + pvPayments = pa * ((1 - Math.pow(1 + periodRate, -periods)) / periodRate); } - // PV of the lump sum final amount discounted over all periods - const pvFinalAmount = - finalAmount / Math.pow(1 + periodRate, numberOfPayments); - + const pvFinalAmount = fa / Math.pow(1 + periodRate, periods); const presentValue = pvPayments + pvFinalAmount; - const totalPayments = paymentAmount * numberOfPayments + finalAmount; + const totalPayments = pa * periods + fa; const discountAmount = totalPayments - presentValue; return { @@ -94,13 +94,7 @@ export default function PresentValueCalculator() { totalPayments, discountAmount, }; - }, [ - paymentAmount, - finalAmount, - paymentInterestRate, - numberOfPayments, - paymentFrequency, - ]); + }, [paymentAmount, finalAmount, paymentInterestRate, numberOfPayments, paymentFrequency]); const formatCurrency = (value: number) => { return new Intl.NumberFormat("en-US", { @@ -114,7 +108,6 @@ export default function PresentValueCalculator() { return (
- {/* Header */}

Present Value Calculator

@@ -132,10 +125,11 @@ export default function PresentValueCalculator() {
- {/* Future Value Input */}

Enter a future value to calculate what it is worth today.

+ + {/* Future Value */}
- {/* Interest Rate Input */} + {/* Interest Rate */}
@@ -223,7 +233,7 @@ export default function PresentValueCalculator() { )}
- {/* Time Period Input */} + {/* Time Period */}
- {/* Results Section */} + {/* Results */}
- {/* Main Present Value Display */}

Present value

{formatCurrency(singleCalculations.presentValue)}

- - {/* Breakdown */}
- {formatCurrency(futureValue)} + {formatCurrency(parseFloat(futureValue) || 0)}
@@ -333,18 +350,19 @@ export default function PresentValueCalculator() {
- {/* Payment Amount Input */}

- Find what a series of payments is worth today. Enter a - payment amount and number of payments to calculate the - present value. + Find what a series of payments is worth today. Enter the + payment per period and number of periods (payments) to + calculate the present value.

+ + {/* Payment Amount */}
{ - const val = - e.target.value === "" ? 0 : Number(e.target.value); + const raw = e.target.value; + const val = Number(raw); if (val < 0) { setPaymentAmountError( "Payment amount must be greater than 0.", @@ -368,9 +386,14 @@ export default function PresentValueCalculator() { return; } setPaymentAmountError(""); - setPaymentAmount(val); + setPaymentAmount(raw); }} - className={`border-1 w-full rounded-md shadow-sm py-2 px-3 [appearance:textfield] [&::-webkit-outer-spin-button]:appearance-none [&::-webkit-inner-spin-button]:appearance-none ${paymentAmount > 0 ? "pl-7" : "pl-8"} ${paymentAmountError ? "border-[var(--color-inline-error)] border-2" : ""}`} + onBlur={(e) => { + const val = parseFloat(e.target.value); + if (!isNaN(val)) setPaymentAmount(String(val)); + else setPaymentAmount(""); + }} + className={`border-1 w-full rounded-md shadow-sm py-2 px-3 [appearance:textfield] [&::-webkit-outer-spin-button]:appearance-none [&::-webkit-inner-spin-button]:appearance-none ${parseFloat(paymentAmount) > 0 ? "pl-7" : "pl-8"} ${paymentAmountError ? "border-[var(--color-inline-error)] border-2" : ""}`} />
{paymentAmountError && ( @@ -383,14 +406,33 @@ export default function PresentValueCalculator() { )}
- {/* Final Amount Input */} + {/* Final Amount */}
- +
+ +
+ + +
+
{finalAmountError && ( @@ -428,7 +479,7 @@ export default function PresentValueCalculator() { )}
- {/* Interest Rate Input */} + {/* Payment Interest Rate */}
- {/* Number of Payments Input */} + {/* Number of Payments */}
{ - const val = - e.target.value === "" ? 0 : Number(e.target.value); - if (val < 1) { + const raw = e.target.value; + const val = Number(raw); + if (val < 0) { setNumberOfPaymentsError( "Number of payments must be greater than 0.", ); return; } setNumberOfPaymentsError(""); - setNumberOfPayments(val); + setNumberOfPayments(raw); + }} + onBlur={(e) => { + const val = parseFloat(e.target.value); + if (!isNaN(val)) setNumberOfPayments(String(val)); + else setNumberOfPayments(""); }} className={`border-1 w-full rounded-md shadow-sm py-2 px-3 [appearance:textfield] [&::-webkit-outer-spin-button]:appearance-none [&::-webkit-inner-spin-button]:appearance-none ${numberOfPaymentsError ? "border-[var(--color-inline-error)] border-2" : ""}`} - min={1} + min={0} max={1000} step={1} /> @@ -521,7 +580,7 @@ export default function PresentValueCalculator() { {/* Payment Frequency */}
-
- {/* Results Section */} + {/* Results */}

Present value @@ -554,8 +618,6 @@ export default function PresentValueCalculator() {

{formatCurrency(paymentCalculations.presentValue)}

- - {/* Breakdown */}