From 59f8c8076b168baef0d96f819941c9f7acab3a49 Mon Sep 17 00:00:00 2001 From: Felipe Cardoso Date: Fri, 31 Oct 2025 21:26:33 +0100 Subject: [PATCH] Add comprehensive frontend requirements document - Created `frontend-requirements.md` outlining detailed specifications for a production-ready Next.js + FastAPI template. - Documented technology stack, architecture, state management, authentication flows, API integration, UI components, and developer guidelines. - Provided a complete directory layout, coding conventions, and error handling practices. - Aimed to establish a solid foundation for modern, scalable, and maintainable web application development. --- frontend/docs/API_INTEGRATION.md | 913 ++++++++++++++ frontend/docs/ARCHITECTURE.md | 1226 ++++++++++++++++++ frontend/docs/CODING_STANDARDS.md | 1290 +++++++++++++++++++ frontend/docs/COMPONENT_GUIDE.md | 802 ++++++++++++ frontend/docs/FEATURE_EXAMPLES.md | 1059 ++++++++++++++++ frontend/frontend-requirements.md | 1933 +++++++++++++++++++++++++++++ 6 files changed, 7223 insertions(+) create mode 100644 frontend/docs/API_INTEGRATION.md create mode 100644 frontend/docs/ARCHITECTURE.md create mode 100644 frontend/docs/CODING_STANDARDS.md create mode 100644 frontend/docs/COMPONENT_GUIDE.md create mode 100644 frontend/docs/FEATURE_EXAMPLES.md create mode 100644 frontend/frontend-requirements.md diff --git a/frontend/docs/API_INTEGRATION.md b/frontend/docs/API_INTEGRATION.md new file mode 100644 index 0000000..bb97d24 --- /dev/null +++ b/frontend/docs/API_INTEGRATION.md @@ -0,0 +1,913 @@ +# API Integration Guide + +**Project**: Next.js + FastAPI Template +**Version**: 1.0 +**Last Updated**: 2025-10-31 + +--- + +## Table of Contents + +1. [Quick Start](#1-quick-start) +2. [Generating the API Client](#2-generating-the-api-client) +3. [Making API Calls](#3-making-api-calls) +4. [Authentication Integration](#4-authentication-integration) +5. [Error Handling](#5-error-handling) +6. [React Query Integration](#6-react-query-integration) +7. [Testing API Integration](#7-testing-api-integration) +8. [Common Patterns](#8-common-patterns) +9. [Troubleshooting](#9-troubleshooting) + +--- + +## 1. Quick Start + +### 1.1 Prerequisites + +1. Backend running at `http://localhost:8000` +2. Frontend environment variables configured: + ```env + NEXT_PUBLIC_API_URL=http://localhost:8000/api/v1 + NEXT_PUBLIC_API_BASE_URL=http://localhost:8000 + ``` + +### 1.2 Generate API Client + +```bash +cd frontend +npm run generate:api +``` + +This fetches the OpenAPI spec from the backend and generates TypeScript types and API client functions. + +### 1.3 Make Your First API Call + +```typescript +import { useQuery } from '@tanstack/react-query'; +import { apiClient } from '@/lib/api/client'; + +function UserList() { + const { data, isLoading } = useQuery({ + queryKey: ['users'], + queryFn: async () => { + const response = await apiClient.get('/users'); + return response.data; + }, + }); + + if (isLoading) return
Loading...
; + return
{/* Render users */}
; +} +``` + +--- + +## 2. Generating the API Client + +### 2.1 Generation Script + +The generation script fetches the OpenAPI specification from the backend and creates TypeScript types and API client code. + +**Script Location**: `frontend/scripts/generate-api-client.sh` + +```bash +#!/bin/bash +set -e + +API_URL="${NEXT_PUBLIC_API_BASE_URL:-http://localhost:8000}" +OUTPUT_DIR="./src/lib/api/generated" + +echo "Fetching OpenAPI spec from $API_URL/api/v1/openapi.json..." + +npx @hey-api/openapi-ts \ + --input "$API_URL/api/v1/openapi.json" \ + --output "$OUTPUT_DIR" \ + --client axios \ + --types + +echo "✅ API client generated successfully in $OUTPUT_DIR" +``` + +### 2.2 Generated Files + +After running the script, you'll have: + +``` +src/lib/api/generated/ +├── index.ts # Main exports +├── models/ # TypeScript interfaces for all models +│ ├── User.ts +│ ├── Organization.ts +│ ├── UserSession.ts +│ └── ... +└── services/ # API service functions + ├── AuthService.ts + ├── UsersService.ts + ├── OrganizationsService.ts + └── ... +``` + +### 2.3 When to Regenerate + +Regenerate the API client when: +- Backend API changes (new endpoints, updated models) +- After pulling backend changes from git +- When types don't match backend responses +- As part of CI/CD pipeline + +--- + +## 3. Making API Calls + +### 3.1 Using Generated Services + +**Example: Fetching users** +```typescript +import { UsersService } from '@/lib/api/generated'; + +async function getUsers() { + const users = await UsersService.getUsers({ + page: 1, + pageSize: 20, + search: 'john' + }); + return users; +} +``` + +**Example: Creating a user** +```typescript +import { AdminService } from '@/lib/api/generated'; + +async function createUser(data: CreateUserDto) { + const newUser = await AdminService.createUser({ + requestBody: data + }); + return newUser; +} +``` + +### 3.2 Using Axios Client Directly + +For more control, use the configured Axios instance: + +```typescript +import { apiClient } from '@/lib/api/client'; + +// GET request +const response = await apiClient.get('/users', { + params: { page: 1, search: 'john' } +}); + +// POST request +const response = await apiClient.post('/admin/users', { + email: 'user@example.com', + first_name: 'John', + password: 'secure123' +}); + +// PATCH request +const response = await apiClient.patch(`/users/${userId}`, { + first_name: 'Jane' +}); + +// DELETE request +await apiClient.delete(`/users/${userId}`); +``` + +### 3.3 Request Configuration + +**Timeouts:** +```typescript +const response = await apiClient.get('/users', { + timeout: 5000 // 5 seconds +}); +``` + +**Custom Headers:** +```typescript +const response = await apiClient.post('/users', data, { + headers: { + 'X-Custom-Header': 'value' + } +}); +``` + +**Request Cancellation:** +```typescript +const controller = new AbortController(); + +const response = await apiClient.get('/users', { + signal: controller.signal +}); + +// Cancel the request +controller.abort(); +``` + +--- + +## 4. Authentication Integration + +### 4.1 Automatic Token Injection + +The Axios client automatically adds the Authorization header to all requests: + +```typescript +// src/lib/api/client.ts +apiClient.interceptors.request.use( + (config) => { + const token = useAuthStore.getState().accessToken; + if (token) { + config.headers.Authorization = `Bearer ${token}`; + } + return config; + } +); +``` + +You don't need to manually add auth headers - they're added automatically! + +### 4.2 Token Refresh Flow + +The response interceptor handles token refresh automatically: + +```typescript +apiClient.interceptors.response.use( + (response) => response, + async (error) => { + const originalRequest = error.config; + + // If 401 and haven't retried yet + if (error.response?.status === 401 && !originalRequest._retry) { + originalRequest._retry = true; + + try { + // Refresh tokens + const refreshToken = getRefreshToken(); + const { access_token, refresh_token } = await AuthService.refreshToken({ + requestBody: { refresh_token: refreshToken } + }); + + // Update stored tokens + useAuthStore.getState().setTokens(access_token, refresh_token); + + // Retry original request with new token + originalRequest.headers.Authorization = `Bearer ${access_token}`; + return apiClient.request(originalRequest); + + } catch (refreshError) { + // Refresh failed - logout user + useAuthStore.getState().clearAuth(); + window.location.href = '/login'; + return Promise.reject(refreshError); + } + } + + return Promise.reject(error); + } +); +``` + +### 4.3 Login Example + +```typescript +import { AuthService } from '@/lib/api/generated'; +import { useAuthStore } from '@/stores/authStore'; + +async function login(email: string, password: string) { + try { + const response = await AuthService.login({ + requestBody: { email, password } + }); + + // Store tokens and user + useAuthStore.getState().setTokens( + response.access_token, + response.refresh_token + ); + useAuthStore.getState().setUser(response.user); + + return response.user; + } catch (error) { + throw parseAPIError(error); + } +} +``` + +--- + +## 5. Error Handling + +### 5.1 Backend Error Format + +The backend returns structured errors: + +```typescript +{ + success: false, + errors: [ + { + code: "AUTH_001", + message: "Invalid credentials", + field: "email" + } + ] +} +``` + +### 5.2 Parsing Errors + +**Error Parser** (`src/lib/api/errors.ts`): +```typescript +import type { AxiosError } from 'axios'; + +export interface APIError { + code: string; + message: string; + field?: string; +} + +export interface APIErrorResponse { + success: false; + errors: APIError[]; +} + +export function parseAPIError(error: AxiosError): APIError[] { + // Backend structured errors + if (error.response?.data?.errors) { + return error.response.data.errors; + } + + // Network errors + if (!error.response) { + return [{ + code: 'NETWORK_ERROR', + message: 'Network error. Please check your connection.', + }]; + } + + // HTTP status errors + const status = error.response.status; + if (status === 403) { + return [{ + code: 'FORBIDDEN', + message: "You don't have permission to perform this action.", + }]; + } + + if (status === 404) { + return [{ + code: 'NOT_FOUND', + message: 'The requested resource was not found.', + }]; + } + + if (status === 429) { + return [{ + code: 'RATE_LIMIT', + message: 'Too many requests. Please slow down.', + }]; + } + + if (status >= 500) { + return [{ + code: 'SERVER_ERROR', + message: 'A server error occurred. Please try again later.', + }]; + } + + // Fallback + return [{ + code: 'UNKNOWN', + message: error.message || 'An unexpected error occurred.', + }]; +} +``` + +### 5.3 Error Code Mapping + +**Error Messages** (`src/lib/api/errorMessages.ts`): +```typescript +export const ERROR_MESSAGES: Record = { + // Authentication errors (AUTH_xxx) + 'AUTH_001': 'Invalid email or password', + 'AUTH_002': 'Account is inactive', + 'AUTH_003': 'Invalid or expired token', + + // User errors (USER_xxx) + 'USER_001': 'User not found', + 'USER_002': 'This email is already registered', + 'USER_003': 'Invalid user data', + + // Validation errors (VAL_xxx) + 'VAL_001': 'Invalid input. Please check your data.', + 'VAL_002': 'Email format is invalid', + 'VAL_003': 'Password does not meet requirements', + + // Organization errors (ORG_xxx) + 'ORG_001': 'Organization name already exists', + 'ORG_002': 'Organization not found', + + // Permission errors (PERM_xxx) + 'PERM_001': 'Insufficient permissions', + 'PERM_002': 'Admin access required', + + // Rate limiting (RATE_xxx) + 'RATE_001': 'Too many requests. Please try again later.', +}; + +export function getErrorMessage(code: string): string { + return ERROR_MESSAGES[code] || 'An error occurred'; +} +``` + +### 5.4 Displaying Errors + +**In React Query:** +```typescript +import { toast } from 'sonner'; +import { parseAPIError, getErrorMessage } from '@/lib/api/errors'; + +export function useUpdateUser() { + return useMutation({ + mutationFn: updateUserFn, + onError: (error: AxiosError) => { + const errors = parseAPIError(error); + const message = getErrorMessage(errors[0]?.code) || errors[0]?.message; + toast.error(message); + }, + }); +} +``` + +**In Forms:** +```typescript +const onSubmit = async (data: FormData) => { + try { + await updateUser(data); + } catch (error) { + const errors = parseAPIError(error); + + // Set field-specific errors + errors.forEach((err) => { + if (err.field) { + form.setError(err.field as any, { + message: getErrorMessage(err.code) || err.message, + }); + } + }); + + // Set general error + if (errors.some(err => !err.field)) { + form.setError('root', { + message: errors.find(err => !err.field)?.message || 'An error occurred', + }); + } + } +}; +``` + +--- + +## 6. React Query Integration + +### 6.1 Creating Query Hooks + +**Pattern: One hook per operation** + +```typescript +// src/lib/api/hooks/useUsers.ts +import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'; +import { UsersService, AdminService } from '@/lib/api/generated'; +import { toast } from 'sonner'; + +// Query: List users +export function useUsers(filters?: UserFilters) { + return useQuery({ + queryKey: ['users', filters], + queryFn: () => UsersService.getUsers(filters), + staleTime: 60000, // 1 minute + }); +} + +// Query: Single user +export function useUser(userId: string | undefined) { + return useQuery({ + queryKey: ['users', userId], + queryFn: () => UsersService.getUser({ userId: userId! }), + enabled: !!userId, // Only run if userId exists + }); +} + +// Mutation: Create user +export function useCreateUser() { + const queryClient = useQueryClient(); + + return useMutation({ + mutationFn: (data: CreateUserDto) => + AdminService.createUser({ requestBody: data }), + onSuccess: () => { + queryClient.invalidateQueries({ queryKey: ['users'] }); + toast.success('User created successfully'); + }, + onError: (error) => { + const errors = parseAPIError(error); + toast.error(errors[0]?.message || 'Failed to create user'); + }, + }); +} + +// Mutation: Update user +export function useUpdateUser() { + const queryClient = useQueryClient(); + + return useMutation({ + mutationFn: ({ id, data }: { id: string; data: UpdateUserDto }) => + UsersService.updateUser({ userId: id, requestBody: data }), + onSuccess: (_, { id }) => { + queryClient.invalidateQueries({ queryKey: ['users', id] }); + queryClient.invalidateQueries({ queryKey: ['users'] }); + toast.success('User updated successfully'); + }, + onError: (error) => { + const errors = parseAPIError(error); + toast.error(errors[0]?.message || 'Failed to update user'); + }, + }); +} + +// Mutation: Delete user +export function useDeleteUser() { + const queryClient = useQueryClient(); + + return useMutation({ + mutationFn: (userId: string) => + AdminService.deleteUser({ userId }), + onSuccess: () => { + queryClient.invalidateQueries({ queryKey: ['users'] }); + toast.success('User deleted successfully'); + }, + onError: (error) => { + const errors = parseAPIError(error); + toast.error(errors[0]?.message || 'Failed to delete user'); + }, + }); +} +``` + +### 6.2 Using Query Hooks in Components + +```typescript +'use client'; + +import { useUsers, useDeleteUser } from '@/lib/api/hooks/useUsers'; + +export function UserList() { + const [search, setSearch] = useState(''); + const { data: users, isLoading, error } = useUsers({ search }); + const deleteUser = useDeleteUser(); + + const handleDelete = (userId: string) => { + if (confirm('Are you sure?')) { + deleteUser.mutate(userId); + } + }; + + if (isLoading) return ; + if (error) return ; + + return ( +
+ setSearch(e.target.value)} + placeholder="Search users..." + /> +
    + {users?.map(user => ( +
  • + {user.name} + +
  • + ))} +
+
+ ); +} +``` + +### 6.3 Optimistic Updates + +For instant UI feedback: + +```typescript +export function useToggleUserActive() { + const queryClient = useQueryClient(); + + return useMutation({ + mutationFn: ({ userId, isActive }: { userId: string; isActive: boolean }) => + AdminService.updateUser({ + userId, + requestBody: { is_active: isActive } + }), + onMutate: async ({ userId, isActive }) => { + // Cancel outgoing refetches + await queryClient.cancelQueries({ queryKey: ['users', userId] }); + + // Snapshot previous value + const previousUser = queryClient.getQueryData(['users', userId]); + + // Optimistically update + queryClient.setQueryData(['users', userId], (old: User) => ({ + ...old, + is_active: isActive, + })); + + return { previousUser }; + }, + onError: (err, variables, context) => { + // Rollback on error + if (context?.previousUser) { + queryClient.setQueryData(['users', variables.userId], context.previousUser); + } + toast.error('Failed to update user'); + }, + onSettled: (_, __, { userId }) => { + // Refetch to ensure consistency + queryClient.invalidateQueries({ queryKey: ['users', userId] }); + }, + }); +} +``` + +--- + +## 7. Testing API Integration + +### 7.1 Mocking API Calls + +**Using MSW (Mock Service Worker):** + +```typescript +// tests/mocks/handlers.ts +import { rest } from 'msw'; + +export const handlers = [ + rest.get('/api/v1/users', (req, res, ctx) => { + return res( + ctx.json({ + data: [ + { id: '1', name: 'John Doe', email: 'john@example.com' }, + { id: '2', name: 'Jane Smith', email: 'jane@example.com' }, + ], + pagination: { + total: 2, + page: 1, + page_size: 20, + total_pages: 1, + }, + }) + ); + }), + + rest.post('/api/v1/admin/users', async (req, res, ctx) => { + const body = await req.json(); + return res( + ctx.json({ + id: '3', + ...body, + }) + ); + }), + + rest.delete('/api/v1/admin/users/:userId', (req, res, ctx) => { + return res( + ctx.json({ success: true, message: 'User deleted' }) + ); + }), +]; +``` + +### 7.2 Testing Query Hooks + +```typescript +import { renderHook, waitFor } from '@testing-library/react'; +import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; +import { useUsers } from './useUsers'; + +function createWrapper() { + const queryClient = new QueryClient(); + return ({ children }: { children: React.ReactNode }) => ( + + {children} + + ); +} + +test('fetches users successfully', async () => { + const { result } = renderHook(() => useUsers(), { + wrapper: createWrapper(), + }); + + await waitFor(() => expect(result.current.isSuccess).toBe(true)); + + expect(result.current.data).toHaveLength(2); + expect(result.current.data[0].name).toBe('John Doe'); +}); +``` + +--- + +## 8. Common Patterns + +### 8.1 Pagination + +```typescript +export function useUsersPaginated(page: number = 1, pageSize: number = 20) { + return useQuery({ + queryKey: ['users', { page, pageSize }], + queryFn: () => UsersService.getUsers({ page, pageSize }), + keepPreviousData: true, // Keep old data while fetching new page + }); +} + +// Component usage +function UserList() { + const [page, setPage] = useState(1); + const { data, isLoading, isFetching } = useUsersPaginated(page); + + return ( +
+ {isLoading ? ( + + ) : ( + <> +
    + {data?.data.map(user =>
  • {user.name}
  • )} +
+ + + + )} +
+ ); +} +``` + +### 8.2 Infinite Scroll + +```typescript +export function useUsersInfinite() { + return useInfiniteQuery({ + queryKey: ['users', 'infinite'], + queryFn: ({ pageParam = 1 }) => + UsersService.getUsers({ page: pageParam, pageSize: 20 }), + getNextPageParam: (lastPage) => + lastPage.pagination.has_next ? lastPage.pagination.page + 1 : undefined, + }); +} + +// Component usage +function InfiniteUserList() { + const { + data, + fetchNextPage, + hasNextPage, + isFetchingNextPage, + } = useUsersInfinite(); + + const allUsers = data?.pages.flatMap(page => page.data) ?? []; + + return ( +
+
    + {allUsers.map(user =>
  • {user.name}
  • )} +
+ {hasNextPage && ( + + )} +
+ ); +} +``` + +### 8.3 Dependent Queries + +```typescript +function UserDetail({ userId }: { userId: string }) { + // First query + const { data: user } = useUser(userId); + + // Second query depends on first + const { data: sessions } = useQuery({ + queryKey: ['sessions', userId], + queryFn: () => SessionService.getUserSessions({ userId }), + enabled: !!user, // Only fetch when user is loaded + }); + + return ( +
+

{user?.name}

+

Active Sessions

+
    + {sessions?.map(session =>
  • {session.device_name}
  • )} +
+
+ ); +} +``` + +--- + +## 9. Troubleshooting + +### 9.1 CORS Errors + +**Symptom**: `Access-Control-Allow-Origin` error in console + +**Solution**: Ensure backend CORS is configured for frontend URL: +```python +# backend/app/main.py +BACKEND_CORS_ORIGINS = ["http://localhost:3000"] +``` + +### 9.2 401 Unauthorized + +**Symptom**: All API calls return 401 + +**Possible Causes**: +1. No token in store: Check `useAuthStore.getState().accessToken` +2. Token expired: Check token expiration +3. Token invalid: Try logging in again +4. Interceptor not working: Check interceptor configuration + +**Debug**: +```typescript +// Log token in interceptor +apiClient.interceptors.request.use((config) => { + const token = useAuthStore.getState().accessToken; + console.log('Token:', token ? 'Present' : 'Missing'); + if (token) { + config.headers.Authorization = `Bearer ${token}`; + } + return config; +}); +``` + +### 9.3 Type Mismatches + +**Symptom**: TypeScript errors about response types + +**Solution**: Regenerate API client to sync with backend +```bash +npm run generate:api +``` + +### 9.4 Stale Data + +**Symptom**: UI shows old data after mutation + +**Solution**: Invalidate queries after mutations +```typescript +onSuccess: () => { + queryClient.invalidateQueries({ queryKey: ['users'] }); +} +``` + +### 9.5 Network Timeout + +**Symptom**: Requests timeout + +**Solution**: Increase timeout or check backend performance +```typescript +const apiClient = axios.create({ + timeout: 60000, // 60 seconds +}); +``` + +--- + +## Conclusion + +This guide covers the essential patterns for integrating with the FastAPI backend. For more advanced use cases, refer to: +- [TanStack Query Documentation](https://tanstack.com/query/latest) +- [Axios Documentation](https://axios-http.com/) +- Backend API documentation at `/docs` endpoint diff --git a/frontend/docs/ARCHITECTURE.md b/frontend/docs/ARCHITECTURE.md new file mode 100644 index 0000000..e28937e --- /dev/null +++ b/frontend/docs/ARCHITECTURE.md @@ -0,0 +1,1226 @@ +# Frontend Architecture Documentation + +**Project**: Next.js + FastAPI Template +**Version**: 1.0 +**Last Updated**: 2025-10-31 +**Status**: Living Document + +--- + +## Table of Contents + +1. [System Overview](#1-system-overview) +2. [Technology Stack](#2-technology-stack) +3. [Architecture Patterns](#3-architecture-patterns) +4. [Data Flow](#4-data-flow) +5. [State Management Strategy](#5-state-management-strategy) +6. [Authentication Architecture](#6-authentication-architecture) +7. [API Integration](#7-api-integration) +8. [Routing Strategy](#8-routing-strategy) +9. [Component Organization](#9-component-organization) +10. [Testing Strategy](#10-testing-strategy) +11. [Performance Considerations](#11-performance-considerations) +12. [Security Architecture](#12-security-architecture) +13. [Design Decisions & Rationale](#13-design-decisions--rationale) +14. [Deployment Architecture](#14-deployment-architecture) + +--- + +## 1. System Overview + +### 1.1 Purpose + +This frontend template provides a production-ready foundation for building modern web applications with Next.js 15 and FastAPI backend integration. It implements comprehensive authentication, admin dashboards, user management, and organization management out of the box. + +### 1.2 High-Level Architecture + +``` +┌─────────────────────────────────────────────────────────────┐ +│ Next.js Frontend │ +├─────────────────────────────────────────────────────────────┤ +│ App Router (RSC) │ Client Components │ API Routes │ +├────────────────────┼────────────────────┼──────────────────┤ +│ Pages & Layouts │ Interactive UI │ Middleware │ +│ (Server-side) │ (Client-side) │ (Auth Guards) │ +└─────────────────────────────────────────────────────────────┘ + ↓ ↑ + ┌──────────────────────┐ + │ State Management │ + ├──────────────────────┤ + │ TanStack Query │ ← Server State + │ (React Query v5) │ + ├──────────────────────┤ + │ Zustand Stores │ ← Client State + │ (Auth, UI) │ + └──────────────────────┘ + ↓ ↑ + ┌──────────────────────┐ + │ API Client Layer │ + ├──────────────────────┤ + │ Axios Instance │ + │ + Interceptors │ + ├──────────────────────┤ + │ Generated Client │ + │ (OpenAPI → TS) │ + └──────────────────────┘ + ↓ ↑ + ┌──────────────────────┐ + │ FastAPI Backend │ + │ /api/v1/* │ + └──────────────────────┘ +``` + +### 1.3 Key Features + +- **Authentication**: JWT-based with token rotation, per-device session tracking +- **Admin Dashboard**: User management, organization management, analytics +- **State Management**: TanStack Query for server state, Zustand for auth/UI +- **Type Safety**: Full TypeScript with generated types from OpenAPI spec +- **Component Library**: shadcn/ui with Radix UI primitives +- **Testing**: 90%+ coverage target with Jest, React Testing Library, Playwright +- **Accessibility**: WCAG 2.1 Level AA compliance +- **Dark Mode**: Full theme support with Tailwind CSS + +--- + +## 2. Technology Stack + +### 2.1 Core Framework + +**Next.js 15.x (App Router)** +- **Why**: Modern React framework with RSC, excellent DX, optimized performance +- **App Router**: Preferred over Pages Router for better data fetching, layouts, and streaming +- **Server Components**: Default for better performance, client components for interactivity +- **TypeScript**: Strict mode enabled for maximum type safety + +### 2.2 State Management + +**TanStack Query (React Query v5)** +- **Purpose**: Server state management (all API data) +- **Why**: Automatic caching, background refetching, request deduplication, optimistic updates +- **Usage**: All data fetching goes through React Query hooks + +**Zustand 4.x** +- **Purpose**: Client-only state (authentication, UI preferences) +- **Why**: Minimal boilerplate, no Context API overhead, simple API +- **Usage**: Auth store, UI store (sidebar, theme, modals) +- **Philosophy**: Use sparingly, prefer server state via React Query + +### 2.3 UI Layer + +**shadcn/ui** +- **Why**: Accessible components (Radix UI), customizable, copy-paste (not npm dependency) +- **Components**: Button, Card, Dialog, Form, Input, Table, Toast, etc. +- **Customization**: Tailwind-based, easy to adapt to design system + +**Tailwind CSS 4.x** +- **Why**: Utility-first, excellent DX, small bundle size, dark mode support +- **Strategy**: Class-based dark mode, mobile-first responsive design +- **Customization**: Custom theme colors, design tokens + +**Recharts 2.x** +- **Purpose**: Charts for admin dashboard +- **Why**: React-native, composable, responsive, themed with Tailwind colors + +### 2.4 API Layer + +**@hey-api/openapi-ts** +- **Purpose**: Generate TypeScript client from backend OpenAPI spec +- **Why**: Type-safe API calls, auto-generated types matching backend +- **Alternative**: Considered `openapi-typescript-codegen` but this is more actively maintained + +**Axios 1.x** +- **Purpose**: HTTP client for API calls +- **Why**: Interceptor support for auth, better error handling than fetch +- **Usage**: Wrapped in generated API client, configured with auth interceptors + +### 2.5 Forms & Validation + +**react-hook-form 7.x** +- **Purpose**: Form state management +- **Why**: Excellent performance, minimal re-renders, great DX + +**Zod 3.x** +- **Purpose**: Runtime type validation and schema definition +- **Why**: Type inference, composable schemas, integrates with react-hook-form +- **Usage**: All forms use Zod schemas with `zodResolver` + +### 2.6 Testing + +**Jest + React Testing Library** +- **Purpose**: Unit and component tests +- **Why**: Industry standard, excellent React support, accessibility-focused + +**Playwright** +- **Purpose**: End-to-end testing +- **Why**: Fast, reliable, multi-browser, great debugging tools +- **Coverage Target**: 90%+ for template robustness + +### 2.7 Additional Libraries + +- **date-fns**: Date manipulation and formatting (lighter than moment.js) +- **clsx** + **tailwind-merge**: Conditional class names with conflict resolution +- **lucide-react**: Icon system (tree-shakeable, consistent design) + +--- + +## 3. Architecture Patterns + +### 3.1 Layered Architecture + +Inspired by backend's 5-layer architecture, frontend follows similar separation of concerns: + +``` +┌────────────────────────────────────────────────────────────┐ +│ Layer 1: Pages & Layouts (app/*) │ +│ - Route definitions, page components, layouts │ +│ - Mostly Server Components, minimal logic │ +│ - Delegates to hooks and components │ +└────────────────────────────────────────────────────────────┘ + ↓ +┌────────────────────────────────────────────────────────────┐ +│ Layer 2: React Hooks (hooks/, lib/api/hooks/) │ +│ - Custom hooks for component logic │ +│ - React Query hooks for data fetching │ +│ - Reusable logic extraction │ +└────────────────────────────────────────────────────────────┘ + ↓ +┌────────────────────────────────────────────────────────────┐ +│ Layer 3: Services (services/) │ +│ - Business logic (if complex) │ +│ - Multi-step operations │ +│ - Data transformations │ +└────────────────────────────────────────────────────────────┘ + ↓ +┌────────────────────────────────────────────────────────────┐ +│ Layer 4: API Client (lib/api/*) │ +│ - Axios instance with interceptors │ +│ - Generated API client from OpenAPI │ +│ - Error handling │ +└────────────────────────────────────────────────────────────┘ + ↓ +┌────────────────────────────────────────────────────────────┐ +│ Layer 5: Types & Models (types/, lib/api/generated/) │ +│ - TypeScript interfaces │ +│ - Generated types from OpenAPI │ +│ - Validation schemas (Zod) │ +└────────────────────────────────────────────────────────────┘ +``` + +**Key Rules:** +- Pages/Layouts should NOT contain business logic +- Components should NOT call API client directly (use hooks) +- Hooks should NOT contain display logic +- API client should NOT contain business logic +- Types should NOT import from upper layers + +### 3.2 Component Patterns + +**Server Components by Default:** +```typescript +// app/(authenticated)/admin/users/page.tsx +// Server Component - can fetch data directly +export default async function UsersPage() { + // Could fetch data here, but we delegate to client components with React Query + return ( +
+ + {/* Client Component with data fetching */} +
+ ); +} +``` + +**Client Components for Interactivity:** +```typescript +// components/admin/UserTable.tsx +'use client'; + +import { useUsers } from '@/lib/api/hooks/useUsers'; + +export function UserTable() { + const { data, isLoading, error } = useUsers(); + // ... render logic +} +``` + +**Composition Over Prop Drilling:** +```typescript +// Good: Use composition + + + Users + + + + + + +// Avoid: Deep prop drilling +} /> +``` + +### 3.3 Single Responsibility Principle + +Each module has one clear responsibility: + +- **Pages**: Routing and layout structure +- **Components**: UI rendering and user interaction +- **Hooks**: Data fetching and reusable logic +- **Services**: Complex business logic (multi-step operations) +- **API Client**: HTTP communication +- **Stores**: Global client state +- **Types**: Type definitions + +--- + +## 4. Data Flow + +### 4.1 Request Flow (API Call) + +``` +┌──────────────┐ +│ User Action │ (e.g., Click "Save User") +└──────┬───────┘ + ↓ +┌──────────────────┐ +│ Component │ Calls hook: updateUser.mutate(data) +└──────┬───────────┘ + ↓ +┌──────────────────┐ +│ React Query Hook │ useMutation with API client call +│ (useUpdateUser) │ +└──────┬───────────┘ + ↓ +┌──────────────────┐ +│ API Client │ Axios PUT request with interceptors +│ (Axios) │ +└──────┬───────────┘ + ↓ +┌──────────────────┐ +│ Request │ Add Authorization header +│ Interceptor │ token = authStore.accessToken +└──────┬───────────┘ + ↓ +┌──────────────────┐ +│ FastAPI Backend │ PUT /api/v1/users/{id} +└──────┬───────────┘ + ↓ +┌──────────────────┐ +│ Response │ Check status code +│ Interceptor │ - 401: Refresh token → retry +│ │ - 200: Parse success +│ │ - 4xx/5xx: Parse error +└──────┬───────────┘ + ↓ +┌──────────────────┐ +│ React Query │ Cache invalidation +│ │ queryClient.invalidateQueries(['users']) +└──────┬───────────┘ + ↓ +┌──────────────────┐ +│ Component │ Re-renders with updated data +│ (via useUsers) │ Shows success toast +└──────────────────┘ +``` + +### 4.2 Authentication Flow + +``` +┌──────────────┐ +│ Login Form │ User enters email + password +└──────┬───────┘ + ↓ +┌──────────────────────┐ +│ authStore.login() │ Zustand action +└──────┬───────────────┘ + ↓ +┌──────────────────────┐ +│ API: POST /auth/login│ Backend validates credentials +└──────┬───────────────┘ + ↓ +┌──────────────────────┐ +│ Backend Response │ { access_token, refresh_token, user } +└──────┬───────────────┘ + ↓ +┌──────────────────────┐ +│ authStore.setTokens()│ Store tokens (sessionStorage + localStorage/cookie) +│ authStore.setUser() │ Store user object +└──────┬───────────────┘ + ↓ +┌──────────────────────┐ +│ Axios Interceptor │ Now adds Authorization header to all requests +└──────┬───────────────┘ + ↓ +┌──────────────────────┐ +│ Redirect to Home │ User is authenticated +└──────────────────────┘ +``` + +**Token Refresh Flow (Automatic):** +``` +API Request → 401 Response → Check if refresh token exists + ↓ Yes ↓ No +POST /auth/refresh Redirect to Login + ↓ +New Tokens → Update Store → Retry Original Request +``` + +### 4.3 State Updates + +**Server State (React Query):** +- Automatic background refetch +- Cache invalidation on mutations +- Optimistic updates where appropriate + +**Client State (Zustand):** +- Direct store updates +- No actions/reducers boilerplate +- Subscriptions for components + +--- + +## 5. State Management Strategy + +### 5.1 Philosophy + +**Use the Right Tool for the Right Job:** +- Server data → TanStack Query +- Auth & tokens → Zustand +- UI state → Zustand (minimal) +- Form state → react-hook-form +- Component state → useState/useReducer + +**Avoid Redundancy:** +- DON'T duplicate server data in Zustand +- DON'T store API responses in global state +- DO keep state as local as possible + +### 5.2 TanStack Query Configuration + +**Global Config** (`src/config/queryClient.ts`): +```typescript +export const queryClient = new QueryClient({ + defaultOptions: { + queries: { + staleTime: 60000, // 1 minute + cacheTime: 300000, // 5 minutes + retry: 3, // Retry failed requests + refetchOnWindowFocus: true, // Refetch on tab focus + refetchOnReconnect: true, // Refetch on network reconnect + }, + mutations: { + retry: 1, // Retry mutations once + }, + }, +}); +``` + +**Query Key Structure:** +```typescript +['users'] // List all users +['users', userId] // Single user +['users', { page: 1, search: 'john' }] // Filtered list +['organizations', orgId, 'members'] // Nested resource +``` + +### 5.3 Zustand Stores + +**Auth Store** (`src/stores/authStore.ts`): +```typescript +interface AuthStore { + user: User | null; + accessToken: string | null; + refreshToken: string | null; + isAuthenticated: boolean; + isLoading: boolean; + login: (credentials) => Promise; + logout: () => Promise; + logoutAll: () => Promise; + setTokens: (access, refresh) => void; + clearAuth: () => void; +} +``` + +**UI Store** (`src/stores/uiStore.ts`): +```typescript +interface UIStore { + sidebarOpen: boolean; + theme: 'light' | 'dark' | 'system'; + setSidebarOpen: (open: boolean) => void; + toggleSidebar: () => void; + setTheme: (theme) => void; +} +``` + +**Store Guidelines:** +- Keep stores small and focused +- Use selectors for computed values +- Persist to localStorage where appropriate +- Document why Zustand over alternatives + +--- + +## 6. Authentication Architecture + +### 6.1 Token Management Strategy + +**Two-Token System:** +- **Access Token**: Short-lived (15 min), stored in memory/sessionStorage +- **Refresh Token**: Long-lived (7 days), stored in httpOnly cookie (preferred) or localStorage + +**Token Storage Decision:** +- **Primary**: httpOnly cookies (most secure, prevents XSS) +- **Fallback**: localStorage with encryption wrapper (if cookies not feasible) +- **Access Token**: sessionStorage or React state (short-lived, acceptable risk) + +**Token Rotation:** +- On refresh, both tokens are rotated +- Old refresh token is invalidated immediately +- Prevents token replay attacks + +### 6.2 Per-Device Session Tracking + +Backend tracks sessions per device: +- Each login creates a unique session with device info +- Users can view all active sessions +- Users can revoke individual sessions +- Logout only affects current device +- "Logout All" deactivates all sessions + +Frontend Implementation: +- Session list page at `/settings/sessions` +- Display device name, IP, location, last used +- Highlight current session +- Revoke button for non-current sessions + +### 6.3 Auth Guard Implementation + +**Layout-Based Protection:** +```typescript +// app/(authenticated)/layout.tsx +export default function AuthenticatedLayout({ children }) { + return ( + +
+
{children}
+