Files
SimpleFinanceDash/.planning/codebase/ARCHITECTURE.md

241 lines
9.7 KiB
Markdown

# Architecture
**Analysis Date:** 2026-03-16
## Pattern Overview
**Overall:** Layered client-side SPA with React, using hooks for data access and state management through TanStack Query, centralized authentication with Supabase, and component-based UI rendering.
**Key Characteristics:**
- Three-tier vertical slice: Pages → Hooks → Library (Supabase + utilities)
- TanStack Query for caching and synchronization of remote state
- Route-based code organization with role-based access control (protected/public routes)
- Supabase PostgreSQL backend with real-time subscriptions capability
- Internationalization (i18n) at the root level with JSON resource files
## Layers
**Presentation Layer (Pages & Components):**
- Purpose: Render UI, handle user interactions, coordinate component composition
- Location: `src/pages/` and `src/components/`
- Contains: Page components (DashboardPage, TemplatePage, BudgetListPage, etc.), UI primitives from `src/components/ui/`, and custom components (AppLayout, QuickAddPicker)
- Depends on: Hooks, UI utilities, i18n, icons (lucide-react), formatting utilities
- Used by: React Router for page routing
**Hooks Layer (Data Access & State):**
- Purpose: Encapsulate all server communication and query/mutation logic, manage request/response caching via TanStack Query
- Location: `src/hooks/`
- Contains: Custom hooks for each domain (useAuth, useCategories, useBudgets, useTemplate, useQuickAdd)
- Depends on: Supabase client, TanStack Query, type definitions
- Used by: Page and component layers for CRUD operations and state retrieval
**Library Layer (Core Services & Utilities):**
- Purpose: Provide primitive implementations, type definitions, and configuration
- Location: `src/lib/`
- Contains:
- `supabase.ts` - Supabase client initialization and environment validation
- `types.ts` - TypeScript interfaces for all domain entities (Profile, Category, Budget, BudgetItem, Template, TemplateItem, QuickAddItem)
- `format.ts` - Currency formatting using Intl.NumberFormat
- `palette.ts` - Category color mapping and labels (internationalized)
- `utils.ts` - General utilities
- Depends on: Supabase SDK, TypeScript types
- Used by: Hooks and components for types and helpers
**Authentication Layer:**
- Purpose: Manage session state and auth operations
- Location: `src/hooks/useAuth.ts`
- Implementation: Reactive Supabase auth listener with automatic session refresh
- State: Session, user, loading flag
- Operations: signUp, signIn, signInWithOAuth (Google, GitHub), signOut
**Internationalization (i18n):**
- Purpose: Provide multi-language support at runtime
- Location: `src/i18n/index.ts` with JSON resource files (`en.json`, `de.json`)
- Framework: react-i18next with i18next
- Initialization: Automatic at app startup before any render
## Data Flow
**Read Pattern (Fetch & Display):**
1. Component renders and mounts
2. Component calls custom hook (e.g., `useBudgets()`, `useCategories()`)
3. Hook initializes TanStack Query with async queryFn
4. Query function calls Supabase table select with filters/joins
5. Supabase returns typed rows from database
6. Hook caches result in Query store with staleTime of 5 minutes
7. Component receives `data`, `loading`, and error states
8. Component renders based on data state
9. Subsequent mounts of same component use cached data (until stale)
**Write Pattern (Mutate & Sync):**
1. Component invokes mutation handler (e.g., click "Save")
2. Handler calls `mutation.mutateAsync(payload)`
3. Mutation function marshals payload and calls Supabase insert/update/delete
4. Supabase executes DB operation and returns modified row(s)
5. Mutation onSuccess callback triggers Query invalidation
6. Query re-fetches from server with fresh data
7. Component re-renders with new cached data
8. Toast notification indicates success or error
**Real-time Budget Updates:**
Example flow for editing a budget item (from `useBudgets.ts`):
1. User edits amount in budget detail table → calls `updateItem.mutateAsync({ id, budgetId, budgeted_amount: X })`
2. Hook serializes to Supabase `.update()` with .eq("id", id)
3. Response contains updated BudgetItem with joined Category data
4. onSuccess invalidates `["budgets", budgetId, "items"]` cache key
5. DashboardContent's useBudgetDetail query re-fetches entire items array
6. Component recalculates totals and re-renders pie chart and progress bars
**State Management:**
- No Redux or Zustand — TanStack Query handles async state
- Local component state for UI interactions (dialogs, forms, selections)
- Session state maintained by Supabase auth listener in useAuth hook
- Cache invalidation is the primary sync mechanism
## Key Abstractions
**Query Key Pattern:**
Each hook defines typed query keys as const arrays:
- `["categories"]` - all user categories
- `["budgets"]` - all user budgets
- `["budgets", id]` - single budget
- `["budgets", id, "items"]` - items for a specific budget
- `["template"]` - user's monthly template
- `["template-items"]` - items in template
- `["quick-add"]` - user's quick-add library
Purpose: Enables granular invalidation (e.g., update one budget doesn't refetch all budgets)
**Mutation Factories:**
Hooks expose mutations as properties of returned object:
- `useCategories()` returns `{ categories, loading, create, update, remove }`
- `useBudgets()` returns `{ budgets, loading, createBudget, generateFromTemplate, updateItem, createItem, deleteItem, deleteBudget }`
Each mutation includes:
- `mutationFn` - async operation against Supabase
- `onSuccess` - cache invalidation strategy
**Hook Composition:**
`useBudgetDetail(id)` is both a standalone export and accessible via `useBudgets().getBudget(id)`:
- Enables flexible usage patterns
- Query with `.enabled: Boolean(id)` prevents queries when id is falsy
- Used by DashboardPage to fetch current month's budget data
**Type Hierarchy:**
Core types in `src/lib/types.ts`:
- `Profile` - user profile with locale and currency
- `Category` - user-defined expense category
- `Template` & `TemplateItem` - monthly budget template (fixed/variable items)
- `Budget` & `BudgetItem` - actual budget with tracked actual/budgeted amounts
- `QuickAddItem` - quick entry library for rapid data entry
Relationships:
- User → many Profiles (one per row, per user)
- User → many Categories
- User → one Template (auto-created)
- Template → many TemplateItems → Category (join)
- User → many Budgets (monthly)
- Budget → many BudgetItems → Category (join)
- User → many QuickAddItems
## Entry Points
**Application Root:**
- Location: `src/main.tsx`
- Initializes React 19 with context providers:
- QueryClientProvider (TanStack Query)
- BrowserRouter (React Router)
- TooltipProvider (Radix UI)
- Toaster (Sonner notifications)
- Creates single QueryClient with 5-minute staleTime default
**Route Configuration:**
- Location: `src/App.tsx`
- Defines protected and public routes
- Protected routes wrapped in `ProtectedRoute` which checks auth state via `useAuth()`
- Public routes wrapped in `PublicRoute` which redirects authenticated users away
- Routes:
- `/login`, `/register` - public (redirect home if logged in)
- `/`, `/categories`, `/template`, `/budgets`, `/budgets/:id`, `/quick-add`, `/settings` - protected
**Layout Root:**
- Location: `src/components/AppLayout.tsx`
- Renders Sidebar with navigation items (using Radix UI primitives)
- Renders Outlet for nested route content
- Provides sign-out button in footer
**Page Entry Points:**
Each page component is stateless renderer with logic split between:
1. Page component (routing layer, layout)
2. Sub-component(s) (content logic)
3. Hook(s) (data fetching)
Example: `DashboardPage.tsx`
- Main component finds current month's budget
- Delegates to `DashboardContent` with `budgetId` prop
- DashboardContent calls `useBudgetDetail(budgetId)` for data
## Error Handling
**Strategy:** Try-catch in mutation handlers with toast notifications for user feedback.
**Patterns:**
1. **Mutation Errors:**
- Try block executes `mutation.mutateAsync()`
- Catch block logs to console and shows `toast.error(t("common.error"))`
- Button remains clickable if error is transient
- Example in `TemplatePage.tsx` lines 182-204
2. **Query Errors:**
- Query errors are captured by TanStack Query internally
- Hooks return loading state but not explicit error state
- Pages render null/loading state during failed queries
- Retry is configured to attempt once by default
3. **Auth Errors:**
- `useAuth.ts` catches Supabase auth errors and re-throws
- LoginPage catches and displays error message in red text
- Session state remains null if auth fails
4. **Missing Environment Variables:**
- `src/lib/supabase.ts` throws during module load if VITE_SUPABASE_URL or VITE_SUPABASE_ANON_KEY missing
- Prevents runtime errors from undefined client
## Cross-Cutting Concerns
**Logging:**
Minimal logging — relies on:
- Browser DevTools React Query Devtools (installable)
- Console errors from try-catch blocks
- Sonner toast notifications for user-facing issues
**Validation:**
- Input validation in mutation handlers (e.g., category ID check, amount > 0)
- Type validation via TypeScript compile-time
- Supabase schema constraints (NOT NULL, FOREIGN KEY, CHECK constraints)
- Database uniqueness constraints (e.g., one template per user)
**Authentication:**
- Supabase session token stored in localStorage (browser SDK default)
- Auth state checked at page render time via `useAuth()` hook
- Protected routes redirect unauthenticated users to /login
- Session persists across page refreshes (Supabase handles recovery)
**Authorization:**
- Row-level security (RLS) policies on Supabase tables enforce user_id filters
- No explicit authorization logic in client (relies on DB policies)
- All queries filter by current user via `await supabase.auth.getUser()`
---
*Architecture analysis: 2026-03-16*