Split into Budget.Core / Budget.Infrastructure / Budget.Api projects

Budget.Core: entities, DTOs, enums, FrequencyCalculator (no EF/ASP.NET deps)
Budget.Infrastructure: AppDbContext, migrations, BudgetAuthorizationService
Budget.Api: controllers, middleware, Program.cs — references both projects

EF and Npgsql packages moved to Infrastructure; Api retains only JwtBearer,
HealthChecks, and EF.Design (needed for dotnet ef CLI). Dockerfile updated
to copy all three project directories before publishing. Migration namespaces
updated from Budget.Api.Data.* to Budget.Infrastructure.Data.* and model
type strings updated to Budget.Core.Models.* in the snapshot.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Spencer Twaddle
2026-05-02 16:30:31 -05:00
parent c3d1420c4c
commit 087fbdd176
37 changed files with 826 additions and 78 deletions
+123
View File
@@ -0,0 +1,123 @@
# Plan: Add TanStack React Query
## Goal
Replace the ad-hoc `useEffect` + `useState` fetch pattern with `@tanstack/react-query`.
All data fetching moves into typed `useQuery` / `useMutation` hooks. A global `MutationCache`
fires toasts on success and error so pages don't have to wire that up individually.
**Prerequisite:** Plan #11 (react-oidc-context) must be complete, as `TokenSync` replaces
the `setTokenProvider` wiring that pages currently rely on.
## Current state
Each page manages its own loading/error state with `useState` and fetches in `useEffect`:
```ts
const [budgets, setBudgets] = useState<BudgetDto[]>([]);
useEffect(() => {
api.get<BudgetDto[]>('/api/budgets').then(setBudgets);
}, []);
```
Mutations are plain `async` calls with no error handling or cache invalidation.
## Target state
```
src/api/
client.ts — unchanged fetch wrapper
queryClient.ts — QueryClient with MutationCache for global toasts
budgets.ts — useQuery/useMutation hooks for budgets
incomes.ts — hooks for incomes
outgos.ts — hooks for outgos
shares.ts — hooks for shares
summary.ts — hooks for summary
```
Pages become thin: call a hook, render data — no fetch logic inline.
## Steps
### Phase 1 — Install and configure QueryClient
1. `npm install @tanstack/react-query` in `src/Budget.Client/`.
2. Create `src/Budget.Client/src/api/queryClient.ts`:
- Create a `QueryClient` with a `MutationCache` that reads `meta.successMessage` and
`meta.errorMessage` from each mutation and fires the existing `toast` utility on
`onSuccess` / `onError`.
- Export the `queryClient` singleton.
3. Wrap the app in `<QueryClientProvider client={queryClient}>` in `main.tsx`
(inside `<AuthProvider>`).
### Phase 2 — Create domain hook files
For each domain, create a hooks file following this pattern:
**`src/api/budgets.ts`**
```ts
export function useBudgets() {
return useQuery({ queryKey: ['budgets'], queryFn: () => api.get<BudgetDto[]>('/api/budgets') });
}
export function useCreateBudget() {
const qc = useQueryClient();
return useMutation({
mutationFn: (req: CreateBudgetRequest) => api.post<BudgetDto>('/api/budgets', req),
onSuccess: () => qc.invalidateQueries({ queryKey: ['budgets'] }),
meta: { successMessage: 'Budget created', errorMessage: 'Failed to create budget' },
});
}
export function useUpdateBudget(id: string) { ... }
export function useDeleteBudget() { ... }
```
4. Create `src/api/budgets.ts` — hooks for List, Create, Update, Delete.
5. Create `src/api/incomes.ts` — hooks for List, Create, Update, Delete, Reorder.
Query key: `['budgets', budgetId, 'incomes']`.
6. Create `src/api/outgos.ts` — hooks for List, Create, Update, Delete, Reorder.
Query key: `['budgets', budgetId, 'outgos']`.
Also hooks for `categories` and `payment-sources` lookups.
7. Create `src/api/shares.ts` — hooks for List, Add, Update, Revoke.
Query key: `['budgets', budgetId, 'shares']`.
8. Create `src/api/summary.ts` — hooks for Get and UpdateTaxRate.
Query key: `['budgets', budgetId, 'summary']`.
### Phase 3 — Update pages to use hooks
9. **`BudgetsPage.tsx`** — replace `useEffect` + `useState` with `useBudgets()` and `useCreateBudget()`.
10. **`IncomePage.tsx`** — replace with `useIncomes()`, `useCreateIncome()`, `useUpdateIncome()`, `useDeleteIncome()`, `useReorderIncomes()`.
11. **`OutgoPage.tsx`** — replace with outgo hooks + category/payment-source lookup hooks.
12. **`SummaryPage.tsx`** — replace with `useSummary()` and `useUpdateTaxRate()`.
13. **`SettingsPage.tsx`** — replace with `useShares()`, `useAddShare()`, `useUpdateShare()`, `useRevokeShare()`.
### Phase 4 — Clean up
14. Remove `setTokenProvider` from `main.tsx` if it is no longer wired there (it should now live only in `TokenSync.tsx` from plan #11).
15. Remove unused `useEffect` / `useState` imports from pages.
16. `npm run build` — zero TypeScript errors.
## Key decisions
- `api/client.ts` is not changed — hooks wrap it, they don't replace it.
- Cache invalidation strategy: mutations invalidate the narrowest relevant query key
(e.g., a deleted income invalidates `['budgets', id, 'incomes']`, not all budgets).
- No optimistic updates in this plan — add separately if needed.
- `meta.successMessage` and `meta.errorMessage` are typed by augmenting the
`@tanstack/react-query` module's `Register` interface so TypeScript validates them.
## Files affected
- `package.json`
- `src/Budget.Client/src/main.tsx`
- New: `src/Budget.Client/src/api/queryClient.ts`
- New: `src/Budget.Client/src/api/budgets.ts`
- New: `src/Budget.Client/src/api/incomes.ts`
- New: `src/Budget.Client/src/api/outgos.ts`
- New: `src/Budget.Client/src/api/shares.ts`
- New: `src/Budget.Client/src/api/summary.ts`
- `src/Budget.Client/src/pages/BudgetsPage.tsx`
- `src/Budget.Client/src/pages/IncomePage.tsx`
- `src/Budget.Client/src/pages/OutgoPage.tsx`
- `src/Budget.Client/src/pages/SummaryPage.tsx`
- `src/Budget.Client/src/pages/SettingsPage.tsx`