Val Town is a social website to write and deploy JavaScript.
Build APIs and schedule functions from your browser.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
/**
* This app creates a comprehensive mortgage refinance offer comparison tool.
* It calculates monthly payments, total interest paid, and displays charts for visual comparison.
* It uses React for the frontend, Chart.js for data visualization, and Val Town's blob storage for data persistence.
*/
/** @jsxImportSource https://esm.sh/react */
import React, { useState, useEffect, useRef } from "https://esm.sh/react";
import { createRoot } from "https://esm.sh/react-dom/client";
import Chart from "https://esm.sh/chart.js/auto";
interface Offer {
id: string;
lender: string;
rate: number;
term: number;
closingCosts: number;
monthlyPayment: number;
totalInterest: number;
}
function App() {
const [offers, setOffers] = useState<Offer[]>([]);
const [newOffer, setNewOffer] = useState<Omit<Offer, 'id' | 'monthlyPayment' | 'totalInterest'>>({
lender: "",
rate: 6,
term: 30,
closingCosts: 0,
});
const [loanAmount, setLoanAmount] = useState(200000);
const [selectedOffer, setSelectedOffer] = useState<Offer | null>(null);
const monthlyPaymentChartRef = useRef<Chart | null>(null);
const principalInterestChartRef = useRef<Chart | null>(null);
useEffect(() => {
fetchOffers();
}, []);
useEffect(() => {
if (offers.length > 0) {
updateMonthlyPaymentChart();
setSelectedOffer(offers[0]);
} else {
setSelectedOffer(null);
}
}, [offers]);
useEffect(() => {
if (selectedOffer) {
updatePrincipalInterestChart();
}
}, [selectedOffer]);
const fetchOffers = async () => {
try {
const response = await fetch("/offers");
if (response.ok) {
const data = await response.json();
const validOffers = data.filter((offer: any) =>
offer.id && offer.lender && typeof offer.rate === 'number' &&
typeof offer.term === 'number' && typeof offer.closingCosts === 'number' &&
typeof offer.monthlyPayment === 'number' && typeof offer.totalInterest === 'number'
);
setOffers(validOffers);
} else {
console.error("Failed to fetch offers");
}
} catch (error) {
console.error("Error fetching offers:", error);
}
};
const handleInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {
const { name, value } = e.target;
setNewOffer((prev) => ({ ...prev, [name]: name === "lender" ? value : Number(value) }));
};
const calculateMonthlyPayment = (principal: number, annualRate: number, termYears: number) => {
const monthlyRate = annualRate / 100 / 12;
const numPayments = termYears * 12;
return (principal * monthlyRate * Math.pow(1 + monthlyRate, numPayments)) / (Math.pow(1 + monthlyRate, numPayments) - 1);
};
const calculateTotalInterest = (monthlyPayment: number, termYears: number, principal: number) => {
return (monthlyPayment * termYears * 12) - principal;
};
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
const monthlyPayment = calculateMonthlyPayment(loanAmount, newOffer.rate, newOffer.term);
const totalInterest = calculateTotalInterest(monthlyPayment, newOffer.term, loanAmount);
const offerWithCalculations = {
...newOffer,
monthlyPayment,
totalInterest,
};
try {
const response = await fetch("/offers", {
method: "POST",
headers: { "Content-Type": "application/json" },
jbwinters-reficomparison.web.val.run
August 24, 2024