Skip to content
Open
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
135 changes: 117 additions & 18 deletions app/interactives/mortgage-calculator/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import React, { useState, useEffect, useCallback } from 'react';
import ThemeToggle from "@/app/lib/theme-toggle";
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/app/ui/components/tabs";
import { Card, CardContent, CardHeader, CardTitle } from "@/app/ui/components/card";
import { BiSolidDownArrow, BiSolidUpArrow } from "react-icons/bi";

export default function MortgageCalculator() {
const [mode, setMode] = useState('afford'); // 'afford', 'affordability', 'payment'
Expand Down Expand Up @@ -123,8 +124,36 @@ export default function MortgageCalculator() {
step="1"
value={monthlyPayment}
onChange={(e) => setMonthlyPayment(e.target.value)}
className="w-full pl-8 pr-4 py-3 border-2 border-gray-300 rounded-lg focus:border-blue-500 focus:ring-2 focus:ring-blue-200 outline-none transition"
className="w-full pl-8 pr-4 py-3 border-2 border-gray-300 rounded-lg focus:border-blue-500 focus:ring-2 focus:ring-blue-200 outline-none transition [appearance:textfield] [&::-webkit-outer-spin-button]:appearance-none [&::-webkit-inner-spin-button]:appearance-none"
/>
<div className="absolute right-2 top-1/2 -translate-y-1/2 flex flex-col">
<button
type="button"
tabIndex={-1}
aria-label="Increase amount"
onClick={() =>
setMonthlyPayment(
String((Number(monthlyPayment) ?? 0) + 1)
)
}
className="mb-[-5px] hover:text-grey-med-dark focus:outline-none [appearance:textfield] [&::-webkit-outer-spin-button]:appearance-none [&::-webkit-inner-spin-button]:appearance-none"
>
<BiSolidUpArrow size={24} />
</button>
<button
type="button"
tabIndex={-1}
aria-label="Decrease amount"
onClick={() =>
setMonthlyPayment(
String((Number(monthlyPayment) ?? 0) - 1)
)
}
className="hover:text-grey-med-dark focus:outline-none"
>
<BiSolidDownArrow size={24} />
</button>
</div>
</div>
<p className="text-xs">Taxes, insurance, and HOA are separate—add estimates below to see your total cost.</p>
</div>
Expand Down Expand Up @@ -183,9 +212,44 @@ export default function MortgageCalculator() {
setDownPaymentPercent((value / Number(homePrice)) * 100);
}
}}
className="w-full pl-4 pr-16 py-3 border-2 border-gray-300 rounded-lg focus:border-blue-500 focus:ring-2 focus:ring-blue-200 outline-none transition"
className="w-full pl-4 pr-16 py-3 border-2 border-gray-300 rounded-lg focus:border-blue-500 focus:ring-2 focus:ring-blue-200 outline-none transition [appearance:textfield] [&::-webkit-outer-spin-button]:appearance-none [&::-webkit-inner-spin-button]:appearance-none"
/>

<div className="absolute right-2 top-1/2 -translate-y-1/2 flex flex-col">
<button
type="button"
tabIndex={-1}
aria-label="Increase amount"
onClick={() => {
if (downPaymentMode === 'percentage') {
setDownPaymentPercent((Number(downPaymentPercent) || 0) + 0.25);
} else {
const newAmount = (Number(downPaymentAmount) || 0) + 1000;
setDownPaymentAmount(newAmount);
setDownPaymentPercent((newAmount / Number(homePrice)) * 100);
}
}}
className="mb-[-5px] hover:text-grey-med-dark focus:outline-none [appearance:textfield] [&::-webkit-outer-spin-button]:appearance-none [&::-webkit-inner-spin-button]:appearance-none"
>
<BiSolidUpArrow size={24} />
</button>
<button
type="button"
tabIndex={-1}
aria-label="Decrease amount"
onClick={() => {
if (downPaymentMode === 'percentage') {
setDownPaymentPercent(Math.max(0, (Number(downPaymentPercent) || 0) - 0.25));
} else {
const newAmount = Math.max(0, (Number(downPaymentAmount) || 0) - 1000);
setDownPaymentAmount(newAmount);
setDownPaymentPercent((newAmount / Number(homePrice)) * 100);
}
}}
className="hover:text-grey-med-dark focus:outline-none"
>
<BiSolidDownArrow size={24} />
</button>
</div>
</div>
</div>

Expand All @@ -200,7 +264,7 @@ export default function MortgageCalculator() {
min="0"
value={interestRate}
onChange={(e) => setInterestRate(e.target.value)}
className="w-full pr-8 pl-4 py-3 border-2 border-gray-300 rounded-lg focus:border-blue-500 focus:ring-2 focus:ring-blue-200 outline-none transition"
className="w-full pr-8 pl-4 py-3 border-2 border-gray-300 rounded-lg focus:border-blue-500 focus:ring-2 focus:ring-blue-200 outline-none transition [appearance:textfield] [&::-webkit-outer-spin-button]:appearance-none [&::-webkit-inner-spin-button]:appearance-none"
/>
<span className="absolute right-3 top-1/2 -translate-y-1/2 font-medium">%</span>
</div>
Expand Down Expand Up @@ -239,7 +303,7 @@ export default function MortgageCalculator() {
{/* Property Taxes */}
<div className="pb-5">
<div className="flex flex-row pb-2 justify-between items-center">
<label className="block text-sm font-semibold">Property taxes (yearly)</label>
<label className="block text-sm font-semibold">Property taxes (annual)</label>
<div className="flex flex-row gap-2">
<button
type="button"
Expand Down Expand Up @@ -291,9 +355,44 @@ export default function MortgageCalculator() {
setPropertyTaxPercent((value / Number(homePrice)) * 100);
}
}}
className="w-full pl-4 pr-16 py-3 border-2 border-gray-300 rounded-lg focus:border-blue-500 focus:ring-2 focus:ring-blue-200 outline-none transition"
className="w-full pl-4 pr-16 py-3 border-2 border-gray-300 rounded-lg focus:border-blue-500 focus:ring-2 focus:ring-blue-200 outline-none transition [appearance:textfield] [&::-webkit-outer-spin-button]:appearance-none [&::-webkit-inner-spin-button]:appearance-none"
/>

<div className="absolute right-2 top-1/2 -translate-y-1/2 flex flex-col">
<button
type="button"
tabIndex={-1}
aria-label="Increase amount"
onClick={() => {
if (propertyTaxMode === 'percentage') {
setPropertyTaxPercent((Number(propertyTaxPercent) || 0) + 0.25);
} else {
const newAmount = (Number(propertyTaxAmount) || 0) + 1000;
setPropertyTaxAmount(newAmount);
setPropertyTaxPercent((newAmount / Number(homePrice)) * 100);
}
}}
className="mb-[-5px] hover:text-grey-med-dark focus:outline-none [appearance:textfield] [&::-webkit-outer-spin-button]:appearance-none [&::-webkit-inner-spin-button]:appearance-none"
>
<BiSolidUpArrow size={24} />
</button>
<button
type="button"
tabIndex={-1}
aria-label="Decrease amount"
onClick={() => {
if (propertyTaxMode === 'percentage') {
setPropertyTaxPercent(Math.max(0, (Number(propertyTaxPercent) || 0) - 0.25));
} else {
const newAmount = Math.max(0, (Number(propertyTaxAmount) || 0) - 1000);
setPropertyTaxAmount(newAmount);
setPropertyTaxPercent((newAmount / Number(homePrice)) * 100);
}
}}
className="hover:text-grey-med-dark focus:outline-none"
>
<BiSolidDownArrow size={24} />
</button>
</div>
</div>
</div>

Expand Down Expand Up @@ -352,7 +451,7 @@ export default function MortgageCalculator() {
setHomeInsurancePercent((value / Number(homePrice)) * 100);
}
}}
className="w-full pl-4 pr-16 py-3 border-2 border-gray-300 rounded-lg focus:border-blue-500 focus:ring-2 focus:ring-blue-200 outline-none transition"
className="w-full pl-4 pr-16 py-3 border-2 border-gray-300 rounded-lg focus:border-blue-500 focus:ring-2 focus:ring-blue-200 outline-none transition [appearance:textfield] [&::-webkit-outer-spin-button]:appearance-none [&::-webkit-inner-spin-button]:appearance-none"
/>

</div>
Expand All @@ -370,7 +469,7 @@ export default function MortgageCalculator() {
min="0"
value={hoaDues}
onChange={(e) => setHoaDues(e.target.value)}
className="w-full pl-8 pr-4 py-3 border-2 border-gray-300 rounded-lg focus:border-blue-500 focus:ring-2 focus:ring-blue-200 outline-none transition"
className="w-full pl-8 pr-4 py-3 border-2 border-gray-300 rounded-lg focus:border-blue-500 focus:ring-2 focus:ring-blue-200 outline-none transition [appearance:textfield] [&::-webkit-outer-spin-button]:appearance-none [&::-webkit-inner-spin-button]:appearance-none"
/>
</div>
</div>
Expand Down Expand Up @@ -417,7 +516,7 @@ export default function MortgageCalculator() {
<div className="flex flex-col sm:flex-row mb-1 sm:bg-[var(--results-white-background)] rounded-lg">
<div
className="w-full sm:w-[50%] text-md p-4 font-bold text-black bg-grey-med-dark rounded-lg sm:rounded-l-lg sm:rounded-r-none flex items-center">
Mortgage (P&I):
Mortgage Payment:
</div>
<div
className="w-full sm:w-[50%] text-lg-title p-4 rounded-lg sm:rounded-r-lg font-bold overflow-hidden text-ellipsis flex items-center bg-[var(--secondary-background)]">
Expand Down Expand Up @@ -497,7 +596,7 @@ export default function MortgageCalculator() {
step="1"
value={homePrice}
onChange={(e) => setHomePrice(e.target.value)}
className="w-full pl-8 pr-4 py-3 border-2 border-gray-300 rounded-lg focus:border-blue-500 focus:ring-2 focus:ring-blue-200 outline-none transition"
className="w-full pl-8 pr-4 py-3 border-2 border-gray-300 rounded-lg focus:border-blue-500 focus:ring-2 focus:ring-blue-200 outline-none transition [appearance:textfield] [&::-webkit-outer-spin-button]:appearance-none [&::-webkit-inner-spin-button]:appearance-none"
/>
</div>
<p className="text-xs">Enter the purchase price of the home.</p>
Expand Down Expand Up @@ -558,7 +657,7 @@ export default function MortgageCalculator() {
setDownPaymentPercent((value / Number(homePrice)) * 100);
}
}}
className="w-full pl-4 pr-16 py-3 border-2 border-gray-300 rounded-lg focus:border-blue-500 focus:ring-2 focus:ring-blue-200 outline-none transition"
className="w-full pl-4 pr-16 py-3 border-2 border-gray-300 rounded-lg focus:border-blue-500 focus:ring-2 focus:ring-blue-200 outline-none transition [appearance:textfield] [&::-webkit-outer-spin-button]:appearance-none [&::-webkit-inner-spin-button]:appearance-none"
/>
</div>
</div>
Expand All @@ -574,7 +673,7 @@ export default function MortgageCalculator() {
min="0"
value={interestRate}
onChange={(e) => setInterestRate(e.target.value)}
className="w-full pr-8 pl-4 py-3 border-2 border-gray-300 rounded-lg focus:border-blue-500 focus:ring-2 focus:ring-blue-200 outline-none transition"
className="w-full pr-8 pl-4 py-3 border-2 border-gray-300 rounded-lg focus:border-blue-500 focus:ring-2 focus:ring-blue-200 outline-none transition [appearance:textfield] [&::-webkit-outer-spin-button]:appearance-none [&::-webkit-inner-spin-button]:appearance-none"
/>
<span className="absolute right-3 top-1/2 -translate-y-1/2 font-medium">%</span>
</div>
Expand Down Expand Up @@ -614,7 +713,7 @@ export default function MortgageCalculator() {
{/* Property Taxes */}
<div className="pb-5">
<div className="flex flex-row pb-2 justify-between items-center">
<label className="block text-sm font-semibold">Property taxes (yearly)</label>
<label className="block text-sm font-semibold">Property taxes (annual)</label>
<div className="flex flex-row gap-2">
<button
type="button"
Expand Down Expand Up @@ -666,7 +765,7 @@ export default function MortgageCalculator() {
setPropertyTaxPercent((value / Number(homePrice)) * 100);
}
}}
className="w-full pl-4 pr-16 py-3 border-2 border-gray-300 rounded-lg focus:border-blue-500 focus:ring-2 focus:ring-blue-200 outline-none transition"
className="w-full pl-4 pr-16 py-3 border-2 border-gray-300 rounded-lg focus:border-blue-500 focus:ring-2 focus:ring-blue-200 outline-none transition [appearance:textfield] [&::-webkit-outer-spin-button]:appearance-none [&::-webkit-inner-spin-button]:appearance-none"
/>

</div>
Expand Down Expand Up @@ -727,7 +826,7 @@ export default function MortgageCalculator() {
setHomeInsurancePercent((value / Number(homePrice)) * 100);
}
}}
className="w-full pl-4 pr-16 py-3 border-2 border-gray-300 rounded-lg focus:border-blue-500 focus:ring-2 focus:ring-blue-200 outline-none transition"
className="w-full pl-4 pr-16 py-3 border-2 border-gray-300 rounded-lg focus:border-blue-500 focus:ring-2 focus:ring-blue-200 outline-none transition [appearance:textfield] [&::-webkit-outer-spin-button]:appearance-none [&::-webkit-inner-spin-button]:appearance-none"
/>

</div>
Expand All @@ -745,7 +844,7 @@ export default function MortgageCalculator() {
min="0"
value={hoaDues}
onChange={(e) => setHoaDues(e.target.value)}
className="w-full pl-8 pr-4 py-3 border-2 border-gray-300 rounded-lg focus:border-blue-500 focus:ring-2 focus:ring-blue-200 outline-none transition"
className="w-full pl-8 pr-4 py-3 border-2 border-gray-300 rounded-lg focus:border-blue-500 focus:ring-2 focus:ring-blue-200 outline-none transition [appearance:textfield] [&::-webkit-outer-spin-button]:appearance-none [&::-webkit-inner-spin-button]:appearance-none"
/>
</div>
</div>
Expand Down Expand Up @@ -794,7 +893,7 @@ export default function MortgageCalculator() {
<div className="flex flex-col sm:flex-row mb-1 sm:bg-[var(--results-white-background)] rounded-lg">
<div
className="w-full sm:w-[50%] text-md p-4 font-bold text-black bg-grey-med-dark rounded-lg sm:rounded-l-lg sm:rounded-r-none flex items-center">
Mortgage (P&I):
Mortgage Payment:
</div>
<div
className="w-full sm:w-[50%] text-lg-title p-4 rounded-lg sm:rounded-r-lg font-bold overflow-hidden text-ellipsis flex items-center bg-[var(--secondary-background)]">
Expand Down