diff --git a/src/Budget.Client/src/pages/BudgetsPage.tsx b/src/Budget.Client/src/pages/BudgetsPage.tsx
index 13dfb11..2e89cf9 100644
--- a/src/Budget.Client/src/pages/BudgetsPage.tsx
+++ b/src/Budget.Client/src/pages/BudgetsPage.tsx
@@ -1,3 +1,47 @@
+import { useEffect, useState } from 'react';
+import { useNavigate } from 'react-router-dom';
+import type { BudgetDto } from '../types';
+import { api } from '../api/client';
+
export function BudgetsPage() {
- return
Budgets — coming soon
;
+ const [budgets, setBudgets] = useState([]);
+ const [newName, setNewName] = useState('');
+ const navigate = useNavigate();
+
+ useEffect(() => {
+ api.get('/api/budgets').then(setBudgets);
+ }, []);
+
+ const handleCreate = async () => {
+ if (!newName.trim()) return;
+ const created = await api.post('/api/budgets', { name: newName.trim() });
+ setBudgets(prev => [...prev, created]);
+ setNewName('');
+ navigate(`/budgets/${created.id}/income`);
+ };
+
+ return (
+
+
My Budgets
+ {budgets.length === 0 &&
No budgets yet. Create one below.
}
+
+ {budgets.map(b => (
+ -
+
+
+ ))}
+
+
+ setNewName(e.target.value)}
+ onKeyDown={e => e.key === 'Enter' && handleCreate()}
+ />
+
+
+
+ );
}
diff --git a/src/Budget.Client/src/pages/SettingsPage.tsx b/src/Budget.Client/src/pages/SettingsPage.tsx
index 434454b..41da328 100644
--- a/src/Budget.Client/src/pages/SettingsPage.tsx
+++ b/src/Budget.Client/src/pages/SettingsPage.tsx
@@ -1,3 +1,142 @@
+import { useEffect, useState } from 'react';
+import { useParams } from 'react-router-dom';
+import type { BudgetDto, ShareDto, SharePermission } from '../types';
+import { api } from '../api/client';
+import { BudgetNav } from '../components/BudgetNav';
+
export function SettingsPage() {
- return Settings — coming soon
;
+ const { id: budgetId } = useParams<{ id: string }>();
+ const [budget, setBudget] = useState(null);
+ const [shares, setShares] = useState([]);
+ const [nameInput, setNameInput] = useState('');
+ const [taxInput, setTaxInput] = useState('');
+ const [shareEmail, setShareEmail] = useState('');
+ const [sharePermission, setSharePermission] = useState('View');
+ const [saving, setSaving] = useState(false);
+
+ useEffect(() => {
+ if (!budgetId) return;
+ api.get(`/api/budgets/${budgetId}`).then(b => {
+ setBudget(b);
+ setNameInput(b.name);
+ setTaxInput(String(Math.round(b.effectiveTaxRate * 100)));
+ });
+ api.get(`/api/budgets/${budgetId}/shares`).then(setShares);
+ }, [budgetId]);
+
+ const saveName = async () => {
+ if (!budgetId || !nameInput.trim()) return;
+ setSaving(true);
+ const updated = await api.put(`/api/budgets/${budgetId}`, { name: nameInput.trim() });
+ setBudget(updated);
+ setSaving(false);
+ };
+
+ const saveTaxRate = async () => {
+ if (!budgetId) return;
+ setSaving(true);
+ const rate = parseFloat(taxInput) / 100;
+ await api.put(`/api/budgets/${budgetId}/summary/tax-rate`, { effectiveTaxRate: rate });
+ const updated = await api.get(`/api/budgets/${budgetId}`);
+ setBudget(updated);
+ setSaving(false);
+ };
+
+ const addShare = async () => {
+ if (!budgetId || !shareEmail.trim()) return;
+ const share = await api.post(`/api/budgets/${budgetId}/shares`, {
+ email: shareEmail.trim(),
+ permission: sharePermission,
+ });
+ setShares(prev => [...prev, share]);
+ setShareEmail('');
+ };
+
+ const updatePermission = async (shareId: string, permission: SharePermission) => {
+ if (!budgetId) return;
+ const updated = await api.put(`/api/budgets/${budgetId}/shares/${shareId}`, { permission });
+ setShares(prev => prev.map(s => s.id === shareId ? updated : s));
+ };
+
+ const revokeShare = async (shareId: string) => {
+ if (!budgetId) return;
+ await api.delete(`/api/budgets/${budgetId}/shares/${shareId}`);
+ setShares(prev => prev.filter(s => s.id !== shareId));
+ };
+
+ if (!budget) return Loading...
;
+
+ return (
+
+
+
Settings
+
+
+
+
+
+
+
+ );
}