Merge pull request #1 from mrtony/02-01-running-the-app

Fix bugs, extract components, and add delete feature
This commit is contained in:
Tony 2026-03-25 16:56:29 +08:00 committed by GitHub
commit 75ea9afd75
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 205 additions and 137 deletions

27
CLAUDE.md Normal file
View file

@ -0,0 +1,27 @@
# CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
## Project Overview
Expense tracker app from Mosh's [Claude Code course](https://codewithmosh.com/p/claude-code). It intentionally has bugs, poor UI, and messy code — all fixed throughout the course.
## Commands
- `npm run dev` — Start Vite dev server (http://localhost:5173)
- `npm run build` — Production build
- `npm run lint` — ESLint
- `npm run preview` — Preview production build
## Architecture
React 19 app with Vite 7. No routing, no state library, no backend. Plain CSS styling via `App.css` and `index.css`.
- `src/App.jsx` — Root component, owns `transactions` state
- `src/Summary.jsx` — Displays income/expenses/balance totals (computes from `transactions` prop)
- `src/TransactionForm.jsx` — Add transaction form (owns form state, calls `onAddTransaction` callback)
- `src/TransactionList.jsx` — Filtered transaction table (owns filter state, receives `transactions` prop)
## Known Intentional Issues
- "Freelance Work" is typed as "expense" instead of "income"

View file

@ -1,155 +1,37 @@
import { useState } from 'react'
import './App.css'
import Summary from './Summary'
import TransactionForm from './TransactionForm'
import TransactionList from './TransactionList'
function App() {
const [transactions, setTransactions] = useState([
{ id: 1, description: "Salary", amount: "5000", type: "income", category: "salary", date: "2025-01-01" },
{ id: 2, description: "Rent", amount: "1200", type: "expense", category: "housing", date: "2025-01-02" },
{ id: 3, description: "Groceries", amount: "150", type: "expense", category: "food", date: "2025-01-03" },
{ id: 4, description: "Freelance Work", amount: "800", type: "expense", category: "salary", date: "2025-01-05" },
{ id: 5, description: "Electric Bill", amount: "95", type: "expense", category: "utilities", date: "2025-01-06" },
{ id: 6, description: "Dinner Out", amount: "65", type: "expense", category: "food", date: "2025-01-07" },
{ id: 7, description: "Gas", amount: "45", type: "expense", category: "transport", date: "2025-01-08" },
{ id: 8, description: "Netflix", amount: "15", type: "expense", category: "entertainment", date: "2025-01-10" },
{ id: 1, description: "Salary", amount: 5000, type: "income", category: "salary", date: "2025-01-01" },
{ id: 2, description: "Rent", amount: 1200, type: "expense", category: "housing", date: "2025-01-02" },
{ id: 3, description: "Groceries", amount: 150, type: "expense", category: "food", date: "2025-01-03" },
{ id: 4, description: "Freelance Work", amount: 800, type: "expense", category: "salary", date: "2025-01-05" },
{ id: 5, description: "Electric Bill", amount: 95, type: "expense", category: "utilities", date: "2025-01-06" },
{ id: 6, description: "Dinner Out", amount: 65, type: "expense", category: "food", date: "2025-01-07" },
{ id: 7, description: "Gas", amount: 45, type: "expense", category: "transport", date: "2025-01-08" },
{ id: 8, description: "Netflix", amount: 15, type: "expense", category: "entertainment", date: "2025-01-10" },
]);
const [description, setDescription] = useState("");
const [amount, setAmount] = useState("");
const [type, setType] = useState("expense");
const [category, setCategory] = useState("food");
const [filterType, setFilterType] = useState("all");
const [filterCategory, setFilterCategory] = useState("all");
const categories = ["food", "housing", "utilities", "transport", "entertainment", "salary", "other"];
const totalIncome = transactions
.filter(t => t.type === "income")
.reduce((sum, t) => sum + t.amount, 0);
const totalExpenses = transactions
.filter(t => t.type === "expense")
.reduce((sum, t) => sum + t.amount, 0);
const balance = totalIncome - totalExpenses;
let filteredTransactions = transactions;
if (filterType !== "all") {
filteredTransactions = filteredTransactions.filter(t => t.type === filterType);
}
if (filterCategory !== "all") {
filteredTransactions = filteredTransactions.filter(t => t.category === filterCategory);
}
const handleSubmit = (e) => {
e.preventDefault();
if (!description || !amount) return;
const newTransaction = {
id: Date.now(),
description,
amount,
type,
category,
date: new Date().toISOString().split('T')[0],
};
setTransactions([...transactions, newTransaction]);
setDescription("");
setAmount("");
setType("expense");
setCategory("food");
const handleAddTransaction = (transaction) => {
setTransactions([...transactions, transaction]);
};
const handleDeleteTransaction = (id) => {
setTransactions(transactions.filter(t => t.id !== id));
};
return (
<div className="app">
<h1>Finance Tracker</h1>
<p className="subtitle">Track your income and expenses</p>
<div className="summary">
<div className="summary-card">
<h3>Income</h3>
<p className="income-amount">${totalIncome}</p>
</div>
<div className="summary-card">
<h3>Expenses</h3>
<p className="expense-amount">${totalExpenses}</p>
</div>
<div className="summary-card">
<h3>Balance</h3>
<p className="balance-amount">${balance}</p>
</div>
</div>
<div className="add-transaction">
<h2>Add Transaction</h2>
<form onSubmit={handleSubmit}>
<input
type="text"
placeholder="Description"
value={description}
onChange={(e) => setDescription(e.target.value)}
/>
<input
type="number"
placeholder="Amount"
value={amount}
onChange={(e) => setAmount(e.target.value)}
/>
<select value={type} onChange={(e) => setType(e.target.value)}>
<option value="income">Income</option>
<option value="expense">Expense</option>
</select>
<select value={category} onChange={(e) => setCategory(e.target.value)}>
{categories.map(cat => (
<option key={cat} value={cat}>{cat}</option>
))}
</select>
<button type="submit">Add</button>
</form>
</div>
<div className="transactions">
<h2>Transactions</h2>
<div className="filters">
<select value={filterType} onChange={(e) => setFilterType(e.target.value)}>
<option value="all">All Types</option>
<option value="income">Income</option>
<option value="expense">Expense</option>
</select>
<select value={filterCategory} onChange={(e) => setFilterCategory(e.target.value)}>
<option value="all">All Categories</option>
{categories.map(cat => (
<option key={cat} value={cat}>{cat}</option>
))}
</select>
</div>
<table>
<thead>
<tr>
<th>Date</th>
<th>Description</th>
<th>Category</th>
<th>Amount</th>
</tr>
</thead>
<tbody>
{filteredTransactions.map(t => (
<tr key={t.id}>
<td>{t.date}</td>
<td>{t.description}</td>
<td>{t.category}</td>
<td className={t.type === "income" ? "income-amount" : "expense-amount"}>
{t.type === "income" ? "+" : "-"}${t.amount}
</td>
</tr>
))}
</tbody>
</table>
</div>
<Summary transactions={transactions} />
<TransactionForm onAddTransaction={handleAddTransaction} />
<TransactionList transactions={transactions} onDeleteTransaction={handleDeleteTransaction} />
</div>
);
}

30
src/Summary.jsx Normal file
View file

@ -0,0 +1,30 @@
function Summary({ transactions }) {
const totalIncome = transactions
.filter(t => t.type === "income")
.reduce((sum, t) => sum + t.amount, 0);
const totalExpenses = transactions
.filter(t => t.type === "expense")
.reduce((sum, t) => sum + t.amount, 0);
const balance = totalIncome - totalExpenses;
return (
<div className="summary">
<div className="summary-card">
<h3>Income</h3>
<p className="income-amount">${totalIncome}</p>
</div>
<div className="summary-card">
<h3>Expenses</h3>
<p className="expense-amount">${totalExpenses}</p>
</div>
<div className="summary-card">
<h3>Balance</h3>
<p className="balance-amount">${balance}</p>
</div>
</div>
);
}
export default Summary;

61
src/TransactionForm.jsx Normal file
View file

@ -0,0 +1,61 @@
import { useState } from 'react';
const categories = ["food", "housing", "utilities", "transport", "entertainment", "salary", "other"];
function TransactionForm({ onAddTransaction }) {
const [description, setDescription] = useState("");
const [amount, setAmount] = useState("");
const [type, setType] = useState("expense");
const [category, setCategory] = useState("food");
const handleSubmit = (e) => {
e.preventDefault();
if (!description || !amount) return;
onAddTransaction({
id: Date.now(),
description,
amount,
type,
category,
date: new Date().toISOString().split('T')[0],
});
setDescription("");
setAmount("");
setType("expense");
setCategory("food");
};
return (
<div className="add-transaction">
<h2>Add Transaction</h2>
<form onSubmit={handleSubmit}>
<input
type="text"
placeholder="Description"
value={description}
onChange={(e) => setDescription(e.target.value)}
/>
<input
type="number"
placeholder="Amount"
value={amount}
onChange={(e) => setAmount(e.target.value)}
/>
<select value={type} onChange={(e) => setType(e.target.value)}>
<option value="income">Income</option>
<option value="expense">Expense</option>
</select>
<select value={category} onChange={(e) => setCategory(e.target.value)}>
{categories.map(cat => (
<option key={cat} value={cat}>{cat}</option>
))}
</select>
<button type="submit">Add</button>
</form>
</div>
);
}
export default TransactionForm;

68
src/TransactionList.jsx Normal file
View file

@ -0,0 +1,68 @@
import { useState } from 'react';
const categories = ["food", "housing", "utilities", "transport", "entertainment", "salary", "other"];
function TransactionList({ transactions, onDeleteTransaction }) {
const [filterType, setFilterType] = useState("all");
const [filterCategory, setFilterCategory] = useState("all");
let filteredTransactions = transactions;
if (filterType !== "all") {
filteredTransactions = filteredTransactions.filter(t => t.type === filterType);
}
if (filterCategory !== "all") {
filteredTransactions = filteredTransactions.filter(t => t.category === filterCategory);
}
return (
<div className="transactions">
<h2>Transactions</h2>
<div className="filters">
<select value={filterType} onChange={(e) => setFilterType(e.target.value)}>
<option value="all">All Types</option>
<option value="income">Income</option>
<option value="expense">Expense</option>
</select>
<select value={filterCategory} onChange={(e) => setFilterCategory(e.target.value)}>
<option value="all">All Categories</option>
{categories.map(cat => (
<option key={cat} value={cat}>{cat}</option>
))}
</select>
</div>
<table>
<thead>
<tr>
<th>Date</th>
<th>Description</th>
<th>Category</th>
<th>Amount</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
{filteredTransactions.map(t => (
<tr key={t.id}>
<td>{t.date}</td>
<td>{t.description}</td>
<td>{t.category}</td>
<td className={t.type === "income" ? "income-amount" : "expense-amount"}>
{t.type === "income" ? "+" : "-"}${t.amount}
</td>
<td>
<button onClick={() => {
if (window.confirm("Are you sure you want to delete this transaction?")) {
onDeleteTransaction(t.id);
}
}}>Delete</button>
</td>
</tr>
))}
</tbody>
</table>
</div>
);
}
export default TransactionList;