refactor: add AuthGuard component and fix API client empty response handling

- Add reusable AuthGuard component for protected routes
- Move auth logic from index.tsx to (tabs)/_layout.tsx via AuthGuard
- Fix ApiClient to handle empty responses (204 No Content)
- Use useFocusEffect in chat.tsx to load messages after auth is ready
- Extract getDateKey() helper function in calendar.tsx
This commit is contained in:
2026-01-25 13:03:17 +01:00
parent 43d40b46d7
commit a42e2a7c1c
7 changed files with 112 additions and 65 deletions

View File

@@ -81,6 +81,7 @@ src/
│ └── note/
│ └── [id].tsx # Note editor for event (dynamic route)
├── components/
│ ├── AuthGuard.tsx # Auth wrapper: loads user, shows loading, redirects if unauthenticated
│ ├── BaseBackground.tsx # Common screen wrapper (themed)
│ ├── BaseButton.tsx # Reusable button component (themed, supports children)
│ ├── Header.tsx # Header component (themed)
@@ -97,7 +98,7 @@ src/
│ └── logger.ts # react-native-logs config (apiLogger, storeLogger)
├── services/
│ ├── index.ts # Re-exports all services
│ ├── ApiClient.ts # HTTP client with X-User-Id header injection, request logging
│ ├── ApiClient.ts # HTTP client with X-User-Id header injection, request logging, handles empty responses (204)
│ ├── AuthService.ts # login(), register(), logout() - calls API and updates AuthStore
│ ├── EventService.ts # getAll(), getById(), getByDateRange(), create(), update(), delete()
│ └── ChatService.ts # sendMessage(), confirmEvent(), rejectEvent(), getConversations(), getConversation()
@@ -112,6 +113,13 @@ src/
**Routing:** Tab-based navigation with Chat, Calendar, and Settings as main screens. Auth screens (login, register) outside tabs. Dynamic routes for event detail and note editing.
**Authentication Flow:**
- `AuthGuard` component wraps the tab layout in `(tabs)/_layout.tsx`
- On app start, `AuthGuard` calls `loadStoredUser()` and shows loading indicator
- If not authenticated, redirects to `/login`
- `index.tsx` simply redirects to `/(tabs)/chat` - AuthGuard handles the rest
- This pattern handles Expo Router's navigation state caching (avoids race conditions)
### Theme System
The app supports multiple themes (light/dark) via a reactive Zustand store.
@@ -412,12 +420,14 @@ NODE_ENV=development # development = pretty logs, production = JSON
- **Authentication fully implemented:**
- `AuthStore`: Manages user state with expo-secure-store (native) / localStorage (web)
- `AuthService`: login(), register(), logout() - calls backend API
- `ApiClient`: Automatically injects X-User-Id header for authenticated requests
- `ApiClient`: Automatically injects X-User-Id header for authenticated requests, handles empty responses (204)
- `AuthGuard`: Reusable component that wraps protected routes - loads user, shows loading, redirects if unauthenticated
- Login screen: Supports email OR userName login
- Register screen: Email validation, checks for existing email/userName
- `AuthButton`: Reusable button component with themed shadow
- `Header`: Themed header component (logout moved to Settings)
- `index.tsx`: Auth redirect - checks stored user on app start
- `(tabs)/_layout.tsx`: Wraps tabs with AuthGuard for protected access
- `index.tsx`: Simple redirect to chat (AuthGuard handles auth)
- **Theme system fully implemented:**
- `ThemeStore`: Zustand store with theme state and setTheme()
- `Themes.tsx`: THEMES object with defaultLight/defaultDark variants
@@ -438,7 +448,7 @@ NODE_ENV=development # development = pretty logs, production = JSON
- Arrow navigation between proposals with "Event X von Y" counter
- Each proposal individually confirmable/rejectable
- **Typing indicator**: Animated dots (. .. ...) shown after 500ms delay while waiting for AI response
- Messages persisted to database via ChatService and loaded on mount
- Messages persisted to database via ChatService, loaded via `useFocusEffect` when screen gains focus
- Tracks conversationId for message continuity across sessions
- ChatStore with addMessages() for bulk loading, chatMessageToMessageData() helper
- KeyboardAvoidingView for proper keyboard handling (iOS padding, Android height)