Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 16 additions & 8 deletions app/interactives/inflation-impact-calculator/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -40,14 +40,22 @@ const formatCurrency = (value: number) =>
export default function InflationCalculator() {
const [initialPriceRaw, setInitialPriceRaw] = useState("100");
const [initialPriceDisplay, setInitialPriceDisplay] = useState("100");
const [priceBlurred, setPriceBlurred] = useState(false);
const [inflationRate, setInflationRate] = useState(3.5);
const [timePeriod, setTimePeriod] = useState(10);
const [futureValue, setFutureValue] = useState<number | null>(0);


const numericPrice = parseFloat(initialPriceRaw);
const priceError: PriceError = getPriceError(initialPriceRaw, numericPrice);
const isValid = priceError === null && !isNaN(numericPrice);

// Range errors (including negatives) show live so the user sees the mistake as
// they type. The empty-field prompt waits until they have left the field.
const showRangeError = priceError === "range";
const showEmptyError = priceError === "empty" && priceBlurred;
const showError = showRangeError || showEmptyError;

useEffect(() => {
if (!isValid) {
setFutureValue(null);
Expand All @@ -71,12 +79,11 @@ export default function InflationCalculator() {
return "bg-badge-red";
};

const priceErrorMessage =
priceError === "empty"
const priceErrorMessage = showRangeError
? "Enter an amount between $0.01 and $1,000,000,000."
: showEmptyError
? "Enter an initial amount to see the impact of inflation over time."
: priceError === "range"
? "Enter an amount between $0.01 and $1,000,000,000."
: null;
: null;

return (
<div className="p-6 max-w-5xl mx-auto">
Expand Down Expand Up @@ -107,7 +114,7 @@ export default function InflationCalculator() {
value={initialPriceDisplay}
onChange={(e) => {
const stripped = e.target.value.replace(/,/g, "");
if (stripped !== "" && !/^\d*\.?\d*$/.test(stripped))
if (stripped !== "" && !/^-?\d*\.?\d*$/.test(stripped))
return;
setInitialPriceRaw(stripped);
const num = parseFloat(stripped);
Expand All @@ -125,17 +132,18 @@ export default function InflationCalculator() {
setInitialPriceDisplay(initialPriceRaw);
}}
onBlur={() => {
setPriceBlurred(true);
const num = parseFloat(initialPriceRaw);
if (!isNaN(num)) {
setInitialPriceDisplay(formatWithCommas(num));
} else {
setInitialPriceDisplay(initialPriceRaw);
}
}}
aria-invalid={priceError !== null}
aria-invalid={showError}
aria-describedby="initial-price-error"
className={`bg-[var(--input-background)] text-[var(--input-text)] text-md w-full rounded-md shadow-sm pl-7 border pr-10 ${
priceError
showError
? "border-2 border-[var(--color-inline-error)]"
: "bg-[var(--input-border)]"
}`}
Expand Down
3 changes: 2 additions & 1 deletion app/interactives/present-value-calculator-v2/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,7 @@ export default function PresentValueCalculator() {
// Whether each tab has a blocking error (or empty required field) that should suppress results.
const singleHasError = singleAnyFieldEmpty || !!futureValueError || !!interestRateError || !!timePeriodError
const seriesHasError = seriesAnyFieldEmpty || !!paymentAmountError || !!finalAmountError || !!paymentInterestRateError || !!numberOfPaymentsError
const seriesHasValidationError = !!paymentAmountError || !!finalAmountError || !!paymentInterestRateError || !!numberOfPaymentsError

// Debounced inputs for calculations — updates after 300ms pause in typing
// to avoid recalculating on every keystroke.
Expand Down Expand Up @@ -1004,7 +1005,7 @@ export default function PresentValueCalculator() {
? "—"
: formatCurrency(paymentCalculations.presentValue)}
</p>
{seriesHasError && (
{seriesHasValidationError && (
<p className="text-sm text-muted-foreground mb-3">
Fix the fields on the left to see results.
</p>
Expand Down